1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| import yt_dlp import os import threading from typing import Dict, Any, Callable, Optional from models import TaskStatus
class YouTubeDownloader: def __init__(self, download_dir: str = "./downloads"): self.download_dir = download_dir def get_video_info(self, url: str) -> Dict[str, Any]: """Extract video information without downloading""" ydl_opts = { 'quiet': True, 'no_warnings': True, 'extract_flat': False, 'extractor_args': { 'youtube': { 'player_client': ['tv', 'mweb', 'web'], } }, 'http_headers': { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, 'ignoreerrors': True } with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download=False) formats = self._process_formats(info.get('formats', [])) return { 'title': info.get('title', 'Unknown'), 'duration': info.get('duration', 0), 'uploader': info.get('uploader', 'Unknown'), 'view_count': info.get('view_count', 0), 'thumbnail': info.get('thumbnail', ''), 'webpage_url': info.get('webpage_url', url), 'formats': formats } def download_video(self, task_id: int, url: str, format_id: str = 'best', progress_callback: Optional[Callable] = None): """Download video in a separate thread""" def download_thread(): try: if not os.path.exists(self.download_dir): os.makedirs(self.download_dir) ydl_opts = { 'format': self._get_format_selector(format_id), 'outtmpl': os.path.join(self.download_dir, "%(title)s-%(id)s.%(ext)s"), 'writeinfojson': True, 'merge_output_format': 'mp4', 'prefer_ffmpeg': True, 'extractor_args': { 'youtube': { 'player_client': ['tv', 'mweb', 'web'], } }, 'retries': 3, 'fragment_retries': 3, 'socket_timeout': 30, } if progress_callback: ydl_opts['progress_hooks'] = [self._progress_hook(task_id, progress_callback)] with yt_dlp.YoutubeDL(ydl_opts) as ydl: ydl.download([url]) except Exception as e: if progress_callback: progress_callback(task_id, 0, TaskStatus.FAILED, None, str(e)) thread = threading.Thread(target=download_thread) thread.daemon = True thread.start() return thread def _progress_hook(self, task_id: int, progress_callback: Callable): """Progress tracking hook for yt-dlp""" def hook(d): if d['status'] == 'downloading': if 'total_bytes' in d: progress = (d['downloaded_bytes'] / d['total_bytes']) * 100 elif 'total_bytes_estimate' in d: progress = (d['downloaded_bytes'] / d['total_bytes_estimate']) * 100 else: progress = 0 progress_callback(task_id, progress, TaskStatus.DOWNLOADING) elif d['status'] == 'finished': filename = d.get('filename') if filename: progress_callback(task_id, 100.0, TaskStatus.COMPLETED, filename) return hook
|