MySafeCode commited on
Commit
14966b4
·
verified ·
1 Parent(s): 66a97e7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +196 -32
app.py CHANGED
@@ -2,52 +2,216 @@ import gradio as gr
2
  from yt_dlp import YoutubeDL
3
  import tempfile
4
  import os
 
5
 
6
  def download_snippet(url, duration_sec):
7
- # Temporary output file
8
- tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
9
- output_path = tmp.name
10
- tmp.close()
11
-
 
 
 
 
 
 
 
 
 
 
12
  ydl_opts = {
13
- "format": "bestaudio",
14
  "outtmpl": output_path,
15
  "external_downloader": "ffmpeg",
16
  "external_downloader_args": ["-t", str(int(duration_sec))],
17
  "quiet": True,
18
  "no_warnings": True,
 
 
 
 
 
19
  }
20
 
21
- with YoutubeDL(ydl_opts) as ydl:
22
- ydl.download([url])
23
-
24
- return output_path
 
 
 
 
 
 
 
 
 
 
 
25
 
26
- with gr.Blocks(title="SoundCloud 30s Stream Snippet") as demo:
27
- gr.Markdown("## 🎵 SoundCloud Snippet Generator")
28
- gr.Markdown("Downloads only the first *N seconds* using yt-dlp + ffmpeg.")
 
 
 
 
 
29
 
30
- url = gr.Textbox(
31
- label="SoundCloud URL",
32
- value="https://soundcloud.com/antonio-antetomaso/mutiny-on-the-bounty-closing-titles-cover"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  )
34
-
35
- duration = gr.Slider(
36
- minimum=1,
37
- maximum=300,
38
- value=30,
39
- step=1,
40
- label="Snippet length (seconds)"
41
  )
42
-
43
- btn = gr.Button("Generate snippet")
44
-
45
- audio = gr.Audio(
46
- type="filepath",
47
- label="Preview & download"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  )
 
 
 
 
 
 
 
49
 
50
- btn.click(download_snippet, inputs=[url, duration], outputs=audio)
51
-
52
- demo.launch()
 
 
 
 
53
 
 
2
  from yt_dlp import YoutubeDL
3
  import tempfile
4
  import os
5
+ import shutil
6
 
7
  def download_snippet(url, duration_sec):
8
+ # Create temporary directory for better file management
9
+ temp_dir = tempfile.mkdtemp()
10
+
11
+ # Generate a more descriptive filename
12
+ try:
13
+ with YoutubeDL({"quiet": True, "no_warnings": True}) as ydl:
14
+ info = ydl.extract_info(url, download=False)
15
+ # Create a safe filename from title
16
+ safe_title = "".join(c for c in info.get('title', 'audio') if c.isalnum() or c in (' ', '-', '_')).rstrip()
17
+ filename = f"{safe_title}_{duration_sec}s.mp3"
18
+ except:
19
+ filename = f"soundcloud_snippet_{duration_sec}s.mp3"
20
+
21
+ output_path = os.path.join(temp_dir, filename)
22
+
23
  ydl_opts = {
24
+ "format": "bestaudio/best",
25
  "outtmpl": output_path,
26
  "external_downloader": "ffmpeg",
27
  "external_downloader_args": ["-t", str(int(duration_sec))],
28
  "quiet": True,
29
  "no_warnings": True,
30
+ "postprocessors": [{
31
+ "key": "FFmpegExtractAudio",
32
+ "preferredcodec": "mp3",
33
+ "preferredquality": "192",
34
+ }],
35
  }
36
 
37
+ try:
38
+ with YoutubeDL(ydl_opts) as ydl:
39
+ ydl.download([url])
40
+
41
+ # Verify file was created and has content
42
+ if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
43
+ return output_path, filename
44
+ else:
45
+ raise Exception("Download failed - empty file")
46
+
47
+ except Exception as e:
48
+ # Clean up on error
49
+ if os.path.exists(temp_dir):
50
+ shutil.rmtree(temp_dir, ignore_errors=True)
51
+ raise gr.Error(f"Download failed: {str(e)}")
52
 
53
+ def cleanup_temp_files(filepath):
54
+ """Clean up temporary files after download"""
55
+ if filepath and os.path.exists(os.path.dirname(filepath)):
56
+ temp_dir = os.path.dirname(filepath)
57
+ try:
58
+ shutil.rmtree(temp_dir, ignore_errors=True)
59
+ except:
60
+ pass
61
 
