Files
Audio-Classifier/backend/src/core/transcoder.py
Benoit 76d014bda2 Transcodage systématique MP3 128kbps
J'ai implémenté une solution complète pour optimiser ton système audio :
1. Backend - Transcodage & Waveforms
Nouveau module de transcodage (transcoder.py):
Transcodage automatique en MP3 128kbps via FFmpeg
Stockage dans dossier transcoded/
Compression ~70-90% selon format source
Waveforms pré-calculées (waveform_generator.py):
Génération lors du scan (800 points)
Stockage JSON dans dossier waveforms/
Chargement instantané
Schema BDD mis à jour (schema.py):
filepath : fichier original (download)
stream_filepath : MP3 128kbps (streaming)
waveform_filepath : JSON pré-calculé
Scanner amélioré (scanner.py):
Transcode automatiquement chaque fichier
Pré-calcule la waveform
Stocke les 3 chemins en BDD
2. API - Endpoints
Endpoint /api/library/scan (library.py):
POST pour lancer un scan
Tâche en arrière-plan
Statut consultable via GET /api/library/scan/status
Streaming optimisé (audio.py):
Utilise stream_filepath (MP3 128kbps) en priorité
Fallback sur fichier original si absent
Waveform chargée depuis JSON pré-calculé
3. Frontend - Interface
Bouton Rescan (page.tsx):
Dans le header à droite
Icône qui tourne pendant le scan
Affichage progression en temps réel
Reload automatique après scan
4. Base de données
Migration appliquée (20251223_003_add_stream_waveform_paths.py):

ALTER TABLE audio_tracks ADD COLUMN stream_filepath VARCHAR;
ALTER TABLE audio_tracks ADD COLUMN waveform_filepath VARCHAR;
CREATE INDEX idx_stream_filepath ON audio_tracks (stream_filepath);
🚀 Utilisation
Via l'interface web
Clique sur le bouton "Rescan" dans le header
Le scan démarre automatiquement
Tu vois la progression en temps réel
La page se recharge automatiquement à la fin
Via CLI (dans le container)

docker-compose exec backend python -m src.cli.scanner /music
📊 Avantages
 Streaming ultra-rapide : MP3 128kbps = ~70-90% plus léger
 Waveform instantanée : Pré-calculée, pas de latence
 Download qualité : Fichier original préservé
 Rescan facile : Bouton dans l'UI
 Prêt pour serveur distant : Optimisé pour la bande passante
2025-12-23 10:08:16 +01:00

131 lines
4.2 KiB
Python

"""Audio transcoding utilities using FFmpeg."""
import os
import subprocess
from pathlib import Path
from typing import Optional
from ..utils.logging import get_logger
logger = get_logger(__name__)
class AudioTranscoder:
"""Audio transcoder for creating streaming-optimized files."""
def __init__(self, output_dir: Optional[str] = None):
"""Initialize transcoder.
Args:
output_dir: Directory to store transcoded files. If None, uses 'transcoded' subdir next to original.
"""
self.output_dir = output_dir
def transcode_to_mp3(
self,
input_path: str,
output_path: Optional[str] = None,
bitrate: str = "128k",
overwrite: bool = False,
) -> Optional[str]:
"""Transcode audio file to MP3.
Args:
input_path: Path to input audio file
output_path: Path to output MP3 file. If None, auto-generated.
bitrate: MP3 bitrate (default: 128k for streaming)
overwrite: Whether to overwrite existing file
Returns:
Path to transcoded MP3 file, or None if failed
"""
try:
input_file = Path(input_path)
if not input_file.exists():
logger.error(f"Input file not found: {input_path}")
return None
# Generate output path if not provided
if output_path is None:
if self.output_dir:
output_dir = Path(self.output_dir)
else:
# Create 'transcoded' directory next to original
output_dir = input_file.parent / "transcoded"
output_dir.mkdir(parents=True, exist_ok=True)
output_path = str(output_dir / f"{input_file.stem}.mp3")
output_file = Path(output_path)
# Skip if already exists and not overwriting
if output_file.exists() and not overwrite:
logger.info(f"Transcoded file already exists: {output_path}")
return str(output_file)
logger.info(f"Transcoding {input_file.name} to MP3 {bitrate}...")
# FFmpeg command for high-quality MP3 encoding
cmd = [
"ffmpeg",
"-i", str(input_file),
"-vn", # No video
"-acodec", "libmp3lame", # MP3 codec
"-b:a", bitrate, # Bitrate
"-q:a", "2", # High quality VBR (if CBR fails)
"-ar", "44100", # Sample rate
"-ac", "2", # Stereo
"-y" if overwrite else "-n", # Overwrite or not
str(output_file),
]
# Run FFmpeg
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=False,
)
if result.returncode != 0:
logger.error(f"FFmpeg failed: {result.stderr}")
return None
if not output_file.exists():
logger.error(f"Transcoding failed: output file not created")
return None
output_size = output_file.stat().st_size
input_size = input_file.stat().st_size
compression_ratio = (1 - output_size / input_size) * 100
logger.info(
f"✓ Transcoded: {input_file.name}{output_file.name} "
f"({output_size / 1024 / 1024:.2f} MB, {compression_ratio:.1f}% reduction)"
)
return str(output_file)
except Exception as e:
logger.error(f"Failed to transcode {input_path}: {e}")
return None
def check_ffmpeg_available(self) -> bool:
"""Check if FFmpeg is available.
Returns:
True if FFmpeg is available, False otherwise
"""
try:
result = subprocess.run(
["ffmpeg", "-version"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
return result.returncode == 0
except FileNotFoundError:
logger.error("FFmpeg not found. Please install FFmpeg.")
return False