developer28 commited on
Commit
39fa25b
Β·
verified Β·
1 Parent(s): ca78672

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +187 -223
app.py CHANGED
@@ -1,232 +1,196 @@
 
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
- import uuid
9
- session_data = {}
10
-
11
- class YouTubeDownloader:
12
  def __init__(self):
13
- self.download_dir = tempfile.mkdtemp()
 
 
 
14
 
15
  def is_valid_youtube_url(self, url):
16
- youtube_regex = re.compile(
17
- r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/'
18
- r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})'
19
- )
20
- return youtube_regex.match(url) is not None
21
-
22
- def format_video_info(self, video_info):
23
- """Format video information into a readable report"""
24
- if not video_info:
25
- return "❌ No video information available."
26
-
27
- # Format duration
28
- duration = video_info.get('duration', 0)
29
- duration_str = f"{duration//3600}:{(duration%3600)//60:02d}:{duration%60:02d}" if duration else "Unknown"
30
-
31
- # Format upload date
32
- upload_date = video_info.get('upload_date', '')
33
- if upload_date and len(upload_date) == 8:
34
- formatted_date = f"{upload_date[:4]}-{upload_date[4:6]}-{upload_date[6:8]}"
35
- else:
36
- formatted_date = upload_date or "Unknown"
37
-
38
- # Format numbers
39
- def format_number(num):
40
- if num >= 1_000_000:
41
- return f"{num/1_000_000:.1f}M"
42
- elif num >= 1_000:
43
- return f"{num/1_000:.1f}K"
44
- else:
45
- return str(num)
46
-
47
- # Build the report
48
- report = f"""
49
- πŸ“Ή VIDEO ANALYSIS REPORT
50
- {'='*50}
51
-
52
- πŸ“ BASIC INFORMATION:
53
- β€’ Title: {video_info.get('title', 'Unknown')}
54
- β€’ Channel: {video_info.get('channel', 'Unknown')}
55
- β€’ Uploader: {video_info.get('uploader', 'Unknown')}
56
- β€’ Upload Date: {formatted_date}
57
- β€’ Duration: {duration_str}
58
-
59
- πŸ“Š STATISTICS:
60
- β€’ Views: {format_number(video_info.get('view_count', 0))}
61
- β€’ Likes: {format_number(video_info.get('like_count', 0))}
62
- β€’ Comments: {format_number(video_info.get('comment_count', 0))}
63
- β€’ Channel Followers: {format_number(video_info.get('channel_followers', 0))}
64
-
65
- 🏷️ CATEGORIES & TAGS:
66
- β€’ Categories: {', '.join(video_info.get('categories', [])) or 'None'}
67
- β€’ Tags: {', '.join(video_info.get('tags', [])[:10]) or 'None'}
68
- {('β€’ More tags...' if len(video_info.get('tags', [])) > 10 else '')}
69
-
70
- πŸ“– DESCRIPTION:
71
- {video_info.get('description', 'No description available')[:500]}
72
- {'...' if len(video_info.get('description', '')) > 500 else ''}
73
-
74
- πŸ”— VIDEO URL:
75
- {video_info.get('webpage_url', 'Unknown')}
76
- """
77
-
78
- return report.strip()
79
-
80
- def get_video_info(self, url, progress=gr.Progress(), cookiefile=None):
81
- if not url or not url.strip():
82
- return None, "❌ Please enter a YouTube URL"
83
-
84
  if not self.is_valid_youtube_url(url):
