Gresekxnol commited on
Commit
fb91ce7
Β·
verified Β·
1 Parent(s): 74b8728

Update app.py

Browse files

Fitur YouTube Integration:
1. Dual Input Method

Radio button untuk memilih antara "Upload Video File" atau "YouTube URL"
Dynamic UI yang menampilkan input yang relevan
Smooth transition antar mode input

2. YouTube Download Engine

yt-dlp integration untuk download video berkualitas tinggi
Smart quality selection (maksimal 720p untuk performa optimal di free tier)
URL validation untuk berbagai format YouTube URL
Metadata extraction (title, uploader, duration, views)

3. Supported URL Formats
βœ… https://www.youtube.com/watch?v=VIDEO_ID
βœ… https://youtu.be/VIDEO_ID
βœ… https://www.youtube.com/embed/VIDEO_ID
βœ… https://youtube.com/watch?v=VIDEO_ID
4. Smart Limitations untuk Free Tier

Maximum duration: 1 jam untuk YouTube videos
Quality limit: 720p maksimal untuk menghemat bandwidth
Error handling: Validasi URL dan penanganan error yang comprehensive
Progress tracking: Real-time status update

5. Enhanced User Experience

Clear instructions untuk setiap input method
Example URLs yang muncul saat memilih YouTube mode
Better status messages dengan informasi video yang diproses
Source tracking dalam clip analysis

Files changed (1) hide show
  1. app.py +179 -12
app.py CHANGED
@@ -14,6 +14,9 @@ import json
14
  import librosa
15
  from textblob import TextBlob
16
  import emoji
 
 
 
17
 
18
  class AIVideoClipper:
19
  def __init__(self):
@@ -50,7 +53,84 @@ class AIVideoClipper:
50
  r"wait for it"
51
  ]
52
 
53
- def extract_audio_features(self, audio_path: str) -> Dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  """Extract audio features for engagement analysis"""
55
  y, sr = librosa.load(audio_path)
56
 
@@ -282,17 +362,39 @@ class AIVideoClipper:
282
 
283
  return output_path
284
 
285
- def process_video(video_file, clip_duration, num_clips, add_subtitles):
286
  """Main function to process video and create clips"""
287
- if video_file is None:
288
- return "Please upload a video file.", [], []
289
 
290
  clipper = AIVideoClipper()
291
 
292
  try:
293
  # Create temporary directory
294
  with tempfile.TemporaryDirectory() as temp_dir:
295
- video_path = video_file.name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
  # Extract audio features
298
  print("Extracting audio features...")
@@ -338,14 +440,15 @@ def process_video(video_file, clip_duration, num_clips, add_subtitles):
338
  'end_time': f"{moment['end']:.1f}s",
339
  'duration': f"{moment['duration']:.1f}s",
340
  'virality_score': f"{moment['virality_score']:.2f}/10",
341
- 'text_preview': moment['text'][:100] + "..." if len(moment['text']) > 100 else moment['text']
 
342
  })
343
 
344
  except Exception as e:
345
  print(f"Error creating clip {i+1}: {str(e)}")
346
  continue
347
 
348
- success_msg = f"Successfully created {len(output_videos)} clips!"
349
  return success_msg, output_videos, clip_info
350
 
351
  except Exception as e:
@@ -359,7 +462,7 @@ def create_interface():
359
  # 🎬 AI Video Clipper
360
 
361
  Transform your long videos into viral short clips automatically!
362
- Upload your video and let AI find the most engaging moments.
363
 
364
  **Features:**
365
  - πŸ€– AI-powered moment detection
@@ -367,15 +470,46 @@ def create_interface():
367
  - πŸ“ Automatic subtitles with emojis
368
  - πŸ“Š Virality scoring
369
  - 🎯 Multi-language support
 
370
  """
371
  )
372
 
373
  with gr.Row():
374
  with gr.Column():
 
 
 
 
 
 
 
 
 
375
  video_input = gr.File(
376
- label="Upload Video",
377
  file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"],
378
- type="filepath"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  )
380
 
381
  with gr.Row():
@@ -410,7 +544,7 @@ def create_interface():
410
  status_output = gr.Textbox(
411
  label="Status",
412
  interactive=False,
413
- lines=2
414
  )
415
 
416
  clips_output = gr.Gallery(
@@ -430,18 +564,51 @@ def create_interface():
430
  visible=True
431
  )
432
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  # Example videos section
434
  gr.Markdown("### πŸ“Ί Tips for Best Results:")
435
  gr.Markdown("""
 
436
  - Upload videos with clear speech (podcasts, interviews, tutorials work great!)
 
 
 
 
 
 
 
 
 
 
437
  - Longer videos (5+ minutes) provide more clip opportunities
438
  - Videos with engaging content and emotional moments score higher
439
  - Good audio quality improves transcription accuracy
 
