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
This commit is contained in:
@@ -53,6 +53,8 @@ export default function Home() {
|
||||
const [page, setPage] = useState(0)
|
||||
const [currentTrack, setCurrentTrack] = useState<Track | null>(null)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [isScanning, setIsScanning] = useState(false)
|
||||
const [scanStatus, setScanStatus] = useState<string>("")
|
||||
const limit = 25
|
||||
|
||||
const { data: tracksData, isLoading: isLoadingTracks } = useQuery({
|
||||
@@ -82,6 +84,49 @@ export default function Home() {
|
||||
|
||||
const totalPages = tracksData ? Math.ceil(tracksData.total / limit) : 0
|
||||
|
||||
const handleRescan = async () => {
|
||||
try {
|
||||
setIsScanning(true)
|
||||
setScanStatus("Démarrage du scan...")
|
||||
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/library/scan`, {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Échec du démarrage du scan')
|
||||
}
|
||||
|
||||
setScanStatus("Scan en cours...")
|
||||
|
||||
// Poll scan status
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const statusResponse = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/library/scan/status`)
|
||||
const status = await statusResponse.json()
|
||||
|
||||
if (!status.is_scanning) {
|
||||
clearInterval(pollInterval)
|
||||
setScanStatus(`Scan terminé ! ${status.processed} fichiers traités`)
|
||||
setIsScanning(false)
|
||||
|
||||
// Refresh tracks after scan
|
||||
window.location.reload()
|
||||
} else {
|
||||
setScanStatus(`Scan : ${status.processed}/${status.total_files} fichiers (${status.progress}%)`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la vérification du statut:', error)
|
||||
}
|
||||
}, 2000)
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du rescan:', error)
|
||||
setScanStatus("Erreur lors du scan")
|
||||
setIsScanning(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex flex-col">
|
||||
{/* Header */}
|
||||
@@ -109,8 +154,30 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ml-6 text-sm text-slate-600">
|
||||
{tracksData?.total || 0} piste{(tracksData?.total || 0) > 1 ? 's' : ''}
|
||||
<div className="ml-6 flex items-center gap-3">
|
||||
<div className="text-sm text-slate-600">
|
||||
{tracksData?.total || 0} piste{(tracksData?.total || 0) > 1 ? 's' : ''}
|
||||
</div>
|
||||
|
||||
{/* Rescan button */}
|
||||
<button
|
||||
onClick={handleRescan}
|
||||
disabled={isScanning}
|
||||
className="px-4 py-2 bg-orange-500 hover:bg-orange-600 disabled:bg-slate-300 disabled:cursor-not-allowed text-white text-sm font-medium rounded-lg transition-colors flex items-center gap-2"
|
||||
title="Rescanner la bibliothèque musicale"
|
||||
>
|
||||
<svg className={`w-4 h-4 ${isScanning ? 'animate-spin' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
{isScanning ? 'Scan en cours...' : 'Rescan'}
|
||||
</button>
|
||||
|
||||
{/* Scan status */}
|
||||
{scanStatus && (
|
||||
<div className="text-xs text-slate-600 bg-slate-100 px-3 py-1 rounded">
|
||||
{scanStatus}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user