dragxd commited on
Commit
a90b1ae
·
verified ·
1 Parent(s): ca4952f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -33
app.py CHANGED
@@ -1,41 +1,90 @@
1
- from fastapi import FastAPI, HTTPException, Query
2
- from fastapi.responses import RedirectResponse
3
  import subprocess
4
- import urllib.parse
 
 
 
5
 
6
- app = FastAPI(
7
- title="YouTube Audio Streamer",
8
- description="Stream best audio of a YouTube video using yt-dlp.",
9
- version="1.0.2",
10
- docs_url="/docs",
11
- redoc_url="/redoc"
12
- )
13
 
14
- @app.get("/", tags=["Health"])
15
- async def root():
16
- return {"status": "FastAPI working", "message": "YT-DLP backend ready"}
17
 
18
- @app.get("/stream", tags=["Stream"])
19
- async def stream_audio(url: str = Query(..., description="Full YouTube video URL")):
20
- decoded_url = urllib.parse.unquote(url)
21
-
22
- if "youtube.com/watch?v=" not in decoded_url and "youtu.be/" not in decoded_url:
23
- raise HTTPException(status_code=400, detail="Invalid YouTube URL.")
24
-
25
- # Remove URL query params (yt-dlp can fail with things like ?si=xxx)
26
- base_url = decoded_url.split("&")[0].split("?")[0]
27
 
 
 
28
  try:
29
- result = subprocess.run(
30
- ["yt-dlp", "-f", "bestaudio", "--get-url", base_url],
31
- capture_output=True, text=True
32
- )
 
 
 
 
33
  if result.returncode != 0:
34
- raise HTTPException(status_code=500, detail=f"yt-dlp error: {result.stderr.strip()}")
35
-
36
- audio_url = result.stdout.strip()
37
- if not audio_url:
38
- raise HTTPException(status_code=404, detail="No audio URL found.")
39
- return RedirectResponse(audio_url)
 
 
 
 
 
40
  except Exception as e:
41
- raise HTTPException(status_code=500, detail=f"Error: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, Query, BackgroundTasks
2
+ from pydantic import BaseModel
3
  import subprocess
4
+ import uuid
5
+ import os
6
+ import json
7
+ import shutil
8
 
9
+ app = FastAPI()
 
 
 
 
 
 
10
 
11
+ tasks = {}
 
 
12
 
13
+ class DownloadRequest(BaseModel):
14
+ url: str
15
+ format_id: str # yt-dlp format code
16
+ output_name: str = None
17
+ convert_to: str = None # e.g. mp3 or mp4
 
 
 
 
18
 
19
+ @app.get("/info")
20
+ async def get_video_info(url: str = Query(...)):
21
  try:
22
+ cmd = [
23
+ "yt-dlp",
24
+ "--no-warnings",
25
+ "--skip-download",
26
+ "--print-json",
27
+ url
28
+ ]
29
+ result = subprocess.run(cmd, capture_output=True, text=True)
30
  if result.returncode != 0:
31
+ raise Exception(result.stderr)
32
+ data = json.loads(result.stdout)
33
+ return {
34
+ "title": data.get("title"),
35
+ "thumbnail": data.get("thumbnail"),
36
+ "formats": [
37
+ {"id": f["format_id"], "ext": f["ext"], "note": f.get("format_note"), "filesize": f.get("filesize")}
38
+ for f in data.get("formats", [])
39
+ if f.get("vcodec") != "none" or f.get("acodec") != "none"
40
+ ]
41
+ }
42
  except Exception as e:
43
+ raise HTTPException(status_code=500, detail=f"Error getting info: {e}")
44
+
45
+ @app.post("/download")
46
+ async def download_media(request: DownloadRequest, background_tasks: BackgroundTasks):
47
+ task_id = str(uuid.uuid4())
48
+ tasks[task_id] = {"status": "queued"}
49
+
50
+ def _do_download():
51
+ try:
52
+ output_file = f"{request.output_name or 'video'}_{task_id}"
53
+ if request.convert_to:
54
+ output_file += f".{request.convert_to}"
55
+ else:
56
+ output_file += ".%(ext)s"
57
+
58
+ cmd = [
59
+ "yt-dlp",
60
+ "-f", request.format_id,
61
+ "-o", f"/tmp/{output_file}",
62
+ request.url
63
+ ]
64
+ if request.convert_to:
65
+ cmd += ["--recode-video", request.convert_to]
66
+
67
+ tasks[task_id]["status"] = "downloading"
68
+ subprocess.run(cmd, capture_output=True, text=True)
69
+ tasks[task_id]["status"] = "completed"
70
+ tasks[task_id]["file"] = f"/tmp/{output_file}"
71
+ except Exception as e:
72
+ tasks[task_id]["status"] = "error"
73
+ tasks[task_id]["error"] = str(e)
74
+
75
+ background_tasks.add_task(_do_download)
76
+ return {"task_id": task_id}
77
+
78
+ @app.get("/progress/{task_id}")
79
+ def check_progress(task_id: str):
80
+ if task_id not in tasks:
81
+ raise HTTPException(status_code=404, detail="Task ID not found")
82
+ return tasks[task_id]
83
+
84
+ @app.get("/cancel/{task_id}")
85
+ def cancel_task(task_id: str):
86
+ # Basic cancellation support stub (real cancellation needs async task mgmt)
87
+ if task_id in tasks and tasks[task_id]["status"] not in ["completed", "error"]:
88
+ tasks[task_id]["status"] = "cancelled"
89
+ return {"detail": "Task cancelled"}
90
+ raise HTTPException(status_code=404, detail="Task not found or already done")