Spaces:
Running
Running
Commit
·
eca162c
1
Parent(s):
0eb1a5b
Add YouTube download fallback and direct file upload option
Browse files
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.
|
45 |
-
return
|
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':
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
except Exception as e:
|
68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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"**
|
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 |
-
|
360 |
-
|
|
|
|
|
|
|
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 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|