import gradio as gr from yt_dlp import YoutubeDL import tempfile import os import subprocess import traceback import shutil # Constants MAX_DURATION = 300 # 5 minutes max DEFAULT_URL = "https://soundcloud.com/emma-eline-pihlstr-m/have-yourself-a-merry-little-christmas" DEFAULT_DURATION = 30 def download_and_trim(url, duration_sec): """Two-step process: download full audio then trim""" # Create temporary directory temp_dir = tempfile.mkdtemp() try: # Step 1: Download with yt-dlp print(f"Downloading from: {url}") # First get info for title with YoutubeDL({'quiet': True, 'no_warnings': True}) as ydl: info = ydl.extract_info(url, download=False) if not info: raise Exception("Could not fetch track information") title = info.get('title', 'soundcloud_track') # Create safe filename safe_title = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).rstrip() safe_title = safe_title[:100] # Limit length # Download options download_path = os.path.join(temp_dir, "original") ydl_opts = { 'format': 'bestaudio/best', 'outtmpl': download_path + '.%(ext)s', 'quiet': True, 'no_warnings': True, 'noplaylist': True, 'extractaudio': True, 'audioformat': 'mp3', 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192', }], 'socket_timeout': 30, 'retries': 3, } # Download with YoutubeDL(ydl_opts) as ydl: ydl.download([url]) # Find the downloaded file downloaded_files = [f for f in os.listdir(temp_dir) if f.startswith('original') and f.endswith('.mp3')] if not downloaded_files: # Try to find any audio file downloaded_files = [f for f in os.listdir(temp_dir) if f.endswith(('.mp3', '.webm', '.m4a'))] if not downloaded_files: raise Exception("No audio file was downloaded") input_file = os.path.join(temp_dir, downloaded_files[0]) # Verify file if not os.path.exists(input_file): raise Exception("Downloaded file not found") file_size = os.path.getsize(input_file) if file_size == 0: raise Exception("Downloaded file is empty") print(f"Downloaded: {input_file} ({file_size} bytes)") # Step 2: Trim with ffmpeg output_filename = f"{safe_title}_{duration_sec}s.mp3" output_file = os.path.join(temp_dir, output_filename) # Use ffmpeg to trim cmd = [ 'ffmpeg', '-i', input_file, # Input file '-t', str(duration_sec), # Duration to keep '-acodec', 'libmp3lame', # MP3 codec '-q:a', '2', # Good quality (0-9, 2=good) '-metadata', f'title={safe_title} [{duration_sec}s snippet]', '-metadata', f'comment=Snippet from {url}', '-y', # Overwrite output output_file ] print(f"Trimming with command: {' '.join(cmd)}") result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: print(f"FFmpeg stderr: {result.stderr}") raise Exception(f"Trimming failed: {result.stderr[:200]}") # Verify trimmed file if not os.path.exists(output_file): raise Exception("Trimmed file was not created") trimmed_size = os.path.getsize(output_file) if trimmed_size == 0: raise Exception("Trimmed file is empty") print(f"Created: {output_file} ({trimmed_size} bytes)") # Clean up original file try: os.unlink(input_file) except: pass return output_file, output_filename except Exception as e: # Clean up on error if os.path.exists(temp_dir): shutil.rmtree(temp_dir, ignore_errors=True) print(f"Error: {str(e)}") traceback.print_exc() raise e def validate_url(url): """Validate SoundCloud URL""" if not url or not url.strip(): return False, "Please enter a URL" url_lower = url.lower() if 'soundcloud.com' not in url_lower: return False, "Please enter a SoundCloud URL" # Check if it's a track, not a playlist or user page if '/sets/' in url_lower: return False, "Playlists not supported. Please use a track URL." # Count slashes to guess if it's a track parts = url_lower.replace('https://', '').replace('http://', '').split('/') if len(parts) < 3 or not parts[2]: return False, "Please enter a specific track URL" return True, "" # Create the Gradio app with gr.Blocks( title="SoundCloud Snippet Generator", theme=gr.themes.Soft(), css=""" .gradio-container { max-width: 1000px !important; margin: auto; } .header { text-align: center; margin-bottom: 20px; } .success { color: #28a745; font-weight: bold; } .error { color: #dc3545; font-weight: bold; } .warning { background-color: #fff3cd; padding: 10px; border-radius: 5px; } .example-url { background: #f8f9fa; padding: 5px 10px; border-radius: 5px; margin: 5px 0; cursor: pointer; } .example-url:hover { background: #e9ecef; } """ ) as demo: # Header gr.Markdown("""

🎵 SoundCloud Snippet Generator