85
- return None, "❌ Invalid YouTube URL. Please enter a valid YouTube video URL"
86
-
87
- try:
88
- progress(0.2, desc="Fetching video information...")
89
- ydl_opts = {
90
- 'noplaylist': True,
91
- 'extract_flat': False,
92
- }
93
- if cookiefile:
94
- ydl_opts['cookiefile'] = cookiefile
95
-
96
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
97
- try:
98
- info = ydl.extract_info(url, download=False)
99
- video_info = {
100
- 'title': info.get('title', 'Unknown'),
101
- 'description': info.get('description', 'No description available'),
102
- 'duration': info.get('duration', 0),
103
- 'view_count': info.get('view_count', 0),
104
- 'like_count': info.get('like_count', 0),
105
- 'comment_count': info.get('comment_count', 0),
106
- 'upload_date': info.get('upload_date', ''),
107
- 'uploader': info.get('uploader', 'Unknown'),
108
- 'channel': info.get('channel', 'Unknown'),
109
- 'channel_followers': info.get('channel_follower_count', 0),
110
- 'tags': info.get('tags', []),
111
- 'categories': info.get('categories', []),
112
- 'thumbnail': info.get('thumbnail', ''),
113
- 'webpage_url': info.get('webpage_url', url)
114
- }
115
- progress(1.0, desc="Information retrieved!")
116
- return video_info, "βœ… Video information retrieved successfully"
117
- except yt_dlp.DownloadError as e:
118
- error_msg = str(e)
119
- if "Video unavailable" in error_msg:
120
- return None, "❌ Video is unavailable or private"
121
- elif "age-restricted" in error_msg.lower():
122
- return None, "❌ Video is age-restricted"
123
- else:
124
- return None, f"❌ Failed to get video info: {error_msg}"
125
- except Exception as e:
126
- return None, f"❌ An unexpected error occurred: {str(e)}"
127
-
128
- def download_video(self, url, progress=gr.Progress(), cookiefile=None):
129
- if not url or not url.strip():
130
- return None, "❌ Please enter a YouTube URL"
131
 
132
- if not self.is_valid_youtube_url(url):
133
- return None, "❌ Invalid YouTube URL. Please enter a valid YouTube video URL"
134
-
135
- try:
136
- progress(0.1, desc="Initializing download...")
137
- ydl_opts = {
138
- 'format': 'best[ext=mp4]/best',
139
- 'outtmpl': os.path.join(self.download_dir, '%(title)s.%(ext)s'),
140
- 'noplaylist': True,
141
- }
142
- if cookiefile:
143
- ydl_opts['cookiefile'] = cookiefile
144
-
145
- progress(0.3, desc="Fetching video information...")
146
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
147
- try:
148
- info = ydl.extract_info(url, download=False)
149
- video_title = info.get('title', 'Unknown')
150
- duration = info.get('duration', 0)
151
- progress(0.5, desc=f"Downloading: {video_title[:50]}...")
152
- ydl.download([url])
153
- progress(0.9, desc="Finalizing download...")
154
- downloaded_files = list(Path(self.download_dir).glob('*'))
155
- if downloaded_files:
156
- downloaded_file = downloaded_files[0]
157
- file_size = downloaded_file.stat().st_size / (1024 * 1024)
158
- progress(1.0, desc="Download completed!")
159
- success_message = f"βœ… Successfully downloaded: {video_title}\n"
160
- success_message += f"πŸ“ File size: {file_size:.1f} MB\n"
161
- success_message += f"⏱️ Duration: {duration//60}:{duration%60:02d}" if duration else ""
162
- return str(downloaded_file), success_message
163
- else:
164
- return None, "❌ Download completed but file not found"
165
- except yt_dlp.DownloadError as e:
166
- error_msg = str(e)
167
- if "Video unavailable" in error_msg:
168
- return None, "❌ Video is unavailable or private"
169
- elif "age-restricted" in error_msg.lower():
170
- return None, "❌ Video is age-restricted and cannot be downloaded"
171
- elif "copyright" in error_msg.lower():
172
- return None, "❌ Video cannot be downloaded due to copyright restrictions"
173
- else:
174
- return None, f"❌ Download failed: {error_msg}"
175
- except Exception as e:
176
- return None, f"❌ An unexpected error occurred: {str(e)}"
177
-
178
- def cleanup(self):
179
- try:
180
- shutil.rmtree(self.download_dir, ignore_errors=True)
181
- except:
182
- pass
183
-
184
- downloader = YouTubeDownloader()
185
-
186
- def create_interface():
187
- with gr.Blocks(title="YouTube Video Downloader & Analyzer") as demo:
188
- url_input = gr.Textbox(label="YouTube URL", placeholder="https://www.youtube.com/watch?v=...")
189
- cookies_input = gr.File(label="Upload cookies.txt (optional)", type="filepath", file_types=[".txt"])
190
- download_btn = gr.Button("Download Video")
191
- status_output = gr.Textbox(label="Download Status")
192
- file_output = gr.File(label="Downloaded Video", visible=False)
193
- analysis_btn = gr.Button("Show Analysis Results", visible=False)
194
- analysis_output = gr.Textbox(label="Video Analysis Results", visible=False, lines=20)
195
- video_info_state = gr.Textbox(value=False)
196
-
197
-
198
- def handle_download(url, cookies_path):
199
- if not url or not url.strip():
200
- return "Please enter a YouTube URL", gr.File(visible=False), gr.Button(visible=False), None
201
-
202
- cookiefile = cookies_path.strip() if cookies_path and os.path.exists(cookies_path.strip()) else None
203
- video_info, info_message = downloader.get_video_info(url, cookiefile=cookiefile)
204
- file_path, download_message = downloader.download_video(url, cookiefile=cookiefile)
205
- print(f"[DEBUG] Download message: {download_message}")
206
-
207
- if file_path and video_info:
208
- success_message = f"{download_message}\n\nVideo downloaded successfully! Click 'Show Analysis Results' to see detailed information."
209
- return success_message, gr.File(value=file_path, visible=True), gr.Button(visible=True), gr.State(video_info)
210
- elif file_path:
211
- return f"{download_message}\n\nVideo downloaded but analysis data unavailable.", gr.File(value=file_path, visible=True), gr.Button(visible=False), gr.State("")
212
- else:
213
- return f"❌ Download failed:\n{download_message}", gr.File(visible=False), gr.Button(visible=False), gr.State(None)
214
-
215
- def show_analysis(video_info):
216
- print("[DEBUG] Received session_id:", session_id)
217
- video_info = session_data.get(session_id)
218
- if video_info:
219
- return downloader.format_video_info(video_info), gr.Textbox(visible=True)
220
- return "❌ No analysis data available.", gr.Textbox(visible=True)
221
-
222
- download_btn.click(handle_download, inputs=[url_input, cookies_input], outputs=[status_output, file_output, analysis_btn, video_info_state])
223
- analysis_btn.click(show_analysis, inputs=[video_info_state], outputs=[analysis_output])
224
- url_input.submit(handle_download, inputs=[url_input, cookies_input], outputs=[status_output, file_output, analysis_btn, video_info_state])
225
-
226
- return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
  if __name__ == "__main__":
