developer28 commited on
Commit
2c78469
Β·
verified Β·
1 Parent(s): 4fde749

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +539 -74
app.py CHANGED
@@ -10,25 +10,36 @@ import json
10
  from datetime import datetime
11
  import google.generativeai as genai
12
 
13
- # Configure Gemini
14
- genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
15
- model = genai.GenerativeModel(model_name="gemini-1.5-flash-latest")
16
-
17
  class YouTubeDownloader:
18
  def __init__(self):
19
  self.download_dir = tempfile.mkdtemp()
 
20
  self.temp_downloads = tempfile.mkdtemp(prefix="youtube_downloads_")
 
21
  self.downloads_folder = os.path.join(os.path.expanduser("~"), "Downloads", "YouTube_Downloads")
22
  os.makedirs(self.downloads_folder, exist_ok=True)
23
-
 
 
 
 
 
 
 
 
 
 
24
  def cleanup(self):
 
25
  try:
26
  if hasattr(self, 'download_dir') and os.path.exists(self.download_dir):
27
  shutil.rmtree(self.download_dir)
 
28
  if hasattr(self, 'temp_downloads') and os.path.exists(self.temp_downloads):
29
  shutil.rmtree(self.temp_downloads)
 
30
  except Exception as e:
31
- print(f"⚠️ Cleanup error: {e}")
32
 
33
  def is_valid_youtube_url(self, url):
