import logging import gradio as gr from yt_dlp import YoutubeDL import os from dotenv import load_dotenv from pathlib import Path import tempfile import shutil from pathlib import Path # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ #logging.FileHandler('youtube_downloader.log'), logging.StreamHandler() ] ) # Create logger instance logger = logging.getLogger('youtube_downloader') def cookies_to_env(cookie_file_path: str) -> str: """Convert cookie file content to environment variable format""" try: with open(cookie_file_path, 'r') as f: lines = f.readlines() # Keep header comments header = [line.strip() for line in lines if line.startswith('#')] # Get cookie content (non-comment lines) cookies = [line.strip() for line in lines if line.strip() and not line.startswith('#')] # Join with escaped newlines content = '\\n'.join(header + [''] + cookies) # Empty line after headers # Create env file content return f'FIREFOX_COOKIES="{content}"' except Exception as e: raise ValueError(f"Error converting cookie file: {str(e)}") def env_to_cookies(env_content: str, output_file: str) -> None: """Convert environment variable content back to cookie file""" try: # Extract content from env format if '="' not in env_content: raise ValueError("Invalid env content format") content = env_content.split('="', 1)[1].strip('"') # Replace escaped newlines with actual newlines cookie_content = content.replace('\\n', '\n') # Write to cookie file with open(output_file, 'w') as f: f.write(cookie_content) except Exception as e: raise ValueError(f"Error converting to cookie file: {str(e)}") def save_to_env_file(env_content: str, env_file: str = '.env') -> None: """Save environment variable content to .env file""" try: with open(env_file, 'w') as f: f.write(env_content) #print(f"Successfully saved to {env_file}") except Exception as e: raise ValueError(f"Error saving to env file: {str(e)}") def env_to_cookies_from_env(output_file: str) -> None: """Convert environment variable from .env file to cookie file""" try: load_dotenv() # Load from .env file env_content = os.getenv('FIREFOX_COOKIES', "") #print(f"Printing env content: \n{env_content}") if not env_content: raise ValueError("FIREFOX_COOKIES not found in .env file") env_to_cookies(f'FIREFOX_COOKIES="{env_content}"', output_file) except Exception as e: raise ValueError(f"Error converting to cookie file: {str(e)}") def get_cookies(): """Get cookies from environment variable""" load_dotenv() cookie_content = os.getenv('FIREFOX_COOKIES', "") #print(cookie_content) if not cookie_content: raise ValueError("FIREFOX_COOKIES environment variable not set") return cookie_content def create_temp_cookie_file(): """Create temporary cookie file from environment variable""" temp_cookie = tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt') try: cookie_content = get_cookies() # Replace escaped newlines with actual newlines cookie_content = cookie_content.replace('\\n', '\n') temp_cookie.write() temp_cookie.flush() return Path(temp_cookie.name) finally: temp_cookie.close() def download_for_browser(url, mode='audio', quality='high'): if not url: return None, "Please enter a valid URL" logger.info(f"Downloading {url} in {mode} mode with {quality} quality") # Create temporary directory that persists until file is served temp_dir = Path(tempfile.mkdtemp()) try: # Configure download options opts = { 'format': 'bestaudio/best' if mode == 'audio' else 'bestvideo+bestaudio/best', 'outtmpl': str(temp_dir / '%(title)s.%(ext)s'), 'restrictfilenames': True, 'windowsfilenames': True, 'quiet': True, 'no_warnings': True } # Add format-specific options if mode == 'audio': opts.update({ 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '320' if quality == 'high' else '192', }], 'prefer_ffmpeg': True, 'keepvideo': False }) else: opts.update({ 'format': 'bestvideo+bestaudio/best' if quality == 'high' else 'best[height<=720]', 'merge_output_format': 'mp4' }) load_dotenv() USE_FIREFOX_COOKIES = os.getenv("USE_FIREFOX_COOKIES", "False") if USE_FIREFOX_COOKIES == "True": # Convert cookie file to env and save locally #cookie_file = "cookies.firefox-private.txt" #env_content = cookies_to_env(cookie_file) #save_to_env_file(env_content) # Convert back to cookie file using .env cookiefile = "firefox-cookies.txt" env_to_cookies_from_env("firefox-cookies.txt") # Add cookies logger.info(f"Using Firefox cookies: {USE_FIREFOX_COOKIES}") opts["cookiefile"] = "firefox-cookies.txt" #create_temp_cookie_file() else: opts["no_cookies"] = True # Forces yt-dlp to access YouTube without any authentication # Download file logger.info(f"Downloading {url} with options: {opts}") with YoutubeDL(opts) as ydl: info = ydl.extract_info(url, download=True) # Find downloaded file files = list(temp_dir.glob('*')) if not files: return None, "Download failed - no files found" download_file = files[0] if not download_file.exists(): return None, f"File not found after download: {download_file.name}" # Return file while it exists in temp directory logger.info(f"Downloaded file: {download_file.name}") return str(download_file), f"Successfully converted: {download_file.name}" except Exception as e: if temp_dir.exists(): shutil.rmtree(temp_dir) # Return raw error message without formatting error_msg = str(e) if "ERROR: [youtube]" in error_msg: # Extract everything after "ERROR: [youtube]" error_msg = error_msg.split("ERROR: [youtube]")[1].strip() logger.error(f"Download error: {error_msg}") return None, error_msg def create_browser_ui(): with gr.Blocks(title="YouTube Downloader", theme=gr.themes.Soft()) as demo: gr.Markdown("""## YouTube Downloader""") with gr.Row(): with gr.Column(scale=1): url_input = gr.Textbox( label="YouTube URL", placeholder="https://youtube.com/watch?v=...", scale=1 ) mode_input = gr.Radio( choices=["audio", "video"], value="audio", label="Format" ) quality_input = gr.Radio( choices=["high", "medium"], value="high", label="Quality" ) download_button = gr.Button("Convert", variant="primary") with gr.Column(scale=1): status_text = gr.Textbox(label="Converting Status", interactive=False) output_file = gr.File(label="Download to your device ...") gr.Markdown("""### ⓘ Note: The downloader currently works only when running locally.
To run it in HF deployed space, cookies are required to access YouTube.
For more details, visit [README](https://huggingface.co/spaces/prasanthntu/youtube-downloader/blob/main/README.md)""", label="Note", show_label=True, container=True) download_button.click( fn=download_for_browser, inputs=[url_input, mode_input, quality_input], outputs=[output_file, status_text] ) return demo demo = create_browser_ui() demo.launch( share=False, debug=True, show_error=True )