developer28 commited on
Commit
ac478f5
·
verified ·
1 Parent(s): 3c0b1d2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +390 -209
app.py CHANGED
@@ -1,9 +1,9 @@
1
  def format_scene_breakdown(scenes):
2
  rows = """
3
- <table style='width:100%; border-collapse: collapse; background-color:#1a1a1a; color: #FFFFFF; border: 2px solid #FF8C00; font-size: 14px;box-shadow: 0 4px 8px rgba(0,0,0,0.3);'>
4
  <tr style='background-color:#FF8C00; color: #000000;'>
5
- <th style='padding: 8px; border: 1px solid #FF8C00;color: #000000;'>Timestamp</th>
6
- <th style='padding: 8px; border: 1px solid #FF8C00;color: #000000;'> Description</th>
7
  </tr>
8
  """
9
  pattern = re.compile(r"\*\*\[(.*?)\]\*\*:\s*(.*)")
@@ -16,8 +16,8 @@ def format_scene_breakdown(scenes):
16
  description = match.group(2).strip()
17
  rows += f"""
18
  <tr style='background-color:#1a1a1a;'>
19
- <td style='padding: 8px; border: 1px solid #444; color: #87CEEB; font-weight: bold;font-size: 12px;vertical-align: top;'>{timestamp}</td>
20
- <td style='padding: 8px; border: 1px solid #444; color: #87CEEB; font-weight: bold;font-size: 12px;line-height: 1.4;'>{description}</td>
21
  </tr>
22
  """
23
 
@@ -40,154 +40,167 @@ from xhtml2pdf import pisa
40
  from io import BytesIO
41
 
42
 
43
- cached_reports = {}
44
-
45
  def generate_pdf_from_html(html_content):
46
- """Generate a compact PDF that matches app appearance"""
47
  try:
48
- pdf_html = f"""
49
- <!DOCTYPE html>
50
- <html>
51
- <head>
52
- <meta charset='UTF-8'>
53
- <style>
54
- @page {{
55
- size: A4;
56
- margin: 0.7cm;
57
- }}
58
- body {{
59
- font-family: 'Segoe UI', sans-serif;
60
- font-size: 9px;
61
- line-height: 1.3;
62
- color: #000;
63
- background-color: #fff;
64
- }}
65
- table {{
66
- width: 100%;
67
- border-collapse: collapse;
68
- font-size: 9px;
69
- margin-bottom: 10px;
70
- }}
71
- th, td {{
72
- border: 1px solid #ccc;
73
- padding: 2px;
74
- vertical-align: top;
75
- line-height: 1.1;
76
- }}
77
- h1, h2, h3 {{
78
- font-size: 11px;
79
- background-color: #D3D3D3;
80
- color: #000;
81
- padding: 4px;
82
- margin: 4px 0;
83
- }}
84
- </style>
85
- </head>
86
- <body>
87
- {html_content}
88
- </body>
89
- </html>
90
- """
91
-
92
- result = BytesIO()
93
- pisa_status = pisa.CreatePDF(pdf_html, dest=result)
94
- if pisa_status.err:
95
- print("❌ Pisa PDF creation error")
96
- return None
97
- result.seek(0)
98
- return result
99
-
100
- except Exception as e:
101
- print(f"❌ PDF generation exception: {e}")
102
- return None
103
-
104
- def generate_pdf_from_html_debug(html_content):
105
- """Debug version to identify PDF generation issues"""
106
- try:
107
- print("🐛 Starting PDF generation...")
108
- print(f"🐛 HTML content length: {len(html_content)} characters")
109
- print(f"🐛 HTML preview: {html_content[:200]}...")
110
-
111
- # Check if we have the required imports
112
- try:
113
- from xhtml2pdf import pisa
114
- print("✅ pisa import successful")
115
- except ImportError as e:
116
- print(f"❌ pisa import failed: {e}")
117
- return None
118
-
119
- try:
120
- import re
121
- print("✅ re import successful")
122
- except ImportError as e:
123
- print(f"❌ re import failed: {e}")
124
- return None
125
-
126
- # Simple HTML cleanup (minimal for debugging)
127
  simplified_html = html_content.replace(
128
  "background: linear-gradient(135deg, #2d3748, #1a202c);",
129
- "background-color: #ffffff;"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  ).replace(
131
  "color: #FFFFFF;",
132
  "color: #000000;"
 
 
 
 
 
 
 
 
 
133
  )
134
 
135
- print("🐛 HTML cleanup completed")
 
 
136
 
137
- # Create a very simple PDF HTML
138
  pdf_html = f"""
139
  <!DOCTYPE html>
140
  <html>
141
  <head>
142
  <meta charset="UTF-8">
143
  <style>
 
 
 
 
144
  body {{
145
  font-family: Arial, sans-serif;
146
  font-size: 12px;
 
147
  color: #000000;
148
  background-color: #ffffff;
149
  }}
150
- table {{ border-collapse: collapse; width: 100%; }}
151
- th, td {{ border: 1px solid #ccc; padding: 4px; }}
152
- th {{ background-color: #FF8C00; color: #000000; }}
153
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  </style>
155
  </head>
156
  <body>
157
- {simplified_html}
 
 
158
  </body>
159
  </html>
160
  """
161
 
162
- print("🐛 PDF HTML template created")
163
-
164
- from io import BytesIO
165
  result = BytesIO()
166
-
167
- print("🐛 Creating PDF with pisa...")
168
  pisa_status = pisa.CreatePDF(pdf_html, dest=result)