34
  youtube_regex = re.compile(
@@ -38,50 +49,166 @@ class YouTubeDownloader:
38
  return youtube_regex.match(url) is not None
39
 
40
  def generate_scene_breakdown_gemini(self, video_info):
41
- title = video_info.get("title", "")
42
- description = video_info.get("description", "")
43
- duration = video_info.get("duration", 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
 
 
 
 
 
45
  if not duration:
46
- return ["[Duration Unknown]: Unable to generate scene breakdown."]
47
-
48
- prompt = f"""
49
- You are a scene breakdown expert. Based on the following video metadata, generate a detailed scene-by-scene breakdown using timestamps.
50
- Each scene should be 5–15 seconds long (estimate). Format should be:
51
-
52
- * **[0:00-0:10]:** A woman opens the door and looks outside.
53
-
54
- Use storytelling tone and avoid brand/product mentions. Do not invent company names.
55
-
56
- Video Title: {title}
57
- Description: {description}
58
- Duration (in seconds): {duration}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- Now generate the scene breakdown:
61
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
- try:
64
- response = model.generate_content(prompt)
65
- if response.text:
66
- return response.text.strip().split("\n")
67
- else:
68
- return ["[Error]: Gemini response was empty."]
69
- except Exception as e:
70
- return [f"[Error generating scenes with Gemini]: {str(e)}"]
 
 
 
 
 
 
 
71
 
72
- def get_video_info(self, url):
73
- if not self.is_valid_youtube_url(url):
74
- return None, "❌ Invalid YouTube URL"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- try:
77
- ydl_opts = {'noplaylist': True, 'extract_flat': False}
78
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
79
- info = ydl.extract_info(url, download=False)
80
- return info, "βœ… Video information extracted"
81
- except Exception as e:
82
- return None, f"❌ Error: {str(e)}"
 
 
 
83
 
84
  def format_video_info(self, video_info):
 
 
 
 
 
85
  title = video_info.get("title", "Unknown")
86
  uploader = video_info.get("uploader", "Unknown")
87
  duration = video_info.get("duration", 0)
@@ -90,48 +217,386 @@ Now generate the scene breakdown:
90
  like_count = video_info.get("like_count", 0)
91
  comment_count = video_info.get("comment_count", 0)
92
  upload_date = video_info.get("upload_date", "Unknown")
93
- formatted_date = f"{upload_date[:4]}-{upload_date[4:6]}-{upload_date[6:]}" if len(upload_date) == 8 else "Unknown"
 
 
 
 
 
94
 
 
95
  scene_descriptions = self.generate_scene_breakdown_gemini(video_info)
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- def fmt(n):
98
- if not n: return "0"
99
- if n > 1_000_000: return f"{n/1_000_000:.1f}M"
100
- if n > 1_000: return f"{n/1_000:.1f}K"
101
- return str(n)
102
-
103
- summary = f"""
104
  πŸ“‹ BASIC INFORMATION
105
- {'─'*30}
106
- πŸ“Ή Title: {title}
107
- πŸ‘€ Uploader: {uploader}
108
- πŸ“… Upload Date: {formatted_date}
109
- ⏱️ Duration: {duration_str}
110
- πŸ‘€ Views: {fmt(view_count)}
111
- πŸ‘ Likes: {fmt(like_count)}
112
- πŸ’¬ Comments: {fmt(comment_count)}
113
-
114
- 🎬 SCENE-BY-SCENE BREAKDOWN
 
 
 
 
 
 
 
 
 
 
 
115
  {'─'*30}
116
  {chr(10).join(scene_descriptions)}
 
 
 
 
 
 
 
 
 
117
  """
118
- return summary
 
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  downloader = YouTubeDownloader()
121
 
122
- def analyze_video(url):
123
- info, msg = downloader.get_video_info(url)
124
- if not info:
125
- return msg
126
- return downloader.format_video_info(info)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- iface = gr.Interface(
129
- fn=analyze_video,
130
- inputs=gr.Textbox(label="YouTube URL"),
131
- outputs=gr.Textbox(label="Gemini-Generated Scene Breakdown", lines=30, show_copy_button=True),
132
- title="🎬 Gemini Scene Breakdown",
133
- description="Generates scene-by-scene descriptions using Gemini Flash based on video metadata"
134
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
  if __name__ == "__main__":
137
- iface.launch(debug=True)
 
 
 
 
10
  from datetime import datetime
11
  import google.generativeai as genai
12
 
 
 
 
 
13
  class YouTubeDownloader:
14
  def __init__(self):
15
  self.download_dir = tempfile.mkdtemp()
16
+ # Use temp directory for Gradio compatibility
17
  self.temp_downloads = tempfile.mkdtemp(prefix="youtube_downloads_")
18
+ # Also create user downloads folder for copying
19
  self.downloads_folder = os.path.join(os.path.expanduser("~"), "Downloads", "YouTube_Downloads")
20
  os.makedirs(self.downloads_folder, exist_ok=True)
21
+ self.gemini_model = None
22
+
23
+ def configure_gemini(self, api_key):
24
+ """Configure Gemini API with the provided key"""
25
+ try:
26
+ genai.configure(api_key=api_key)
27
+ self.gemini_model = genai.GenerativeModel(model_name="gemini-1.5-flash-latest")
28
+ return True, "βœ… Gemini API configured successfully!"
29
+ except Exception as e:
30
+ return False, f"❌ Failed to configure Gemini API: {str(e)}"
31
+
32
  def cleanup(self):
33
+ """Clean up temporary directories and files"""
34
  try:
35
  if hasattr(self, 'download_dir') and os.path.exists(self.download_dir):
36
  shutil.rmtree(self.download_dir)
37
+ print(f"βœ… Cleaned up temporary directory: {self.download_dir}")
38
  if hasattr(self, 'temp_downloads') and os.path.exists(self.temp_downloads):
39
  shutil.rmtree(self.temp_downloads)
40
+ print(f"βœ… Cleaned up temp downloads directory: {self.temp_downloads}")
41
  except Exception as e:
42
+ print(f"⚠️ Warning: Could not clean up temporary directory: {e}")
43
 
44
  def is_valid_youtube_url(self, url):
45
  youtube_regex = re.compile(
 
49
  return youtube_regex.match(url) is not None
50
 
51
  def generate_scene_breakdown_gemini(self, video_info):
52
+ """Generate AI-powered scene breakdown using Gemini"""
53
+ if not self.gemini_model:
54
+ return self.generate_scene_breakdown_fallback(video_info)
55
+
56
+ try:
57
+ duration = video_info.get('duration', 0)
58
+ title = video_info.get('title', '')
59
+ description = video_info.get('description', '')[:1000] # Limit description length
60
+
61
+ if not duration:
62
+ return ["**[Duration Unknown]**: Unable to generate timestamped breakdown - video duration not available"]
63
+
64
+ # Create prompt for Gemini
65
+ prompt = f"""
66
+ Analyze this YouTube video and create a detailed scene-by-scene breakdown with timestamps:
67
+
68
+ Title: {title}
69
+ Duration: {duration} seconds
70
+ Description: {description}
71
+
72
+ Please provide a scene breakdown with the following format:
73
+ - Divide the video into logical segments based on typical content flow
74
+ - For videos under 2 minutes: 10-15 second segments
75
+ - For videos 2-10 minutes: 30-45 second segments
76
+ - For videos over 10 minutes: 60-90 second segments
77
+ - Maximum 15 scenes total
78
+
79
+ For each scene, provide:
80
+ **[START_TIME-END_TIME]**: Detailed description of what likely happens in this segment, including visual elements, audio cues, potential dialogue or narration, and scene transitions.
81
+
82
+ Consider the video type (tutorial, music video, vlog, etc.) and provide contextually appropriate descriptions.
83
+ Format timestamps as MM:SS.
84
+ """
85
+
86
+ response = self.gemini_model.generate_content(prompt)
87
+
88
+ # Parse the response into individual scenes
89
+ if response and response.text:
90
+ scenes = []
91
+ lines = response.text.split('\n')
92
+ for line in lines:
93
+ line = line.strip()
94
+ if line and ('**[' in line or line.startswith('*')):
95
+ scenes.append(line)
96
+
97
+ return scenes if scenes else self.generate_scene_breakdown_fallback(video_info)
98
+ else:
99
+ return self.generate_scene_breakdown_fallback(video_info)
100
+
101
+ except Exception as e:
102
+ print(f"Gemini API error: {e}")
103
+ return self.generate_scene_breakdown_fallback(video_info)
104
 
105
+ def generate_scene_breakdown_fallback(self, video_info):
106
+ """Fallback scene generation when Gemini is not available"""
107
+ duration = video_info.get('duration', 0)
108
+ title = video_info.get('title', '').lower()
109
+
110
  if not duration:
111
+ return ["**[Duration Unknown]**: Unable to generate timestamped breakdown"]
112
+
113
+ # Simple fallback logic
114
+ if duration <= 120:
115
+ segment_length = 15
116
+ elif duration <= 600:
117
+ segment_length = 45
118
+ else:
119
+ segment_length = 90
120
+
121
+ scenes = []
122
+ num_segments = min(duration // segment_length + 1, 15)
123
+
124
+ for i in range(num_segments):
125
+ start_time = i * segment_length
126
+ end_time = min(start_time + segment_length - 1, duration)
127
+
128
+ start_formatted = f"{start_time//60}:{start_time%60:02d}"
129
+ end_formatted = f"{end_time//60}:{end_time%60:02d}"
130
+
131
+ if i == 0:
132
+ desc = "Opening sequence with introduction and setup"
133
+ elif i == num_segments - 1:
134
+ desc = "Conclusion with final thoughts and call-to-action"
135
+ else:
136
+ desc = f"Main content segment {i} with key information and details"
137
+
138
+ scenes.append(f"**[{start_formatted}-{end_formatted}]**: {desc}")
139
+
140
+ return scenes
141
 
142
+ def detect_video_type(self, title, description):
143
+ """Detect video type based on title and description"""
144
+ text = (title + " " + description).lower()
145
+
146
+ if any(word in text for word in ['music', 'song', 'album', 'artist', 'band', 'lyrics']):
147
+ return "🎡 Music Video"
148
+ elif any(word in text for word in ['tutorial', 'how to', 'guide', 'learn', 'teaching']):
149
+ return "πŸ“š Tutorial/Educational"
150
+ elif any(word in text for word in ['funny', 'comedy', 'entertainment', 'vlog', 'challenge']):
151
+ return "🎭 Entertainment/Comedy"
152
+ elif any(word in text for word in ['news', 'breaking', 'report', 'update']):
153
+ return "πŸ“° News/Information"
154
+ elif any(word in text for word in ['review', 'unboxing', 'test', 'comparison']):
155
+ return "⭐ Review/Unboxing"
156
+ elif any(word in text for word in ['commercial', 'ad', 'brand', 'product']):
157
+ return "πŸ“Ί Commercial/Advertisement"
158
+ else:
159
+ return "🎬 General Content"
160
 
161
+ def detect_background_music(self, video_info):
162
+ """Detect background music style"""
163
+ title = video_info.get('title', '').lower()
164
+ description = video_info.get('description', '').lower()
165
+
166
+ if any(word in title for word in ['music', 'song', 'soundtrack']):
167
+ return "🎡 Original Music/Soundtrack - Primary audio content"
168
+ elif any(word in title for word in ['commercial', 'ad', 'brand']):
169
+ return "🎢 Upbeat Commercial Music - Designed to enhance brand appeal"
170
+ elif any(word in title for word in ['tutorial', 'how to', 'guide']):
171
+ return "πŸ”‡ Minimal/No Background Music - Focus on instruction"
172
+ elif any(word in title for word in ['vlog', 'daily', 'life']):
173
+ return "🎼 Ambient Background Music - Complementary to narration"
174
+ else:
175
+ return "🎡 Background Music - Complementing video mood and pacing"
176
 
177
+ def detect_influencer_status(self, video_info):
178
+ """Detect influencer status"""
179
+ subscriber_count = video_info.get('channel_followers', 0)
180
+ view_count = video_info.get('view_count', 0)
181
+
182
+ if subscriber_count > 10000000:
183
+ return "🌟 Mega Influencer (10M+ subscribers)"
184
+ elif subscriber_count > 1000000:
185
+ return "⭐ Major Influencer (1M+ subscribers)"
186
+ elif subscriber_count > 100000:
187
+ return "🎯 Mid-tier Influencer (100K+ subscribers)"
188
+ elif subscriber_count > 10000:
189
+ return "πŸ“ˆ Micro Influencer (10K+ subscribers)"
190
+ elif view_count > 100000:
191
+ return "πŸ”₯ Viral Content Creator"
192
+ else:
193
+ return "πŸ‘€ Regular Content Creator"
194
 
195
+ def format_number(self, num):
196
+ if num is None or num == 0:
197
+ return "0"
198
+ if num >= 1_000_000_000:
199
+ return f"{num/1_000_000_000:.1f}B"
200
+ elif num >= 1_000_000:
201
+ return f"{num/1_000_000:.1f}M"
202
+ elif num >= 1_000:
203
+ return f"{num/1_000:.1f}K"
204
+ return str(num)
205
 
206
  def format_video_info(self, video_info):
207
+ """Streamlined video information formatting"""
208
+ if not video_info:
209
+ return "❌ No video information available."
210
+
211
+ # Basic information
212
  title = video_info.get("title", "Unknown")
213
  uploader = video_info.get("uploader", "Unknown")
214
  duration = video_info.get("duration", 0)
 
217
  like_count = video_info.get("like_count", 0)
218
  comment_count = video_info.get("comment_count", 0)
219
  upload_date = video_info.get("upload_date", "Unknown")
220
+
221
+ # Format upload date
222
+ if len(upload_date) == 8:
223
+ formatted_date = f"{upload_date[:4]}-{upload_date[4:6]}-{upload_date[6:8]}"
224
+ else:
225
+ formatted_date = upload_date
226
 
227
+ # Generate enhanced analysis
228
  scene_descriptions = self.generate_scene_breakdown_gemini(video_info)
229
+ video_type = self.detect_video_type(title, video_info.get('description', ''))
230
+ background_music = self.detect_background_music(video_info)
231
+ influencer_status = self.detect_influencer_status(video_info)
232
+
233
+ # Calculate engagement metrics
234
+ engagement_rate = (like_count / view_count) * 100 if view_count > 0 else 0
235
+
236
+ # Generate streamlined report
237
+ report = f"""
238
+ 🎬 YOUTUBE VIDEO ANALYSIS REPORT
239
+ {'='*50}
240
 
 
 
 
 
 
 
 
241
  πŸ“‹ BASIC INFORMATION
242
+ {'─'*25}
243
+ πŸ“Ή **Title:** {title}
244
+ πŸ‘€ **Uploader:** {uploader}
245
+ πŸ“… **Upload Date:** {formatted_date}
246
+ ⏱️ **Duration:** {duration_str}
247
+ πŸ†” **Video ID:** {video_info.get('id', 'Unknown')}
248
+
249
+ πŸ“Š PERFORMANCE METRICS
250
+ {'─'*25}
251
+ πŸ‘€ **Views:** {self.format_number(view_count)} ({view_count:,})
252
+ πŸ‘ **Likes:** {self.format_number(like_count)} ({like_count:,})
253
+ πŸ’¬ **Comments:** {self.format_number(comment_count)} ({comment_count:,})
254
+ πŸ“ˆ **Engagement Rate:** {engagement_rate:.2f}%
255
+
256
+ 🎯 CONTENT ANALYSIS
257
+ {'─'*25}
258
+ πŸ“‚ **Video Type:** {video_type}
259
+ 🎡 **Background Music:** {background_music}
260
+ πŸ‘‘ **Creator Status:** {influencer_status}
261
+
262
+ 🎬 DETAILED SCENE BREAKDOWN
263
  {'─'*30}
264
  {chr(10).join(scene_descriptions)}
265
+
266
+ πŸ“ DESCRIPTION PREVIEW
267
+ {'─'*25}
268
+ {video_info.get('description', 'No description available')[:500]}
269
+ {'...(truncated)' if len(video_info.get('description', '')) > 500 else ''}
270
+
271
+ {'='*50}
272
+ πŸ“Š **Analysis completed:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
273
+ πŸ€– **AI Enhancement:** {'Gemini AI' if self.gemini_model else 'Standard Analysis'}
274
  """
275
+
276
+ return report.strip()
277
 
278
+ def get_video_info(self, url, progress=gr.Progress(), cookiefile=None):
279
+ """Extract video information"""
280
+ if not url or not url.strip():
281
+ return None, "❌ Please enter a YouTube URL"
282
+
283
+ if not self.is_valid_youtube_url(url):
284
+ return None, "❌ Invalid YouTube URL format"
285
+
286
+ try:
287
+ progress(0.1, desc="Initializing YouTube extractor...")
288
+
289
+ ydl_opts = {
290
+ 'noplaylist': True,
291
+ 'extract_flat': False,
292
+ }
293
+
294
+ if cookiefile and os.path.exists(cookiefile):
295
+ ydl_opts['cookiefile'] = cookiefile
296
+
297
+ progress(0.5, desc="Extracting video metadata...")
298
+
299
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
300
+ info = ydl.extract_info(url, download=False)
301
+
302
+ progress(1.0, desc="βœ… Analysis complete!")
303
+
304
+ return info, "βœ… Video information extracted successfully"
305
+
306
+ except Exception as e:
307
+ return None, f"❌ Error: {str(e)}"
308
+
309
+ def download_video(self, url, quality="best", audio_only=False, progress=gr.Progress(), cookiefile=None):
310
+ """Download video with progress tracking"""
311
+ if not url or not url.strip():
312
+ return None, "❌ Please enter a YouTube URL"
313
+
314
+ if not self.is_valid_youtube_url(url):
315
+ return None, "❌ Invalid YouTube URL format"
316
+
317
+ try:
318
+ progress(0.1, desc="Preparing download...")
319
+
320
+ # Create unique filename
321
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
322
+
323
+ # Download to temp directory first (Gradio compatible)
324
+ ydl_opts = {
325
+ 'outtmpl': os.path.join(self.temp_downloads, f'%(title)s_{timestamp}.%(ext)s'),
326
+ 'noplaylist': True,
327
+ }
328
+
329
+ if audio_only:
330
+ ydl_opts['format'] = 'bestaudio/best'
331
+ ydl_opts['postprocessors'] = [{
332
+ 'key': 'FFmpegExtractAudio',
333
+ 'preferredcodec': 'mp3',
334
+ 'preferredquality': '192',
335
+ }]
336
+ else:
337
+ if quality == "best":
338
+ ydl_opts['format'] = 'best[height<=1080]'
339
+ elif quality == "720p":
340
+ ydl_opts['format'] = 'best[height<=720]'
341
+ elif quality == "480p":
342
+ ydl_opts['format'] = 'best[height<=480]'
343
+ else:
344
+ ydl_opts['format'] = 'best'
345
+
346
+ if cookiefile and os.path.exists(cookiefile):
347
+ ydl_opts['cookiefile'] = cookiefile
348
+
349
+ # Progress hook
350
+ def progress_hook(d):
351
+ if d['status'] == 'downloading':
352
+ if 'total_bytes' in d:
353
+ percent = (d['downloaded_bytes'] / d['total_bytes']) * 100
354
+ progress(0.1 + (percent / 100) * 0.7, desc=f"Downloading... {percent:.1f}%")
355
+ else:
356
+ progress(0.5, desc="Downloading...")
357
+ elif d['status'] == 'finished':
358
+ progress(0.8, desc="Processing download...")
359
+
360
+ ydl_opts['progress_hooks'] = [progress_hook]
361
+
362
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
363
+ info = ydl.extract_info(url, download=True)
364
+
365
+ progress(0.9, desc="Copying to Downloads folder...")
366
+
367
+ # Find the downloaded file in temp directory
368
+ downloaded_file_temp = None
369
+
370
+ for file in os.listdir(self.temp_downloads):
371
+ if timestamp in file:
372
+ downloaded_file_temp = os.path.join(self.temp_downloads, file)
373
+ break
374
+
375
+ if not downloaded_file_temp:
376
+ return None, "❌ Downloaded file not found in temp directory"
377
+
378
+ # Copy to user's Downloads folder
379
+ final_filename = os.path.basename(downloaded_file_temp)
380
+ final_path = os.path.join(self.downloads_folder, final_filename)
381
+
382
+ try:
383
+ shutil.copy2(downloaded_file_temp, final_path)
384
+ copy_success = True
385
+ except Exception as e:
386
+ print(f"Warning: Could not copy to Downloads folder: {e}")
387
+ copy_success = False
388
+ final_path = "File downloaded to temp location only"
389
+
390
+ progress(1.0, desc="βœ… Download complete!")
391
+
392
+ success_msg = f"""βœ… Download successful!
393
+ πŸ“ Temp file (for download): {os.path.basename(downloaded_file_temp)}
394
+ πŸ“ Permanent location: {final_path if copy_success else 'Copy failed'}
395
+ 🎯 File size: {os.path.getsize(downloaded_file_temp) / (1024*1024):.1f} MB"""
396
+
397
+ return downloaded_file_temp, success_msg
398
+
399
+ except Exception as e:
400
+ return None, f"❌ Download failed: {str(e)}"
401
+
402
+ # Initialize global downloader
403
  downloader = YouTubeDownloader()
404
 
405
+ def configure_api_key(api_key):
406
+ """Configure Gemini API key"""
407
+ if not api_key or not api_key.strip():
408
+ return "❌ Please enter a valid Google API key", gr.update(visible=False)
409
+
410
+ success, message = downloader.configure_gemini(api_key.strip())
411
+
412
+ if success:
413
+ return message, gr.update(visible=True)
414
+ else:
415
+ return message, gr.update(visible=False)
416
+
417
+ def analyze_with_cookies(url, cookies_file, progress=gr.Progress()):
418
+ """Main analysis function"""
419
+ try:
420
+ progress(0.05, desc="Starting analysis...")
421
+
422
+ cookiefile = None
423
+ if cookies_file and os.path.exists(cookies_file):
424
+ cookiefile = cookies_file
425
+
426
+ info, msg = downloader.get_video_info(url, progress=progress, cookiefile=cookiefile)
427
+
428
+ if info:
429
+ progress(0.95, desc="Generating comprehensive report...")
430
+ formatted_info = downloader.format_video_info(info)
431
+ progress(1.0, desc="βœ… Complete!")
432
+ return formatted_info
433
+ else:
434
+ return f"❌ Analysis Failed: {msg}"
435
+
436
+ except Exception as e:
437
+ return f"❌ System Error: {str(e)}"
438
 
439
+ def download_with_cookies(url, quality, audio_only, cookies_file, progress=gr.Progress()):
440
+ """Main download function"""
441
+ try:
442
+ progress(0.05, desc="Preparing download...")
443
+
444
+ cookiefile = None
445
+ if cookies_file and os.path.exists(cookies_file):
446
+ cookiefile = cookies_file
447
+
448
+ file_path, msg = downloader.download_video(url, quality, audio_only, progress=progress, cookiefile=cookiefile)
449
+
450
+ if file_path:
451
+ return file_path, msg
452
+ else:
453
+ return None, msg
454
+
455
+ except Exception as e:
456
+ return None, f"❌ System Error: {str(e)}"
457
+
458
+ def create_interface():
459
+ """Create and configure the Gradio interface"""
460
+ with gr.Blocks(theme=gr.themes.Soft(), title="πŸŽ₯ YouTube Video Analyzer & Downloader Pro") as interface:
461
+
462
+ gr.HTML("<h1>πŸŽ₯ YouTube Video Analyzer & Downloader Pro</h1>")
463
+
464
+ # API Key Configuration Section
465
+ with gr.Group():
466
+ gr.HTML("<h3>πŸ”‘ Google Gemini API Configuration</h3>")
467
+ with gr.Row():
468
+ api_key_input = gr.Textbox(
469
+ label="πŸ”‘ Google API Key",
470
+ placeholder="Enter your Google API Key for enhanced AI analysis...",
471
+ type="password",
472
+ value=""
473
+ )
474
+ configure_btn = gr.Button("πŸ”§ Configure API", variant="secondary")
475
+
476
+ api_status = gr.Textbox(
477
+ label="API Status",
478
+ value="❌ Gemini API not configured - Using fallback analysis",
479
+ interactive=False,
480
+ lines=1
481
+ )
482
+
483
+ # Main Interface (initially hidden until API is configured)
484
+ main_interface = gr.Group(visible=False)
485
+
486
+ with main_interface:
487
+ with gr.Row():
488
+ url_input = gr.Textbox(
489
+ label="πŸ”— YouTube URL",
490
+ placeholder="Paste your YouTube video URL here...",
491
+ value=""
492
+ )
493
+
494
+ cookies_input = gr.File(
495
+ label="πŸͺ Upload cookies.txt (Optional)",
496
+ file_types=[".txt"],
497
+ type="filepath"
498
+ )
499
+
500
+ with gr.Tabs():
501
+ with gr.TabItem("πŸ“Š Video Analysis"):
502
+ analyze_btn = gr.Button("πŸ” Analyze Video", variant="primary")
503
+
504
+ analysis_output = gr.Textbox(
505
+ label="πŸ“Š Analysis Report",
506
+ lines=25,
507
+ show_copy_button=True
508
+ )
509
+
510
+ analyze_btn.click(
511
+ fn=analyze_with_cookies,
512
+ inputs=[url_input, cookies_input],
513
+ outputs=analysis_output,
514
+ show_progress=True
515
+ )
516
+
517
+ with gr.TabItem("⬇️ Video Download"):
518
+ with gr.Row():
519
+ quality_dropdown = gr.Dropdown(
520
+ choices=["best", "720p", "480p"],
521
+ value="best",
522
+ label="πŸ“Ί Video Quality"
523
+ )
524
+
525
+ audio_only_checkbox = gr.Checkbox(
526
+ label="🎡 Audio Only (MP3)",
527
+ value=False
528
+ )
529
+
530
+ download_btn = gr.Button("⬇️ Download Video", variant="primary")
531
+
532
+ download_status = gr.Textbox(
533
+ label="πŸ“₯ Download Status",
534
+ lines=5,
535
+ show_copy_button=True
536
+ )
537
+
538
+ download_file = gr.File(
539
+ label="πŸ“ Downloaded File",
540
+ visible=False
541
+ )
542
+
543
+ def download_and_update(url, quality, audio_only, cookies_file, progress=gr.Progress()):
544
+ file_path, status = download_with_cookies(url, quality, audio_only, cookies_file, progress)
545
+ if file_path and os.path.exists(file_path):
546
+ return status, gr.update(value=file_path, visible=True)
547
+ else:
548
+ return status, gr.update(visible=False)
549
+
550
+ download_btn.click(
551
+ fn=download_and_update,
552
+ inputs=[url_input, quality_dropdown, audio_only_checkbox, cookies_input],
553
+ outputs=[download_status, download_file],
554
+ show_progress=True
555
+ )
556
+
557
+ # Configure API key button action
558
+ configure_btn.click(
559
+ fn=configure_api_key,
560
+ inputs=[api_key_input],
561
+ outputs=[api_status, main_interface]
562
+ )
563
+
564
+ # Always show interface option (for fallback mode)
565
+ with gr.Row():
566
+ show_interface_btn = gr.Button("πŸš€ Use Without Gemini API (Fallback Mode)", variant="secondary")
567
+
568
+ def show_fallback_interface():
569
+ return "⚠️ Using fallback analysis mode", gr.update(visible=True)
570
+
571
+ show_interface_btn.click(
572
+ fn=show_fallback_interface,
573
+ outputs=[api_status, main_interface]
574
+ )
575
+
576
+ gr.HTML("""
577
+ <div style="margin-top: 20px; padding: 15px; background-color: #f0f8ff; border-radius: 10px; border-left: 5px solid #4285f4;">
578
+ <h3>πŸ”‘ How to Get Google API Key:</h3>
579
+ <ol>
580
+ <li>Go to <a href="https://console.cloud.google.com/" target="_blank">Google Cloud Console</a></li>
581
+ <li>Create a new project or select an existing one</li>
582
+ <li>Enable the "Generative Language API"</li>
583
+ <li>Go to "Credentials" and create an API key</li>
584
+ <li>Copy the API key and paste it above</li>
585
+ </ol>
586
+ <p><strong>✨ Benefits of using Gemini API:</strong></p>
587
+ <ul>
588
+ <li>πŸ€– AI-powered scene descriptions with contextual understanding</li>
589
+ <li>🎯 More accurate content type detection</li>
590
+ <li>πŸ“Š Enhanced analysis based on video content</li>
591
+ <li>⏰ Intelligent timestamp segmentation</li>
592
+ </ul>
593
+ </div>
594
+ """)
595
+
596
+ return interface
597
 
598
  if __name__ == "__main__":
599
+ demo = create_interface()
600
+ import atexit
601
+ atexit.register(downloader.cleanup)
602
+ demo.launch(debug=True, show_error=True)