from pathlib import Path import yt_dlp import os from concurrent.futures import ThreadPoolExecutor import logging import shutil class LinkDownloader: def __init__(self, output_dir, max_workers=2): self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self.executor = ThreadPoolExecutor(max_workers=max_workers) # Setup logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) self.logger = logging.getLogger(__name__) def download_from_url(self, url): """Downloads a video from the given URL using yt_dlp.""" # Create a unique subdirectory to prevent file conflicts unique_download_dir = self.output_dir / "download" unique_download_dir.mkdir(parents=True, exist_ok=True) temp_filepath = str(unique_download_dir / 'downloaded_video.%(ext)s') ydl_opts = { 'format': 'best[height<=1080]', 'outtmpl': temp_filepath, 'quiet': False, 'no_warnings': True, 'extract_audio': False, 'concurrent_fragment_downloads': 3, 'file_access_retries': 3, 'retries': 3, 'socket_timeout': 30, 'nocheckcertificate': True, } try: with yt_dlp.YoutubeDL(ydl_opts) as ydl: # Get video information info = ydl.extract_info(url, download=False) ext = info.get('ext', 'mp4') # Submit download task to executor future = self.executor.submit(ydl.download, [url]) future.result() # Wait for download to complete # Find the downloaded file downloaded_files = list(unique_download_dir.glob(f'downloaded_video.{ext}')) if not downloaded_files: self.logger.error("No files found after download.") return None # Take the first file (should be the only one) actual_filepath = str(downloaded_files[0]) if os.path.exists(actual_filepath): # Copy the file to the main output directory to prevent deletion final_filepath = str(self.output_dir / f"downloaded_video.{ext}") shutil.copy2(actual_filepath, final_filepath) self.logger.info(f"Download completed: {final_filepath}") return final_filepath else: self.logger.error("File not found after download.") return None except yt_dlp.DownloadError as e: self.logger.error(f"yt_dlp error: {e}") except Exception as e: self.logger.error(f"Unexpected error: {e}") return None def __del__(self): """Ensure proper shutdown of the executor.""" self.executor.shutdown(wait=True) self.logger.info("ThreadPoolExecutor shut down.")