169
-
170
- print(f"🐛 Pisa status - err: {pisa_status.err}")
171
- print(f"🐛 PDF buffer size: {len(result.getvalue())} bytes")
172
 
173
  if pisa_status.err:
174
- print(f"PDF generation error: {pisa_status.err}")
175
- return None
176
-
177
- if len(result.getvalue()) == 0:
178
- print("❌ PDF buffer is empty")
179
  return None
180
 
181
  result.seek(0)
182
- print("✅ PDF generation successful!")
183
  return result
184
 
185
  except Exception as e:
186
- print(f"PDF generation exception: {e}")
187
- import traceback
188
- traceback.print_exc()
189
  return None
190
-
191
  class YouTubeDownloader:
192
  def __init__(self):
193
  self.download_dir = tempfile.mkdtemp()
@@ -241,7 +254,7 @@ class YouTubeDownloader:
241
 
242
  # Create enhanced prompt for Gemini
243
  prompt = f"""
244
- Analyze this YouTube video and create a detailed, scene-by-scene breakdown with precise timestamps and specific descriptions:
245
 
246
  Title: {title}
247
  Duration: {duration} seconds
@@ -430,19 +443,19 @@ class YouTubeDownloader:
430
  text = (title + " " + description).lower()
431
 
432
  if any(word in text for word in ['music', 'song', 'album', 'artist', 'band', 'lyrics']):
433
- return "Music Video"
434
  elif any(word in text for word in ['tutorial', 'how to', 'guide', 'learn', 'teaching']):
435
- return "Tutorial/Educational"
436
  elif any(word in text for word in ['funny', 'comedy', 'entertainment', 'vlog', 'challenge']):
437
- return "Entertainment/Comedy"
438
  elif any(word in text for word in ['news', 'breaking', 'report', 'update']):
439
- return "News/Information"
440
  elif any(word in text for word in ['review', 'unboxing', 'test', 'comparison']):
441
- return "Review/Unboxing/Promotional"
442
  elif any(word in text for word in ['commercial', 'ad', 'brand', 'product']):
443
- return "Commercial/Advertisement"
444
  else:
445
- return "General Content"
446
 
447
  def detect_background_music(self, video_info):
448
  """Detect background music style"""
@@ -450,15 +463,15 @@ class YouTubeDownloader:
450
  description = video_info.get('description', '').lower()
451
 
452
  if any(word in title for word in ['music', 'song', 'soundtrack']):
453
- return "Original Music/Soundtrack - Primary audio content"
454
  elif any(word in title for word in ['commercial', 'ad', 'brand']):
455
- return "Upbeat Commercial Music - Designed to enhance brand appeal"
456
  elif any(word in title for word in ['tutorial', 'how to', 'guide']):
457
- return "Minimal/No Background Music - Focus on instruction"
458
  elif any(word in title for word in ['vlog', 'daily', 'life']):
459
- return "Ambient Background Music - Complementary to narration"
460
  else:
461
- return "Background Music - Complementing video mood and pacing"
462
 
463
  def detect_influencer_status(self, video_info):
464
  """Detect influencer status"""
@@ -466,17 +479,17 @@ class YouTubeDownloader:
466
  view_count = video_info.get('view_count', 0)
467
 
468
  if subscriber_count > 10000000:
469
- return "Mega Influencer (10M+ subscribers)"
470
  elif subscriber_count > 1000000:
471
- return "Major Influencer (1M+ subscribers)"
472
  elif subscriber_count > 100000:
473
- return "Mid-tier Influencer (100K+ subscribers)"
474
  elif subscriber_count > 10000:
475
- return "Micro Influencer (10K+ subscribers)"
476
  elif view_count > 100000:
477
- return "Viral Content Creator"
478
  else:
479
- return "Regular Content Creator"
480
 
481
  def format_number(self, num):
482
  if num is None or num == 0:
@@ -522,53 +535,62 @@ class YouTubeDownloader:
522
 
523
  # Generate compact report with contrasting background
524
  report = f"""
525
- <div style='font-family: "Roboto", "Segoe UI", "Open Sans", sans-serif; background-color: rgb(250, 250, 250); padding: 12px; border-radius: 6px; border: 1px solid #ddd;'>
526
- <!-- TITLE -->
527
- <div style='text-align: center; margin-bottom: 8px;'>
528
- <h1 style='font-size: 16px; color: #FF6F00; margin-bottom: 3px; line-height: 1.2;'>{title}</h1>
529
- <div style='height: 2px; background-color:rgb(211, 211, 211); width: 80px; margin: 0 auto;'></div>
530
- </div>
531
- <!-- INFO GRID (2 Columns) -->
532
- <div style='display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px;'>
533
- <!-- LEFT: BASIC INFO -->
534
- <div style='background-color:rgb(211, 211, 211); border: 1px solid #CCC; border-left: 3px solid #FF6F00; border-radius: 4px; padding: 6px; max-width: 100%;'>
535
- <h3 style='margin: 0 0 4px 0; font-size: 14px; background-color:#D3D3D3; color: #000000 !important; line-height: 1.2;'>Basic Information</h3>
536
- <table style='width: 100%; font-size: 11px; color: #212121; border-spacing: 0;'>
537
- <tr><td style='padding: 0px 1px;'><strong>Creator</strong></td><td style='padding: 1px 2px;'>{uploader[:20]}{'...' if len(uploader) > 20 else ''}</td></tr>
538
- <tr><td style='padding: 0px 1px;'><strong>Date</strong></td><td style='padding: 1px 2px;'>{formatted_date}</td></tr>
539
- <tr><td style='padding: 0px 1px;'><strong>Duration</strong></td><td style='padding: 1px 2px;'>{duration_str}</td></tr>
540
- </table>
541
  </div>
