feat: integrate custom yt-dl solver for YouTube downloads
This commit is contained in:
+51
-118
@@ -457,7 +457,6 @@ def get_video_resolution(video_path):
|
||||
cap.release()
|
||||
return width, height
|
||||
|
||||
|
||||
def sanitize_filename(filename):
|
||||
"""Remove invalid characters from filename."""
|
||||
filename = re.sub(r'[<>:"/\\|?*#]', '', filename)
|
||||
@@ -467,131 +466,65 @@ def sanitize_filename(filename):
|
||||
|
||||
def download_youtube_video(url, output_dir="."):
|
||||
"""
|
||||
Downloads a YouTube video using yt-dlp.
|
||||
Returns the path to the downloaded video and the video title.
|
||||
Downloads a YouTube video using the custom fixed yt-dl wrapper in /home/ren/yt/yt-dl.
|
||||
"""
|
||||
print(f"🔍 Debug: yt-dlp version: {yt_dlp.version.__version__}")
|
||||
print("📥 Downloading video from YouTube...")
|
||||
import subprocess
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
|
||||
print("📥 Downloading video from YouTube using custom solver...")
|
||||
step_start_time = time.time()
|
||||
|
||||
cookies_path = '/app/cookies.txt'
|
||||
cookies_env = os.environ.get("YOUTUBE_COOKIES")
|
||||
if cookies_env:
|
||||
print("🍪 Found YOUTUBE_COOKIES env var, creating cookies file inside container...")
|
||||
try:
|
||||
with open(cookies_path, 'w') as f:
|
||||
f.write(cookies_env)
|
||||
if os.path.exists(cookies_path):
|
||||
print(f" Debug: Cookies file created. Size: {os.path.getsize(cookies_path)} bytes")
|
||||
with open(cookies_path, 'r') as f:
|
||||
content = f.read(100)
|
||||
print(f" Debug: First 100 chars of cookie file: {content}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to write cookies file: {e}")
|
||||
cookies_path = None
|
||||
else:
|
||||
cookies_path = None
|
||||
print("⚠️ YOUTUBE_COOKIES env var not found.")
|
||||
|
||||
# Common yt-dlp options to work around YouTube bot detection.
|
||||
# extractor_args tries multiple player clients in order; tv_embed / android
|
||||
# avoid the OAuth/PO-token checks that block server IPs.
|
||||
_COMMON_YDL_OPTS = {
|
||||
'quiet': False,
|
||||
'verbose': True,
|
||||
'no_warnings': False,
|
||||
'cookiefile': cookies_path if cookies_path else None,
|
||||
'socket_timeout': 30,
|
||||
'retries': 10,
|
||||
'fragment_retries': 10,
|
||||
'nocheckcertificate': True,
|
||||
'cachedir': False,
|
||||
'extractor_args': {
|
||||
'youtube': {
|
||||
'player_client': ['tv_embed', 'android', 'mweb', 'web'],
|
||||
'player_skip': ['webpage', 'configs'],
|
||||
}
|
||||
},
|
||||
'http_headers': {
|
||||
'User-Agent': (
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
|
||||
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
'Chrome/120.0.0.0 Safari/537.36'
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
with yt_dlp.YoutubeDL(_COMMON_YDL_OPTS) as ydl:
|
||||
try:
|
||||
info = ydl.extract_info(url, download=False)
|
||||
video_title = info.get('title', 'youtube_video')
|
||||
sanitized_title = sanitize_filename(video_title)
|
||||
except Exception as e:
|
||||
# Force print to stderr/stdout immediately so it's captured before crash
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
# Print minimal error first to ensure something gets out
|
||||
print("🚨 YOUTUBE DOWNLOAD ERROR 🚨", file=sys.stderr)
|
||||
|
||||
error_msg = f"""
|
||||
|
||||
❌ ================================================================= ❌
|
||||
❌ FATAL ERROR: YOUTUBE DOWNLOAD FAILED
|
||||
❌ ================================================================= ❌
|
||||
|
||||
REASON: YouTube has blocked the download request (Error 429/Unavailable).
|
||||
This is likely a temporary IP ban on this server.
|
||||
|
||||
👇 SOLUTION FOR USER 👇
|
||||
---------------------------------------------------------------------
|
||||
1. Download the video manually to your computer.
|
||||
2. Use the 'Upload Video' tab in this app to process it.
|
||||
---------------------------------------------------------------------
|
||||
|
||||
Technical Details: {str(e)}
|
||||
"""
|
||||
# Print to both streams to ensure capture
|
||||
print(error_msg, file=sys.stdout)
|
||||
print(error_msg, file=sys.stderr)
|
||||
|
||||
# Force flush
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
# Wait a split second to allow buffer to drain before raising
|
||||
time.sleep(0.5)
|
||||
|
||||
raise e
|
||||
# Ensure output_dir is absolute path
|
||||
abs_output_dir = os.path.abspath(output_dir)
|
||||
|
||||
output_template = os.path.join(output_dir, f'{sanitized_title}.%(ext)s')
|
||||
expected_file = os.path.join(output_dir, f'{sanitized_title}.mp4')
|
||||
if os.path.exists(expected_file):
|
||||
os.remove(expected_file)
|
||||
print(f"🗑️ Removed existing file to re-download with H.264 codec")
|
||||
# We run the command with --json to get structured output
|
||||
cmd = [
|
||||
"/home/ren/yt/venv/bin/python",
|
||||
"/home/ren/yt/yt-dl",
|
||||
"--cookies", "/home/ren/yt/cookies.txt",
|
||||
"--json",
|
||||
"-o", abs_output_dir,
|
||||
url
|
||||
]
|
||||
|
||||
ydl_opts = {
|
||||
**_COMMON_YDL_OPTS,
|
||||
'format': 'bestvideo[vcodec^=avc1][ext=mp4]+bestaudio[ext=m4a]/bestvideo[vcodec^=avc1]+bestaudio/best[ext=mp4]/best',
|
||||
'outtmpl': output_template,
|
||||
'merge_output_format': 'mp4',
|
||||
'overwrites': True,
|
||||
}
|
||||
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download([url])
|
||||
|
||||
downloaded_file = os.path.join(output_dir, f'{sanitized_title}.mp4')
|
||||
|
||||
if not os.path.exists(downloaded_file):
|
||||
for f in os.listdir(output_dir):
|
||||
if f.startswith(sanitized_title) and f.endswith('.mp4'):
|
||||
downloaded_file = os.path.join(output_dir, f)
|
||||
break
|
||||
print(f" Executing: {' '.join(cmd)}")
|
||||
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
print("🚨 YOUTUBE DOWNLOAD ERROR 🚨", file=sys.stderr)
|
||||
print(result.stderr, file=sys.stderr)
|
||||
raise Exception(f"YouTube download failed: {result.stderr}")
|
||||
|
||||
try:
|
||||
output_data = json.loads(result.stdout.strip())
|
||||
downloaded_file = output_data['filepath']
|
||||
video_title = output_data.get('title', 'youtube_video')
|
||||
sanitized_title = sanitize_filename(video_title)
|
||||
except Exception as e:
|
||||
print(f"⚠️ JSON parsing failed, trying fallback detection. Error: {e}")
|
||||
print(f" Raw stdout: {result.stdout}")
|
||||
|
||||
# Fallback parsing: if not JSON, the script prints the absolute filepath to stdout
|
||||
downloaded_file = result.stdout.strip()
|
||||
if not os.path.exists(downloaded_file):
|
||||
# Search output_dir for files created recently
|
||||
downloaded_file = None
|
||||
for file in os.listdir(abs_output_dir):
|
||||
if file.endswith('.mp4'):
|
||||
full_path = os.path.join(abs_output_dir, file)
|
||||
if time.time() - os.path.getmtime(full_path) < 60:
|
||||
downloaded_file = full_path
|
||||
break
|
||||
|
||||
if not downloaded_file or not os.path.exists(downloaded_file):
|
||||
raise Exception("Failed to locate downloaded video file.")
|
||||
|
||||
sanitized_title = sanitize_filename(os.path.splitext(os.path.basename(downloaded_file))[0])
|
||||
|
||||
step_end_time = time.time()
|
||||
print(f"✅ Video downloaded in {step_end_time - step_start_time:.2f}s: {downloaded_file}")
|
||||
|
||||
return downloaded_file, sanitized_title
|
||||
|
||||
def process_video_to_vertical(input_video, final_output_video, mute_ranges=None):
|
||||
|
||||
Reference in New Issue
Block a user