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()): """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, } 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()): """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 } 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("""

YouTube Video Downloader & Analyzer

Download YouTube videos and get detailed analysis results

""") gr.HTML("""

How to use:

Note: This tool respects YouTube's terms of service. Only download videos you have permission to download.

""") # 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 ) 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 # First get video info and store it video_info, info_message = downloader.get_video_info(url) # Then download the video file_path, download_message = downloader.download_video(url) 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], 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], outputs=[status_output, file_output, analysis_btn, video_info_state], show_progress="full" ) gr.HTML("""

Legal Notice: Please ensure you have the right to download the content. Respect copyright laws and YouTube's terms of service.

""") 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 )