triflix commited on
Commit
56d879f
·
verified ·
1 Parent(s): 383b0a0

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -0
app.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ from typing import Optional
4
+ from fastapi import FastAPI, Request, Form, Query, HTTPException
5
+ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
6
+ from fastapi.staticfiles import StaticFiles
7
+ from fastapi.templating import Jinja2Templates
8
+ import instaloader
9
+ from instaloader import Post
10
+ import uvicorn
11
+
12
+ # Configuration
13
+ DOWNLOAD_DIR = "downloads"
14
+ TEMPLATES_DIR = "templates"
15
+ CHROMA_ENABLED = os.getenv("CHROMA_ENABLED", "0") == "1" # optional server-side Chroma integration
16
+
17
+ os.makedirs(DOWNLOAD_DIR, exist_ok=True)
18
+ app = FastAPI()
19
+ templates = Jinja2Templates(directory=TEMPLATES_DIR)
20
+
21
+ # Mount static route for convenience (not required — html is self-contained)
22
+ app.mount("/static", StaticFiles(directory="static"), name="static")
23
+
24
+ loader = instaloader.Instaloader(dirname_pattern=DOWNLOAD_DIR, download_comments=False, save_metadata=False)
25
+ # If you want login for private posts, set env INSTAGRAM_USER / INSTAGRAM_PASS and uncomment login logic:
26
+ # try:
27
+ # user = os.getenv("INSTAGRAM_USER")
28
+ # pwd = os.getenv("INSTAGRAM_PASS")
29
+ # if user and pwd:
30
+ # loader.login(user, pwd)
31
+ # except Exception:
32
+ # pass
33
+
34
+ def shortcode_from_url(url: str) -> str:
35
+ if not url:
36
+ raise ValueError("empty url")
37
+ s = url.rstrip("/").split("/")[-1]
38
+ if "?" in s:
39
+ s = s.split("?")[0]
40
+ return s
41
+
42
+ def find_downloaded_video(shortcode: str) -> Optional[str]:
43
+ # instaloader creates a folder per profile under DOWNLOAD_DIR. Search recursively.
44
+ for root, _, files in os.walk(DOWNLOAD_DIR):
45
+ for f in files:
46
+ if shortcode in f and f.lower().endswith((".mp4",)):
47
+ return os.path.join(root, f)
48
+ return None
49
+
50
+ @app.get("/", response_class=HTMLResponse)
51
+ def index(request: Request):
52
+ return templates.TemplateResponse("index.html", {"request": request})
53
+
54
+ @app.post("/api/download")
55
+ def download_post_form(reel_url: str = Form(...)):
56
+ return _download_and_respond(reel_url)
57
+
58
+ @app.get("/api/download")
59
+ def download_post_get(url: str = Query(...)):
60
+ return _download_and_respond(url)
61
+
62
+ def _download_and_respond(reel_url: str):
63
+ try:
64
+ shortcode = shortcode_from_url(reel_url)
65
+ except Exception as e:
66
+ raise HTTPException(status_code=400, detail=f"Invalid URL: {e}")
67
+
68
+ try:
69
+ post = Post.from_shortcode(loader.context, shortcode)
70
+ except Exception as e:
71
+ raise HTTPException(status_code=404, detail=f"Failed to fetch post: {e}")
72
+
73
+ if not post.is_video:
74
+ raise HTTPException(status_code=400, detail="Provided URL is not a video (Reel).")
75
+
76
+ # Clear stale files for shortcode to avoid duplicates
77
+ # (optional) Remove previous files for same shortcode to save disk.
78
+ for root, _, files in os.walk(DOWNLOAD_DIR):
79
+ for f in files:
80
+ if shortcode in f:
81
+ try:
82
+ os.remove(os.path.join(root, f))
83
+ except Exception:
84
+ pass
85
+
86
+ # Download. Instaloader will create subfolder: DOWNLOAD_DIR/<owner>/...
87
+ try:
88
+ loader.download_post(post, target=DOWNLOAD_DIR)
89
+ except Exception as e:
90
+ raise HTTPException(status_code=500, detail=f"Download failed: {e}")
91
+
92
+ video_path = find_downloaded_video(shortcode)
93
+ if not video_path or not os.path.exists(video_path):
94
+ raise HTTPException(status_code=500, detail="Video file not found after download.")
95
+
96
+ filename = os.path.basename(video_path)
97
+ return FileResponse(path=video_path, filename=filename, media_type="video/mp4")
98
+
99
+ @app.get("/api/health")
100
+ def health():
101
+ return JSONResponse({"status": "ok"})
102
+
103
+ if __name__ == "__main__":
104
+ # For HuggingFace Spaces they expect the app to be reachable at port 7860
105
+ uvicorn.run("app:app", host="0.0.0.0", port=7860, log_level="info")