ig / app.py
triflix's picture
Create app.py
56d879f verified
import os
import shutil
from typing import Optional
from fastapi import FastAPI, Request, Form, Query, HTTPException
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import instaloader
from instaloader import Post
import uvicorn
# Configuration
DOWNLOAD_DIR = "downloads"
TEMPLATES_DIR = "templates"
CHROMA_ENABLED = os.getenv("CHROMA_ENABLED", "0") == "1" # optional server-side Chroma integration
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
app = FastAPI()
templates = Jinja2Templates(directory=TEMPLATES_DIR)
# Mount static route for convenience (not required β€” html is self-contained)
app.mount("/static", StaticFiles(directory="static"), name="static")
loader = instaloader.Instaloader(dirname_pattern=DOWNLOAD_DIR, download_comments=False, save_metadata=False)
# If you want login for private posts, set env INSTAGRAM_USER / INSTAGRAM_PASS and uncomment login logic:
# try:
# user = os.getenv("INSTAGRAM_USER")
# pwd = os.getenv("INSTAGRAM_PASS")
# if user and pwd:
# loader.login(user, pwd)
# except Exception:
# pass
def shortcode_from_url(url: str) -> str:
if not url:
raise ValueError("empty url")
s = url.rstrip("/").split("/")[-1]
if "?" in s:
s = s.split("?")[0]
return s
def find_downloaded_video(shortcode: str) -> Optional[str]:
# instaloader creates a folder per profile under DOWNLOAD_DIR. Search recursively.
for root, _, files in os.walk(DOWNLOAD_DIR):
for f in files:
if shortcode in f and f.lower().endswith((".mp4",)):
return os.path.join(root, f)
return None
@app.get("/", response_class=HTMLResponse)
def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/api/download")
def download_post_form(reel_url: str = Form(...)):
return _download_and_respond(reel_url)
@app.get("/api/download")
def download_post_get(url: str = Query(...)):
return _download_and_respond(url)
def _download_and_respond(reel_url: str):
try:
shortcode = shortcode_from_url(reel_url)
except Exception as e:
raise HTTPException(status_code=400, detail=f"Invalid URL: {e}")
try:
post = Post.from_shortcode(loader.context, shortcode)
except Exception as e:
raise HTTPException(status_code=404, detail=f"Failed to fetch post: {e}")
if not post.is_video:
raise HTTPException(status_code=400, detail="Provided URL is not a video (Reel).")
# Clear stale files for shortcode to avoid duplicates
# (optional) Remove previous files for same shortcode to save disk.
for root, _, files in os.walk(DOWNLOAD_DIR):
for f in files:
if shortcode in f:
try:
os.remove(os.path.join(root, f))
except Exception:
pass
# Download. Instaloader will create subfolder: DOWNLOAD_DIR/<owner>/...
try:
loader.download_post(post, target=DOWNLOAD_DIR)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Download failed: {e}")
video_path = find_downloaded_video(shortcode)
if not video_path or not os.path.exists(video_path):
raise HTTPException(status_code=500, detail="Video file not found after download.")
filename = os.path.basename(video_path)
return FileResponse(path=video_path, filename=filename, media_type="video/mp4")
@app.get("/api/health")
def health():
return JSONResponse({"status": "ok"})
if __name__ == "__main__":
# For HuggingFace Spaces they expect the app to be reachable at port 7860
uvicorn.run("app:app", host="0.0.0.0", port=7860, log_level="info")