feat: Add sounds routes for serving audio and thumbnail files
This commit is contained in:
@@ -101,6 +101,7 @@ def create_app():
|
||||
main,
|
||||
player,
|
||||
soundboard,
|
||||
sounds,
|
||||
stream,
|
||||
)
|
||||
|
||||
@@ -109,6 +110,7 @@ def create_app():
|
||||
app.register_blueprint(admin.bp, url_prefix="/api/admin")
|
||||
app.register_blueprint(admin_sounds.bp, url_prefix="/api/admin/sounds")
|
||||
app.register_blueprint(soundboard.bp, url_prefix="/api/soundboard")
|
||||
app.register_blueprint(sounds.bp, url_prefix="/api/sounds")
|
||||
app.register_blueprint(stream.bp, url_prefix="/api/stream")
|
||||
app.register_blueprint(player.bp, url_prefix="/api/player")
|
||||
|
||||
|
||||
99
app/routes/sounds.py
Normal file
99
app/routes/sounds.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""Routes for serving sound files and thumbnails."""
|
||||
|
||||
import os
|
||||
from flask import Blueprint, send_from_directory, abort
|
||||
|
||||
from app.services.decorators import require_auth
|
||||
|
||||
bp = Blueprint("sounds", __name__)
|
||||
|
||||
|
||||
@bp.route("/<sound_type>/thumbnails/<path:filename>")
|
||||
def serve_thumbnail(sound_type, filename):
|
||||
"""Serve thumbnail files for sounds."""
|
||||
try:
|
||||
# Map sound type codes to directory names
|
||||
type_mapping = {
|
||||
"str": "stream",
|
||||
"sdb": "soundboard",
|
||||
"say": "say"
|
||||
}
|
||||
|
||||
# Security: validate sound type
|
||||
if sound_type not in type_mapping:
|
||||
abort(404)
|
||||
|
||||
# Basic filename validation (no path traversal)
|
||||
if ".." in filename or "/" in filename or "\\" in filename:
|
||||
abort(404)
|
||||
|
||||
if not filename or not filename.strip():
|
||||
abort(404)
|
||||
|
||||
# Get the actual directory name
|
||||
directory_name = type_mapping[sound_type]
|
||||
|
||||
# Construct the thumbnail directory path
|
||||
sounds_dir = os.path.join(os.getcwd(), "sounds")
|
||||
thumbnail_dir = os.path.join(sounds_dir, directory_name, "thumbnails")
|
||||
|
||||
# Check if thumbnail directory exists
|
||||
if not os.path.exists(thumbnail_dir):
|
||||
abort(404)
|
||||
|
||||
# Check if file exists
|
||||
file_path = os.path.join(thumbnail_dir, filename)
|
||||
if not os.path.exists(file_path):
|
||||
abort(404)
|
||||
|
||||
# Serve the file
|
||||
return send_from_directory(thumbnail_dir, filename)
|
||||
|
||||
except Exception:
|
||||
abort(404)
|
||||
|
||||
|
||||
@bp.route("/<sound_type>/audio/<path:filename>")
|
||||
@require_auth
|
||||
def serve_audio(sound_type, filename):
|
||||
"""Serve audio files for sounds."""
|
||||
try:
|
||||
# Map sound type codes to directory names
|
||||
type_mapping = {
|
||||
"str": "stream",
|
||||
"sdb": "soundboard",
|
||||
"say": "say"
|
||||
}
|
||||
|
||||
# Security: validate sound type
|
||||
if sound_type not in type_mapping:
|
||||
abort(404)
|
||||
|
||||
# Basic filename validation (no path traversal)
|
||||
if ".." in filename or "/" in filename or "\\" in filename:
|
||||
abort(404)
|
||||
|
||||
if not filename or not filename.strip():
|
||||
abort(404)
|
||||
|
||||
# Get the actual directory name
|
||||
directory_name = type_mapping[sound_type]
|
||||
|
||||
# Construct the audio directory path
|
||||
sounds_dir = os.path.join(os.getcwd(), "sounds")
|
||||
audio_dir = os.path.join(sounds_dir, directory_name)
|
||||
|
||||
# Check if audio directory exists
|
||||
if not os.path.exists(audio_dir):
|
||||
abort(404)
|
||||
|
||||
# Check if file exists
|
||||
file_path = os.path.join(audio_dir, filename)
|
||||
if not os.path.exists(file_path):
|
||||
abort(404)
|
||||
|
||||
# Serve the file
|
||||
return send_from_directory(audio_dir, filename)
|
||||
|
||||
except Exception:
|
||||
abort(404)
|
||||
@@ -6,7 +6,7 @@ import time
|
||||
from typing import Any, Optional
|
||||
|
||||
import vlc
|
||||
from flask import current_app
|
||||
from flask import current_app, request
|
||||
|
||||
from app.models.playlist import Playlist
|
||||
from app.models.sound import Sound
|
||||
@@ -125,6 +125,20 @@ class MusicPlayerService:
|
||||
logger.error(f"Error loading playlist {playlist_id}: {e}")
|
||||
return False
|
||||
|
||||
def _build_thumbnail_url(self, sound_type: str, thumbnail_filename: str) -> str:
|
||||
"""Build absolute thumbnail URL."""
|
||||
try:
|
||||
# Try to get base URL from current request context
|
||||
if request:
|
||||
base_url = request.url_root.rstrip('/')
|
||||
else:
|
||||
# Fallback to localhost if no request context
|
||||
base_url = "http://localhost:5000"
|
||||
return f"{base_url}/api/sounds/{sound_type.lower()}/thumbnails/{thumbnail_filename}"
|
||||
except Exception:
|
||||
# Fallback if request context is not available
|
||||
return f"http://localhost:5000/api/sounds/{sound_type.lower()}/thumbnails/{thumbnail_filename}"
|
||||
|
||||
def _load_playlist_with_context(self, playlist) -> bool:
|
||||
"""Load playlist with database context already established."""
|
||||
try:
|
||||
@@ -402,7 +416,7 @@ class MusicPlayerService:
|
||||
"artist": None, # Could be extracted from metadata
|
||||
"duration": sound.duration or 0,
|
||||
"thumbnail": (
|
||||
f"/api/sounds/{sound.type.lower()}/thumbnails/{sound.thumbnail}"
|
||||
self._build_thumbnail_url(sound.type, sound.thumbnail)
|
||||
if sound.thumbnail
|
||||
else None
|
||||
),
|
||||
@@ -437,7 +451,7 @@ class MusicPlayerService:
|
||||
"artist": None,
|
||||
"duration": sound.duration or 0,
|
||||
"thumbnail": (
|
||||
f"/api/sounds/{sound.type.lower()}/thumbnails/{sound.thumbnail}"
|
||||
self._build_thumbnail_url(sound.type, sound.thumbnail)
|
||||
if sound.thumbnail
|
||||
else None
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user