542
- <!-- RIGHT: METRICS + ANALYSIS in a NESTED GRID -->
543
- <div style='display: grid; grid-template-columns: 1fr 1fr; gap: 6px;'>
544
- <!-- METRICS -->
545
- <div style='background-color:rgb(211, 211, 211); border: 1px solid #CCC; border-left: 3px solid #FF6F00; border-radius: 4px; padding: 6px;'>
546
- <h3 style='margin: 0 0 4px 0; font-size: 14px; color: #000000 !important; line-height: 1.2;'>Metrics</h3>
547
- <table style='width: 100%; font-size: 11px; color: #212121;'>
548
- <tr><td style='padding: 0px 1px;'><strong>Views</strong></td><td style='padding: 1px 2px;'>{self.format_number(view_count)}</td></tr>
549
- <tr><td style='padding: 0px 1px;'><strong>Likes</strong></td><td style='padding: 1px 2px;'>{self.format_number(like_count)}</td></tr>
550
- <tr><td style='padding: 0px 1px;'><strong>Comments</strong></td><td style='padding: 1px 2px;'>{self.format_number(comment_count)}</td></tr>
551
- <tr><td style='padding: 0px 1px;'><strong>Engagement</strong></td><td style='padding: 1px 2px;'>{engagement_rate:.2f}%</td></tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
  </table>
553
  </div>
554
- <!-- ANALYSIS -->
555
- <div style='background-color:rgb(211, 211, 211); border: 1px solid #CCC; border-left: 3px solid #FF6F00; border-radius: 4px; padding: 6px;'>
556
- <h3 style='margin: 0 0 4px 0; font-size: 14px; color: #000000 !important; line-height: 1.2;'>Analysis</h3>
557
- <table style='width: 100%; font-size: 11px; color: #212121;'>
558
- <tr><td style='padding: 0px 1px;'><strong>Type</strong></td><td style='padding: 1px 2px;'>{video_type[:15]}{'...' if len(video_type) > 15 else ''}</td></tr>
559
- <tr><td style='padding: 0px 1px;'><strong>Music</strong></td><td style='padding: 1px 2px;'>{background_music[:25]}{'...' if len(background_music) > 25 else ''}</td></tr>
560
- <tr><td style='padding: 0px 1px;'><strong>Status</strong></td><td style='padding: 1px 2px;'>{influencer_status[:25]}{'...' if len(influencer_status) > 25 else ''}</td></tr>
 
 
 
 
561
  </table>
562
  </div>
563
  </div>
 
 
 
 
 
 
 
564
  </div>
565
- <!-- SCENE-BY-SCENE SECTION (FULL WIDTH) -->
566
- <div style='background-color:rgb(211, 211, 211); border: 1px solid #CCC; border-radius: 4px; padding: 8px;'>
567
- <h3 style='text-align: center; font-size: 16px; color: #000000 !important; margin-bottom: 6px; line-height: 1.2;'> Scene-by-Scene Breakdown</h3>
568
- {scene_table_html}
569
- </div>
570
- </div>
571
- """
572
  return report.strip()
573
 
574
  def get_video_info(self, url, progress=gr.Progress(), cookiefile=None):
@@ -589,12 +611,6 @@ class YouTubeDownloader:
589
 
590
  if cookiefile and os.path.exists(cookiefile):
591
  ydl_opts['cookiefile'] = cookiefile
592
- else:
593
- print(f"⚠️ Cookie file not provided or not found: {cookiefile}")
594
-
595
- # 🧪 Log yt_dlp options
596
- print("🔍 yt_dlp options:")
597
- print(json.dumps(ydl_opts, indent=2))
598
 
599
  progress(0.5, desc="Extracting video metadata...")
600
 
@@ -606,19 +622,7 @@ class YouTubeDownloader:
606
  return info, "✅ Video information extracted successfully"
607
 
608
  except Exception as e:
609
- error_msg = str(e)
610
- print(f"❌ yt_dlp extraction error: {error_msg}")
611
-
612
- if "Video unavailable" in error_msg or "This content isn’t available" in error_msg:
613
- return None, (
614
- "❌ This video is unavailable or restricted. "
615
- "Please check if it's private, deleted, age-restricted, or try again with a valid cookies.txt file."
616
- )
617
- elif "cookies" in error_msg.lower():
618
- return None, f"❌ Error: {str(e)}"
619
-
620
- return None, f"❌ Unexpected extraction error: {str(e)}"
621
-
622
 
623
  def download_video(self, url, quality="best", audio_only=False, progress=gr.Progress(), cookiefile=None):
624
  """Download video with progress tracking"""
@@ -733,13 +737,15 @@ def analyze_with_cookies(url, cookies_file, progress=gr.Progress()):
733
  try:
734
  progress(0.05, desc="Starting analysis...")
735
 
736
- cookiefile = cookies_file if cookies_file and os.path.exists(cookies_file) else None
 
 
 
737
  info, msg = downloader.get_video_info(url, progress=progress, cookiefile=cookiefile)
738
 
739
  if info:
740
  progress(0.95, desc="Generating comprehensive report...")
741
  formatted_info = downloader.format_video_info(info)
742
- cached_reports[url] = formatted_info # Cache the result
743
  progress(1.0, desc="✅ Complete!")
744
  return formatted_info
745
  else:
@@ -749,34 +755,202 @@ def analyze_with_cookies(url, cookies_file, progress=gr.Progress()):
749
  return f"❌ System Error: {str(e)}"
750
 
751
 
752
- def analyze_and_generate_pdf(url, cookies_file, progress=None):
753
- """Generate PDF from cached HTML only"""
754
  try:
755
- if progress: progress(0.1, desc="Checking cached analysis...")
 
756
 
757
- if url not in cached_reports:
758
- print("❌ No cached report found.")
759
- return None
760
 
761
- report_html = cached_reports[url]
762
- if progress: progress(0.8, desc="Generating PDF...")
763
 
764
- pdf_buffer = generate_pdf_from_html(report_html)
765
- if pdf_buffer is None:
766
- print("❌ PDF buffer is empty.")
767
- return None
 
 
 
 
768
 
 
769
  pdf_path = os.path.join(tempfile.gettempdir(), f"analysis_report_{uuid.uuid4().hex}.pdf")
770
  with open(pdf_path, "wb") as f:
771
- f.write(pdf_buffer.read())
772
 
773
- if progress: progress(1.0, desc="✅ PDF ready!")
774
- return pdf_path
 
775
 
776
  except Exception as e:
777
- print(f"❌ Error during PDF generation: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
778
  return None
779
 
 
 
780
  def download_with_cookies(url, quality, audio_only, cookies_file, progress=gr.Progress()):
781
  """Main download function"""
782
  try:
@@ -810,22 +984,27 @@ def create_interface():
810
  h3, .gr-group h3, .gradio-container h3 {
811
  color: #87CEEB !important;
812
  }
 
813
  label, .gr-textbox label, .gr-file label, .gr-dropdown label, .gr-checkbox label {
814
  color: #00008B !important;
815
  font-weight: bold !important;
816
  }
 
817
  .gr-file .file-name {
818
  color: #00008B !important;
819
  font-weight: bold !important;
820
  }
 
821
  /* Make tab labels dark blue too */
822
  .gr-tab-nav button {
823
  color: #00008B !important;
824
  }
 
825
  .gr-tab-nav button.selected {
826
  background-color: #FF8C00 !important;
827
  color: #000000 !important;
828
  }
 
829
  /* Light blue text for API status */
830
  .light-blue-text textarea {
831
  color: #87CEEB !important;
@@ -835,12 +1014,14 @@ def create_interface():
835
  background-color: #2a2a2a !important;
836
  border: 2px dashed #444 !important;
837
  }
 
838
  .gr-group, .gr-form, .gr-row {
839
  background-color: #1a1a1a !important;
840
  border: 1px solid #444 !important;
841
  border-radius: 10px;
842
  padding: 15px;
843
  }
 
844
  """,
