| | import gradio as gr |
| | from yt_dlp import YoutubeDL |
| | import tempfile |
| | import os |
| | import subprocess |
| |
|
| | def download_snippet(url, start_sec, end_sec): |
| | """Download and trim audio snippet with custom start/end times""" |
| | |
| | temp_dir = tempfile.mkdtemp() |
| | |
| | try: |
| | |
| | if start_sec >= end_sec: |
| | raise Exception("Start time must be before end time") |
| | |
| | duration = end_sec - start_sec |
| | if duration > 600: |
| | raise Exception("Maximum duration is 600 seconds (10 minutes)") |
| | |
| | |
| | ydl_opts = { |
| | 'format': 'bestaudio/best', |
| | 'outtmpl': os.path.join(temp_dir, 'full_audio.%(ext)s'), |
| | 'quiet': True, |
| | 'no_warnings': True, |
| | 'noplaylist': True, |
| | } |
| | |
| | with YoutubeDL(ydl_opts) as ydl: |
| | |
| | info = ydl.extract_info(url, download=False) |
| | title = info.get('title', 'soundcloud_track') |
| | duration_full = info.get('duration', 0) |
| | |
| | |
| | if duration_full and end_sec > duration_full: |
| | raise Exception(f"End time ({end_sec}s) exceeds track duration ({duration_full}s)") |
| | |
| | safe_title = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).rstrip() |
| | |
| | |
| | ydl.download([url]) |
| | |
| | |
| | downloaded_files = [f for f in os.listdir(temp_dir) if f.startswith('full_audio')] |
| | if not downloaded_files: |
| | raise Exception("No file was downloaded") |
| | |
| | input_file = os.path.join(temp_dir, downloaded_files[0]) |
| | |
| | |
| | if not os.path.exists(input_file) or os.path.getsize(input_file) == 0: |
| | raise Exception("Downloaded file is empty") |
| | |
| | |
| | output_file = os.path.join(temp_dir, f"{safe_title}_{start_sec}-{end_sec}s.mp3") |
| | |
| | |
| | cmd = [ |
| | 'ffmpeg', |
| | '-i', input_file, |
| | '-ss', str(start_sec), |
| | '-to', str(end_sec), |
| | '-acodec', 'libmp3lame', |
| | '-q:a', '2', |
| | '-y', |
| | output_file |
| | ] |
| | |
| | |
| | result = subprocess.run(cmd, capture_output=True, text=True) |
| | |
| | if result.returncode != 0: |
| | raise Exception(f"FFmpeg error: {result.stderr}") |
| | |
| | |
| | if not os.path.exists(output_file) or os.path.getsize(output_file) == 0: |
| | raise Exception("Trimmed file is empty") |
| | |
| | return output_file, f"{safe_title}_{start_sec}-{end_sec}s.mp3", duration_full |
| | |
| | except Exception as e: |
| | |
| | if os.path.exists(temp_dir): |
| | import shutil |
| | shutil.rmtree(temp_dir, ignore_errors=True) |
| | raise e |
| |
|
| | |
| | with gr.Blocks(title="SoundCloud Snippet", theme=gr.themes.Soft()) as demo: |
| | gr.Markdown(""" |
| | # 🎵 SoundCloud Snippet Downloader |
| | Download any segment of a public SoundCloud track |
| | """) |
| | |
| | with gr.Row(): |
| | url = gr.Textbox( |
| | label="SoundCloud URL", |
| | placeholder="https://soundcloud.com/artist/track-name", |
| | value="https://soundcloud.com/emma-eline-pihlstr-m/have-yourself-a-merry-little-christmas", |
| | lines=2 |
| | ) |
| | |
| | with gr.Row(): |
| | with gr.Column(scale=1): |
| | start_slider = gr.Slider( |
| | minimum=0, |
| | maximum=600, |
| | value=0, |
| | step=1, |
| | label="Start Time (seconds)" |
| | ) |
| | start_number = gr.Number( |
| | value=0, |
| | label="Start (seconds)", |
| | precision=0, |
| | minimum=0, |
| | maximum=600 |
| | ) |
| | |
| | with gr.Column(scale=1): |
| | end_slider = gr.Slider( |
| | minimum=1, |
| | maximum=600, |
| | value=30, |
| | step=1, |
| | label="End Time (seconds)" |
| | ) |
| | end_number = gr.Number( |
| | value=30, |
| | label="End (seconds)", |
| | precision=0, |
| | minimum=1, |
| | maximum=600 |
| | ) |
| | |
| | with gr.Column(scale=1): |
| | duration_display = gr.Textbox( |
| | label="Segment Duration", |
| | value="30 seconds", |
| | interactive=False |
| | ) |
| | max_duration = gr.Textbox( |
| | label="Track Duration", |
| | value="Unknown", |
| | interactive=False, |
| | visible=False |
| | ) |
| | |
| | with gr.Row(): |
| | download_btn = gr.Button("Download Snippet", variant="primary") |
| | |
| | with gr.Row(): |
| | audio_player = gr.Audio(label="Preview", type="filepath") |
| | download_file = gr.DownloadButton("Save MP3", visible=False) |
| | |
| | |
| | file_path = gr.State() |
| | track_duration = gr.State(0) |
| | |
| | |
| | def sync_start(start_val): |
| | return start_val, start_val |
| | |
| | def sync_end(end_val): |
| | return end_val, end_val |
| | |
| | start_slider.change( |
| | sync_start, |
| | inputs=[start_slider], |
| | outputs=[start_number, start_slider] |
| | ) |
| | |
| | start_number.change( |
| | sync_start, |
| | inputs=[start_number], |
| | outputs=[start_slider, start_number] |
| | ) |
| | |
| | end_slider.change( |
| | sync_end, |
| | inputs=[end_slider], |
| | outputs=[end_number, end_slider] |
| | ) |
| | |
| | end_number.change( |
| | sync_end, |
| | inputs=[end_number], |
| | outputs=[end_slider, end_number] |
| | ) |
| | |
| | |
| | def update_duration(start, end): |
| | duration = end - start |
| | if duration <= 0: |
| | return "Invalid (start must be before end)", gr.update(visible=False) |
| | return f"{duration} seconds", gr.update(visible=True) |
| | |
| | start_slider.change( |
| | update_duration, |
| | inputs=[start_slider, end_slider], |
| | outputs=[duration_display, download_btn] |
| | ) |
| | |
| | end_slider.change( |
| | update_duration, |
| | inputs=[start_slider, end_slider], |
| | outputs=[duration_display, download_btn] |
| | ) |
| | |
| | def process_download(url, start, end): |
| | if not url or 'soundcloud.com' not in url.lower(): |
| | raise gr.Error("Please enter a valid SoundCloud URL") |
| | |
| | if start >= end: |
| | raise gr.Error("Start time must be before end time") |
| | |
| | try: |
| | filepath, filename, full_duration = download_snippet(url, start, end) |
| | |
| | |
| | max_dur_update = gr.update( |
| | value=f"{full_duration} seconds" if full_duration > 0 else "Unknown", |
| | visible=True |
| | ) |
| | |
| | return { |
| | audio_player: filepath, |
| | download_file: gr.DownloadButton(visible=True), |
| | file_path: filepath, |
| | max_duration: max_dur_update, |
| | track_duration: full_duration |
| | } |
| | except Exception as e: |
| | raise gr.Error(f"Download failed: {str(e)}") |
| | |
| | download_btn.click( |
| | process_download, |
| | inputs=[url, start_slider, end_slider], |
| | outputs=[audio_player, download_file, file_path, max_duration, track_duration] |
| | ) |
| | |
| | download_file.click( |
| | lambda x: x if x and os.path.exists(x) else None, |
| | inputs=[file_path], |
| | outputs=None |
| | ) |
| | |
| | |
| | def adjust_sliders(full_duration): |
| | if full_duration and full_duration > 0: |
| | return ( |
| | gr.update(maximum=min(600, full_duration)), |
| | gr.update(maximum=min(600, full_duration), value=min(30, full_duration)), |
| | gr.update(maximum=min(600, full_duration)), |
| | gr.update(maximum=min(600, full_duration), value=min(30, full_duration)) |
| | ) |
| | return ( |
| | gr.update(maximum=600), |
| | gr.update(maximum=600), |
| | gr.update(maximum=600), |
| | gr.update(maximum=600) |
| | ) |
| | |
| | |
| | |
| |
|
| | if __name__ == "__main__": |
| | demo.launch() |
| |
|