350 lines
7 KiB
Markdown
350 lines
7 KiB
Markdown
|
|
# 🎯 DESIGN - Pipeline d'encodage audio minimal mais robuste
|
||
|
|
|
||
|
|
**Date**: 2025-01-27
|
||
|
|
**Mission**: P0 - Rendre le stream-server fonctionnel
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. ARCHITECTURE
|
||
|
|
|
||
|
|
### 1.1 Vue d'ensemble
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────┐
|
||
|
|
│ API Routes │
|
||
|
|
│ /transcode │
|
||
|
|
│ /job/{id} │
|
||
|
|
│ /hls/{id}/... │
|
||
|
|
└────────┬────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────┐
|
||
|
|
│ TranscodingEngine│
|
||
|
|
│ - Queue │
|
||
|
|
│ - JobManager │
|
||
|
|
│ - WorkerPool │
|
||
|
|
└────────┬────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────┐
|
||
|
|
│ Worker Pool │
|
||
|
|
│ (N workers) │
|
||
|
|
└────────┬────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────┐
|
||
|
|
│ FFmpeg Process │
|
||
|
|
│ (HLS output) │
|
||
|
|
└─────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
### 1.2 Composants principaux
|
||
|
|
|
||
|
|
#### **TranscodingEngine**
|
||
|
|
- **Responsabilité**: Orchestrer le transcodage
|
||
|
|
- **Composants**:
|
||
|
|
- `PriorityQueue`: File d'attente des jobs
|
||
|
|
- `JobManager`: Stockage et suivi des jobs
|
||
|
|
- `WorkerPool`: Pool de workers parallèles
|
||
|
|
|
||
|
|
#### **JobManager**
|
||
|
|
- **Responsabilité**: Stocker et suivre l'état des jobs
|
||
|
|
- **Stockage**: `HashMap<Uuid, TranscodingJob>` (en mémoire pour P0)
|
||
|
|
- **Méthodes**:
|
||
|
|
- `enqueue(job) -> Uuid`
|
||
|
|
- `get_status(job_id) -> Option<JobStatus>`
|
||
|
|
- `update_status(job_id, status)`
|
||
|
|
|
||
|
|
#### **WorkerPool**
|
||
|
|
- **Responsabilité**: Traiter les jobs en parallèle
|
||
|
|
- **Implémentation**: Tokio tasks avec canal async
|
||
|
|
- **Workers**: N workers (configurable, défaut: `num_cpus()`)
|
||
|
|
|
||
|
|
#### **Pipeline**
|
||
|
|
- **Responsabilité**: Exécuter FFmpeg et gérer les erreurs
|
||
|
|
- **Fonction**: `transcode_to_hls(input_path, output_dir, codec, bitrate) -> Result<TranscodeResult>`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. PIPELINE D'ENCODAGE
|
||
|
|
|
||
|
|
### 2.1 Fonction principale
|
||
|
|
|
||
|
|
```rust
|
||
|
|
async fn transcode_to_hls(
|
||
|
|
input_path: &Path,
|
||
|
|
output_dir: &Path,
|
||
|
|
codec: AudioCodec,
|
||
|
|
bitrate: u32,
|
||
|
|
profile: &QualityProfile,
|
||
|
|
) -> Result<TranscodeResult, TranscodeError>
|
||
|
|
```
|
||
|
|
|
||
|
|
**Étapes**:
|
||
|
|
1. Créer le répertoire de sortie `{output_dir}/{job_id}/`
|
||
|
|
2. Construire la commande FFmpeg
|
||
|
|
3. Exécuter FFmpeg avec timeout
|
||
|
|
4. Vérifier que `index.m3u8` et segments existent
|
||
|
|
5. Retourner `TranscodeResult` avec chemins
|
||
|
|
|
||
|
|
### 2.2 Structure de sortie HLS
|
||
|
|
|
||
|
|
```
|
||
|
|
{output_dir}/
|
||
|
|
{job_id}/
|
||
|
|
index.m3u8 # Master playlist HLS
|
||
|
|
segment_00001.ts
|
||
|
|
segment_00002.ts
|
||
|
|
segment_00003.ts
|
||
|
|
...
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.3 Format du manifest HLS
|
||
|
|
|
||
|
|
```m3u8
|
||
|
|
#EXTM3U
|
||
|
|
#EXT-X-VERSION:3
|
||
|
|
#EXT-X-TARGETDURATION:6
|
||
|
|
#EXT-X-MEDIA-SEQUENCE:0
|
||
|
|
#EXTINF:6.0,
|
||
|
|
segment_00001.ts
|
||
|
|
#EXTINF:6.0,
|
||
|
|
segment_00002.ts
|
||
|
|
...
|
||
|
|
#EXT-X-ENDLIST
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. COMMAND BUILDER FFMPEG
|
||
|
|
|
||
|
|
### 3.1 Arguments par codec
|
||
|
|
|
||
|
|
#### **AAC**
|
||
|
|
```bash
|
||
|
|
ffmpeg -i input.wav \
|
||
|
|
-c:a aac \
|
||
|
|
-b:a 192k \
|
||
|
|
-ar 44100 \
|
||
|
|
-ac 2 \
|
||
|
|
-f hls \
|
||
|
|
-hls_time 6 \
|
||
|
|
-hls_playlist_type vod \
|
||
|
|
-hls_segment_filename segment_%05d.ts \
|
||
|
|
index.m3u8
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **MP3**
|
||
|
|
```bash
|
||
|
|
ffmpeg -i input.wav \
|
||
|
|
-c:a libmp3lame \
|
||
|
|
-b:a 192k \
|
||
|
|
-ar 44100 \
|
||
|
|
-ac 2 \
|
||
|
|
-f hls \
|
||
|
|
-hls_time 6 \
|
||
|
|
-hls_playlist_type vod \
|
||
|
|
-hls_segment_filename segment_%05d.ts \
|
||
|
|
index.m3u8
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **Opus**
|
||
|
|
```bash
|
||
|
|
ffmpeg -i input.wav \
|
||
|
|
-c:a libopus \
|
||
|
|
-b:a 192k \
|
||
|
|
-ar 48000 \
|
||
|
|
-ac 2 \
|
||
|
|
-f hls \
|
||
|
|
-hls_time 6 \
|
||
|
|
-hls_playlist_type vod \
|
||
|
|
-hls_segment_filename segment_%05d.ts \
|
||
|
|
index.m3u8
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **FLAC**
|
||
|
|
```bash
|
||
|
|
ffmpeg -i input.wav \
|
||
|
|
-c:a flac \
|
||
|
|
-compression_level 5 \
|
||
|
|
-ar 44100 \
|
||
|
|
-ac 2 \
|
||
|
|
-f hls \
|
||
|
|
-hls_time 6 \
|
||
|
|
-hls_playlist_type vod \
|
||
|
|
-hls_segment_filename segment_%05d.ts \
|
||
|
|
index.m3u8
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.2 Améliorations du builder
|
||
|
|
|
||
|
|
- ✅ Ajouter `-hls_list_size 0` pour VOD (liste complète)
|
||
|
|
- ✅ Valider que FFmpeg est installé au démarrage
|
||
|
|
- ✅ Gérer les erreurs de construction de commande
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. GESTION DES ERREURS
|
||
|
|
|
||
|
|
### 4.1 Types d'erreurs
|
||
|
|
|
||
|
|
```rust
|
||
|
|
#[derive(Debug, thiserror::Error)]
|
||
|
|
pub enum TranscodeError {
|
||
|
|
#[error("Input file not found: {0}")]
|
||
|
|
InputNotFound(PathBuf),
|
||
|
|
|
||
|
|
#[error("FFmpeg execution failed: {0}")]
|
||
|
|
FfmpegError(String),
|
||
|
|
|
||
|
|
#[error("Transcoding timed out after {0} seconds")]
|
||
|
|
Timeout(u64),
|
||
|
|
|
||
|
|
#[error("Output directory creation failed: {0}")]
|
||
|
|
OutputDirError(String),
|
||
|
|
|
||
|
|
#[error("HLS manifest not generated")]
|
||
|
|
ManifestMissing,
|
||
|
|
|
||
|
|
#[error("Insufficient disk space")]
|
||
|
|
DiskSpaceError,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.2 Gestion du timeout
|
||
|
|
|
||
|
|
- **Timeout**: 5 minutes par défaut
|
||
|
|
- **Action**: Tuer le processus FFmpeg si timeout
|
||
|
|
- **Implémentation**: Utiliser `Command::kill()` sur le child process
|
||
|
|
|
||
|
|
### 4.3 Retry logic
|
||
|
|
|
||
|
|
- **Max retries**: 3
|
||
|
|
- **Conditions de retry**:
|
||
|
|
- Erreur temporaire (timeout, IO error)
|
||
|
|
- Pas de retry sur erreur permanente (fichier corrompu)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. API REST
|
||
|
|
|
||
|
|
### 5.1 POST `/v1/stream/transcode`
|
||
|
|
|
||
|
|
**Request**:
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"file": "<multipart file>",
|
||
|
|
"codec": "aac",
|
||
|
|
"bitrate": 192000,
|
||
|
|
"quality_profile": "high"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response**:
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"status": "queued",
|
||
|
|
"message": "Job submitted successfully"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5.2 GET `/v1/stream/job/{id}`
|
||
|
|
|
||
|
|
**Response**:
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"status": "processing",
|
||
|
|
"progress": 45.5,
|
||
|
|
"created_at": "2025-01-27T10:00:00Z",
|
||
|
|
"started_at": "2025-01-27T10:00:05Z",
|
||
|
|
"completed_at": null,
|
||
|
|
"error": null
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5.3 GET `/v1/stream/hls/{job_id}/index.m3u8`
|
||
|
|
|
||
|
|
**Response**: Contenu du fichier `index.m3u8` avec `Content-Type: application/vnd.apple.mpegurl`
|
||
|
|
|
||
|
|
### 5.4 GET `/v1/stream/hls/{job_id}/{segment}`
|
||
|
|
|
||
|
|
**Response**: Contenu binaire du segment `.ts` avec `Content-Type: video/mp2t`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. INTÉGRATION DANS APPSTATE
|
||
|
|
|
||
|
|
### 6.1 Ajout de TranscodingEngine
|
||
|
|
|
||
|
|
```rust
|
||
|
|
pub struct AppState {
|
||
|
|
// ... existing fields ...
|
||
|
|
pub transcoding_engine: Arc<TranscodingEngine>,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 6.2 Initialisation dans `AppState::new()`
|
||
|
|
|
||
|
|
```rust
|
||
|
|
let transcoding_engine = Arc::new(
|
||
|
|
TranscodingEngine::new(num_cpus::get())
|
||
|
|
);
|
||
|
|
transcoding_engine.start();
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. LOGS STRUCTURÉS
|
||
|
|
|
||
|
|
### 7.1 Événements à logger
|
||
|
|
|
||
|
|
- `job_queued`: Job soumis
|
||
|
|
- `job_started`: Worker commence le traitement
|
||
|
|
- `job_progress`: Progression (toutes les 10%)
|
||
|
|
- `job_completed`: Job terminé avec succès
|
||
|
|
- `job_failed`: Job échoué avec erreur
|
||
|
|
|
||
|
|
### 7.2 Format des logs
|
||
|
|
|
||
|
|
```rust
|
||
|
|
tracing::info!(
|
||
|
|
job_id = %job.id,
|
||
|
|
status = "queued",
|
||
|
|
priority = ?job.priority,
|
||
|
|
"Job queued for transcoding"
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. TESTS
|
||
|
|
|
||
|
|
### 8.1 Tests unitaires
|
||
|
|
|
||
|
|
- ✅ Test command builder pour chaque codec
|
||
|
|
- ✅ Test pipeline avec fichier WAV de test
|
||
|
|
- ✅ Test job manager en mémoire
|
||
|
|
|
||
|
|
### 8.2 Tests d'intégration
|
||
|
|
|
||
|
|
- ✅ Upload fichier → job → index.m3u8 généré
|
||
|
|
- ✅ Vérification existence segments
|
||
|
|
- ✅ Test timeout FFmpeg
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. LIMITATIONS P0
|
||
|
|
|
||
|
|
- ❌ Pas de multi-bitrate adaptatif (ABR)
|
||
|
|
- ❌ Pas de DRM
|
||
|
|
- ❌ Pas de persistance en base de données (mémoire uniquement)
|
||
|
|
- ❌ Pas de métriques Prometheus détaillées
|
||
|
|
- ❌ Pas de cleanup automatique des anciens jobs
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Prochaine étape**: Phase 3 - Implémentation
|
||
|
|
|