845
  theme=gr.themes.Soft(),
846
  title="📊 YouTube Video Analyzer & Downloader"
 
1
  def format_scene_breakdown(scenes):
2
  rows = """
3
+ <table style='width:100%; border-collapse: collapse; background-color:#1a1a1a; color: #FFFFFF; border: 2px solid #FF8C00; font-size: 16px;box-shadow: 0 4px 8px rgba(0,0,0,0.3);'>
4
  <tr style='background-color:#FF8C00; color: #000000;'>
5
+ <th style='padding: 8px; border: 1px solid #FF8C00; color: #000000; font-weight: bold;'>⏱️ Timestamp</th>
6
+ <th style='padding: 8px; border: 1px solid #FF8C00; color: #000000; font-weight: bold;'>📝 Description</th>
7
  </tr>
8
  """
9
  pattern = re.compile(r"\*\*\[(.*?)\]\*\*:\s*(.*)")
 
16
  description = match.group(2).strip()
17
  rows += f"""
18
  <tr style='background-color:#1a1a1a;'>
19
+ <td style='padding: 8px; border: 1px solid #444; color: #87CEEB; font-weight: bold;font-size: 16px;vertical-align: top;'>{timestamp}</td>
20
+ <td style='padding: 8px; border: 1px solid #444; color: #87CEEB; font-weight: bold;font-size: 16px;line-height: 1.4;'>{description}</td>
21
  </tr>
22
  """
23
 
 
40
  from io import BytesIO
41
 
42
 
 
 
43
  def generate_pdf_from_html(html_content):
44
+ """Generate PDF with simplified HTML that works better with xhtml2pdf"""
45
  try:
46
+ # Create a simplified version of the HTML for PDF generation
47
+ # Remove complex CSS that xhtml2pdf can't handle
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  simplified_html = html_content.replace(
49
  "background: linear-gradient(135deg, #2d3748, #1a202c);",
50
+ "background-color: #f5f5f5;"
51
+ ).replace(
52
+ "background: linear-gradient(90deg, #FF8C00, #87CEEB);",
53
+ "background-color: #FF8C00;"
54
+ ).replace(
55
+ "rgba(135, 206, 235, 0.1)",
56
+ "#f9f9f9"
57
+ ).replace(
58
+ "rgba(0, 0, 0, 0.3)",
59
+ "#ffffff"
60
+ ).replace(
61
+ "text-shadow: 2px 2px 4px rgba(0,0,0,0.5);",
62
+ ""
63
+ ).replace(
64
+ "box-shadow: 0 8px 32px rgba(255, 140, 0, 0.3);",
65
+ ""
66
+ ).replace(
67
+ "box-shadow: 0 4px 8px rgba(0,0,0,0.3);",
68
+ ""
69
+ ).replace(
70
+ "display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px;",
71
+ "display: block;"
72
+ ).replace(
73
+ "background-color:#1a1a1a;",
74
+ "background-color:#ffffff;"
75
  ).replace(
76
  "color: #FFFFFF;",
77
  "color: #000000;"
78
+ ).replace(
79
+ "background-color:#FF8C00; color: #000000;",
80
+ "background-color:#FF8C00; color: #000000;"
81
+ ).replace(
82
+ "color: #87CEEB;",
83
+ "color: #000080;"
84
+ ).replace(
85
+ "border: 2px solid #FF8C00;",
86
+ "border: 1px solid #FF8C00;"
87
  )