440
  """)
441
 
442
  process_btn.click(
443
  process_video,
444
- inputs=[video_input, clip_duration, num_clips, add_subtitles],
445
  outputs=[status_output, clips_output, info_output]
446
  )
447
 
 
14
  import librosa
15
  from textblob import TextBlob
16
  import emoji
17
+ import yt_dlp
18
+ import requests
19
+ from urllib.parse import urlparse, parse_qs
20
 
21
  class AIVideoClipper:
22
  def __init__(self):
 
53
  r"wait for it"
54
  ]
55
 
56
+ def download_youtube_video(self, url: str, temp_dir: str) -> Tuple[str, Dict]:
57
+ """Download YouTube video and return path + metadata"""
58
+ print(f"Downloading YouTube video: {url}")
59
+
60
+ # Validate YouTube URL
61
+ if not self.is_valid_youtube_url(url):
62
+ raise ValueError("Invalid YouTube URL. Please provide a valid YouTube video link.")
63
+
64
+ # Configure yt-dlp options for free tier optimization
65
+ ydl_opts = {
66
+ 'format': 'best[height<=720][ext=mp4]/best[ext=mp4]/best', # Limit to 720p for performance
67
+ 'outtmpl': os.path.join(temp_dir, '%(title)s.%(ext)s'),
68
+ 'noplaylist': True,
69
+ 'extractaudio': False,
70
+ 'audioformat': 'mp3',
71
+ 'ignoreerrors': False,
72
+ 'no_warnings': False,
73
+ 'extract_flat': False,
74
+ }
75
+
76
+ try:
77
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
78
+ # Extract info first
79
+ info = ydl.extract_info(url, download=False)
80
+
81
+ # Check video duration (limit to 60 minutes for free tier)
82
+ duration = info.get('duration', 0)
83
+ if duration > 3600: # 1 hour limit
84
+ raise ValueError("Video too long. Please use videos shorter than 1 hour.")
85
+
86
+ # Download the video
87
+ ydl.download([url])
88
+
89
+ # Find the downloaded file
90
+ video_title = info.get('title', 'video')
91
+ video_ext = info.get('ext', 'mp4')
92
+ video_path = os.path.join(temp_dir, f"{video_title}.{video_ext}")
93
+
94
+ # Sometimes yt-dlp changes the filename, so find the actual file
95
+ downloaded_files = [f for f in os.listdir(temp_dir) if f.endswith(('.mp4', '.mkv', '.webm'))]
96
+ if downloaded_files:
97
+ video_path = os.path.join(temp_dir, downloaded_files[0])
98
+
99
+ metadata = {
100
+ 'title': video_title,
101
+ 'duration': duration,
102
+ 'uploader': info.get('uploader', 'Unknown'),
103
+ 'view_count': info.get('view_count', 0),
104
+ 'upload_date': info.get('upload_date', 'Unknown')
105
+ }
106
+
107
+ print(f"Successfully downloaded: {video_title}")
108
+ return video_path, metadata
109
+
110
+ except Exception as e:
111
+ raise Exception(f"Failed to download YouTube video: {str(e)}")
112
+
113
+ def is_valid_youtube_url(self, url: str) -> bool:
114
+ """Check if URL is a valid YouTube URL"""
115
+ youtube_regex = re.compile(
116
+ r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/'
117
+ r'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})'
118
+ )
119
+ return youtube_regex.match(url) is not None
120
+
121
+ def extract_video_id(self, url: str) -> str:
122
+ """Extract video ID from YouTube URL"""
123
+ patterns = [
124
+ r'(?:v=|\/)([0-9A-Za-z_-]{11}).*',
125
+ r'(?:embed\/)([0-9A-Za-z_-]{11})',
126
+ r'(?:v\/)([0-9A-Za-z_-]{11})'
127
+ ]
128
+
129
+ for pattern in patterns:
130
+ match = re.search(pattern, url)
131
+ if match:
132
+ return match.group(1)
133
+ return None
134
  """Extract audio features for engagement analysis"""
135
  y, sr = librosa.load(audio_path)
136
 
 
362
 
363
  return output_path
364
 
365
+ def process_video(input_type, video_file, youtube_url, clip_duration, num_clips, add_subtitles):
366
  """Main function to process video and create clips"""
 
 
367
 
368
  clipper = AIVideoClipper()
369
 
370
  try:
371
  # Create temporary directory
372
  with tempfile.TemporaryDirectory() as temp_dir:
373
+ video_path = None
374
+ video_metadata = {}
375
+
376
+ # Handle input based on type
377
+ if input_type == "Upload Video File":
378
+ if video_file is None:
379
+ return "Please upload a video file.", [], []
380
+ video_path = video_file.name
381
+ video_metadata = {'title': 'Uploaded Video', 'source': 'upload'}
382
+
383
+ elif input_type == "YouTube URL":
384
+ if not youtube_url or not youtube_url.strip():
385
+ return "Please enter a YouTube URL.", [], []
386
+
387
+ try:
388
+ video_path, video_metadata = clipper.download_youtube_video(youtube_url.strip(), temp_dir)
389
+ video_metadata['source'] = 'youtube'
390
+ except Exception as e:
391
+ return f"Error downloading YouTube video: {str(e)}", [], []
392
+
393
+ else:
394
+ return "Please select an input method.", [], []
395
+
396
+ if not video_path or not os.path.exists(video_path):
397
+ return "Video file not found or invalid.", [], []
398
 
399
  # Extract audio features
400
  print("Extracting audio features...")
 
440
  'end_time': f"{moment['end']:.1f}s",
441
  'duration': f"{moment['duration']:.1f}s",
442
  'virality_score': f"{moment['virality_score']:.2f}/10",
443
+ 'text_preview': moment['text'][:100] + "..." if len(moment['text']) > 100 else moment['text'],
444
+ 'source_video': video_metadata.get('title', 'Unknown')
445
  })
446
 
447
  except Exception as e:
448
  print(f"Error creating clip {i+1}: {str(e)}")
449
  continue
450
 
451
+ success_msg = f"βœ… Successfully created {len(output_videos)} clips from: {video_metadata.get('title', 'video')}"
452
  return success_msg, output_videos, clip_info
453
 
454
  except Exception as e:
 
462
  # 🎬 AI Video Clipper
463
 
464
  Transform your long videos into viral short clips automatically!
465
+ Upload a video file or paste a YouTube URL and let AI find the most engaging moments.
466
 
467
  **Features:**
468
  - πŸ€– AI-powered moment detection
 
470
  - πŸ“ Automatic subtitles with emojis
471
  - πŸ“Š Virality scoring
472
  - 🎯 Multi-language support
473
+ - πŸ”— YouTube video download support
474
  """
