Spaces:
Sleeping
Sleeping
import gradio as gr | |
import yt_dlp | |
import os | |
import tempfile | |
import shutil | |
from pathlib import Path | |
import re | |
class YouTubeDownloader: | |
def __init__(self): | |
self.download_dir = tempfile.mkdtemp() | |
def is_valid_youtube_url(self, url): | |
"""Validate if the URL is a valid YouTube URL""" | |
youtube_regex = re.compile( | |
r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/' | |
r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})' | |
) | |
return youtube_regex.match(url) is not None | |
def get_video_info(self, url, progress=gr.Progress(),cookiefile=None): | |
"""Get detailed video information without downloading""" | |
if not url or not url.strip(): | |
return None, "β Please enter a YouTube URL" | |
if not self.is_valid_youtube_url(url): | |
return None, "β Invalid YouTube URL. Please enter a valid YouTube video URL" | |
try: | |
progress(0.2, desc="Fetching video information...") | |
# Configure yt-dlp options for info extraction | |
ydl_opts = { | |
'noplaylist': True, | |
'extract_flat': False, | |
} | |
if cookiefile: | |
ydl_opts['cookiefile'] = cookiefile | |
with yt_dlp.YoutubeDL(ydl_opts) as ydl: | |
try: | |
info = ydl.extract_info(url, download=False) | |
# Extract detailed information | |
video_info = { | |
'title': info.get('title', 'Unknown'), | |
'description': info.get('description', 'No description available'), | |
'duration': info.get('duration', 0), | |
'view_count': info.get('view_count', 0), | |
'like_count': info.get('like_count', 0), | |
'comment_count': info.get('comment_count', 0), | |
'upload_date': info.get('upload_date', ''), | |
'uploader': info.get('uploader', 'Unknown'), | |
'channel': info.get('channel', 'Unknown'), | |
'channel_followers': info.get('channel_follower_count', 0), | |
'tags': info.get('tags', []), | |
'categories': info.get('categories', []), | |
'thumbnail': info.get('thumbnail', ''), | |
'webpage_url': info.get('webpage_url', url) | |
} | |
progress(1.0, desc="Information retrieved!") | |
return video_info, "β Video information retrieved successfully" | |
except yt_dlp.DownloadError as e: | |
error_msg = str(e) | |
if "Video unavailable" in error_msg: | |
return None, "β Video is unavailable or private" | |
elif "age-restricted" in error_msg.lower(): | |
return None, "β Video is age-restricted" | |
else: | |
return None, f"β Failed to get video info: {error_msg}" | |
except Exception as e: | |
return None, f"β An unexpected error occurred: {str(e)}" | |
def download_video(self, url, progress=gr.Progress(),cookiefile=None): | |
"""Download YouTube video using yt-dlp""" | |
if not url or not url.strip(): | |
return None, "β Please enter a YouTube URL" | |
if not self.is_valid_youtube_url(url): | |
return None, "β Invalid YouTube URL. Please enter a valid YouTube video URL" | |
try: | |
progress(0.1, desc="Initializing download...") | |
# Configure yt-dlp options | |
ydl_opts = { | |
'format': 'best[ext=mp4]/best', # Prefer mp4 format | |
'outtmpl': os.path.join(self.download_dir, '%(title)s.%(ext)s'), | |
'noplaylist': True, # Download only single video, not playlist | |
} | |
if cookiefile: | |
ydl_opts['cookiefile'] = cookiefile | |
progress(0.3, desc="Fetching video information...") | |
with yt_dlp.YoutubeDL(ydl_opts) as ydl: | |
# Get video info first | |
try: | |
info = ydl.extract_info(url, download=False) | |
video_title = info.get('title', 'Unknown') | |
duration = info.get('duration', 0) | |
progress(0.5, desc=f"Downloading: {video_title[:50]}...") | |
# Download the video | |
ydl.download([url]) | |
progress(0.9, desc="Finalizing download...") | |
# Find the downloaded file | |
downloaded_files = list(Path(self.download_dir).glob('*')) | |
if downloaded_files: | |
downloaded_file = downloaded_files[0] | |
file_size = downloaded_file.stat().st_size / (1024 * 1024) # Size in MB | |
progress(1.0, desc="Download completed!") | |
success_message = f"β Successfully downloaded: {video_title}\n" | |
success_message += f"π File size: {file_size:.1f} MB\n" | |
success_message += f"β±οΈ Duration: {duration//60}:{duration%60:02d}" if duration else "" | |
return str(downloaded_file), success_message | |
else: | |
return None, "β Download completed but file not found" | |
except yt_dlp.DownloadError as e: | |
error_msg = str(e) | |
if "Video unavailable" in error_msg: | |
return None, "β Video is unavailable or private" | |
elif "age-restricted" in error_msg.lower(): | |
return None, "β Video is age-restricted and cannot be downloaded" | |
elif "copyright" in error_msg.lower(): | |
return None, "β Video cannot be downloaded due to copyright restrictions" | |
else: | |
return None, f"β Download failed: {error_msg}" | |
except Exception as e: | |
return None, f"β An unexpected error occurred: {str(e)}" | |
def format_video_info(self, video_info): | |
"""Format video information for display""" | |
if not video_info: | |
return "No video information available" | |
# Format duration | |
duration = video_info.get('duration', 0) | |
if duration: | |
minutes = duration // 60 | |
seconds = duration % 60 | |
duration_str = f"{minutes}:{seconds:02d}" | |
else: | |
duration_str = "Unknown" | |
# Format upload date | |
upload_date = video_info.get('upload_date', '') | |
if upload_date and len(upload_date) == 8: | |
formatted_date = f"{upload_date[6:8]}/{upload_date[4:6]}/{upload_date[:4]}" | |
else: | |
formatted_date = "Unknown" | |
# Format numbers with commas | |
def format_number(num): | |
if isinstance(num, (int, float)) and num > 0: | |
return f"{int(num):,}" | |
return "0" | |
# Get video details | |
title = video_info.get('title', 'Unknown') | |
channel = video_info.get('channel', 'Unknown') | |
uploader = video_info.get('uploader', 'Unknown') | |
views = format_number(video_info.get('view_count', 0)) | |
likes = format_number(video_info.get('like_count', 0)) | |
comments = format_number(video_info.get('comment_count', 0)) | |
subscribers = format_number(video_info.get('channel_followers', 0)) | |
description = video_info.get('description', 'No description available') | |
tags = video_info.get('tags', []) | |
# Create detailed analysis | |
info_text = f"""VIDEO ANALYSIS RESULTS | |
======================== | |
BASIC INFORMATION: | |
- Title: {title} | |
- Channel: {channel} | |
- Uploader: {uploader} | |
- Duration: {duration_str} | |
- Upload Date: {formatted_date} | |
ENGAGEMENT STATISTICS: | |
- Total Views: {views} | |
- Total Likes: {likes} | |
- Total Comments: {comments} | |
- Channel Subscribers: {subscribers} | |
CONTENT TAGS: | |
{', '.join(tags[:15]) if tags else 'No tags available'} | |
VIDEO DESCRIPTION: | |
{description[:1000]}{'...(truncated)' if len(description) > 1000 else ''} | |
ANALYSIS SUMMARY: | |
- Video has {views} views with {likes} likes | |
- Engagement rate based on likes to views ratio | |
- Video duration is {duration_str} | |
- Posted on {formatted_date} | |
- Contains {len(tags)} tags for discoverability""" | |
return info_text | |
def cleanup(self): | |
"""Clean up temporary files""" | |
try: | |
shutil.rmtree(self.download_dir, ignore_errors=True) | |
except: | |
pass | |
# Initialize downloader | |
downloader = YouTubeDownloader() | |
# Create Gradio interface | |
def create_interface(): | |
with gr.Blocks( | |
title="YouTube Video Downloader & Analyzer", | |
theme="soft", | |
css=""" | |
.container { | |
max-width: 900px; | |
margin: 0 auto; | |
} | |
.header { | |
text-align: center; | |
margin-bottom: 2rem; | |
} | |
.instructions { | |
background-color: #f8f9fa; | |
padding: 1rem; | |
border-radius: 8px; | |
margin-bottom: 1rem; | |
} | |
""" | |
) as demo: | |
gr.HTML(""" | |
<div class="header"> | |
<h1>YouTube Video Downloader & Analyzer</h1> | |
<p>Download YouTube videos and get detailed analysis results</p> | |
</div> | |
""") | |
gr.HTML(""" | |
<div class="instructions"> | |
<h3>How to use:</h3> | |
<ul> | |
<li>1. Enter a YouTube video URL</li> | |
<li>2. Click "Download Video" to download the video file</li> | |
<li>3. After download, click "Show Analysis Results" to see detailed video information</li> | |
<li>4. View comprehensive statistics including views, likes, comments, description, and more</li> | |
</ul> | |
<p><strong>Note:</strong> This tool respects YouTube's terms of service. Only download videos you have permission to download.</p> | |
</div> | |
""") | |
# State to store video info | |
video_info_state = gr.State(value=None) | |
url_input = gr.Textbox( | |
label="YouTube URL", | |
placeholder="https://www.youtube.com/watch?v=...", | |
lines=1, | |
max_lines=1 | |
) | |
cookies_input = gr.File( | |
label="Upload cookies.txt (optional for age-restricted/protected videos)", | |
type="filepath", | |
file_types=[".txt"], | |
visible=True | |
) | |
download_btn = gr.Button( | |
"Download Video", | |
variant="primary", | |
size="lg" | |
) | |
status_output = gr.Textbox( | |
label="Download Status", | |
lines=3, | |
max_lines=5, | |
interactive=False | |
) | |
file_output = gr.File( | |
label="Downloaded Video", | |
visible=False | |
) | |
analysis_btn = gr.Button( | |
"Show Analysis Results", | |
variant="secondary", | |
size="lg", | |
visible=False | |
) | |
analysis_output = gr.Textbox( | |
label="Video Analysis Results", | |
lines=20, | |
max_lines=25, | |
interactive=False, | |
visible=False | |
) | |
# Event handlers | |
def handle_download(url): | |
if not url or not url.strip(): | |
return "Please enter a YouTube URL", gr.File(visible=False), gr.Button(visible=False), None | |
# Set cookies path if provided | |
cookiefile = cookies_path if cookies_path and os.path.exists(cookies_path) else None | |
# First get video info and store it | |
video_info, info_message = downloader.get_video_info(url,cookiefile=cookiefile) | |
# Then download the video | |
file_path, download_message = downloader.download_video(url,cookiefile=cookiefile) | |
if file_path and video_info: | |
success_message = f"{download_message}\n\nVideo downloaded successfully! Click 'Show Analysis Results' to see detailed information." | |
return ( | |
success_message, | |
gr.File(value=file_path, visible=True), | |
gr.Button(visible=True), | |
video_info | |
) | |
elif file_path: | |
return ( | |
f"{download_message}\n\nVideo downloaded but analysis data unavailable.", | |
gr.File(value=file_path, visible=True), | |
gr.Button(visible=False), | |
None | |
) | |
else: | |
return ( | |
download_message, | |
gr.File(visible=False), | |
gr.Button(visible=False), | |
None | |
) | |
def show_analysis(video_info): | |
if video_info: | |
formatted_info = downloader.format_video_info(video_info) | |
return formatted_info, gr.Textbox(visible=True) | |
else: | |
return "No analysis data available.", gr.Textbox(visible=True) | |
# Button click events | |
download_btn.click( | |
fn=handle_download, | |
inputs=[url_input,cookies_input], | |
outputs=[status_output, file_output, analysis_btn, video_info_state], | |
show_progress="full" | |
) | |
analysis_btn.click( | |
fn=show_analysis, | |
inputs=[video_info_state], | |
outputs=[analysis_output, analysis_output] | |
) | |
# Allow Enter key to trigger download | |
url_input.submit( | |
fn=handle_download, | |
inputs=[url_input,cookies_input], | |
outputs=[status_output, file_output, analysis_btn, video_info_state], | |
show_progress="full" | |
) | |
gr.HTML(""" | |
<div style="margin-top: 2rem; text-align: center; color: #666;"> | |
<p><strong>Legal Notice:</strong> Please ensure you have the right to download the content. | |
Respect copyright laws and YouTube's terms of service.</p> | |
</div> | |
""") | |
return demo | |
# Create and launch the app | |
if __name__ == "__main__": | |
demo = create_interface() | |
# Clean up on exit | |
import atexit | |
atexit.register(downloader.cleanup) | |
# Launch the app | |
demo.launch() | |
#server_name="0.0.0.0", | |
#server_port=5000, | |
#share=False, | |
#show_error=True | |