88
 
89
+ # Remove table styling that causes issues
90
+ simplified_html = re.sub(r"style='[^']*background-color:#1a1a1a[^']*'", "style='background-color:#ffffff;'", simplified_html)
91
+ simplified_html = re.sub(r"style='[^']*color: #87CEEB[^']*'", "style='color: #000080; padding: 8px;'", simplified_html)
92
 
93
+ # Wrap in a complete HTML document with PDF-friendly CSS
94
  pdf_html = f"""
95
  <!DOCTYPE html>
96
  <html>
97
  <head>
98
  <meta charset="UTF-8">
99
  <style>
100
+ @page {{
101
+ size: A4;
102
+ margin: 1cm;
103
+ }}
104
  body {{
105
  font-family: Arial, sans-serif;
106
  font-size: 12px;
107
+ line-height: 1.4;
108
  color: #000000;
109
  background-color: #ffffff;
110
  }}
111
+ .report-container {{
112
+ background-color: #ffffff;
113
+ padding: 15px;
114
+ border: 2px solid #FF8C00;
115
+ border-radius: 8px;
116
+ }}
117
+ .header {{
118
+ text-align: center;
119
+ color: #FF8C00;
120
+ font-size: 20px;
121
+ font-weight: bold;
122
+ margin-bottom: 15px;
123
+ border-bottom: 2px solid #FF8C00;
124
+ padding-bottom: 8px;
125
+ }}
126
+ .info-card {{
127
+ background-color: #f9f9f9;
128
+ padding: 12px;
129
+ margin: 8px 0;
130
+ border-left: 3px solid #87CEEB;
131
+ border-radius: 4px;
132
+ page-break-inside: avoid;
133
+ }}
134
+ .info-title {{
135
+ color: #000080;
136
+ font-size: 14px;
137
+ font-weight: bold;
138
+ margin-bottom: 8px;
139
+ }}
140
+ table {{
141
+ width: 100%;
142
+ border-collapse: collapse;
143
+ margin: 8px 0;
144
+ page-break-inside: avoid;
145
+ }}
146
+ th, td {{
147
+ padding: 6px 8px;
148
+ border: 1px solid #cccccc;
149
+ text-align: left;
150
+ vertical-align: top;
151
+ font-size: 11px;
152
+ }}
153
+ th {{
154
+ background-color: #FF8C00;
155
+ color: #000000;
156
+ font-weight: bold;
157
+ }}
158
+ tr:nth-child(even) {{
159
+ background-color: #f9f9f9;
160
+ }}
161
+ .scene-table {{
162
+ margin-top: 15px;
163
+ }}
164
+ .scene-header {{
165
+ color: #000080;
166
+ font-size: 16px;
167
+ font-weight: bold;
168
+ text-align: center;
169
+ margin-bottom: 10px;
170
+ }}
171
+ div[style*="display: grid"] {{
172
+ display: block !important;
173
+ }}
174
+ div[style*="grid-template-columns"] > div {{
175
+ display: block !important;
176
+ margin-bottom: 10px !important;
177
+ width: 100% !important;
178
+ }}
179
  </style>
180
  </head>
181
  <body>
182
+ <div class="report-container">
183
+ {simplified_html}
184
+ </div>
185
  </body>
186
  </html>
187
  """
188
 
 
 
 
189
  result = BytesIO()
 
 
190
  pisa_status = pisa.CreatePDF(pdf_html, dest=result)
191
+ print("PDF buffer length:", len(result.getvalue()))
 
 
192
 
193
  if pisa_status.err:
194
+ print(f"PDF generation error: {pisa_status.err}")
 
 
 
 
195
  return None
196
 
197
  result.seek(0)
 
198
  return result
199
 
200
  except Exception as e:
201
+ print(f"PDF generation exception: {e}")
 
 
202
  return None
203
+
204
  class YouTubeDownloader:
205
  def __init__(self):
206
  self.download_dir = tempfile.mkdtemp()
 
254
 
255
  # Create enhanced prompt for Gemini