229
- demo = create_interface()
230
- import atexit
231
- atexit.register(downloader.cleanup)
232
- demo.launch()
 
1
+ import os
2
  import gradio as gr
3
  import yt_dlp
4
+ import webvtt
5
+ import google.generativeai as genai
6
+ from datetime import datetime
7
+
8
+ genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) # Set your Gemini API key in environment
9
+ MODEL_ID = "gemini-1.5-flash-latest"
10
+ model = genai.GenerativeModel(model_name=MODEL_ID)
11
+
12
+ class VideoAnalyzer:
13
  def __init__(self):
14
+ self.temp_dir = "temp_subs"
15
+ os.makedirs(self.temp_dir, exist_ok=True)
16
+ self.downloads_dir = os.path.join(os.path.expanduser("~"), "Downloads", "YT_Reports")
17
+ os.makedirs(self.downloads_dir, exist_ok=True)
18
 
19
  def is_valid_youtube_url(self, url):
20
+ return "youtube.com" in url or "youtu.be" in url
21
+
22
+ def download_info_and_subs(self, url, cookiefile=None):
23
+ ydl_opts = {
24
+ 'writesubtitles': True,
25
+ 'writeautomaticsub': True,
26
+ 'subtitleslangs': ['en', 'en-HI', 'hi'],
27
+ 'skip_download': True,
28
+ 'outtmpl': os.path.join(self.temp_dir, 'video'),
29
+ }
30
+ if cookiefile:
31
+ ydl_opts['cookiefile'] = cookiefile
32
+
33
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
34
+ info = ydl.extract_info(url, download=False)
35
+ subs = info.get('automatic_captions') or info.get('subtitles')
36
+ return info if subs else None, info
37
+
38
+ def extract_captions(self, vtt_path):
39
+ captions = []
40
+ for caption in webvtt.read(vtt_path):
41
+ text = caption.text.strip().replace("\n", " ")
42
+ captions.append(f"[{caption.start} - {caption.end}]: {text}")
43
+ return captions
44
+
45
+ def generate_scene_descriptions(self, captions):
46
+ prompt = """You are a visual scene narrator. Turn the following subtitles into timestamped scene descriptions.
47
+ Use natural language and visual imagination. Avoid brand names.
48
+
49
+ Example output:
50
+ * **[0:00-0:01]:** A young woman with dark hair, wearing a red shirt, sits at a desk in a modern office...
51
+
52
+ Now convert these subtitles:
53
+
54
+ """
55
+ prompt += "\n".join(captions[:20]) # Limit to first 20 captions
56
+
57
+ response = model.generate_content(prompt)
58
+ return response.text
59
+
60
+ def detect_influencer_status(self, info):
61
+ prompt = f"""You are a media analyst. Based on this metadata, tell if this channel is a famous influencer, brand, or regular user.
62
+
63
+ Channel: {info.get('channel', '')}
64
+ Uploader: {info.get('uploader', '')}
65
+ Subscribers: {info.get('channel_followers', 0)}
66
+ Title: {info.get('title', '')}
67
+ Description: {info.get('description', '')}
68
+
69
+ Respond with 1 line like 'Famous Influencer', 'Verified Brand Channel', or 'Regular Content Creator'."""
70
+ result = model.generate_content(prompt)
71
+ return result.text.strip()
72
+
73
+ def format_number(self, num):
74
+ if not num: return "0"
75
+ if num >= 1_000_000_000: return f"{num / 1_000_000_000:.1f}B"
76
+ elif num >= 1_000_000: return f"{num / 1_000_000:.1f}M"
77
+ elif num >= 1_000: return f"{num / 1_000:.1f}K"
78
+ return str(num)
79
+
80
+ def analyze(self, url, cookiefile=None):
 
 
 
 
 
 
 
