import subprocess from pathlib import Path from database.database import SessionLocal VIDEOS_DIR = "/videos" CHUNK_SIZE = 64 * 1024 def streamAndSave(videoId: int, youtubeUrl: str, maxHeight: int = 1080): from model.video import Video # Lazy-Import gegen Zirkular outputPath = f"{VIDEOS_DIR}/{videoId}.mp4" yield from streamVideoLive(videoId, youtubeUrl, maxHeight) if Path(outputPath).exists(): db = SessionLocal() try: Video.updateFilePath(db, videoId, outputPath) finally: db.close() def _getStreamUrls(youtubeUrl: str, maxHeight: int = 1080): formatFilter = f"bestvideo[ext=mp4][vcodec^=avc][height<={maxHeight}]+bestaudio[ext=m4a]/best[ext=mp4]" result = subprocess.run( [ "yt-dlp", "--cookies", "/app/cookies.txt", "--remote-components", "ejs:github", "-f", formatFilter, "--print", "urls", youtubeUrl, ], capture_output=True, text=True, timeout=30, ) if result.returncode != 0: return None, None lines = result.stdout.strip().splitlines() if len(lines) >= 2: return lines[0], lines[1] elif len(lines) == 1: return lines[0], None return None, None def streamVideoLive(videoId: int, youtubeUrl: str, maxHeight: int = 1080): outputPath = f"{VIDEOS_DIR}/{videoId}.mp4" videoUrl, audioUrl = _getStreamUrls(youtubeUrl, maxHeight) if not videoUrl: return if audioUrl: cmd = [ "ffmpeg", "-reconnect", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "5", "-i", videoUrl, "-reconnect", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "5", "-i", audioUrl, "-c:v", "copy", "-c:a", "aac", "-movflags", "frag_keyframe+empty_moov+default_base_moof", "-f", "mp4", "pipe:1", ] else: cmd = [ "ffmpeg", "-reconnect", "1", "-reconnect_streamed", "1", "-reconnect_delay_max", "5", "-i", videoUrl, "-c", "copy", "-movflags", "frag_keyframe+empty_moov+default_base_moof", "-f", "mp4", "pipe:1", ] process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, ) try: with open(outputPath, "wb") as f: while True: chunk = process.stdout.read(CHUNK_SIZE) if not chunk: break f.write(chunk) yield chunk except GeneratorExit: pass finally: if process.poll() is None: process.kill() process.wait() if process.stdout: process.stdout.close() path = Path(outputPath) if process.returncode != 0 and path.exists(): path.unlink()