Update app.py
Browse files
app.py
CHANGED
@@ -1,41 +1,90 @@
|
|
1 |
-
from fastapi import FastAPI, HTTPException, Query
|
2 |
-
from
|
3 |
import subprocess
|
4 |
-
import
|
|
|
|
|
|
|
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 |
-
|
15 |
-
async def root():
|
16 |
-
return {"status": "FastAPI working", "message": "YT-DLP backend ready"}
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
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 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
|
|
|
|
|
|
|
|
33 |
if result.returncode != 0:
|
34 |
-
raise
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
40 |
except Exception as e:
|
41 |
-
raise HTTPException(status_code=500, detail=f"Error: {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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")
|