|
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 |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.INFO, |
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', |
|
handlers=[ |
|
|
|
logging.StreamHandler() |
|
] |
|
) |
|
|
|
|
|
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() |
|
|
|
|
|
header = [line.strip() for line in lines if line.startswith('#')] |
|
|
|
cookies = [line.strip() for line in lines if line.strip() and not line.startswith('#')] |
|
|
|
|
|
content = '\\n'.join(header + [''] + cookies) |
|
|
|
|
|
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: |
|
|
|
if '="' not in env_content: |
|
raise ValueError("Invalid env content format") |
|
|
|
content = env_content.split('="', 1)[1].strip('"') |
|
|
|
|
|
cookie_content = content.replace('\\n', '\n') |
|
|
|
|
|
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) |
|
|
|
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() |
|
env_content = os.getenv('FIREFOX_COOKIES', "") |
|
|
|
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', "") |
|
|
|
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() |
|
|
|
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") |
|
|
|
|
|
temp_dir = Path(tempfile.mkdtemp()) |
|
|
|
try: |
|
|
|
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 |
|
} |
|
|
|
|
|
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": |
|
|
|
|
|
|
|
|
|
|
|
|
|
cookiefile = "firefox-cookies.txt" |
|
env_to_cookies_from_env("firefox-cookies.txt") |
|
|
|
|
|
logger.info(f"Using Firefox cookies: {USE_FIREFOX_COOKIES}") |
|
opts["cookiefile"] = "firefox-cookies.txt" |
|
else: |
|
opts["no_cookies"] = True |
|
|
|
|
|
logger.info(f"Downloading {url} with options: {opts}") |
|
with YoutubeDL(opts) as ydl: |
|
info = ydl.extract_info(url, download=True) |
|
|
|
|
|
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}" |
|
|
|
|
|
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) |
|
|
|
error_msg = str(e) |
|
if "ERROR: [youtube]" in error_msg: |
|
|
|
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("""## <span style='display: flex; align-items: center; gap: 10px;'><img src='https://cdn-icons-png.flaticon.com/512/1384/1384060.png' width='25'/> <img src='https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Youtube_Music_icon.svg/2048px-Youtube_Music_icon.svg.png' width='25'/>YouTube Downloader</span>""") |
|
|
|
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.<br> |
|
To run it in HF deployed space, cookies are required to access YouTube.<br> |
|
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 |
|
) |