Erfan commited on
Commit ·
b73f3ca
1
Parent(s): 7b04a3b
fix: use pytubefix primary, yt-dlp fallback
Browse files- bot/youtube.py +55 -23
- requirements.txt +1 -0
bot/youtube.py
CHANGED
|
@@ -8,19 +8,6 @@ os.makedirs(DOWNLOAD_DIR, exist_ok=True)
|
|
| 8 |
COOKIES_FILE = os.path.join(DATA_DIR, "cookies.txt")
|
| 9 |
|
| 10 |
|
| 11 |
-
def _get_ydl_opts(format_spec: str):
|
| 12 |
-
opts = {
|
| 13 |
-
"format": format_spec,
|
| 14 |
-
"outtmpl": os.path.join(DOWNLOAD_DIR, f"{uuid.uuid4()}.%(ext)s"),
|
| 15 |
-
"quiet": True,
|
| 16 |
-
"no_warnings": True,
|
| 17 |
-
"extractor_args": {"youtube": {"player_client": ["web"]}},
|
| 18 |
-
}
|
| 19 |
-
if os.path.exists(COOKIES_FILE):
|
| 20 |
-
opts["cookiefile"] = COOKIES_FILE
|
| 21 |
-
return opts
|
| 22 |
-
|
| 23 |
-
|
| 24 |
def _find_downloaded_file():
|
| 25 |
for f in os.listdir(DOWNLOAD_DIR):
|
| 26 |
p = os.path.join(DOWNLOAD_DIR, f)
|
|
@@ -30,27 +17,72 @@ def _find_downloaded_file():
|
|
| 30 |
|
| 31 |
|
| 32 |
async def get_video_info(url: str):
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
return {
|
| 37 |
-
"title":
|
| 38 |
-
"duration":
|
| 39 |
-
"thumbnail":
|
| 40 |
"url": url,
|
| 41 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
|
| 44 |
async def download_yt(url: str, quality: str = "audio"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
if quality == "audio":
|
| 46 |
-
formats = ["bestaudio
|
| 47 |
else:
|
| 48 |
-
formats = ["
|
| 49 |
|
| 50 |
for fmt in formats:
|
| 51 |
try:
|
| 52 |
-
opts =
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
with yt_dlp.YoutubeDL(opts) as ydl:
|
| 55 |
info = ydl.extract_info(url, download=True)
|
| 56 |
file_path = ydl.prepare_filename(info)
|
|
|
|
| 8 |
COOKIES_FILE = os.path.join(DATA_DIR, "cookies.txt")
|
| 9 |
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
def _find_downloaded_file():
|
| 12 |
for f in os.listdir(DOWNLOAD_DIR):
|
| 13 |
p = os.path.join(DOWNLOAD_DIR, f)
|
|
|
|
| 17 |
|
| 18 |
|
| 19 |
async def get_video_info(url: str):
|
| 20 |
+
try:
|
| 21 |
+
from pytubefix import YouTube
|
| 22 |
+
yt = YouTube(url)
|
| 23 |
return {
|
| 24 |
+
"title": yt.title,
|
| 25 |
+
"duration": yt.length,
|
| 26 |
+
"thumbnail": yt.thumbnail_url,
|
| 27 |
"url": url,
|
| 28 |
}
|
| 29 |
+
except Exception:
|
| 30 |
+
ydl_opts = {"quiet": True, "no_warnings": True}
|
| 31 |
+
if os.path.exists(COOKIES_FILE):
|
| 32 |
+
ydl_opts["cookiefile"] = COOKIES_FILE
|
| 33 |
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
| 34 |
+
info = ydl.extract_info(url, download=False)
|
| 35 |
+
return {
|
| 36 |
+
"title": info.get("title", "Unknown"),
|
| 37 |
+
"duration": info.get("duration", 0),
|
| 38 |
+
"thumbnail": info.get("thumbnail", ""),
|
| 39 |
+
"url": url,
|
| 40 |
+
}
|
| 41 |
|
| 42 |
|
| 43 |
async def download_yt(url: str, quality: str = "audio"):
|
| 44 |
+
try:
|
| 45 |
+
from pytubefix import YouTube
|
| 46 |
+
from pytubefix.cli import on_progress
|
| 47 |
+
|
| 48 |
+
yt = YouTube(url, on_progress_callback=on_progress)
|
| 49 |
+
|
| 50 |
+
if quality == "audio":
|
| 51 |
+
stream = yt.streams.filter(only_audio=True).first()
|
| 52 |
+
elif quality == "720p":
|
| 53 |
+
stream = yt.streams.filter(res="720p").first() or yt.streams.filter(res="480p").first() or yt.streams.get_highest_resolution()
|
| 54 |
+
elif quality == "1080p":
|
| 55 |
+
stream = yt.streams.filter(res="1080p").first() or yt.streams.get_highest_resolution()
|
| 56 |
+
else:
|
| 57 |
+
stream = yt.streams.get_highest_resolution()
|
| 58 |
+
|
| 59 |
+
if stream:
|
| 60 |
+
file_path = stream.download(output_path=DOWNLOAD_DIR)
|
| 61 |
+
return file_path, yt.title
|
| 62 |
+
except Exception as e:
|
| 63 |
+
print(f"[youtube] pytubefix failed: {e}")
|
| 64 |
+
|
| 65 |
+
print("[youtube] Falling back to yt-dlp...")
|
| 66 |
+
return await _download_ytdlp(url, quality)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
async def _download_ytdlp(url: str, quality: str = "audio"):
|
| 70 |
if quality == "audio":
|
| 71 |
+
formats = ["bestaudio", "best"]
|
| 72 |
else:
|
| 73 |
+
formats = ["best", "worst"]
|
| 74 |
|
| 75 |
for fmt in formats:
|
| 76 |
try:
|
| 77 |
+
opts = {
|
| 78 |
+
"format": fmt,
|
| 79 |
+
"outtmpl": os.path.join(DOWNLOAD_DIR, f"{uuid.uuid4()}.%(ext)s"),
|
| 80 |
+
"quiet": True,
|
| 81 |
+
"no_warnings": True,
|
| 82 |
+
"extractor_args": {"youtube": {"player_client": ["web"]}},
|
| 83 |
+
}
|
| 84 |
+
if os.path.exists(COOKIES_FILE):
|
| 85 |
+
opts["cookiefile"] = COOKIES_FILE
|
| 86 |
with yt_dlp.YoutubeDL(opts) as ydl:
|
| 87 |
info = ydl.extract_info(url, download=True)
|
| 88 |
file_path = ydl.prepare_filename(info)
|
requirements.txt
CHANGED
|
@@ -3,6 +3,7 @@ uvicorn[standard]>=0.23.0
|
|
| 3 |
httpx>=0.25.0
|
| 4 |
aiosqlite>=0.19.0
|
| 5 |
yt-dlp>=2024.0.0
|
|
|
|
| 6 |
openai>=1.0.0
|
| 7 |
python-multipart>=0.0.6
|
| 8 |
jinja2>=3.1.0
|
|
|
|
| 3 |
httpx>=0.25.0
|
| 4 |
aiosqlite>=0.19.0
|
| 5 |
yt-dlp>=2024.0.0
|
| 6 |
+
pytubefix>=7.0.0
|
| 7 |
openai>=1.0.0
|
| 8 |
python-multipart>=0.0.6
|
| 9 |
jinja2>=3.1.0
|