Replace stems URL input with radio buttons + add download button
- Radio buttons to select Version A or B MP3 as stems source - New /api/audio-source endpoint serves single MP3 for Extraction API - Backend reads radio selection, constructs public URL automatically - Download Stems button appears when stems are ready - Generate button disabled when no MP3s are available
This commit is contained in:
parent
25edd635c3
commit
ae9c061225
2 changed files with 114 additions and 14 deletions
91
app.py
91
app.py
|
|
@ -714,6 +714,73 @@ def stream_audio(token, version):
|
|||
return response
|
||||
|
||||
|
||||
@app.route('/api/audio-source/<token>/<version>.mp3')
|
||||
def audio_source(token, version):
|
||||
"""
|
||||
Serve a single MP3 file for stems extraction.
|
||||
|
||||
Unlike /api/stream (which requires both A and B to exist for the customer
|
||||
player), this endpoint serves whichever version is requested, as long as
|
||||
that file exists. This URL is given to the MusicGPT Extraction API so it
|
||||
can download the source audio for stem separation.
|
||||
"""
|
||||
req = get_request_by_token(token)
|
||||
if not req:
|
||||
abort(404)
|
||||
if version not in ('a', 'b'):
|
||||
abort(404)
|
||||
path = req.get('song_a_path') if version == 'a' else req.get('song_b_path')
|
||||
if not path or not Path(path).exists():
|
||||
abort(404)
|
||||
|
||||
file_path = Path(path)
|
||||
file_size = file_path.stat().st_size
|
||||
|
||||
range_header = request.headers.get('Range', '')
|
||||
start = 0
|
||||
end = file_size - 1
|
||||
status_code = 200
|
||||
|
||||
if range_header and range_header.startswith('bytes='):
|
||||
try:
|
||||
range_value = range_header[len('bytes='):].strip()
|
||||
if '-' in range_value:
|
||||
parts = range_value.split('-')
|
||||
if parts[0]:
|
||||
start = int(parts[0])
|
||||
if parts[1]:
|
||||
end = min(int(parts[1]), file_size - 1)
|
||||
if start >= file_size or start > end:
|
||||
abort(416)
|
||||
status_code = 206
|
||||
except ValueError:
|
||||
start = 0
|
||||
end = file_size - 1
|
||||
status_code = 200
|
||||
|
||||
def generate():
|
||||
with open(file_path, 'rb') as f:
|
||||
f.seek(start)
|
||||
remaining = end - start + 1
|
||||
chunk_size = 64 * 1024
|
||||
while remaining > 0:
|
||||
to_read = min(chunk_size, remaining)
|
||||
data = f.read(to_read)
|
||||
if not data:
|
||||
break
|
||||
yield data
|
||||
remaining -= len(data)
|
||||
|
||||
response = current_app.response_class(generate(), mimetype='audio/mpeg')
|
||||
response.status_code = status_code
|
||||
response.headers['Accept-Ranges'] = 'bytes'
|
||||
response.headers['Content-Disposition'] = 'inline'
|
||||
response.headers['Content-Length'] = str(end - start + 1)
|
||||
if status_code == 206:
|
||||
response.headers['Content-Range'] = f'bytes {start}-{end}/{file_size}'
|
||||
return response
|
||||
|
||||
|
||||
@app.route('/audio/<token>/<version>.mp3')
|
||||
def audio(token, version):
|
||||
"""
|
||||
|
|
@ -1053,7 +1120,7 @@ def admin_request(rid):
|
|||
return redirect(url_for('admin_request', rid=rid))
|
||||
|
||||
elif action == 'generate_stems':
|
||||
# Queue a stem extraction job for Version A MP3.
|
||||
# Queue a stem extraction job for the selected MP3 (Version A or B).
|
||||
api_key = get_musicgpt_api_key()
|
||||
if not api_key:
|
||||
flash('MusicGPT API key is not configured.', 'error')
|
||||
|
|
@ -1061,16 +1128,20 @@ def admin_request(rid):
|
|||
if req.get('stems_status') in ('IN_QUEUE', 'IN_PROGRESS'):
|
||||
flash('A stems extraction is already in progress.', 'error')
|
||||
return redirect(url_for('admin_request', rid=rid))
|
||||
audio_url = request.form.get('stems_source_url', '').strip()
|
||||
if not audio_url:
|
||||
# Fall back to using the locally stored Version A MP3 path; upload not supported by extraction API.
|
||||
a_path = req.get('song_a_path')
|
||||
if a_path:
|
||||
# Extraction API supports audio_url; we need a public URL. Local path won't work.
|
||||
flash('Please provide a public audio URL for stems extraction.', 'error')
|
||||
return redirect(url_for('admin_request', rid=rid))
|
||||
flash('No Version A audio available to extract stems from.', 'error')
|
||||
# Operator selects which version (A or B) to extract stems from.
|
||||
selected = request.form.get('stems_source', '').strip()
|
||||
if selected == 'a':
|
||||
song_path = req.get('song_a_path')
|
||||
elif selected == 'b':
|
||||
song_path = req.get('song_b_path')
|
||||
else:
|
||||
flash('Please select Version A or Version B for stems extraction.', 'error')
|
||||
return redirect(url_for('admin_request', rid=rid))
|
||||
if not song_path or not Path(song_path).exists():
|
||||
flash(f'Version {selected.upper()} MP3 is not available. Generate or upload it first.', 'error')
|
||||
return redirect(url_for('admin_request', rid=rid))
|
||||
# Build a public URL the MusicGPT Extraction API can fetch.
|
||||
audio_url = f"{current_app.config['PUBLIC_BASE_URL'].rstrip('/')}/api/audio-source/{req['player_token']}/{selected}.mp3"
|
||||
stems = request.form.getlist('stems') or ['vocals', 'instrumental']
|
||||
task_id, conv_id, estimate, error = musicgpt_queue_stems(rid, audio_url, stems=stems)
|
||||
if error:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue