Spaces:
Building
Building
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# app.py
|
| 2 |
from flask import Flask, request, send_file, jsonify
|
| 3 |
import yt_dlp
|
| 4 |
import os
|
|
@@ -8,82 +8,90 @@ import traceback
|
|
| 8 |
|
| 9 |
app = Flask(__name__)
|
| 10 |
|
| 11 |
-
|
|
|
|
| 12 |
os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
|
|
|
| 16 |
return jsonify({
|
| 17 |
-
"
|
| 18 |
-
"
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
"
|
| 22 |
-
|
| 23 |
-
"format": "mp3 or mp4"
|
| 24 |
-
}
|
| 25 |
}
|
| 26 |
})
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
|
|
|
| 30 |
data = request.get_json()
|
| 31 |
|
| 32 |
-
url = data.get(
|
| 33 |
-
fmt = data.get(
|
| 34 |
|
| 35 |
if not url:
|
| 36 |
-
return jsonify({"error": "
|
| 37 |
-
|
| 38 |
-
if fmt not in ['mp3', 'mp4']:
|
| 39 |
return jsonify({"error": "Format must be 'mp3' or 'mp4'"}), 400
|
| 40 |
|
| 41 |
job_id = str(uuid.uuid4())[:8]
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
}
|
| 63 |
|
| 64 |
try:
|
| 65 |
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
| 66 |
info = ydl.extract_info(url, download=True)
|
| 67 |
-
title = info.get(
|
| 68 |
-
ext =
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
if not matching_files:
|
| 73 |
-
return jsonify({"error": "File not found after download."}), 500
|
| 74 |
|
| 75 |
-
file_path = matching_files[0]
|
| 76 |
return send_file(
|
| 77 |
-
|
| 78 |
as_attachment=True,
|
| 79 |
-
download_name=f"{title}.{ext}"
|
| 80 |
-
mimetype='audio/mpeg' if fmt == 'mp3' else 'video/mp4'
|
| 81 |
)
|
| 82 |
|
| 83 |
except Exception as e:
|
| 84 |
tb = ''.join(traceback.format_exception(None, e, e.__traceback__))
|
| 85 |
-
return jsonify({
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py - YouTube Downloader API (No UI)
|
| 2 |
from flask import Flask, request, send_file, jsonify
|
| 3 |
import yt_dlp
|
| 4 |
import os
|
|
|
|
| 8 |
|
| 9 |
app = Flask(__name__)
|
| 10 |
|
| 11 |
+
# Use /tmp (only writable directory on Hugging Face)
|
| 12 |
+
DOWNLOAD_FOLDER = "/tmp/downloads"
|
| 13 |
os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
|
| 14 |
|
| 15 |
+
|
| 16 |
+
@app.route("/", methods=["GET"])
|
| 17 |
+
def info():
|
| 18 |
return jsonify({
|
| 19 |
+
"api": "/download",
|
| 20 |
+
"method": "POST",
|
| 21 |
+
"body": {"url": "YouTube_URL", "format": "mp3 or mp4"},
|
| 22 |
+
"example": {
|
| 23 |
+
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
| 24 |
+
"format": "mp3"
|
|
|
|
|
|
|
| 25 |
}
|
| 26 |
})
|
| 27 |
|
| 28 |
+
|
| 29 |
+
@app.route("/download", methods=["POST"])
|
| 30 |
+
def download():
|
| 31 |
data = request.get_json()
|
| 32 |
|
| 33 |
+
url = data.get("url")
|
| 34 |
+
fmt = (data.get("format") or "").lower().strip()
|
| 35 |
|
| 36 |
if not url:
|
| 37 |
+
return jsonify({"error": "Missing 'url' in request"}), 400
|
| 38 |
+
if fmt not in ["mp3", "mp4"]:
|
|
|
|
| 39 |
return jsonify({"error": "Format must be 'mp3' or 'mp4'"}), 400
|
| 40 |
|
| 41 |
job_id = str(uuid.uuid4())[:8]
|
| 42 |
|
| 43 |
+
# Robust yt-dlp options with retries and delays
|
| 44 |
+
ydl_opts = {
|
| 45 |
+
'format': 'bestaudio/best' if fmt == 'mp3' else 'bestvideo+bestaudio/best',
|
| 46 |
+
'outtmpl': os.path.join(DOWNLOAD_FOLDER, f'{job_id}_%(title)s.%(ext)s'),
|
| 47 |
+
'merge_output_format': 'mp4' if fmt == 'mp4' else None,
|
| 48 |
+
'noplaylist': True,
|
| 49 |
+
'quiet': True,
|
| 50 |
+
'retries': 6,
|
| 51 |
+
'fragment_retries': 10,
|
| 52 |
+
'socket_timeout': 15,
|
| 53 |
+
'http_headers': {
|
| 54 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
|
| 55 |
+
'(KHTML, like Gecko) Chrome/120.0 Safari/537.36'
|
| 56 |
+
},
|
| 57 |
+
'sleep_interval': 5,
|
| 58 |
+
'max_sleep_interval': 15,
|
| 59 |
+
'source_address': None,
|
| 60 |
+
'extractor_retries': 3,
|
| 61 |
+
}
|
|
|
|
| 62 |
|
| 63 |
try:
|
| 64 |
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
| 65 |
info = ydl.extract_info(url, download=True)
|
| 66 |
+
title = info.get("title", "media").replace("/", "_").replace("\\", "_")
|
| 67 |
+
ext = "mp3" if fmt == "mp3" else "mp4"
|
| 68 |
+
|
| 69 |
+
pattern = os.path.join(DOWNLOAD_FOLDER, f"{job_id}_*.{ext}")
|
| 70 |
+
matches = glob.glob(pattern)
|
| 71 |
|
| 72 |
+
if not matches:
|
| 73 |
+
return jsonify({"error": "Download completed but file not found."}), 500
|
|
|
|
|
|
|
| 74 |
|
|
|
|
| 75 |
return send_file(
|
| 76 |
+
matches[0],
|
| 77 |
as_attachment=True,
|
| 78 |
+
download_name=f"{title}.{ext}"
|
|
|
|
| 79 |
)
|
| 80 |
|
| 81 |
except Exception as e:
|
| 82 |
tb = ''.join(traceback.format_exception(None, e, e.__traceback__))
|
| 83 |
+
return jsonify({
|
| 84 |
+
"error": str(e),
|
| 85 |
+
"details": tb.splitlines()[-1] # Show last line of error
|
| 86 |
+
}), 500
|
| 87 |
|
| 88 |
|
| 89 |
+
# Optional: Health check route
|
| 90 |
+
@app.route("/ping", methods=["GET"])
|
| 91 |
+
def ping():
|
| 92 |
+
try:
|
| 93 |
+
import socket
|
| 94 |
+
socket.gethostbyname("www.youtube.com")
|
| 95 |
+
return jsonify({"status": "ok", "dns": "resolved"})
|
| 96 |
+
except Exception as e:
|
| 97 |
+
return jsonify({"status": "fail", "dns_error": str(e)})
|