developer28 commited on
Commit
758e755
·
verified ·
1 Parent(s): 9de2267

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +392 -0
app.py ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import yt_dlp
3
+ import os
4
+ import tempfile
5
+ import shutil
6
+ from pathlib import Path
7
+ import re
8
+
9
+ class YouTubeDownloader:
10
+ def __init__(self):
11
+ self.download_dir = tempfile.mkdtemp()
12
+
13
+ def is_valid_youtube_url(self, url):
14
+ """Validate if the URL is a valid YouTube URL"""
15
+ youtube_regex = re.compile(
16
+ r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/'
17
+ r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})'
18
+ )
19
+ return youtube_regex.match(url) is not None
20
+
21
+ def get_video_info(self, url, progress=gr.Progress()):
22
+ """Get detailed video information without downloading"""
23
+ if not url or not url.strip():
24
+ return None, "❌ Please enter a YouTube URL"
25
+
26
+ if not self.is_valid_youtube_url(url):
27
+ return None, "❌ Invalid YouTube URL. Please enter a valid YouTube video URL"
28
+
29
+ try:
30
+ progress(0.2, desc="Fetching video information...")
31
+
32
+ # Configure yt-dlp options for info extraction
33
+ ydl_opts = {
34
+ 'noplaylist': True,
35
+ 'extract_flat': False,
36
+ }
37
+
38
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
39
+ try:
40
+ info = ydl.extract_info(url, download=False)
41
+
42
+ # Extract detailed information
43
+ video_info = {
44
+ 'title': info.get('title', 'Unknown'),
45
+ 'description': info.get('description', 'No description available'),
46
+ 'duration': info.get('duration', 0),
47
+ 'view_count': info.get('view_count', 0),
48
+ 'like_count': info.get('like_count', 0),
49
+ 'comment_count': info.get('comment_count', 0),
50
+ 'upload_date': info.get('upload_date', ''),
51
+ 'uploader': info.get('uploader', 'Unknown'),
52
+ 'channel': info.get('channel', 'Unknown'),
53
+ 'channel_followers': info.get('channel_follower_count', 0),
54
+ 'tags': info.get('tags', []),
55
+ 'categories': info.get('categories', []),
56
+ 'thumbnail': info.get('thumbnail', ''),
57
+ 'webpage_url': info.get('webpage_url', url)
58
+ }
59
+
60
+ progress(1.0, desc="Information retrieved!")
61
+
62
+ return video_info, "✅ Video information retrieved successfully"
63
+
64
+ except yt_dlp.DownloadError as e:
65
+ error_msg = str(e)
66
+ if "Video unavailable" in error_msg:
67
+ return None, "❌ Video is unavailable or private"
68
+ elif "age-restricted" in error_msg.lower():
69
+ return None, "❌ Video is age-restricted"
70
+ else:
71
+ return None, f"❌ Failed to get video info: {error_msg}"
72
+
73
+ except Exception as e:
74
+ return None, f"❌ An unexpected error occurred: {str(e)}"
75
+
76
+ def download_video(self, url, progress=gr.Progress()):
77
+ """Download YouTube video using yt-dlp"""
78
+ if not url or not url.strip():
79
+ return None, "❌ Please enter a YouTube URL"
80
+
81
+ if not self.is_valid_youtube_url(url):
82
+ return None, "❌ Invalid YouTube URL. Please enter a valid YouTube video URL"
83
+
84
+ try:
85
+ progress(0.1, desc="Initializing download...")
86
+
87
+ # Configure yt-dlp options
88
+ ydl_opts = {
89
+ 'format': 'best[ext=mp4]/best', # Prefer mp4 format
90
+ 'outtmpl': os.path.join(self.download_dir, '%(title)s.%(ext)s'),
91
+ 'noplaylist': True, # Download only single video, not playlist
92
+ }
93
+
94
+ progress(0.3, desc="Fetching video information...")
95
+
96
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
97
+ # Get video info first
98
+ try:
99
+ info = ydl.extract_info(url, download=False)
100
+ video_title = info.get('title', 'Unknown')
101
+ duration = info.get('duration', 0)
102
+
103
+ progress(0.5, desc=f"Downloading: {video_title[:50]}...")
104
+
105
+ # Download the video
106
+ ydl.download([url])
107
+
108
+ progress(0.9, desc="Finalizing download...")
109
+
110
+ # Find the downloaded file
111
+ downloaded_files = list(Path(self.download_dir).glob('*'))
112
+ if downloaded_files:
113
+ downloaded_file = downloaded_files[0]
114
+ file_size = downloaded_file.stat().st_size / (1024 * 1024) # Size in MB
115
+
116
+ progress(1.0, desc="Download completed!")
117
+
118
+ success_message = f"✅ Successfully downloaded: {video_title}\n"
119
+ success_message += f"📁 File size: {file_size:.1f} MB\n"
120
+ success_message += f"⏱️ Duration: {duration//60}:{duration%60:02d}" if duration else ""
121
+
122
+ return str(downloaded_file), success_message
123
+ else:
124
+ return None, "❌ Download completed but file not found"
125
+
126
+ except yt_dlp.DownloadError as e:
127
+ error_msg = str(e)
128
+ if "Video unavailable" in error_msg:
129
+ return None, "❌ Video is unavailable or private"
130
+ elif "age-restricted" in error_msg.lower():
131
+ return None, "❌ Video is age-restricted and cannot be downloaded"
132
+ elif "copyright" in error_msg.lower():
133
+ return None, "❌ Video cannot be downloaded due to copyright restrictions"
134
+ else:
135
+ return None, f"❌ Download failed: {error_msg}"
136
+
137
+ except Exception as e:
138
+ return None, f"❌ An unexpected error occurred: {str(e)}"
139
+
140
+ def format_video_info(self, video_info):
141
+ """Format video information for display"""
142
+ if not video_info:
143
+ return "No video information available"
144
+
145
+ # Format duration
146
+ duration = video_info.get('duration', 0)
147
+ if duration:
148
+ minutes = duration // 60
149
+ seconds = duration % 60
150
+ duration_str = f"{minutes}:{seconds:02d}"
151
+ else:
152
+ duration_str = "Unknown"
153
+
154
+ # Format upload date
155
+ upload_date = video_info.get('upload_date', '')
156
+ if upload_date and len(upload_date) == 8:
157
+ formatted_date = f"{upload_date[6:8]}/{upload_date[4:6]}/{upload_date[:4]}"
158
+ else:
159
+ formatted_date = "Unknown"
160
+
161
+ # Format numbers with commas
162
+ def format_number(num):
163
+ if isinstance(num, (int, float)) and num > 0:
164
+ return f"{int(num):,}"
165
+ return "0"
166
+
167
+ # Get video details
168
+ title = video_info.get('title', 'Unknown')
169
+ channel = video_info.get('channel', 'Unknown')
170
+ uploader = video_info.get('uploader', 'Unknown')
171
+ views = format_number(video_info.get('view_count', 0))
172
+ likes = format_number(video_info.get('like_count', 0))
173
+ comments = format_number(video_info.get('comment_count', 0))
174
+ subscribers = format_number(video_info.get('channel_followers', 0))
175
+ description = video_info.get('description', 'No description available')
176
+ tags = video_info.get('tags', [])
177
+
178
+ # Create detailed analysis
179
+ info_text = f"""VIDEO ANALYSIS RESULTS
180
+ ========================
181
+
182
+ BASIC INFORMATION:
183
+ - Title: {title}
184
+ - Channel: {channel}
185
+ - Uploader: {uploader}
186
+ - Duration: {duration_str}
187
+ - Upload Date: {formatted_date}
188
+
189
+ ENGAGEMENT STATISTICS:
190
+ - Total Views: {views}
191
+ - Total Likes: {likes}
192
+ - Total Comments: {comments}
193
+ - Channel Subscribers: {subscribers}
194
+
195
+ CONTENT TAGS:
196
+ {', '.join(tags[:15]) if tags else 'No tags available'}
197
+
198
+ VIDEO DESCRIPTION:
199
+ {description[:1000]}{'...(truncated)' if len(description) > 1000 else ''}
200
+
201
+ ANALYSIS SUMMARY:
202
+ - Video has {views} views with {likes} likes
203
+ - Engagement rate based on likes to views ratio
204
+ - Video duration is {duration_str}
205
+ - Posted on {formatted_date}
206
+ - Contains {len(tags)} tags for discoverability"""
207
+
208
+ return info_text
209
+
210
+ def cleanup(self):
211
+ """Clean up temporary files"""
212
+ try:
213
+ shutil.rmtree(self.download_dir, ignore_errors=True)
214
+ except:
215
+ pass
216
+
217
+ # Initialize downloader
218
+ downloader = YouTubeDownloader()
219
+
220
+ # Create Gradio interface
221
+ def create_interface():
222
+ with gr.Blocks(
223
+ title="YouTube Video Downloader & Analyzer",
224
+ theme="soft",
225
+ css="""
226
+ .container {
227
+ max-width: 900px;
228
+ margin: 0 auto;
229
+ }
230
+ .header {
231
+ text-align: center;
232
+ margin-bottom: 2rem;
233
+ }
234
+ .instructions {
235
+ background-color: #f8f9fa;
236
+ padding: 1rem;
237
+ border-radius: 8px;
238
+ margin-bottom: 1rem;
239
+ }
240
+ """
241
+ ) as demo:
242
+
243
+ gr.HTML("""
244
+ <div class="header">
245
+ <h1>YouTube Video Downloader & Analyzer</h1>
246
+ <p>Download YouTube videos and get detailed analysis results</p>
247
+ </div>
248
+ """)
249
+
250
+ gr.HTML("""
251
+ <div class="instructions">
252
+ <h3>How to use:</h3>
253
+ <ul>
254
+ <li>1. Enter a YouTube video URL</li>
255
+ <li>2. Click "Download Video" to download the video file</li>
256
+ <li>3. After download, click "Show Analysis Results" to see detailed video information</li>
257
+ <li>4. View comprehensive statistics including views, likes, comments, description, and more</li>
258
+ </ul>
259
+ <p><strong>Note:</strong> This tool respects YouTube's terms of service. Only download videos you have permission to download.</p>
260
+ </div>
261
+ """)
262
+
263
+ # State to store video info
264
+ video_info_state = gr.State(value=None)
265
+
266
+ url_input = gr.Textbox(
267
+ label="YouTube URL",
268
+ placeholder="https://www.youtube.com/watch?v=...",
269
+ lines=1,
270
+ max_lines=1
271
+ )
272
+
273
+ download_btn = gr.Button(
274
+ "Download Video",
275
+ variant="primary",
276
+ size="lg"
277
+ )
278
+
279
+ status_output = gr.Textbox(
280
+ label="Download Status",
281
+ lines=3,
282
+ max_lines=5,
283
+ interactive=False
284
+ )
285
+
286
+ file_output = gr.File(
287
+ label="Downloaded Video",
288
+ visible=False
289
+ )
290
+
291
+ analysis_btn = gr.Button(
292
+ "Show Analysis Results",
293
+ variant="secondary",
294
+ size="lg",
295
+ visible=False
296
+ )
297
+
298
+ analysis_output = gr.Textbox(
299
+ label="Video Analysis Results",
300
+ lines=20,
301
+ max_lines=25,
302
+ interactive=False,
303
+ visible=False
304
+ )
305
+
306
+ # Event handlers
307
+ def handle_download(url):
308
+ if not url or not url.strip():
309
+ return "Please enter a YouTube URL", gr.File(visible=False), gr.Button(visible=False), None
310
+
311
+ # First get video info and store it
312
+ video_info, info_message = downloader.get_video_info(url)
313
+
314
+ # Then download the video
315
+ file_path, download_message = downloader.download_video(url)
316
+
317
+ if file_path and video_info:
318
+ success_message = f"{download_message}\n\nVideo downloaded successfully! Click 'Show Analysis Results' to see detailed information."
319
+ return (
320
+ success_message,
321
+ gr.File(value=file_path, visible=True),
322
+ gr.Button(visible=True),
323
+ video_info
324
+ )
325
+ elif file_path:
326
+ return (
327
+ f"{download_message}\n\nVideo downloaded but analysis data unavailable.",
328
+ gr.File(value=file_path, visible=True),
329
+ gr.Button(visible=False),
330
+ None
331
+ )
332
+ else:
333
+ return (
334
+ download_message,
335
+ gr.File(visible=False),
336
+ gr.Button(visible=False),
337
+ None
338
+ )
339
+
340
+ def show_analysis(video_info):
341
+ if video_info:
342
+ formatted_info = downloader.format_video_info(video_info)
343
+ return formatted_info, gr.Textbox(visible=True)
344
+ else:
345
+ return "No analysis data available.", gr.Textbox(visible=True)
346
+
347
+ # Button click events
348
+ download_btn.click(
349
+ fn=handle_download,
350
+ inputs=[url_input],
351
+ outputs=[status_output, file_output, analysis_btn, video_info_state],
352
+ show_progress="full"
353
+ )
354
+
355
+ analysis_btn.click(
356
+ fn=show_analysis,
357
+ inputs=[video_info_state],
358
+ outputs=[analysis_output, analysis_output]
359
+ )
360
+
361
+ # Allow Enter key to trigger download
362
+ url_input.submit(
363
+ fn=handle_download,
364
+ inputs=[url_input],
365
+ outputs=[status_output, file_output, analysis_btn, video_info_state],
366
+ show_progress="full"
367
+ )
368
+
369
+ gr.HTML("""
370
+ <div style="margin-top: 2rem; text-align: center; color: #666;">
371
+ <p><strong>Legal Notice:</strong> Please ensure you have the right to download the content.
372
+ Respect copyright laws and YouTube's terms of service.</p>
373
+ </div>
374
+ """)
375
+
376
+ return demo
377
+
378
+ # Create and launch the app
379
+ if __name__ == "__main__":
380
+ demo = create_interface()
381
+
382
+ # Clean up on exit
383
+ import atexit
384
+ atexit.register(downloader.cleanup)
385
+
386
+ # Launch the app
387
+ demo.launch(
388
+ server_name="0.0.0.0",
389
+ server_port=5000,
390
+ share=False,
391
+ show_error=True
392
+ )