Download app2.py from MySafeCode/SoundCloudDurationDownload: direct link, hf CLI and curl.
- Browser
- Download file 14 kB
-
https://huggingface.co/spaces/MySafeCode/SoundCloudDurationDownload/resolve/main/app2.py
- Command line
-
hf download hf://spaces/MySafeCode/SoundCloudDurationDownload/app2.py
-
curl -L -o app2.py https://huggingface.co/spaces/MySafeCode/SoundCloudDurationDownload/resolve/main/app2.py
14 kB
| 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(""" | |
| <div class="header"> | |
| <h1>🎵 SoundCloud Snippet Generator</h1> | |
| <p>Download the first <i>N</i> seconds of any public SoundCloud track</p> | |
| </div> | |
| """) | |
| # 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(""" | |
| <div style="font-size: 0.9em;"> | |
| <div class="example-url" onclick="document.getElementById('url_input').value='https://soundcloud.com/emma-eline-pihlstr-m/have-yourself-a-merry-little-christmas'">🎄 Have Yourself a Merry Little Christmas</div> | |
| <div class="example-url" onclick="document.getElementById('url_input').value='https://soundcloud.com/nocopyrightsounds/that-new-new'">🎵 That New New - NCS</div> | |
| <div class="example-url" onclick="document.getElementById('url_input').value='https://soundcloud.com/lofi_girl/coffee-jazz-music'">☕ Coffee Jazz - Lofi Girl</div> | |
| <div class="example-url" onclick="document.getElementById('url_input').value='https://soundcloud.com/chillhopdotcom/blue-window'">🔵 Blue Window - Chillhop</div> | |
| </div> | |
| """) | |
| # 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(""" | |
| <div class="warning"> | |
| <h4>⚠️ Important Notes</h4> | |
| <ul> | |
| <li>Works with <b>public tracks only</b></li> | |
| <li>Some tracks have download restrictions</li> | |
| <li>Maximum duration: 5 minutes</li> | |
| <li>Processing takes 10-30 seconds</li> | |
| <li>Files are automatically deleted after download</li> | |
| </ul> | |
| </div> | |
| <h4>✅ How to use:</h4> | |
| <ol> | |
| <li>Paste a SoundCloud URL</li> | |
| <li>Set the duration</li> | |
| <li>Click "Generate Snippet"</li> | |
| <li>Preview the audio</li> | |
| <li>Click "Download MP3"</li> | |
| </ol> | |
| """) | |
| # 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(""" | |
| <div style="text-align: center; color: #666; font-size: 0.9em;"> | |
| <p>Built with ❤️ using Gradio & yt-dlp</p> | |
| <p>Respect artists' rights. For personal use only.</p> | |
| </div> | |
| """) | |
| # Launch the app | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| share=False, | |
| debug=False, | |
| show_error=True | |
| ) |