developer28's picture
Update app.py
312b1c2 verified
raw
history blame
15.2 kB
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