475
  )
476
 
477
  with gr.Row():
478
  with gr.Column():
479
+ # Input method selection
480
+ input_type = gr.Radio(
481
+ choices=["Upload Video File", "YouTube URL"],
482
+ value="Upload Video File",
483
+ label="Choose Input Method",
484
+ interactive=True
485
+ )
486
+
487
+ # Video file upload (conditional)
488
  video_input = gr.File(
489
+ label="Upload Video File",
490
  file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"],
491
+ type="filepath",
492
+ visible=True
493
+ )
494
+
495
+ # YouTube URL input (conditional)
496
+ youtube_input = gr.Textbox(
497
+ label="YouTube URL",
498
+ placeholder="https://www.youtube.com/watch?v=...",
499
+ visible=False,
500
+ info="Paste any YouTube video URL (supports various formats)"
501
+ )
502
+
503
+ # Show example URLs
504
+ gr.Markdown(
505
+ """
506
+ **Supported URL formats:**
507
+ - `https://www.youtube.com/watch?v=VIDEO_ID`
508
+ - `https://youtu.be/VIDEO_ID`
509
+ - `https://www.youtube.com/embed/VIDEO_ID`
510
+ """,
511
+ visible=False,
512
+ elem_id="url_examples"
513
  )
514
 
515
  with gr.Row():
 
544
  status_output = gr.Textbox(
545
  label="Status",
546
  interactive=False,
547
+ lines=3
548
  )
549
 
550
  clips_output = gr.Gallery(
 
564
  visible=True
565
  )
566
 
567
+ # Dynamic input visibility
568
+ def update_input_visibility(choice):
569
+ if choice == "Upload Video File":
570
+ return (
571
+ gr.update(visible=True), # video_input
572
+ gr.update(visible=False), # youtube_input
573
+ gr.update(visible=False) # url_examples
574
+ )
575
+ else: # YouTube URL
576
+ return (
577
+ gr.update(visible=False), # video_input
578
+ gr.update(visible=True), # youtube_input
579
+ gr.update(visible=True) # url_examples
580
+ )
581
+
582
+ input_type.change(
583
+ update_input_visibility,
584
+ inputs=[input_type],
585
+ outputs=[video_input, youtube_input, gr.Markdown(elem_id="url_examples")]
586
+ )
587
+
588
  # Example videos section
589
  gr.Markdown("### πŸ“Ί Tips for Best Results:")
590
  gr.Markdown("""
591
+ **πŸ“ File Upload:**
592
  - Upload videos with clear speech (podcasts, interviews, tutorials work great!)
593
+ - Supported formats: MP4, AVI, MOV, MKV, WebM
594
+ - Maximum recommended duration: 2 hours
595
+
596
+ **πŸ”— YouTube Videos:**
597
+ - Any public YouTube video (no age restrictions)
598
+ - Automatically downloads in optimal quality (720p max for performance)
599
+ - Works with livestreams, premieres, and regular videos
600
+ - Maximum duration: 1 hour for free tier
601
+
602
+ **🎯 Content Tips:**
603
  - Longer videos (5+ minutes) provide more clip opportunities
604
  - Videos with engaging content and emotional moments score higher
605
  - Good audio quality improves transcription accuracy
606
+ - Educational content, podcasts, and interviews work exceptionally well
607
  """)
608
 
609
  process_btn.click(
610
  process_video,
611
+ inputs=[input_type, video_input, youtube_input, clip_duration, num_clips, add_subtitles],
612
  outputs=[status_output, clips_output, info_output]
613
  )
614