feat: stream MP3s through backend proxy endpoint

- Replace /audio/<token>/<v>.mp3 with /api/stream/<token>/<v>.mp3.
- Stream bytes from private storage with Accept-Ranges and Content-Range support.
- Keep old /audio route returning 404.
- Update player page JS to use new stream endpoint.
This commit is contained in:
Troll (Hermes Agent) 2026-08-03 18:59:00 +00:00
parent 5863c32e35
commit 3057eb8eda
2 changed files with 73 additions and 11 deletions

80
app.py
View file

@ -634,27 +634,89 @@ def revise(token):
return redirect(url_for('play', token=token)) return redirect(url_for('play', token=token))
@app.route('/audio/<token>/<version>.mp3') @app.route('/api/stream/<token>/<version>.mp3')
def audio(token, version): def stream_audio(token, version):
""" """
Serve an uploaded MP3 file for a specific request token and version ('a' or 'b'). Stream an uploaded MP3 through a backend proxy endpoint.
This keeps the files off the public static path and ties them to the private token.
This hides the real file path from the customer. The endpoint checks the
player token and serves bytes with Range request support so the HTML audio
player can seek. The URL is still interceptable in-browser, but it is not a
direct file path and can be gated or expired later.
""" """
req = get_request_by_token(token) req = get_request_by_token(token)
if not req: if not req:
abort(404) abort(404)
if version not in ('a', 'b'): if version not in ('a', 'b'):
abort(404) abort(404)
field = f'song_{version}_path'
path = req.get(field) # Both versions must exist before any streaming happens.
if not path or not Path(path).exists(): a_path = req.get('song_a_path')
b_path = req.get('song_b_path')
if not a_path or not b_path:
abort(404) abort(404)
response = send_from_directory(Path(path).parent, Path(path).name) for p in (a_path, b_path):
if not Path(p).exists():
abort(404)
path = a_path if version == 'a' else b_path
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-Disposition'] = 'inline'
response.headers['X-Content-Type-Options'] = 'nosniff' response.headers['Content-Length'] = str(end - start + 1)
if status_code == 206:
response.headers['Content-Range'] = f'bytes {start}-{end}/{file_size}'
return response return response
@app.route('/audio/<token>/<version>.mp3')
def audio(token, version):
"""
Legacy audio endpoint. Replaced by /api/stream/<token>/<version>.mp3.
Returns 404 so old direct links do not work.
"""
abort(404)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Admin routes # Admin routes
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View file

@ -143,8 +143,8 @@
<script> <script>
(function(){ (function(){
// Audio sources are assigned via JS so they do not appear in static HTML source. // Audio sources are assigned via JS so they do not appear in static HTML source.
document.getElementById('player-a').src = "{{ url_for('audio', token=req.player_token, version='a') }}"; document.getElementById('player-a').src = "{{ url_for('stream_audio', token=req.player_token, version='a') }}";
document.getElementById('player-b').src = "{{ url_for('audio', token=req.player_token, version='b') }}"; document.getElementById('player-b').src = "{{ url_for('stream_audio', token=req.player_token, version='b') }}";
})(); })();
</script> </script>