256
  prompt = f"""
257
+ Analyze this YouTube video and create a highly detailed, scene-by-scene breakdown with precise timestamps and specific descriptions:
258
 
259
  Title: {title}
260
  Duration: {duration} seconds
 
443
  text = (title + " " + description).lower()
444
 
445
  if any(word in text for word in ['music', 'song', 'album', 'artist', 'band', 'lyrics']):
446
+ return "🎵 Music Video"
447
  elif any(word in text for word in ['tutorial', 'how to', 'guide', 'learn', 'teaching']):
448
+ return "📚 Tutorial/Educational"
449
  elif any(word in text for word in ['funny', 'comedy', 'entertainment', 'vlog', 'challenge']):
450
+ return "🎭 Entertainment/Comedy"
451
  elif any(word in text for word in ['news', 'breaking', 'report', 'update']):
452
+ return "📰 News/Information"
453
  elif any(word in text for word in ['review', 'unboxing', 'test', 'comparison']):
454
+ return "Review/Unboxing"
455
  elif any(word in text for word in ['commercial', 'ad', 'brand', 'product']):
456
+ return "📺 Commercial/Advertisement"
457
  else:
458
+ return "🎬 General Content"
459
 
460
  def detect_background_music(self, video_info):
461
  """Detect background music style"""
 
463
  description = video_info.get('description', '').lower()
464
 
465
  if any(word in title for word in ['music', 'song', 'soundtrack']):
466
+ return "🎵 Original Music/Soundtrack - Primary audio content"
467
  elif any(word in title for word in ['commercial', 'ad', 'brand']):
468
+ return "🎶 Upbeat Commercial Music - Designed to enhance brand appeal"
469
  elif any(word in title for word in ['tutorial', 'how to', 'guide']):
470
+ return "🔇 Minimal/No Background Music - Focus on instruction"
471
  elif any(word in title for word in ['vlog', 'daily', 'life']):
472
+ return "🎼 Ambient Background Music - Complementary to narration"
473
  else:
474
+ return "🎵 Background Music - Complementing video mood and pacing"
475
 
476
  def detect_influencer_status(self, video_info):
477
  """Detect influencer status"""
 
479
  view_count = video_info.get('view_count', 0)
480
 
481
  if subscriber_count > 10000000:
482
+ return "🌟 Mega Influencer (10M+ subscribers)"
483
  elif subscriber_count > 1000000:
484
+ return "Major Influencer (1M+ subscribers)"
485
  elif subscriber_count > 100000:
486
+ return "🎯 Mid-tier Influencer (100K+ subscribers)"
487
  elif subscriber_count > 10000:
488
+ return "📈 Micro Influencer (10K+ subscribers)"
489
  elif view_count > 100000:
490
+ return "🔥 Viral Content Creator"
491
  else:
492
+ return "👤 Regular Content Creator"
493
 
494
  def format_number(self, num):
495
  if num is None or num == 0:
 
535
 
536
  # Generate compact report with contrasting background
537
  report = f"""
538
+ <div style='font-family: Arial, sans-serif; background: linear-gradient(135deg, #2d3748, #1a202c); padding: 20px; border-radius: 15px; border: 2px solid #FF8C00; box-shadow: 0 8px 32px rgba(255, 140, 0, 0.3);'>
539
+
540
+ <div style='text-align: center; margin-bottom: 20px;'>
541
+ <h2 style='color: #FF8C00; font-size: 24px; margin: 0; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);'>🎬 YouTube Video Analysis Report</h2>
542
+ <div style='height: 3px; background: linear-gradient(90deg, #FF8C00, #87CEEB); margin: 10px 0; border-radius: 5px;'></div>
 
 
 
 
 
 
 
 
 
 
 
543
  </div>
544
+
545
+ <!-- Compact Information Grid -->
546
+ <div style='display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-bottom: 20px;'>
547
+
548
+ <!-- Basic Information Card -->
549
+ <div style='background: rgba(135, 206, 235, 0.1); padding: 15px; border-radius: 10px; border-left: 4px solid #87CEEB;'>
550
+ <h3 style='color: #FF8C00; margin: 0 0 10px 0; font-size: 16px;'>📋 Basic Info</h3>
551
+ <table style='width: 100%; font-size: 14px;'>
552
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>📹 Title:</td></tr>
553
+ <tr><td style='color: #87CEEB; font-weight: bold: padding: 4px 0 8px 0; word-wrap: wrap-word; white-space: normal; max-width: 200px;'>{title}</td></tr>
554
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>👤 Creator:</td><td style='color: #87CEEB; padding: 2px 0;'>{uploader[:20]}{'...' if len(uploader) > 20 else ''}</td></tr>
555
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>📅 Date:</td><td style='color: #87CEEB; padding: 2px 0;'>{formatted_date}</td></tr>
556
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>⏱️ Duration:</td><td style='color: #87CEEB; padding: 2px 0;'>{duration_str}</td></tr>
557
+ </table>
558
+ </div>
559
+
560
+ <!-- Performance Metrics Card -->
561
+ <div style='background: rgba(135, 206, 235, 0.1); padding: 15px; border-radius: 10px; border-left: 4px solid #FF8C00;border: 1px solid #444'>
562
+ <h3 style='color: #FF8C00; margin: 0 0 10px 0; font-size: 16px;'>📊 Metrics</h3>
563
+ <table style='width: 100%; font-size: 12px;'>
564
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>👀 Views:</td><td style='color: #87CEEB; padding: 4px 0;'>{self.format_number(view_count)}</td></tr>
565
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>👍 Likes:</td><td style='color: #87CEEB; padding: 4px 0;'>{self.format_number(like_count)}</td></tr>
566
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>💬 Comments:</td><td style='color: #87CEEB; padding: 4px 0;'>{self.format_number(comment_count)}</td></tr>
567
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>📈 Engagement:</td><td style='color: #87CEEB; padding: 4px 0;'>{engagement_rate:.2f}%</td></tr>
568
  </table>
569
  </div>
570
+
571
+ <!-- Content Analysis Card -->
572
+ <div style='background:rgba(135, 206, 235, 0.1); padding: 15px; border-radius: 10px; border-left: 4px solid #87CEEB;border: 1px solid #444'>
573
+ <h3 style='color:#FF8C00; margin: 0 0 10px 0; font-size: 16px;'>🎯 Analysis</h3>
574
+ <table style='width: 100%; font-size: 12px;'>
575
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>📂 Type:</td></tr>
576
+ <tr><td style='color: 87CEEB; padding: 4px 0 8px 0; word-break: break-word;'>{video_type}</td></tr>
577
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>🎵 Music:</td></tr>
578
+ <tr><td style='color: #87CEEB; padding: 4px 0 8px 0; word-break: break-word;'>{background_music[:30]}{'...' if len(background_music) > 30 else ''}</td></tr>
579
+ <tr><td style='color: #87CEEB; font-weight: bold; padding: 4px 0;'>👑 Status:</td></tr>
580
+ <tr><td style='color: #87CEEB; padding: 4px 0; word-break: break-word;'>{influencer_status[:25]}{'...' if len(influencer_status) > 25 else ''}</td></tr>
581
  </table>