Download the first N seconds of any public SoundCloud track

""") # Main content with gr.Row(): # Left column - Inputs with gr.Column(scale=2): # URL input with default url_input = gr.Textbox( label="SoundCloud Track URL", placeholder="https://soundcloud.com/artist/track-name", value=DEFAULT_URL, elem_id="url_input" ) # URL validation status url_status = gr.Markdown("", elem_id="url_status") # Duration controls with gr.Row(): duration_slider = gr.Slider( minimum=5, maximum=MAX_DURATION, value=DEFAULT_DURATION, step=5, label=f"Duration (5-{MAX_DURATION} seconds)" ) duration_number = gr.Number( value=DEFAULT_DURATION, label="Seconds", precision=0, minimum=5, maximum=MAX_DURATION ) # Link slider and number duration_slider.change( lambda x: x, inputs=[duration_slider], outputs=[duration_number] ) duration_number.change( lambda x: x, inputs=[duration_number], outputs=[duration_slider] ) # Example tracks with gr.Accordion("🎯 Try these example tracks", open=False): gr.Markdown("""
🎄 Have Yourself a Merry Little Christmas
🎵 That New New - NCS
☕ Coffee Jazz - Lofi Girl
🔵 Blue Window - Chillhop
""") # Generate button generate_btn = gr.Button( "🎬 Generate Snippet", variant="primary", size="lg", elem_id="generate_btn" ) # Status display status_display = gr.Markdown("", elem_id="status") # Download section with gr.Group(): gr.Markdown("### 💾 Download") filename_display = gr.Textbox( label="File will be saved as:", interactive=False, elem_id="filename" ) download_btn = gr.DownloadButton( "⬇️ Download MP3", visible=False, elem_id="download_btn" ) # Right column - Output and info with gr.Column(scale=1): # Audio preview audio_preview = gr.Audio( label="🎧 Preview", type="filepath", interactive=False, visible=False, elem_id="audio_preview" ) # Info box gr.Markdown("""

⚠️ Important Notes

✅ How to use:

  1. Paste a SoundCloud URL
  2. Set the duration
  3. Click "Generate Snippet"
  4. Preview the audio
  5. Click "Download MP3"
""") # Store file path file_path = gr.State() # URL validation function def on_url_change(url): is_valid, msg = validate_url(url) if not url: return gr.Markdown("", visible=False) elif is_valid: return gr.Markdown("✅ Valid SoundCloud track URL", visible=True) else: return gr.Markdown(f"⚠️ {msg}", visible=True) # Main processing function def process_snippet(url, duration): # Clear previous outputs yield { status_display: "⏳ Starting download...", audio_preview: gr.Audio(visible=False), download_btn: gr.DownloadButton(visible=False), filename_display: "", } # Validate is_valid, msg = validate_url(url) if not is_valid: yield { status_display: f"❌ {msg}", audio_preview: gr.Audio(visible=False), download_btn: gr.DownloadButton(visible=False), } return try: # Step 1: Download yield { status_display: "⏳ Downloading track...", audio_preview: gr.Audio(visible=False), download_btn: gr.DownloadButton(visible=False), } filepath, filename = download_and_trim(url, duration) # Step 2: Success yield { status_display: "✅ Snippet created successfully!", audio_preview: gr.Audio(value=filepath, visible=True), download_btn: gr.DownloadButton(visible=True), filename_display: filename, file_path: filepath, } except Exception as e: error_msg = str(e) # Clean up error message if "Private" in error_msg or "not accessible" in error_msg: error_msg = "Track is private or not accessible" elif "unavailable" in error_msg or "not found" in error_msg: error_msg = "Track not found or unavailable" elif "Copyright" in error_msg or "restricted" in error_msg: error_msg = "Track has download restrictions" yield { status_display: f"❌ {error_msg}", audio_preview: gr.Audio(visible=False), download_btn: gr.DownloadButton(visible=False), filename_display: "", } # Set up interactions url_input.change( fn=on_url_change, inputs=[url_input], outputs=[url_status] ) generate_btn.click( fn=process_snippet, inputs=[url_input, duration_slider], outputs=[ status_display, audio_preview, download_btn, filename_display, file_path ] ) # Download button download_btn.click( fn=lambda fp: fp if fp and os.path.exists(fp) else None, inputs=[file_path], outputs=None ) # Footer gr.Markdown("---") gr.Markdown("""

Built with ❤️ using Gradio & yt-dlp

Respect artists' rights. For personal use only.

""") # Launch the app if __name__ == "__main__": demo.launch( server_name="0.0.0.0", share=False, debug=False, show_error=True )