import gradio as gr from yt_dlp import YoutubeDL import os from pydub import AudioSegment import tempfile import json # Use temp directory for better security DOWNLOADS_FOLDER = tempfile.mkdtemp(prefix="audio_downloader_") os.makedirs(DOWNLOADS_FOLDER, exist_ok=True) def download_youtube_audio(url, file_format, duration_sec): """Download and process audio from YouTube""" try: # Download best audio available ydl_opts = { 'format': 'bestaudio/best', 'outtmpl': os.path.join(DOWNLOADS_FOLDER, '%(title)s.%(ext)s'), 'quiet': False, 'no_warnings': False, 'extract_flat': False, 'noplaylist': True, } with YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download=True) original_file = os.path.join(DOWNLOADS_FOLDER, f"{info['title']}.{info['ext']}") # Clean filename safe_title = "".join(c for c in info['title'] if c.isalnum() or c in (' ', '-', '_')).rstrip() safe_title = safe_title[:100] original_file_safe = os.path.join(DOWNLOADS_FOLDER, f"{safe_title}.{info['ext']}") if original_file != original_file_safe: if os.path.exists(original_file_safe): os.remove(original_file_safe) os.rename(original_file, original_file_safe) original_file = original_file_safe # Load audio audio = AudioSegment.from_file(original_file) # Handle duration if duration_sec > 0: duration_ms = min(len(audio), int(duration_sec * 1000)) trimmed_audio = audio[:duration_ms] else: trimmed_audio = audio # Determine output file base_name = os.path.splitext(original_file)[0] if file_format.lower() == "mp3": output_file = base_name + ".mp3" trimmed_audio.export(output_file, format="mp3", bitrate="192k") elif file_format.lower() == "opus": output_file = base_name + ".opus" trimmed_audio.export(output_file, format="opus", bitrate="128k") elif file_format.lower() == "wav": output_file = base_name + ".wav" trimmed_audio.export(output_file, format="wav") elif file_format.lower() == "m4a": output_file = base_name + ".m4a" trimmed_audio.export(output_file, format="ipod", codec="aac") else: output_file = original_file # Clean up original if converted if output_file != original_file and os.path.exists(original_file): os.remove(original_file) # For Gradio 6+, return a dictionary for gr.File component return { "path": output_file, "name": os.path.basename(output_file) }, f"✅ Downloaded: {os.path.basename(output_file)}" except Exception as e: return None, f"❌ Error: {str(e)}" def download_soundcloud_audio(url, file_format, duration_sec): """Download and process audio from SoundCloud""" try: ydl_opts = { 'format': 'bestaudio/best', 'outtmpl': os.path.join(DOWNLOADS_FOLDER, '%(title)s.%(ext)s'), 'quiet': False, 'no_warnings': False, 'extract_flat': False, } with YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download=True) original_file = os.path.join(DOWNLOADS_FOLDER, f"{info['title']}.{info['ext']}") # Clean filename safe_title = "".join(c for c in info['title'] if c.isalnum() or c in (' ', '-', '_')).rstrip() safe_title = safe_title[:100] original_file_safe = os.path.join(DOWNLOADS_FOLDER, f"{safe_title}.{info['ext']}") if original_file != original_file_safe: if os.path.exists(original_file_safe): os.remove(original_file_safe) os.rename(original_file, original_file_safe) original_file = original_file_safe # Load and process audio audio = AudioSegment.from_file(original_file) if duration_sec > 0: duration_ms = min(len(audio), int(duration_sec * 1000)) trimmed_audio = audio[:duration_ms] else: trimmed_audio = audio # Convert to desired format base_name = os.path.splitext(original_file)[0] if file_format.lower() == "mp3": output_file = base_name + ".mp3" trimmed_audio.export(output_file, format="mp3", bitrate="192k") elif file_format.lower() == "opus": output_file = base_name + ".opus" trimmed_audio.export(output_file, format="opus", bitrate="128k") elif file_format.lower() == "wav": output_file = base_name + ".wav" trimmed_audio.export(output_file, format="wav") elif file_format.lower() == "m4a": output_file = base_name + ".m4a" trimmed_audio.export(output_file, format="ipod", codec="aac") else: output_file = original_file # Clean up if output_file != original_file and os.path.exists(original_file): os.remove(original_file) # For Gradio 6+, return a dictionary for gr.File component return { "path": output_file, "name": os.path.basename(output_file) }, f"✅ Downloaded: {os.path.basename(output_file)}" except Exception as e: return None, f"❌ Error: {str(e)}" # Create Gradio interface with gr.Blocks(title="Audio Downloader") as iface: # Removed theme from here gr.Markdown("# 🎵 Audio Downloader") gr.Markdown("Download audio from YouTube or SoundCloud") with gr.Tabs(): # YouTube Tab with gr.Tab("YouTube"): with gr.Row(): with gr.Column(): youtube_url = gr.Textbox( label="YouTube URL", placeholder="https://www.youtube.com/watch?v=...", lines=1 ) with gr.Row(): youtube_format = gr.Dropdown( choices=["mp3", "m4a", "opus", "wav"], value="mp3", label="Output Format" ) youtube_duration = gr.Slider( minimum=0, maximum=1800, value=60, step=5, label="Duration (seconds)", info="0 = full video" ) youtube_btn = gr.Button( "Download from YouTube", variant="primary", size="lg" ) youtube_status = gr.Textbox( label="Status", interactive=False ) youtube_output = gr.File( label="Downloaded File", interactive=False, file_count="single" # Explicitly specify file count ) # YouTube examples with gr.Accordion("YouTube Examples", open=False): gr.Examples( examples=[ ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"], ["https://www.youtube.com/watch?v=JGwWNGJdvx8"], ["https://www.youtube.com/watch?v=9bZkp7q19f0"], ["https://youtu.be/kJQP7kiw5Fk"] ], inputs=youtube_url, label="Try these URLs:" ) # SoundCloud Tab with gr.Tab("SoundCloud"): with gr.Row(): with gr.Column(): soundcloud_url = gr.Textbox( label="SoundCloud URL", placeholder="https://soundcloud.com/...", lines=1 ) with gr.Row(): soundcloud_format = gr.Dropdown( choices=["mp3", "m4a", "opus", "wav"], value="mp3", label="Output Format" ) soundcloud_duration = gr.Slider( minimum=0, maximum=600, value=60, step=5, label="Duration (seconds)", info="0 = full track" ) soundcloud_btn = gr.Button( "Download from SoundCloud", variant="primary", size="lg" ) soundcloud_status = gr.Textbox( label="Status", interactive=False ) soundcloud_output = gr.File( label="Downloaded File", interactive=False, file_count="single" # Explicitly specify file count ) # SoundCloud examples with gr.Accordion("SoundCloud Examples", open=False): gr.Examples( examples=[ ["https://soundcloud.com/antonio-antetomaso/mutiny-on-the-bounty-closing-titles-cover"], ["https://soundcloud.com/officialnikkig/kill-bill-sza-kill-bill-remix"], ["https://soundcloud.com/lofi-girl"], ["https://soundcloud.com/monstercat/pegboard-nerds-disconnected"] ], inputs=soundcloud_url, label="Try these URLs:" ) # Instructions with gr.Accordion("📖 Instructions & Info", open=False): gr.Markdown(""" ### How to Use: 1. **Select the platform tab** (YouTube or SoundCloud) 2. **Paste a URL** from the selected platform 3. **Choose output format** (MP3, M4A, Opus, or WAV) 4. **Set duration** (0 for full track, or specify seconds) 5. **Click Download** button ### Supported Formats: - **MP3**: Most compatible, good quality - **M4A**: Apple format, smaller file size - **Opus**: Best quality at low bitrates - **WAV**: Lossless, large file size ### Notes: - Files are saved in a temporary folder - Some content may have download restrictions - Long videos may take time to process """) # Connect button events youtube_btn.click( fn=download_youtube_audio, inputs=[youtube_url, youtube_format, youtube_duration], outputs=[youtube_output, youtube_status] ) soundcloud_btn.click( fn=download_soundcloud_audio, inputs=[soundcloud_url, soundcloud_format, soundcloud_duration], outputs=[soundcloud_output, soundcloud_status] ) # Launch the app if __name__ == "__main__": iface.launch( server_name="127.0.0.1", server_port=7860, show_error=True, share=False, theme=gr.themes.Soft() # Moved theme to launch() method )