81
  if not self.is_valid_youtube_url(url):
82
+ return "❌ Invalid YouTube URL."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
+ info, raw_info = self.download_info_and_subs(url, cookiefile)
85
+ if not info:
86
+ return "❌ Subtitles not found for this video."
87
+
88
+ vtt_path = None
89
+ for file in os.listdir(self.temp_dir):
90
+ if file.endswith(".vtt"):
91
+ vtt_path = os.path.join(self.temp_dir, file)
92
+ break
93
+
94
+ if not vtt_path:
95
+ return "❌ Subtitle file not found."
96
+
97
+ captions = self.extract_captions(vtt_path)
98
+ scene_block = self.generate_scene_descriptions(captions)
99
+
100
+ duration = raw_info.get('duration', 0)
101
+ duration_str = f"{duration//3600}:{(duration%3600)//60:02d}:{duration%60:02d}" if duration else "Unknown"
102
+ upload_date = raw_info.get('upload_date', '')
103
+ formatted_date = f"{upload_date[:4]}-{upload_date[4:6]}-{upload_date[6:8]}" if len(upload_date) == 8 else "Unknown"
104
+
105
+ view_count = raw_info.get('view_count', 0)
106
+ like_count = raw_info.get('like_count', 0)
107
+ dislike_count = raw_info.get('dislike_count', 0)
108
+ comment_count = raw_info.get('comment_count', 0)
109
+ subscriber_count = raw_info.get('channel_followers', 0)
110
+ engagement_rate = (like_count / view_count) * 100 if view_count else 0
111
+ like_ratio = (like_count / (like_count + dislike_count)) * 100 if (like_count + dislike_count) else 0
112
+ comment_ratio = (comment_count / view_count) * 100 if view_count else 0
113
+
114
+ influencer_status = self.detect_influencer_status(raw_info)
115
+
116
+ summary = f"""
117
+ πŸ“‹ BASIC INFORMATION
118
+ {'─'*30}
119
+ πŸ“Ή **Title:** {raw_info.get('title', 'Unknown')}
120
+ πŸ“Ί **Channel:** {raw_info.get('channel', 'Unknown')}
121
+ πŸ‘€ **Uploader:** {raw_info.get('uploader', 'Unknown')}
122
+ πŸ“… **Upload Date:** {formatted_date}
123
+ ⏱️ **Duration:** {duration_str}
124
+ πŸ†” **Video ID:** {raw_info.get('id', 'Unknown')}
125
+ πŸ”— **Video URL:** {raw_info.get('webpage_url', 'Unknown')}
126
+
127
+ πŸ“Š PERFORMANCE METRICS
128
+ {'─'*30}
129
+ πŸ‘€ **Views:** {self.format_number(view_count)} ({view_count:,} exact)
130
+ πŸ‘ **Likes:** {self.format_number(like_count)} ({like_count:,} exact)
131
+ πŸ‘Ž **Dislikes:** {self.format_number(dislike_count)} ({dislike_count:,} exact)
132
+ πŸ’¬ **Comments:** {self.format_number(comment_count)} ({comment_count:,} exact)
133
+ πŸ‘₯ **Subscribers:** {self.format_number(subscriber_count)} ({subscriber_count:,} exact)
134
+ πŸ“ˆ **Engagement Rate:** {engagement_rate:.2f}% (likes/views)
135
+ ❀️ **Like Ratio:** {like_ratio:.1f}% (likes vs total reactions)
136
+ πŸ’­ **Comment Ratio:** {comment_ratio:.3f}% (comments/views)
137
+
138
+ πŸ‘‘ INFLUENCER STATUS
139
+ {'─'*30}
140
+ {influencer_status}
141
+
142
+ 🎬 SCENE-BY-SCENE BREAKDOWN
143
+ {'─'*30}
144
+ {scene_block}
145
+ """.strip()
146
+
147
+ return summary
148
+
149
+ def download_video(self, url, quality="best", audio_only=False, cookiefile=None):
150
+ ydl_opts = {
151
+ 'outtmpl': os.path.join(self.downloads_dir, '%(title)s.%(ext)s'),
152
+ 'format': 'bestaudio/best' if audio_only else 'best',
153
+ 'noplaylist': True
154
+ }
155
+ if cookiefile:
156
+ ydl_opts['cookiefile'] = cookiefile
157
+
158
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
159
+ info = ydl.extract_info(url, download=True)
160
+ filename = ydl.prepare_filename(info)
161
+ return filename
162
+
163
+ analyzer = VideoAnalyzer()
164
+
165
+ def analyze_video(url, cookies_file):
166
+ cookiefile = cookies_file.name if cookies_file else None
167
+ return analyzer.analyze(url, cookiefile)
168
+
169
+ def download_video(url, quality, audio_only, cookies_file):
170
+ cookiefile = cookies_file.name if cookies_file else None
171
+ return analyzer.download_video(url, quality, audio_only, cookiefile)
172
+
173
+ with gr.Blocks(title="YouTube Analyzer + Downloader") as iface:
174
+ gr.Markdown("# 🎬 YouTube Video Analyzer + Downloader")
175
+ url = gr.Textbox(label="YouTube URL")
176
+ cookies = gr.File(label="Upload cookies.txt (Optional)", file_types=[".txt"], type="file")
177
+
178
+ with gr.Tab("πŸ“Š Analyze Video"):
179
+ analyze_btn = gr.Button("Analyze")
180
+ analysis_output = gr.Textbox(label="Analysis Report", lines=40, show_copy_button=True)
181
+ analyze_btn.click(fn=analyze_video, inputs=[url, cookies], outputs=analysis_output)
182
+
183
+ with gr.Tab("⬇️ Download Video"):
184
+ quality = gr.Dropdown(["best", "720p", "480p"], label="Quality", value="best")
185
+ audio_only = gr.Checkbox(label="Audio Only", value=False)
186
+ download_btn = gr.Button("Download")
187
+ download_output = gr.Textbox(label="Download Status")
188
+
189
+ def handle_download(url, quality, audio_only, cookies_file):
190
+ path = download_video(url, quality, audio_only, cookies_file)
191
+ return f"βœ… Downloaded to: {path}"
192
+
193
+ download_btn.click(fn=handle_download, inputs=[url, quality, audio_only, cookies], outputs=download_output)
194
 
195
  if __name__ == "__main__":
196
+ iface.launch(debug=True)