582
  </div>
583
  </div>
584
+
585
+ <!-- Scene Breakdown Section -->
586
+ <div style='background: rgba(0, 0, 0, 0.3); padding: 15px; border-radius: 10px; border: 1px solid #444;'>
587
+ <h3 style='color: #87CEEB; margin: 0 0 15px 0; font-size: 18px; text-align: center;'>🎬 Scene-by-Scene Breakdown</h3>
588
+ {scene_table_html}
589
+ </div>
590
+
591
  </div>
592
+ """
593
+
 
 
 
 
 
594
  return report.strip()
595
 
596
  def get_video_info(self, url, progress=gr.Progress(), cookiefile=None):
 
611
 
612
  if cookiefile and os.path.exists(cookiefile):
613
  ydl_opts['cookiefile'] = cookiefile
 
 
 
 
 
 
614
 
615
  progress(0.5, desc="Extracting video metadata...")
616
 
 
622
  return info, "✅ Video information extracted successfully"
623
 
624
  except Exception as e:
625
+ return None, f"❌ Error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
626
 
627
  def download_video(self, url, quality="best", audio_only=False, progress=gr.Progress(), cookiefile=None):
628
  """Download video with progress tracking"""
 
737
  try:
738
  progress(0.05, desc="Starting analysis...")
739
 
740
+ cookiefile = None
741
+ if cookies_file and os.path.exists(cookies_file):
742
+ cookiefile = cookies_file
743
+
744
  info, msg = downloader.get_video_info(url, progress=progress, cookiefile=cookiefile)
745
 
746
  if info:
747
  progress(0.95, desc="Generating comprehensive report...")
748
  formatted_info = downloader.format_video_info(info)
 
749
  progress(1.0, desc="✅ Complete!")
750
  return formatted_info
751
  else:
 
755
  return f"❌ System Error: {str(e)}"
756
 
757
 
758
+ def analyze_and_generate_pdf(url, cookies_file, progress=gr.Progress()):
 
759
  try:
760
+ progress(0.1, desc="Extracting video info...")
761
+ cookiefile = cookies_file if cookies_file and os.path.exists(cookies_file) else None
762
 
763
+ info, _ = downloader.get_video_info(url, progress=progress, cookiefile=cookiefile)
764
+ if not info:
765
+ return "❌ Failed to extract video info"
766
 
767
+ progress(0.6, desc="Generating HTML report...")
768
+ report_html = downloader.format_video_info(info)
769
 
770
+ progress(0.8, desc="Creating PDF...")
771
+
772
+ # Generate PDF from HTML
773
+ pdf_data = BytesIO()
774
+ result = pisa.CreatePDF(report_html, dest=pdf_data)
775
+
776
+ if result.err:
777
+ return "❌ PDF generation failed"
778
 
779
+ pdf_data.seek(0)
780
  pdf_path = os.path.join(tempfile.gettempdir(), f"analysis_report_{uuid.uuid4().hex}.pdf")
781
  with open(pdf_path, "wb") as f:
782
+ f.write(pdf_data.read())
783
 
784
+ progress(1.0, desc="✅ PDF ready!")
785
+ print("✅ PDF generated at:", pdf_path)
786
+ return pdf_path # ✅ Return direct path like in test code
787
 
788
  except Exception as e:
789
+ print("❌ Exception:", e)
790
+ return f"❌ Error: {str(e)}"
791
+
792
+ def generate_pdf_from_html(html_content):
793
+ """Generate PDF with simplified HTML that works better with xhtml2pdf"""
794
+ try:
795
+ # Create a simplified version of the HTML for PDF generation
796
+ # Remove complex CSS that xhtml2pdf can't handle
797
+ simplified_html = html_content.replace(
798
+ "background: linear-gradient(135deg, #2d3748, #1a202c);",
799
+ "background-color: #f5f5f5;"
800
+ ).replace(
801
+ "background: linear-gradient(90deg, #FF8C00, #87CEEB);",
802
+ "background-color: #FF8C00;"
803
+ ).replace(
804
+ "rgba(135, 206, 235, 0.1)",
805
+ "#f9f9f9"
806
+ ).replace(
807
+ "rgba(0, 0, 0, 0.3)",
808
+ "#ffffff"
809
+ ).replace(
810
+ "text-shadow: 2px 2px 4px rgba(0,0,0,0.5);",
811
+ ""
812
+ ).replace(
813
+ "box-shadow: 0 8px 32px rgba(255, 140, 0, 0.3);",
814
+ ""
815
+ ).replace(
816
+ "box-shadow: 0 4px 8px rgba(0,0,0,0.3);",
817
+ ""
818
+ ).replace(
819
+ "display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px;",
820
+ "display: block;"
821
+ ).replace(
822
+ "background-color:#1a1a1a;",
823
+ "background-color:#ffffff;"
824
+ ).replace(
825
+ "color: #FFFFFF;",
826
+ "color: #000000;"
827
+ ).replace(
828
+ "background-color:#FF8C00; color: #000000;",
829
+ "background-color:#FF8C00; color: #000000;"
830
+ ).replace(
831
+ "color: #87CEEB;",
832
+ "color: #000080;"
833
+ ).replace(
834
+ "border: 2px solid #FF8C00;",
835
+ "border: 1px solid #FF8C00;"
836
+ )
837
+
838
+ # Remove table styling that causes issues
839
+ simplified_html = re.sub(r"style='[^']*background-color:#1a1a1a[^']*'", "style='background-color:#ffffff;'", simplified_html)
840
+ simplified_html = re.sub(r"style='[^']*color: #87CEEB[^']*'", "style='color: #000080; padding: 8px;'", simplified_html)
841
+
842
+ # Wrap in a complete HTML document with PDF-friendly CSS
843
+ pdf_html = f"""
844
+ <!DOCTYPE html>
845
+ <html>
846
+ <head>
847
+ <meta charset="UTF-8">
848
+ <style>
849
+ @page {{
850
+ size: A4;
851
+ margin: 1cm;
852
+ }}
853
+ body {{
854
+ font-family: Arial, sans-serif;
855
+ font-size: 12px;
856
+ line-height: 1.4;
857
+ color: #000000;
858
+ background-color: #ffffff;
859
+ }}
860
+ .report-container {{
861
+ background-color: #ffffff;
862
+ padding: 15px;
863
+ border: 2px solid #FF8C00;
864
+ border-radius: 8px;
865
+ }}
866
+ .header {{
867
+ text-align: center;
868
+ color: #FF8C00;
869
+ font-size: 20px;
870
+ font-weight: bold;
871
+ margin-bottom: 15px;
872
+ border-bottom: 2px solid #FF8C00;
873
+ padding-bottom: 8px;
874
+ }}
875
+ .info-card {{
876
+ background-color: #f9f9f9;
877
+ padding: 12px;
878
+ margin: 8px 0;
879
+ border-left: 3px solid #87CEEB;
880
+ border-radius: 4px;
881
+ page-break-inside: avoid;
882
+ }}
883
+ .info-title {{
884
+ color: #000080;
885
+ font-size: 14px;
886
+ font-weight: bold;
887
+ margin-bottom: 8px;
888
+ }}
889
+ table {{
890
+ width: 100%;
891
+ border-collapse: collapse;
892
+ margin: 8px 0;
893
+ page-break-inside: avoid;
894
+ }}
895
+ th, td {{
896
+ padding: 6px 8px;
897
+ border: 1px solid #cccccc;
898
+ text-align: left;
899
+ vertical-align: top;
900
+ font-size: 11px;
901
+ }}
902
+ th {{
903
+ background-color: #FF8C00;
904
+ color: #000000;
905
+ font-weight: bold;
906
+ }}
907
+ tr:nth-child(even) {{
908
+ background-color: #f9f9f9;
909
+ }}
910
+ .scene-table {{
911
+ margin-top: 15px;
912
+ }}
913
+ .scene-header {{
914
+ color: #000080;
915
+ font-size: 16px;
916
+ font-weight: bold;
917
+ text-align: center;
918
+ margin-bottom: 10px;
919
+ }}
920
+ div[style*="display: grid"] {{
921
+ display: block !important;
922
+ }}
923
+ div[style*="grid-template-columns"] > div {{
924
+ display: block !important;
925
+ margin-bottom: 10px !important;
926
+ width: 100% !important;
927
+ }}
928
+ </style>
929
+ </head>
930
+ <body>
931
+ <div class="report-container">
932
+ {simplified_html}
933
+ </div>
934
+ </body>
935
+ </html>
936
+ """
937
+
938
+ result = BytesIO()
939
+ pisa_status = pisa.CreatePDF(pdf_html, dest=result)
940
+
941
+ if pisa_status.err:
942
+ print(f"PDF generation error: {pisa_status.err}")
943
+ return None
944
+
945
+ result.seek(0)
946
+ return result
947
+
948
+ except Exception as e:
949
+ print(f"PDF generation exception: {e}")
950
  return None
951
 
952
+
953
+
954
  def download_with_cookies(url, quality, audio_only, cookies_file, progress=gr.Progress()):
955
  """Main download function"""
956
  try:
 
984
  h3, .gr-group h3, .gradio-container h3 {
985
  color: #87CEEB !important;
986
  }
987
+
988
  label, .gr-textbox label, .gr-file label, .gr-dropdown label, .gr-checkbox label {
989
  color: #00008B !important;
990
  font-weight: bold !important;
991
  }
992
+
993
  .gr-file .file-name {
994
  color: #00008B !important;
995
  font-weight: bold !important;
996
  }
997
+
998
  /* Make tab labels dark blue too */
999
  .gr-tab-nav button {
1000
  color: #00008B !important;
1001
  }
1002
+
1003
  .gr-tab-nav button.selected {
1004
  background-color: #FF8C00 !important;
1005
  color: #000000 !important;
1006
  }
1007
+
1008
  /* Light blue text for API status */
1009
  .light-blue-text textarea {
1010
  color: #87CEEB !important;
 
1014
  background-color: #2a2a2a !important;
1015
  border: 2px dashed #444 !important;
1016
  }
1017
+
1018
  .gr-group, .gr-form, .gr-row {
1019
  background-color: #1a1a1a !important;
1020
  border: 1px solid #444 !important;
1021
  border-radius: 10px;
1022
  padding: 15px;
1023
  }
1024
+
1025
  """,
1026
  theme=gr.themes.Soft(),
1027
  title="📊 YouTube Video Analyzer & Downloader"