dennisvdang commited on
Commit
eca162c
·
1 Parent(s): 0eb1a5b

Add YouTube download fallback and direct file upload option

Browse files
Files changed (1) hide show
  1. app.py +104 -28
app.py CHANGED
@@ -41,14 +41,15 @@ def extract_audio(url):
41
  os.rename(out_file, audio_file)
42
  return audio_file, video_title, temp_dir
43
  else:
44
- st.error("No audio stream found")
45
- return None, None, None
46
  except Exception as e:
47
  st.warning(f"pytube error: {e}. Falling back to yt-dlp.")
48
  return extract_audio_with_ytdlp(url)
49
 
50
  def extract_audio_with_ytdlp(url):
51
  try:
 
52
  ydl_opts = {
53
  'format': 'bestaudio/best',
54
  'postprocessors': [{
@@ -57,15 +58,54 @@ def extract_audio_with_ytdlp(url):
57
  'preferredquality': '192',
58
  }],
59
  'outtmpl': '%(title)s.%(ext)s',
60
- 'paths': {'home': tempfile.mkdtemp()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
63
  info_dict = ydl.extract_info(url, download=True)
 
 
 
 
64
  video_title = info_dict.get('title', None)
65
- audio_file = os.path.join(ydl_opts['paths']['home'], f"{video_title}.mp3")
66
- return audio_file, video_title, ydl_opts['paths']['home']
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  except Exception as e:
68
- st.error(f"An error occurred with yt-dlp: {e}")
 
 
 
 
 
 
69
  return None, None, None
70
 
71
 
@@ -352,12 +392,15 @@ def make_predictions(model, processed_audio, audio_features, url, video_name):
352
  chorus_start_times = [meter_grid_times[i] for i in range(len(smoothed_predictions)) if smoothed_predictions[i] == 1 and (i == 0 or smoothed_predictions[i - 1] == 0)]
353
  chorus_end_times = [meter_grid_times[i + 1] for i in range(len(smoothed_predictions)) if smoothed_predictions[i] == 1 and (i == len(smoothed_predictions) - 1 or smoothed_predictions[i + 1] == 0)]
354
 
355
- st.write(f"**Video Title:** {video_name}")
356
  st.write(f"**Number of choruses identified:** {len(chorus_start_times)}")
357
 
358
- for start_time, end_time in zip(chorus_start_times, chorus_end_times):
359
- link = f"{url}&t={int(start_time)}s"
360
- st.write(f"Chorus from {format_time(start_time)} to {format_time(end_time)}: [{link}]({link})")
 
 
 
361
 
362
  if len(chorus_start_times) == 0:
363
  st.write("No choruses identified.")
@@ -419,26 +462,59 @@ def main():
419
  st.image(cover_image, use_column_width=True)
420
  st.title("Chorus Finder")
421
  st.write("This app uses a pre-trained convolutional recurrent neural network to predict chorus locations in music. To learn more about this project, visit [github.com/dennisvdang/chorus-detection](https://github.com/dennisvdang/chorus-detection).")
422
- st.write("Enter a YouTube song URL to find the chorus!")
423
- url = st.text_input("YouTube URL")
424
- if st.button("Find Chorus"):
425
- if url:
426
- with st.spinner('Analyzing YouTube link...'):
427
- audio_file, video_title, temp_dir = extract_audio(url)
428
- if audio_file:
429
- with st.spinner('Analyzing YouTube link...'):
430
- strip_silence(audio_file)
431
- with st.spinner('Processing audio...'):
432
- processed_audio, audio_features = process_audio(audio_path=audio_file)
433
- with st.spinner('Loading model...'):
434
- model = load_model(MODEL_PATH)
435
- with st.spinner('Making predictions...'):
436
- smoothed_predictions = make_predictions(model, processed_audio, audio_features, url, video_title)
437
- with st.spinner('Plotting predictions...'):
438
- plot_predictions(audio_features, smoothed_predictions)
439
- shutil.rmtree(temp_dir)
 
 
 
 
 
 
 
440
  else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  st.error("Please enter a valid YouTube URL")
 
 
442
 
443
  if __name__ == "__main__":
444
  main()
 
41
  os.rename(out_file, audio_file)
42
  return audio_file, video_title, temp_dir
43
  else:
44
+ st.warning("No audio stream found with pytube. Trying yt-dlp...")
45
+ return extract_audio_with_ytdlp(url)
46
  except Exception as e:
47
  st.warning(f"pytube error: {e}. Falling back to yt-dlp.")
48
  return extract_audio_with_ytdlp(url)
49
 
50
  def extract_audio_with_ytdlp(url):
51
  try:
52
+ temp_dir = tempfile.mkdtemp()
53
  ydl_opts = {
54
  'format': 'bestaudio/best',
55
  'postprocessors': [{
 
58
  'preferredquality': '192',
59
  }],
60
  'outtmpl': '%(title)s.%(ext)s',
61
+ 'paths': {'home': temp_dir},
62
+ # Add options to help bypass YouTube restrictions
63
+ 'nocheckcertificate': True,
64
+ 'ignoreerrors': True,
65
+ 'no_warnings': True,
66
+ 'quiet': True,
67
+ 'cookiefile': None, # Use cookies if available
68
+ 'extractor_args': {'youtube': {'player_client': ['web']}}, # Use web player
69
+ # Add more random headers to avoid detection
70
+ 'http_headers': {
71
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36',
72
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
73
+ 'Accept-Language': 'en-US,en;q=0.5',
74
+ 'Accept-Encoding': 'gzip, deflate',
75
+ 'DNT': '1',
76
+ 'Connection': 'keep-alive',
77
+ }
78
  }
79
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
80
  info_dict = ydl.extract_info(url, download=True)
81
+ if not info_dict:
82
+ st.error("Could not process this YouTube URL. The video might be age-restricted or protected.")
83
+ return None, None, None
84
+
85
  video_title = info_dict.get('title', None)
86
+ if not video_title:
87
+ st.error("Could not retrieve video title")
88
+ return None, None, None
89
+
90
+ audio_file = os.path.join(temp_dir, f"{video_title}.mp3")
91
+ if not os.path.exists(audio_file):
92
+ # Try alternate filename format which might be used by yt-dlp
93
+ possible_files = [f for f in os.listdir(temp_dir) if f.endswith('.mp3')]
94
+ if possible_files:
95
+ audio_file = os.path.join(temp_dir, possible_files[0])
96
+ else:
97
+ st.error("Downloaded file not found")
98
+ return None, None, None
99
+
100
+ return audio_file, video_title, temp_dir
101
  except Exception as e:
102
+ error_message = str(e)
103
+ if "Sign in to confirm you're not a bot" in error_message:
104
+ st.error("YouTube is detecting automated access. Try a different video or try again later.")
105
+ elif "This video is unavailable" in error_message:
106
+ st.error("This video is unavailable or may be restricted.")
107
+ else:
108
+ st.error(f"An error occurred with yt-dlp: {e}")
109
  return None, None, None
110
 
111
 
 
392
  chorus_start_times = [meter_grid_times[i] for i in range(len(smoothed_predictions)) if smoothed_predictions[i] == 1 and (i == 0 or smoothed_predictions[i - 1] == 0)]
393
  chorus_end_times = [meter_grid_times[i + 1] for i in range(len(smoothed_predictions)) if smoothed_predictions[i] == 1 and (i == len(smoothed_predictions) - 1 or smoothed_predictions[i + 1] == 0)]
394
 
395
+ st.write(f"**Title:** {video_name}")
396
  st.write(f"**Number of choruses identified:** {len(chorus_start_times)}")
397
 
398
+ for i, (start_time, end_time) in enumerate(zip(chorus_start_times, chorus_end_times)):
399
+ if url: # Only create YouTube timestamp links if we have a URL
400
+ link = f"{url}&t={int(start_time)}s"
401
+ st.write(f"Chorus {i+1}: {format_time(start_time)} to {format_time(end_time)}: [{link}]({link})")
402
+ else:
403
+ st.write(f"Chorus {i+1}: {format_time(start_time)} to {format_time(end_time)}")
404
 
405
  if len(chorus_start_times) == 0:
406
  st.write("No choruses identified.")
 
462
  st.image(cover_image, use_column_width=True)
463
  st.title("Chorus Finder")
464
  st.write("This app uses a pre-trained convolutional recurrent neural network to predict chorus locations in music. To learn more about this project, visit [github.com/dennisvdang/chorus-detection](https://github.com/dennisvdang/chorus-detection).")
465
+
466
+ st.write("### Option 1: YouTube URL")
467
+ url = st.text_input("Enter a YouTube URL")
468
+ process_youtube = st.button("Find Chorus from YouTube")
469
+
470
+ st.write("### Option 2: Upload Audio File")
471
+ st.write("If YouTube downloading fails, you can upload an MP3 file directly")
472
+ uploaded_file = st.file_uploader("Choose an MP3 file", type=['mp3'])
473
+ process_upload = st.button("Find Chorus from Upload")
474
+
475
+ if process_youtube and url:
476
+ with st.spinner('Analyzing YouTube link...'):
477
+ audio_file, video_title, temp_dir = extract_audio(url)
478
+ if audio_file:
479
+ with st.spinner('Processing audio...'):
480
+ # Ensure we strip silence for YouTube downloads
481
+ strip_silence(audio_file)
482
+ processed_audio, audio_features = process_audio(audio_path=audio_file, trim_silence=False)
483
+ with st.spinner('Loading model...'):
484
+ model = load_model(MODEL_PATH)
485
+ with st.spinner('Making predictions...'):
486
+ smoothed_predictions = make_predictions(model, processed_audio, audio_features, url, video_title)
487
+ with st.spinner('Plotting predictions...'):
488
+ plot_predictions(audio_features, smoothed_predictions)
489
+ shutil.rmtree(temp_dir)
490
  else:
491
+ st.error("Failed to download from YouTube. Please try uploading the audio file directly instead.")
492
+
493
+ elif process_upload and uploaded_file is not None:
494
+ # Save the uploaded file to a temporary directory
495
+ with st.spinner('Processing uploaded file...'):
496
+ temp_dir = tempfile.mkdtemp()
497
+ temp_path = os.path.join(temp_dir, "uploaded_audio.mp3")
498
+ with open(temp_path, "wb") as f:
499
+ f.write(uploaded_file.getbuffer())
500
+
501
+ # Use the filename as the video title
502
+ file_title = uploaded_file.name.replace('.mp3', '')
503
+
504
+ # Process the uploaded file
505
+ strip_silence(temp_path)
506
+ processed_audio, audio_features = process_audio(audio_path=temp_path, trim_silence=False)
507
+ model = load_model(MODEL_PATH)
508
+ # Since we don't have a YouTube URL for the uploaded file, pass an empty string
509
+ smoothed_predictions = make_predictions(model, processed_audio, audio_features, "", file_title)
510
+ plot_predictions(audio_features, smoothed_predictions)
511
+ shutil.rmtree(temp_dir)
512
+
513
+ elif (process_youtube and not url) or (process_upload and uploaded_file is None):
514
+ if process_youtube and not url:
515
  st.error("Please enter a valid YouTube URL")
516
+ if process_upload and uploaded_file is None:
517
+ st.error("Please upload an MP3 file")
518
 
519
  if __name__ == "__main__":
520
  main()