From f2c64848dde295cdfeb335e80e556823bc05741b Mon Sep 17 00:00:00 2001 From: "Troll (Hermes Agent)" Date: Tue, 11 Aug 2026 19:36:40 +0000 Subject: [PATCH] Download Stems button now zips all stem files into a single .zip - New /admin/request//download-stems endpoint - Parses stems_url (label: url; label: url format), fetches each stem file, bundles them into a zip served as an attachment download - If MusicGPT returns a single .zip bundle URL, redirects directly - Template button now points to the zip endpoint instead of raw CDN URLs --- app.py | 72 ++++++++++++++++++++++++++++++++++++ templates/admin/request.html | 2 +- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 28ef28a..b41e9d4 100644 --- a/app.py +++ b/app.py @@ -34,6 +34,9 @@ import hmac import shutil import time import json +import io +import zipfile +import re from pathlib import Path # Flask and related imports @@ -790,6 +793,75 @@ def audio(token, version): abort(404) +@app.route('/admin/request//download-stems') +def admin_download_stems(rid): + """Download all stems files for a request as a single .zip archive.""" + redir = require_admin() + if redir: + return redir + req = get_request_by_id(rid) + if not req: + abort(404) + req = dict(req) + + stems_url = req.get('stems_url') or '' + if not stems_url: + flash('No stems available to download.', 'error') + return redirect(url_for('admin_request', rid=rid)) + + # Parse the stems_url field. It can be: + # 1. A single bundle/zip URL (use directly) + # 2. "label1: url1; label2: url2" (multiple individual stem files) + # 3. Just a bare URL + urls = [] + if ';' in stems_url or ': ' in stems_url: + # Multiple stems: "instrumental: https://...; vocals: https://..." + parts = stems_url.split(';') + for part in parts: + part = part.strip() + if not part: + continue + # Split on first ": " to separate label from URL + if ': ' in part: + label, url = part.split(': ', 1) + urls.append((label.strip(), url.strip())) + else: + urls.append(('stem', part)) + else: + urls.append(('stems', stems_url.strip())) + + if not urls: + flash('No stems files found.', 'error') + return redirect(url_for('admin_request', rid=rid)) + + # If there's only one URL and it's already a .zip, redirect directly. + if len(urls) == 1 and urls[0][1].endswith('.zip'): + return redirect(urls[0][1]) + + # Download each stem file and bundle them into a zip in memory. + import requests as req_lib + buf = io.BytesIO() + with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf: + for label, url in urls: + try: + r = req_lib.get(url, timeout=60) + r.raise_for_status() + # Derive a filename from the URL or label. + filename = url.split('/')[-1] if '/' in url else f"{label}.mp3" + # If the URL has no extension, use the label. + if '.' not in filename: + filename = f"{label}.mp3" + zf.writestr(filename, r.content) + except Exception: + # Skip files that fail to download, but include the rest. + pass + buf.seek(0) + + title = req.get('suno_title') or req.get('name') or f'request_{rid}' + download_name = f"stems_{title}.zip" + return send_file(buf, mimetype='application/zip', as_attachment=True, download_name=download_name) + + # --------------------------------------------------------------------------- # Admin routes # --------------------------------------------------------------------------- diff --git a/templates/admin/request.html b/templates/admin/request.html index e24430b..aef7ea4 100644 --- a/templates/admin/request.html +++ b/templates/admin/request.html @@ -618,7 +618,7 @@ {% endif %} {% if req.stems_url %} {% endif %}