Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import yt_dlp | |
| import os | |
| import tempfile | |
| import shutil | |
| from pathlib import Path | |
| import re | |
| import uuid | |
| session_data = {} | |
| class YouTubeDownloader: | |
| def __init__(self): | |
| self.download_dir = tempfile.mkdtemp() | |
| def is_valid_youtube_url(self, 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 format_video_info(self, video_info): | |
| """Format video information into a readable report""" | |
| if not video_info: | |
| return "β No video information available." | |
| # Format duration | |
| duration = video_info.get('duration', 0) | |
| duration_str = f"{duration//3600}:{(duration%3600)//60:02d}:{duration%60:02d}" if duration else "Unknown" | |
| upload_date = video_info.get('upload_date', '') | |
| formatted_date = f"{upload_date[:4]}-{upload_date[4:6]}-{upload_date[6:8]}" if len(upload_date) == 8 else upload_date or "Unknown" | |
| # Format numbers | |
| def format_number(num): | |
| if num >= 1_000_000: | |
| return f"{num/1_000_000:.1f}M" | |
| elif num >= 1_000: | |
| return f"{num/1_000:.1f}K" | |
| else: | |
| return str(num) | |
| scene_descriptions = [] | |
| if duration: | |
| chunk = 3 | |
| for start in range(0, duration, chunk): | |
| end = min(start + chunk - 1, duration) | |
| description = f"Visual segment from {start}s to {end}s. (e.g., close-up, presenter talks, etc.)" | |
| scene_descriptions.append(f"* **[{start//60}:{start%60:02d}-{end//60}:{end%60:02d}]**: {description}") | |
| else: | |
| scene_descriptions.append("* No timestamped breakdown available.") | |
| tags = ' '.join(video_info.get('tags', [])).lower() | |
| title = video_info.get('title', '').lower() | |
| description_text = video_info.get('description', '').lower() | |
| channel = video_info.get('channel', '').lower() | |
| # π΅ Background music guess | |
| if any(word in description_text for word in ['calm music', 'soft', 'soothing']): | |
| music_style = "Calm" | |
| elif any(word in description_text for word in ['energetic', 'upbeat', 'lively']): | |
| music_style = "Upbeat" | |
| elif "music" not in description_text: | |
| music_style = "No music" | |
| else: | |
| music_style = "Unknown" | |
| # π€ Influencer / Celebrity Detection (Offline logic) | |
| known_names = [ | |
| "Kartik Aaryan", "Virat Kohli", "Deepika Padukone", "Alia Bhatt", "Ranveer Singh", | |
| "MrBeast", "PewDiePie", "CarryMinati", "Prajakta Koli", "Bhuvan Bam", | |
| "Amitabh Bachchan", "Katrina Kaif", "Salman Khan", "Kiara Advani", | |
| "Kylie Jenner", "Shahrukh Khan", "Ananya Pandey", "Ashish Chanchlani", | |
| "Sundar Pichai", "Elon Musk", "Taylor Swift" | |
| ] | |
| metadata = " ".join([ | |
| video_info.get('title', ''), | |
| video_info.get('description', ''), | |
| video_info.get('uploader', ''), | |
| video_info.get('channel', ''), | |
| ' '.join(video_info.get('tags', [])) | |
| ]).lower() | |
| matched = [name for name in known_names if name.lower() in metadata] | |
| if matched: | |
| influencer_note = f"Yes, known influencer/celebrity detected: {', '.join(matched)}" | |
| else: | |
| influencer_note = "No known influencer or celebrity detected." | |
| if any(word in metadata for word in ["actor", "brand ambassador", "featured", "with", "hosted by"]): | |
| influencer_note += " (Someone might be featured β check visually)" | |
| if "review" in title or "demo" in title or "how to" in title: | |
| video_type = "Educational" | |
| elif "ad" in title or "promo" in title or "launch" in title: | |
| video_type = "Promotional" | |
| elif "funny" in title or "challenge" in title: | |
| video_type = "Entertainment" | |
| else: | |
| video_type = "Informational" | |
| if any(word in description_text for word in ['excited', 'amazing', 'love']): | |
| emotion = "Positive" | |
| elif any(word in description_text for word in ['calm', 'soothing']): | |
| emotion = "Neutral" | |
| elif any(word in description_text for word in ['warning', 'serious']): | |
| emotion = "Serious" | |
| else: | |
| emotion = "Neutral" | |
| report = f""" | |
| πΉ VIDEO ANALYSIS REPORT | |
| {'='*50} | |
| π BASIC INFORMATION: | |
| β’ Title: {video_info.get('title', 'Unknown')} | |
| β’ Channel: {video_info.get('channel', 'Unknown')} | |
| β’ Uploader: {video_info.get('uploader', 'Unknown')} | |
| β’ Upload Date: {formatted_date} | |
| β’ Duration: {duration_str} | |
| π STATISTICS: | |
| β’ 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))} | |
| β’ Channel Followers: {format_number(video_info.get('channel_followers', 0))} | |
| π·οΈ CATEGORIES & TAGS: | |
| β’ Categories: {', '.join(video_info.get('categories', [])) or 'None'} | |
| β’ Tags: {', '.join(video_info.get('tags', [])[:10]) or 'None'} | |
| {('β’ More tags...' if len(video_info.get('tags', [])) > 10 else '')} | |
| π DESCRIPTION (first 500 chars): | |
| {video_info.get('description', 'No description available')[:500]} | |
| {'...' if len(video_info.get('description', '')) > 500 else ''} | |
| π¬ SCENE-BY-SCENE BREAKDOWN: | |
| {chr(10).join(scene_descriptions)} | |
| π΅ BACKGROUND MUSIC STYLE: {music_style} | |
| π€ INFLUENCER PRESENT: {influencer_note} | |
| π₯ VIDEO TYPE: {video_type} | |
| π OVERALL EMOTION: {emotion} | |
| π VIDEO URL: | |
| {video_info.get('webpage_url', 'Unknown')} | |
| """ | |
| return report.strip() | |
| # Build the report | |
| report = f""" | |
| πΉ VIDEO ANALYSIS REPORT | |
| {'='*50} | |
| π BASIC INFORMATION: | |
| β’ Title: {video_info.get('title', 'Unknown')} | |
| β’ Channel: {video_info.get('channel', 'Unknown')} | |
| β’ Uploader: {video_info.get('uploader', 'Unknown')} | |
| β’ Upload Date: {formatted_date} | |
| β’ Duration: {duration_str} | |
| π STATISTICS: | |
| β’ 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))} | |
| β’ Channel Followers: {format_number(video_info.get('channel_followers', 0))} | |
| π·οΈ CATEGORIES & TAGS: | |
| β’ Categories: {', '.join(video_info.get('categories', [])) or 'None'} | |
| β’ Tags: {', '.join(video_info.get('tags', [])[:10]) or 'None'} | |
| {('β’ More tags...' if len(video_info.get('tags', [])) > 10 else '')} | |
| π DESCRIPTION: | |
| {video_info.get('description', 'No description available')[:500]} | |
| {'...' if len(video_info.get('description', '')) > 500 else ''} | |
| π VIDEO URL: | |
| {video_info.get('webpage_url', 'Unknown')} | |
| """ | |
| return report.strip() | |
| def get_video_info(self, url, progress=gr.Progress(), cookiefile=None): | |
| 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...") | |
| 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) | |
| 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): | |
| 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...") | |
| ydl_opts = { | |
| 'format': 'best[ext=mp4]/best', | |
| 'outtmpl': os.path.join(self.download_dir, '%(title)s.%(ext)s'), | |
| 'noplaylist': True, | |
| } | |
| if cookiefile: | |
| ydl_opts['cookiefile'] = cookiefile | |
| progress(0.3, desc="Fetching video information...") | |
| with yt_dlp.YoutubeDL(ydl_opts) as ydl: | |
| 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]}...") | |
| ydl.download([url]) | |
| progress(0.9, desc="Finalizing download...") | |
| 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) | |
| 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 cleanup(self): | |
| try: | |
| shutil.rmtree(self.download_dir, ignore_errors=True) | |
| except: | |
| pass | |
| downloader = YouTubeDownloader() | |
| def create_interface(): | |
| with gr.Blocks(title="YouTube Video Downloader & Analyzer") as demo: | |
| url_input = gr.Textbox(label="YouTube URL", placeholder="https://www.youtube.com/watch?v=...") | |
| cookies_input = gr.File(label="Upload cookies.txt (optional)", type="filepath", file_types=[".txt"]) | |
| download_btn = gr.Button("Download Video") | |
| status_output = gr.Textbox(label="Download Status") | |
| file_output = gr.File(label="Downloaded Video", visible=False) | |
| analysis_btn = gr.Button("Show Analysis Results", visible=False) | |
| analysis_output = gr.Textbox(label="Video Analysis Results", visible=False, lines=20) | |
| video_info_state = gr.Textbox(value=False) | |
| def handle_download(url, cookies_path): | |
| if not url or not url.strip(): | |
| return "Please enter a YouTube URL", gr.File(visible=False), gr.Button(visible=False), None | |
| cookiefile = cookies_path.strip() if cookies_path and os.path.exists(cookies_path.strip()) else None | |
| video_info, info_message = downloader.get_video_info(url, cookiefile=cookiefile) | |
| file_path, download_message = downloader.download_video(url, cookiefile=cookiefile) | |
| print(f"[DEBUG] Download message: {download_message}") | |
| 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), gr.State(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), gr.State("") | |
| else: | |
| return f"β Download failed:\n{download_message}", gr.File(visible=False), gr.Button(visible=False), gr.State(None) | |
| def show_analysis(video_info): | |
| print("[DEBUG] Received session_id:", session_id) | |
| video_info = session_data.get(session_id) | |
| if video_info: | |
| return downloader.format_video_info(video_info), gr.Textbox(visible=True) | |
| return "β No analysis data available.", gr.Textbox(visible=True) | |
| download_btn.click(handle_download, inputs=[url_input, cookies_input], outputs=[status_output, file_output, analysis_btn, video_info_state]) | |
| analysis_btn.click(show_analysis, inputs=[video_info_state], outputs=[analysis_output]) | |
| url_input.submit(handle_download, inputs=[url_input, cookies_input], outputs=[status_output, file_output, analysis_btn, video_info_state]) | |
| return demo | |
| if __name__ == "__main__": | |
| demo = create_interface() | |
| import atexit | |
| atexit.register(downloader.cleanup) | |
| demo.launch() |