Reels-app: integracija z yt.biba.live API. get_cookies_file fetcha sve\u017ee cookies iz /cookies/raw (5 min cache), get_playlist najprej probava /download/playlist API, fallback na lokalni yt-dlp. ENV: YT_API_URL, YT_API_TOKEN.
This commit is contained in:
parent
77075795ce
commit
bc73fd8dd3
@ -9,10 +9,75 @@ import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
|
||||
YT_API_URL = os.environ.get("YT_API_URL", "https://yt.biba.live")
|
||||
YT_API_TOKEN = os.environ.get("YT_API_TOKEN", "")
|
||||
|
||||
|
||||
def fetch_cookies_from_api(cache_path="/tmp/yt_cookies_api.txt", max_age_seconds=300):
|
||||
"""Pull sveže YouTube cookies iz yt.biba.live API.
|
||||
|
||||
Cache za 5 min (TTL) — če imamo nedavno fetched, uporabi local copy.
|
||||
Vrne path do cookies file-a ali None če API ne deluje.
|
||||
"""
|
||||
if not YT_API_TOKEN:
|
||||
return None # ni configured
|
||||
|
||||
cache = Path(cache_path)
|
||||
if cache.exists():
|
||||
age = time.time() - cache.stat().st_mtime
|
||||
if age < max_age_seconds and cache.stat().st_size > 100:
|
||||
return str(cache)
|
||||
|
||||
try:
|
||||
import urllib.request
|
||||
req = urllib.request.Request(
|
||||
f"{YT_API_URL}/cookies/raw",
|
||||
headers={"Authorization": f"Bearer {YT_API_TOKEN}"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
content = resp.read().decode("utf-8")
|
||||
if len(content) < 100:
|
||||
print(f"⚠ yt.biba.live vrnil prekratek cookies file ({len(content)} bytes)", file=sys.stderr)
|
||||
return None
|
||||
cache.write_text(content, encoding="utf-8")
|
||||
print(f"🍪 Cookies fetched iz yt.biba.live ({len(content)} bytes)", file=sys.stderr)
|
||||
return str(cache)
|
||||
except Exception as e:
|
||||
print(f"⚠ yt.biba.live cookies fetch failed: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_cookies_file(cookies_file=None):
|
||||
"""Vrne path do cookies file-a — prioriteta:
|
||||
1. eksplicitno podano (cookies_file param)
|
||||
2. yt.biba.live API (sveže, hourly refreshed)
|
||||
3. /data/cookies/youtube.txt (lokalni fallback)
|
||||
4. YT_COOKIES_FILE env
|
||||
"""
|
||||
if cookies_file and Path(cookies_file).exists():
|
||||
return cookies_file
|
||||
|
||||
# Try API
|
||||
api_cookies = fetch_cookies_from_api()
|
||||
if api_cookies:
|
||||
return api_cookies
|
||||
|
||||
# Fallback to local
|
||||
for candidate in [
|
||||
"/data/cookies/youtube.txt",
|
||||
os.environ.get("YT_COOKIES_FILE", ""),
|
||||
]:
|
||||
if candidate and Path(candidate).exists():
|
||||
return candidate
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def download(url, output, max_height=1080, format_str=None, cookies_file=None):
|
||||
"""
|
||||
Download YT video. Privzeto: best mp4 ≤1080p z audiotrackom.
|
||||
@ -33,17 +98,8 @@ def download(url, output, max_height=1080, format_str=None, cookies_file=None):
|
||||
"-o", str(output),
|
||||
]
|
||||
|
||||
# Auto-detect cookies file če ni eksplicitno podan
|
||||
if cookies_file is None:
|
||||
for candidate in [
|
||||
"/data/cookies/youtube.txt",
|
||||
os.environ.get("YT_COOKIES_FILE", ""),
|
||||
]:
|
||||
if candidate and Path(candidate).exists():
|
||||
cookies_file = candidate
|
||||
break
|
||||
|
||||
if cookies_file and Path(cookies_file).exists():
|
||||
cookies_file = get_cookies_file(cookies_file)
|
||||
if cookies_file:
|
||||
cmd += ["--cookies", str(cookies_file)]
|
||||
print(f"🍪 Using cookies: {cookies_file}", file=sys.stderr)
|
||||
|
||||
@ -60,12 +116,8 @@ def download(url, output, max_height=1080, format_str=None, cookies_file=None):
|
||||
def get_info(url, cookies_file=None):
|
||||
"""Vrni metadata brez prenosa."""
|
||||
cmd = ["yt-dlp", "--dump-json", "--no-playlist"]
|
||||
if cookies_file is None:
|
||||
for candidate in ["/data/cookies/youtube.txt", os.environ.get("YT_COOKIES_FILE", "")]:
|
||||
if candidate and Path(candidate).exists():
|
||||
cookies_file = candidate
|
||||
break
|
||||
if cookies_file and Path(cookies_file).exists():
|
||||
cookies_file = get_cookies_file(cookies_file)
|
||||
if cookies_file:
|
||||
cmd += ["--cookies", str(cookies_file)]
|
||||
cmd.append(url)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
@ -77,6 +129,11 @@ def get_info(url, cookies_file=None):
|
||||
def get_playlist(url, cookies_file=None):
|
||||
"""Vrni listo videov v playlistu (samo metadata, ne pobere).
|
||||
|
||||
Strategija:
|
||||
1. Najprej probaj yt.biba.live API /download/playlist (centralizirano,
|
||||
vedno sveže cookies + signature solver na strežniku).
|
||||
2. Fallback na lokalni yt-dlp --flat-playlist.
|
||||
|
||||
Returns:
|
||||
{
|
||||
"is_playlist": bool,
|
||||
@ -86,19 +143,40 @@ def get_playlist(url, cookies_file=None):
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
Če URL ni playlist, vrne is_playlist=False in samo en item.
|
||||
"""
|
||||
# ─── 1. Try yt.biba.live API ───
|
||||
if YT_API_TOKEN:
|
||||
try:
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
params = urllib.parse.urlencode({"url": url, "max_items": 200})
|
||||
req = urllib.request.Request(
|
||||
f"{YT_API_URL}/download/playlist?{params}",
|
||||
headers={"Authorization": f"Bearer {YT_API_TOKEN}"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=120) as resp:
|
||||
api_data = json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
if api_data.get("ok"):
|
||||
print(f"📋 Playlist resolved via yt.biba.live API: {api_data.get('count', 0)} items", file=sys.stderr)
|
||||
return {
|
||||
"is_playlist": api_data.get("is_playlist", False),
|
||||
"playlist_title": api_data.get("playlist_title", ""),
|
||||
"items": api_data.get("items", []),
|
||||
}
|
||||
else:
|
||||
print(f"⚠ yt.biba.live API error: {api_data.get('error', 'unknown')[:200]}", file=sys.stderr)
|
||||
# Fall through to local
|
||||
except Exception as e:
|
||||
print(f"⚠ yt.biba.live API failed: {e}, fallback to local yt-dlp", file=sys.stderr)
|
||||
|
||||
# ─── 2. Fallback: lokalni yt-dlp ───
|
||||
cmd = ["yt-dlp", "--flat-playlist", "--dump-json"]
|
||||
if cookies_file is None:
|
||||
for candidate in ["/data/cookies/youtube.txt", os.environ.get("YT_COOKIES_FILE", "")]:
|
||||
if candidate and Path(candidate).exists():
|
||||
cookies_file = candidate
|
||||
break
|
||||
if cookies_file and Path(cookies_file).exists():
|
||||
cookies_file = get_cookies_file(cookies_file)
|
||||
if cookies_file:
|
||||
cmd += ["--cookies", str(cookies_file)]
|
||||
cmd.append(url)
|
||||
print(f"📋 Resolving playlist: {url}", file=sys.stderr)
|
||||
print(f"📋 Resolving playlist (lokalno): {url}", file=sys.stderr)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||
if result.returncode != 0:
|
||||
print(f"❌ yt-dlp playlist napaka:\n{result.stderr[-1500:]}", file=sys.stderr)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user