62
+ with gr.Blocks(title="SoundCloud Snippet Generator", theme=gr.themes.Soft()) as demo:
63
+ gr.Markdown("""
64
+ # 🎵 SoundCloud Snippet Generator
65
+ Download only the first *N seconds* of any SoundCloud track.
66
+ """)
67
+
68
+ with gr.Row():
69
+ with gr.Column(scale=3):
70
+ url = gr.Textbox(
71
+ label="SoundCloud URL",
72
+ placeholder="https://soundcloud.com/...",
73
+ value="https://soundcloud.com/antonio-antetomaso/mutiny-on-the-bounty-closing-titles-cover"
74
+ )
75
+
76
+ with gr.Row():
77
+ duration = gr.Slider(
78
+ minimum=1,
79
+ maximum=300,
80
+ value=30,
81
+ step=1,
82
+ label="Snippet length (seconds)"
83
+ )
84
+ duration_text = gr.Number(
85
+ value=30,
86
+ label="Seconds",
87
+ precision=0,
88
+ minimum=1,
89
+ maximum=300
90
+ )
91
+
92
+ gr.Markdown("### Output")
93
+ filename_display = gr.Textbox(
94
+ label="Download as",
95
+ interactive=False
96
+ )
97
+
98
+ download_btn = gr.DownloadButton(
99
+ "Download MP3 File",
100
+ visible=False
101
+ )
102
+
103
+ generate_btn = gr.Button(
104
+ "Generate Snippet",
105
+ variant="primary",
106
+ size="lg"
107
+ )
108
+
109
+ status = gr.Textbox(
110
+ label="Status",
111
+ interactive=False,
112
+ visible=False
113
+ )
114
+
115
+ with gr.Column(scale=2):
116
+ audio_player = gr.Audio(
117
+ label="Preview",
118
+ type="filepath",
119
+ interactive=False
120
+ )
121
+
122
+ # Link slider and number input
123
+ duration.change(lambda x: x, inputs=[duration], outputs=[duration_text])
124
+ duration_text.change(lambda x: x, inputs=[duration_text], outputs=[duration])
125
+
126
+ # Store filename for download
127
+ file_path_store = gr.State()
128
+ file_name_store = gr.State()
129
+
130
+ def process_and_preview(url, duration_sec):
131
+ """Process the download and update UI"""
132
+ if not url or not url.strip():
133
+ raise gr.Error("Please enter a SoundCloud URL")
134
+
135
+ # Clear previous
136
+ yield "", None, None, "⏳ Downloading...", gr.DownloadButton(visible=False), gr.Textbox(visible=True)
137
+
138
+ try:
139
+ filepath, filename = download_snippet(url, duration_sec)
140
+
141
+ yield (
142
+ filename, # filename_display
143
+ filepath, # file_path_store
144
+ filename, # file_name_store
145
+ "✅ Ready to download!", # status
146
+ gr.DownloadButton(visible=True), # download_btn
147
+ gr.Textbox(visible=True), # status visible
148
+ )
149
+
150
+ # Return audio preview
151
+ return filepath
152
+
153
+ except Exception as e:
154
+ raise gr.Error(f"Error: {str(e)}")
155
+
156
+ # Generate button click
157
+ generate_btn.click(
158
+ fn=process_and_preview,
159
+ inputs=[url, duration],
160
+ outputs=[
161
+ filename_display,
162
+ file_path_store,
163
+ file_name_store,
164
+ status,
165
+ download_btn,
166
+ status
167
+ ]
168
+ ).then(
169
+ fn=lambda x: x,
170
+ inputs=[file_path_store],
171
+ outputs=[audio_player]
172
  )
173
+
174
+ # Setup download button
175
+ download_btn.click(
176
+ fn=lambda filepath, filename: (filepath, filename) if filepath else None,
177
+ inputs=[file_path_store, file_name_store],
178
+ outputs=None
 
179
  )
180
+
181
+ # Cleanup on new generation
182
+ url.change(
183
+ fn=lambda: (
184
+ "", # Clear filename
185
+ None, # Clear file_path_store
186
+ None, # Clear file_name_store
187
+ "", # Clear status
188
+ gr.DownloadButton(visible=False), # Hide download
189
+ gr.Textbox(visible=False), # Hide status box
190
+ None # Clear audio player
191
+ ),
192
+ outputs=[
193
+ filename_display,
194
+ file_path_store,
195
+ file_name_store,
196
+ status,
197
+ download_btn,
198
+ status,
199
+ audio_player
200
+ ]
201
  )
202
+
203
+ # Add footer with info
204
+ gr.Markdown("""
205
+ ---
206
+ **Note:** Downloads are limited to 5 minutes (300 seconds) maximum.
207
+ Files are temporarily stored and will be cleaned up after download.
208
+ """)
209
 
210
+ if __name__ == "__main__":
211
+ demo.launch(
212
+ server_name="0.0.0.0",
213
+ server_port=7860,
214
+ share=False,
215
+ show_error=True
216
+ )
217