File size: 8,884 Bytes
06925e6
5eb395b
66a97e7
 
5eb395b
66a97e7
67ae8ed
 
5eb395b
 
14966b4
5eb395b
67ae8ed
 
 
 
 
 
 
 
5eb395b
 
 
 
 
 
 
 
 
 
 
 
 
67ae8ed
 
 
 
 
 
5eb395b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67ae8ed
5eb395b
67ae8ed
5eb395b
 
 
67ae8ed
 
5eb395b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67ae8ed
5eb395b
 
 
 
 
 
 
 
 
 
 
 
67ae8ed
5eb395b
14966b4
5eb395b
 
 
 
5d2d0c3
76ffd7d
5eb395b
14966b4
5eb395b
67ae8ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5eb395b
 
 
 
 
 
 
 
67ae8ed
5eb395b
67ae8ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5eb395b
67ae8ed
5eb395b
 
 
67ae8ed
 
 
5eb395b
67ae8ed
 
 
 
 
 
 
 
5eb395b
 
 
67ae8ed
 
 
5eb395b
 
 
 
 
 
67ae8ed
 
5eb395b
 
 
 
 
 
 
67ae8ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
06925e6
5eb395b
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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"""
    # Create temp directory
    temp_dir = tempfile.mkdtemp()
    
    try:
        # Validate times
        if start_sec >= end_sec:
            raise Exception("Start time must be before end time")
        
        duration = end_sec - start_sec
        if duration > 600:  # Limit to 10 minutes max
            raise Exception("Maximum duration is 600 seconds (10 minutes)")
        
        # First, download the full track (or best we can get)
        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:
            # Get info for filename
            info = ydl.extract_info(url, download=False)
            title = info.get('title', 'soundcloud_track')
            duration_full = info.get('duration', 0)
            
            # Validate against full duration
            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()
            
            # Download
            ydl.download([url])
        
        # Find the downloaded file
        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])
        
        # Check if file exists and has content
        if not os.path.exists(input_file) or os.path.getsize(input_file) == 0:
            raise Exception("Downloaded file is empty")
        
        # Create output filename
        output_file = os.path.join(temp_dir, f"{safe_title}_{start_sec}-{end_sec}s.mp3")
        
        # Use ffmpeg to trim with start and end times
        cmd = [
            'ffmpeg',
            '-i', input_file,          # Input file
            '-ss', str(start_sec),     # Start time
            '-to', str(end_sec),       # End time
            '-acodec', 'libmp3lame',   # MP3 codec
            '-q:a', '2',               # Good quality
            '-y',                      # Overwrite output
            output_file
        ]
        
        # Run ffmpeg
        result = subprocess.run(cmd, capture_output=True, text=True)
        
        if result.returncode != 0:
            raise Exception(f"FFmpeg error: {result.stderr}")
        
        # Check if output was created
        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:
        # Clean up on error
        if os.path.exists(temp_dir):
            import shutil
            shutil.rmtree(temp_dir, ignore_errors=True)
        raise e

# Simple Gradio interface
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)
    
    # Store file path and track duration
    file_path = gr.State()
    track_duration = gr.State(0)
    
    # Sync sliders and number inputs
    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]
    )
    
    # Update duration display
    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)
            
            # Update max duration display if we have it
            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
    )
    
    # Auto-adjust end slider when track duration is known
    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)
        )
    
    # This would need to be triggered after we get track duration
    # For simplicity, we'll update when the download completes

if __name__ == "__main__":
    demo.launch()