403 lines
8.9 KiB
Markdown
403 lines
8.9 KiB
Markdown
|
|
# 🌐 API REST - Documentation complète
|
||
|
|
|
||
|
|
**Date**: 2025-01-27
|
||
|
|
**Version**: 0.2.0
|
||
|
|
**Base URL**: `http://localhost:8082`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Vue d'ensemble
|
||
|
|
|
||
|
|
L'API REST du stream-server permet de:
|
||
|
|
- Soumettre des fichiers audio pour transcodage
|
||
|
|
- Récupérer l'état des jobs
|
||
|
|
- Servir les fichiers HLS générés
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Endpoints
|
||
|
|
|
||
|
|
### POST `/v1/stream/transcode`
|
||
|
|
|
||
|
|
Soumet un fichier audio pour transcodage en HLS.
|
||
|
|
|
||
|
|
**Request**:
|
||
|
|
- **Method**: `POST`
|
||
|
|
- **Content-Type**: `multipart/form-data`
|
||
|
|
- **Body**:
|
||
|
|
- `file` (required): Fichier audio (WAV, MP3, FLAC, etc.)
|
||
|
|
- `codec` (optional): Codec cible (`aac`, `mp3`, `opus`, `flac`)
|
||
|
|
- `bitrate` (optional): Bitrate en bps (ex: `192000`)
|
||
|
|
- `quality_profile` (optional): Profil (`hi_res`, `high`, `medium`, `low`)
|
||
|
|
- `priority` (optional): Priorité (`urgent`, `normal`, `background`)
|
||
|
|
|
||
|
|
**Response** (200 OK):
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"job_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"status": "queued",
|
||
|
|
"message": "Job submitted successfully"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Erreurs**:
|
||
|
|
- `400 Bad Request`: Fichier manquant ou invalide
|
||
|
|
- `500 Internal Server Error`: Erreur serveur
|
||
|
|
|
||
|
|
**Exemple cURL**:
|
||
|
|
```bash
|
||
|
|
curl -X POST http://localhost:8082/v1/stream/transcode \
|
||
|
|
-F "file=@audio.wav" \
|
||
|
|
-F "quality_profile=high" \
|
||
|
|
-F "priority=normal"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Exemple JavaScript**:
|
||
|
|
```javascript
|
||
|
|
const formData = new FormData();
|
||
|
|
formData.append('file', audioFile);
|
||
|
|
formData.append('quality_profile', 'high');
|
||
|
|
|
||
|
|
const response = await fetch('http://localhost:8082/v1/stream/transcode', {
|
||
|
|
method: 'POST',
|
||
|
|
body: formData
|
||
|
|
});
|
||
|
|
|
||
|
|
const { job_id, status } = await response.json();
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### GET `/v1/stream/job/{id}`
|
||
|
|
|
||
|
|
Récupère l'état d'un job de transcodage.
|
||
|
|
|
||
|
|
**Request**:
|
||
|
|
- **Method**: `GET`
|
||
|
|
- **Path Parameter**: `id` (UUID du job)
|
||
|
|
|
||
|
|
**Response** (200 OK):
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||
|
|
"track_id": "my_track",
|
||
|
|
"status": "processing",
|
||
|
|
"progress": 45.5,
|
||
|
|
"created_at": "SystemTime { tv_sec: 1737979200, tv_nsec: 0 }",
|
||
|
|
"started_at": "Some(SystemTime { tv_sec: 1737979205, tv_nsec: 0 })",
|
||
|
|
"completed_at": null,
|
||
|
|
"error": null
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Statuts possibles**:
|
||
|
|
- `"Pending"`: Job en attente dans la queue
|
||
|
|
- `"Processing"`: Job en cours de traitement
|
||
|
|
- `"Completed"`: Job terminé avec succès
|
||
|
|
- `"Failed(String)"`: Job échoué (le message d'erreur est dans `error`)
|
||
|
|
- `"Cancelled"`: Job annulé
|
||
|
|
|
||
|
|
**Erreurs**:
|
||
|
|
- `404 Not Found`: Job introuvable
|
||
|
|
|
||
|
|
**Exemple cURL**:
|
||
|
|
```bash
|
||
|
|
curl http://localhost:8082/v1/stream/job/550e8400-e29b-41d4-a716-446655440000
|
||
|
|
```
|
||
|
|
|
||
|
|
**Exemple JavaScript**:
|
||
|
|
```javascript
|
||
|
|
const response = await fetch(`http://localhost:8082/v1/stream/job/${jobId}`);
|
||
|
|
const status = await response.json();
|
||
|
|
|
||
|
|
if (status.status === 'Completed') {
|
||
|
|
console.log('Job terminé!');
|
||
|
|
} else if (status.status.startsWith('Failed')) {
|
||
|
|
console.error('Erreur:', status.error);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### GET `/v1/stream/hls/{job_id}/index.m3u8`
|
||
|
|
|
||
|
|
Sert le manifest HLS pour un job terminé.
|
||
|
|
|
||
|
|
**Request**:
|
||
|
|
- **Method**: `GET`
|
||
|
|
- **Path Parameter**: `job_id` (UUID du job)
|
||
|
|
- **Headers**: Aucun requis
|
||
|
|
|
||
|
|
**Response** (200 OK):
|
||
|
|
- **Content-Type**: `application/vnd.apple.mpegurl`
|
||
|
|
- **Body**: Contenu du fichier `index.m3u8`
|
||
|
|
|
||
|
|
**Exemple de contenu**:
|
||
|
|
```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
|
||
|
|
#EXTINF:6.0,
|
||
|
|
segment_00003.ts
|
||
|
|
#EXT-X-ENDLIST
|
||
|
|
```
|
||
|
|
|
||
|
|
**Erreurs**:
|
||
|
|
- `404 Not Found`: Job introuvable ou manifest non généré
|
||
|
|
- `400 Bad Request`: Job non terminé
|
||
|
|
|
||
|
|
**Exemple cURL**:
|
||
|
|
```bash
|
||
|
|
curl http://localhost:8082/v1/stream/hls/550e8400-e29b-41d4-a716-446655440000/index.m3u8
|
||
|
|
```
|
||
|
|
|
||
|
|
**Exemple JavaScript**:
|
||
|
|
```javascript
|
||
|
|
const manifestUrl = `http://localhost:8082/v1/stream/hls/${jobId}/index.m3u8`;
|
||
|
|
|
||
|
|
// Utiliser avec hls.js
|
||
|
|
import Hls from 'hls.js';
|
||
|
|
const hls = new Hls();
|
||
|
|
hls.loadSource(manifestUrl);
|
||
|
|
hls.attachMedia(audioElement);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### GET `/v1/stream/hls/{job_id}/{segment}`
|
||
|
|
|
||
|
|
Sert un segment HLS (.ts).
|
||
|
|
|
||
|
|
**Request**:
|
||
|
|
- **Method**: `GET`
|
||
|
|
- **Path Parameters**:
|
||
|
|
- `job_id`: UUID du job
|
||
|
|
- `segment`: Nom du segment (ex: `segment_00001.ts`)
|
||
|
|
|
||
|
|
**Response** (200 OK):
|
||
|
|
- **Content-Type**: `video/mp2t`
|
||
|
|
- **Body**: Contenu binaire du segment
|
||
|
|
|
||
|
|
**Erreurs**:
|
||
|
|
- `404 Not Found`: Segment introuvable
|
||
|
|
|
||
|
|
**Exemple cURL**:
|
||
|
|
```bash
|
||
|
|
curl http://localhost:8082/v1/stream/hls/550e8400-e29b-41d4-a716-446655440000/segment_00001.ts \
|
||
|
|
--output segment_00001.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
**Note**: Généralement appelé automatiquement par le player HLS lors du parsing du manifest.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Workflow complet
|
||
|
|
|
||
|
|
### 1. Soumettre un job
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
const formData = new FormData();
|
||
|
|
formData.append('file', audioFile);
|
||
|
|
formData.append('quality_profile', 'high');
|
||
|
|
|
||
|
|
const submitResponse = await fetch('http://localhost:8082/v1/stream/transcode', {
|
||
|
|
method: 'POST',
|
||
|
|
body: formData
|
||
|
|
});
|
||
|
|
|
||
|
|
const { job_id } = await submitResponse.json();
|
||
|
|
console.log('Job soumis:', job_id);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Poller l'état
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
async function pollJobStatus(jobId) {
|
||
|
|
while (true) {
|
||
|
|
const response = await fetch(`http://localhost:8082/v1/stream/job/${jobId}`);
|
||
|
|
const status = await response.json();
|
||
|
|
|
||
|
|
console.log(`Statut: ${status.status}, Progression: ${status.progress}%`);
|
||
|
|
|
||
|
|
if (status.status === 'Completed') {
|
||
|
|
return status;
|
||
|
|
} else if (status.status.startsWith('Failed')) {
|
||
|
|
throw new Error(status.error);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Attendre 2 secondes avant le prochain poll
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const completedJob = await pollJobStatus(jobId);
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Charger le manifest HLS
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
const manifestUrl = `http://localhost:8082/v1/stream/hls/${jobId}/index.m3u8`;
|
||
|
|
|
||
|
|
// Avec hls.js
|
||
|
|
import Hls from 'hls.js';
|
||
|
|
|
||
|
|
if (Hls.isSupported()) {
|
||
|
|
const hls = new Hls();
|
||
|
|
hls.loadSource(manifestUrl);
|
||
|
|
hls.attachMedia(audioElement);
|
||
|
|
|
||
|
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||
|
|
console.log('Manifest chargé, prêt à jouer');
|
||
|
|
audioElement.play();
|
||
|
|
});
|
||
|
|
} else if (audioElement.canPlayType('application/vnd.apple.mpegurl')) {
|
||
|
|
// Support natif (Safari)
|
||
|
|
audioElement.src = manifestUrl;
|
||
|
|
audioElement.addEventListener('loadedmetadata', () => {
|
||
|
|
audioElement.play();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Codes d'erreur
|
||
|
|
|
||
|
|
### 400 Bad Request
|
||
|
|
- Fichier manquant dans la requête
|
||
|
|
- Format de fichier invalide
|
||
|
|
- Paramètres invalides
|
||
|
|
|
||
|
|
### 404 Not Found
|
||
|
|
- Job introuvable
|
||
|
|
- Manifest HLS introuvable
|
||
|
|
- Segment introuvable
|
||
|
|
|
||
|
|
### 500 Internal Server Error
|
||
|
|
- Erreur serveur interne
|
||
|
|
- Échec de soumission du job
|
||
|
|
- Erreur de traitement
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Rate limiting
|
||
|
|
|
||
|
|
Actuellement non implémenté (P0). À ajouter en P1.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Authentification
|
||
|
|
|
||
|
|
Actuellement non requise (P0). À ajouter en P1 avec JWT.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## CORS
|
||
|
|
|
||
|
|
CORS configuré pour:
|
||
|
|
- `http://localhost:5176` (Vite dev)
|
||
|
|
- `http://localhost:3000` (React dev)
|
||
|
|
|
||
|
|
Configurable via variable d'environnement `ALLOWED_ORIGINS`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Exemples complets
|
||
|
|
|
||
|
|
### Python
|
||
|
|
|
||
|
|
```python
|
||
|
|
import requests
|
||
|
|
import time
|
||
|
|
import uuid
|
||
|
|
|
||
|
|
# 1. Soumettre un job
|
||
|
|
with open('audio.wav', 'rb') as f:
|
||
|
|
response = requests.post(
|
||
|
|
'http://localhost:8082/v1/stream/transcode',
|
||
|
|
files={'file': f},
|
||
|
|
data={'quality_profile': 'high'}
|
||
|
|
)
|
||
|
|
job_id = response.json()['job_id']
|
||
|
|
|
||
|
|
# 2. Poller l'état
|
||
|
|
while True:
|
||
|
|
response = requests.get(f'http://localhost:8082/v1/stream/job/{job_id}')
|
||
|
|
status = response.json()
|
||
|
|
|
||
|
|
print(f"Statut: {status['status']}, Progression: {status['progress']}%")
|
||
|
|
|
||
|
|
if status['status'] == 'Completed':
|
||
|
|
break
|
||
|
|
elif status['status'].startswith('Failed'):
|
||
|
|
raise Exception(status['error'])
|
||
|
|
|
||
|
|
time.sleep(2)
|
||
|
|
|
||
|
|
# 3. URL du manifest
|
||
|
|
manifest_url = f'http://localhost:8082/v1/stream/hls/{job_id}/index.m3u8'
|
||
|
|
print(f"Manifest HLS: {manifest_url}")
|
||
|
|
```
|
||
|
|
|
||
|
|
### Node.js
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
const FormData = require('form-data');
|
||
|
|
const fs = require('fs');
|
||
|
|
const fetch = require('node-fetch');
|
||
|
|
|
||
|
|
async function transcodeAudio(filePath) {
|
||
|
|
// 1. Soumettre
|
||
|
|
const form = new FormData();
|
||
|
|
form.append('file', fs.createReadStream(filePath));
|
||
|
|
form.append('quality_profile', 'high');
|
||
|
|
|
||
|
|
const submitRes = await fetch('http://localhost:8082/v1/stream/transcode', {
|
||
|
|
method: 'POST',
|
||
|
|
body: form
|
||
|
|
});
|
||
|
|
|
||
|
|
const { job_id } = await submitRes.json();
|
||
|
|
|
||
|
|
// 2. Poller
|
||
|
|
while (true) {
|
||
|
|
const statusRes = await fetch(`http://localhost:8082/v1/stream/job/${job_id}`);
|
||
|
|
const status = await statusRes.json();
|
||
|
|
|
||
|
|
if (status.status === 'Completed') {
|
||
|
|
return `http://localhost:8082/v1/stream/hls/${job_id}/index.m3u8`;
|
||
|
|
} else if (status.status.startsWith('Failed')) {
|
||
|
|
throw new Error(status.error);
|
||
|
|
}
|
||
|
|
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Usage
|
||
|
|
transcodeAudio('./audio.wav')
|
||
|
|
.then(manifestUrl => console.log('Manifest:', manifestUrl))
|
||
|
|
.catch(console.error);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Changelog
|
||
|
|
|
||
|
|
### v0.2.0 (2025-01-27)
|
||
|
|
- ✅ Ajout endpoints `/v1/stream/transcode`
|
||
|
|
- ✅ Ajout endpoint `/v1/stream/job/{id}`
|
||
|
|
- ✅ Ajout endpoints HLS `/v1/stream/hls/{job_id}/...`
|
||
|
|
- ✅ Support multipart/form-data pour upload
|
||
|
|
- ✅ Gestion des erreurs et statuts
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Prochaine étape**: Voir `STREAM_PIPELINE.md` pour l'architecture interne.
|
||
|
|
|