612 lines
11 KiB
Markdown
612 lines
11 KiB
Markdown
# Transcriptarr REST API
|
|
|
|
Documentación completa de las APIs REST del backend de Transcriptarr.
|
|
|
|
## 🚀 Inicio Rápido
|
|
|
|
### Ejecutar el servidor
|
|
|
|
```bash
|
|
# Usando el CLI
|
|
python backend/cli.py server --host 0.0.0.0 --port 8000
|
|
|
|
# Con auto-reload (desarrollo)
|
|
python backend/cli.py server --reload
|
|
|
|
# Con múltiples workers (producción)
|
|
python backend/cli.py server --workers 4
|
|
```
|
|
|
|
### Documentación interactiva
|
|
|
|
Una vez iniciado el servidor, accede a:
|
|
- **Swagger UI**: http://localhost:8000/docs
|
|
- **ReDoc**: http://localhost:8000/redoc
|
|
|
|
## 📋 Endpoints
|
|
|
|
### System Status
|
|
|
|
#### `GET /`
|
|
Información básica de la API.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"name": "Transcriptarr API",
|
|
"version": "1.0.0",
|
|
"status": "running"
|
|
}
|
|
```
|
|
|
|
#### `GET /health`
|
|
Health check para monitoring.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"status": "healthy",
|
|
"database": "connected",
|
|
"workers": 2,
|
|
"queue_size": 5
|
|
}
|
|
```
|
|
|
|
#### `GET /api/status`
|
|
Estado completo del sistema.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"system": {
|
|
"status": "running",
|
|
"uptime_seconds": 3600.5
|
|
},
|
|
"workers": {
|
|
"total_workers": 2,
|
|
"cpu_workers": 1,
|
|
"gpu_workers": 1,
|
|
"idle_workers": 1,
|
|
"busy_workers": 1,
|
|
"total_jobs_completed": 42,
|
|
"total_jobs_failed": 2
|
|
},
|
|
"queue": {
|
|
"total": 100,
|
|
"queued": 5,
|
|
"processing": 2,
|
|
"completed": 90,
|
|
"failed": 3
|
|
},
|
|
"scanner": {
|
|
"scheduler_running": true,
|
|
"next_scan_time": "2026-01-13T02:00:00",
|
|
"watcher_running": true
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 👷 Workers API (`/api/workers`)
|
|
|
|
### `GET /api/workers`
|
|
Lista todos los workers activos.
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"worker_id": "worker-cpu-0",
|
|
"worker_type": "cpu",
|
|
"device_id": null,
|
|
"status": "busy",
|
|
"current_job_id": "abc123",
|
|
"jobs_completed": 10,
|
|
"jobs_failed": 0,
|
|
"uptime_seconds": 3600.5,
|
|
"current_job_progress": 45.2,
|
|
"current_job_eta": 120
|
|
}
|
|
]
|
|
```
|
|
|
|
### `GET /api/workers/stats`
|
|
Estadísticas del pool de workers.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"total_workers": 2,
|
|
"cpu_workers": 1,
|
|
"gpu_workers": 1,
|
|
"idle_workers": 1,
|
|
"busy_workers": 1,
|
|
"stopped_workers": 0,
|
|
"error_workers": 0,
|
|
"total_jobs_completed": 42,
|
|
"total_jobs_failed": 2,
|
|
"uptime_seconds": 3600.5,
|
|
"is_running": true
|
|
}
|
|
```
|
|
|
|
### `GET /api/workers/{worker_id}`
|
|
Obtener estado de un worker específico.
|
|
|
|
**Response:** Same as individual worker in list
|
|
|
|
### `POST /api/workers`
|
|
Añadir un nuevo worker al pool.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"worker_type": "gpu",
|
|
"device_id": 0
|
|
}
|
|
```
|
|
|
|
**Response:** Worker status object
|
|
|
|
### `DELETE /api/workers/{worker_id}`
|
|
Remover un worker del pool.
|
|
|
|
**Query Params:**
|
|
- `timeout` (float, default=30.0): Timeout en segundos
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Worker worker-cpu-0 removed successfully"
|
|
}
|
|
```
|
|
|
|
### `POST /api/workers/pool/start`
|
|
Iniciar el pool de workers.
|
|
|
|
**Query Params:**
|
|
- `cpu_workers` (int, default=0)
|
|
- `gpu_workers` (int, default=0)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Worker pool started: 1 CPU workers, 1 GPU workers"
|
|
}
|
|
```
|
|
|
|
### `POST /api/workers/pool/stop`
|
|
Detener el pool de workers.
|
|
|
|
**Query Params:**
|
|
- `timeout` (float, default=30.0)
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Worker pool stopped successfully"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 Jobs API (`/api/jobs`)
|
|
|
|
### `GET /api/jobs`
|
|
Lista de trabajos con paginación.
|
|
|
|
**Query Params:**
|
|
- `status_filter` (optional): queued, processing, completed, failed, cancelled
|
|
- `page` (int, default=1): Número de página
|
|
- `page_size` (int, default=50): Items por página
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"jobs": [
|
|
{
|
|
"id": "abc123",
|
|
"file_path": "/media/anime/episode.mkv",
|
|
"file_name": "episode.mkv",
|
|
"status": "completed",
|
|
"priority": 10,
|
|
"source_lang": "ja",
|
|
"target_lang": "es",
|
|
"quality_preset": "fast",
|
|
"transcribe_or_translate": "transcribe",
|
|
"progress": 100.0,
|
|
"current_stage": "finalizing",
|
|
"eta_seconds": null,
|
|
"created_at": "2026-01-12T10:00:00",
|
|
"started_at": "2026-01-12T10:00:05",
|
|
"completed_at": "2026-01-12T10:05:30",
|
|
"output_path": "/media/anime/episode.es.srt",
|
|
"segments_count": 245,
|
|
"error": null,
|
|
"retry_count": 0,
|
|
"worker_id": "worker-gpu-0",
|
|
"vram_used_mb": 4096,
|
|
"processing_time_seconds": 325.5,
|
|
"model_used": "large-v3",
|
|
"device_used": "cuda:0"
|
|
}
|
|
],
|
|
"total": 100,
|
|
"page": 1,
|
|
"page_size": 50
|
|
}
|
|
```
|
|
|
|
### `GET /api/jobs/stats`
|
|
Estadísticas de la cola.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"total_jobs": 100,
|
|
"queued": 5,
|
|
"processing": 2,
|
|
"completed": 90,
|
|
"failed": 3,
|
|
"cancelled": 0
|
|
}
|
|
```
|
|
|
|
### `GET /api/jobs/{job_id}`
|
|
Obtener un trabajo específico.
|
|
|
|
**Response:** Job object (same as in list)
|
|
|
|
### `POST /api/jobs`
|
|
Crear un nuevo trabajo de transcripción.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"file_path": "/media/anime/Attack on Titan S04E01.mkv",
|
|
"file_name": "Attack on Titan S04E01.mkv",
|
|
"source_lang": "ja",
|
|
"target_lang": "es",
|
|
"quality_preset": "fast",
|
|
"transcribe_or_translate": "transcribe",
|
|
"priority": 10,
|
|
"is_manual_request": true
|
|
}
|
|
```
|
|
|
|
**Response:** Created job object
|
|
|
|
### `POST /api/jobs/{job_id}/retry`
|
|
Reintentar un trabajo fallido.
|
|
|
|
**Response:** Updated job object
|
|
|
|
### `DELETE /api/jobs/{job_id}`
|
|
Cancelar un trabajo.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Job abc123 cancelled successfully"
|
|
}
|
|
```
|
|
|
|
### `POST /api/jobs/queue/clear`
|
|
Limpiar trabajos completados.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Cleared 42 completed jobs"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 📏 Scan Rules API (`/api/scan-rules`)
|
|
|
|
### `GET /api/scan-rules`
|
|
Lista todas las reglas de escaneo.
|
|
|
|
**Query Params:**
|
|
- `enabled_only` (bool, default=false): Solo reglas habilitadas
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": 1,
|
|
"name": "Japanese anime without Spanish subs",
|
|
"enabled": true,
|
|
"priority": 10,
|
|
"conditions": {
|
|
"audio_language_is": "ja",
|
|
"audio_language_not": null,
|
|
"audio_track_count_min": null,
|
|
"has_embedded_subtitle_lang": null,
|
|
"missing_embedded_subtitle_lang": "es",
|
|
"missing_external_subtitle_lang": "es",
|
|
"file_extension": ".mkv,.mp4"
|
|
},
|
|
"action": {
|
|
"action_type": "transcribe",
|
|
"target_language": "es",
|
|
"quality_preset": "fast",
|
|
"job_priority": 5
|
|
},
|
|
"created_at": "2026-01-12T10:00:00",
|
|
"updated_at": null
|
|
}
|
|
]
|
|
```
|
|
|
|
### `GET /api/scan-rules/{rule_id}`
|
|
Obtener una regla específica.
|
|
|
|
**Response:** Rule object (same as in list)
|
|
|
|
### `POST /api/scan-rules`
|
|
Crear una nueva regla de escaneo.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"name": "Japanese anime without Spanish subs",
|
|
"enabled": true,
|
|
"priority": 10,
|
|
"conditions": {
|
|
"audio_language_is": "ja",
|
|
"missing_embedded_subtitle_lang": "es",
|
|
"missing_external_subtitle_lang": "es",
|
|
"file_extension": ".mkv,.mp4"
|
|
},
|
|
"action": {
|
|
"action_type": "transcribe",
|
|
"target_language": "es",
|
|
"quality_preset": "fast",
|
|
"job_priority": 5
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response:** Created rule object
|
|
|
|
### `PUT /api/scan-rules/{rule_id}`
|
|
Actualizar una regla.
|
|
|
|
**Request:** Same as POST (all fields optional)
|
|
|
|
**Response:** Updated rule object
|
|
|
|
### `DELETE /api/scan-rules/{rule_id}`
|
|
Eliminar una regla.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Scan rule 1 deleted successfully"
|
|
}
|
|
```
|
|
|
|
### `POST /api/scan-rules/{rule_id}/toggle`
|
|
Activar/desactivar una regla.
|
|
|
|
**Response:** Updated rule object
|
|
|
|
---
|
|
|
|
## 🔍 Scanner API (`/api/scanner`)
|
|
|
|
### `GET /api/scanner/status`
|
|
Estado del scanner.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"scheduler_enabled": true,
|
|
"scheduler_running": true,
|
|
"next_scan_time": "2026-01-13T02:00:00",
|
|
"watcher_enabled": true,
|
|
"watcher_running": true,
|
|
"watched_paths": ["/media/anime", "/media/movies"],
|
|
"last_scan_time": "2026-01-12T02:00:00",
|
|
"total_scans": 1523
|
|
}
|
|
```
|
|
|
|
### `POST /api/scanner/scan`
|
|
Ejecutar escaneo manual.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"paths": ["/media/anime", "/media/movies"],
|
|
"recursive": true
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"scanned_files": 150,
|
|
"matched_files": 25,
|
|
"jobs_created": 25,
|
|
"skipped_files": 125,
|
|
"paths_scanned": ["/media/anime", "/media/movies"]
|
|
}
|
|
```
|
|
|
|
### `POST /api/scanner/scheduler/start`
|
|
Iniciar escaneo programado.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"enabled": true,
|
|
"cron_expression": "0 2 * * *",
|
|
"paths": ["/media/anime"],
|
|
"recursive": true
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Scheduler started successfully"
|
|
}
|
|
```
|
|
|
|
### `POST /api/scanner/scheduler/stop`
|
|
Detener escaneo programado.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "Scheduler stopped successfully"
|
|
}
|
|
```
|
|
|
|
### `POST /api/scanner/watcher/start`
|
|
Iniciar observador de archivos.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"enabled": true,
|
|
"paths": ["/media/anime"],
|
|
"recursive": true
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "File watcher started successfully"
|
|
}
|
|
```
|
|
|
|
### `POST /api/scanner/watcher/stop`
|
|
Detener observador de archivos.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"message": "File watcher stopped successfully"
|
|
}
|
|
```
|
|
|
|
### `POST /api/scanner/analyze`
|
|
Analizar un archivo específico.
|
|
|
|
**Query Params:**
|
|
- `file_path` (required): Ruta al archivo
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"file_path": "/media/anime/episode.mkv",
|
|
"audio_tracks": [
|
|
{
|
|
"index": 0,
|
|
"codec": "aac",
|
|
"language": "ja",
|
|
"channels": 2
|
|
}
|
|
],
|
|
"embedded_subtitles": [],
|
|
"external_subtitles": [
|
|
{
|
|
"path": "/media/anime/episode.en.srt",
|
|
"language": "en"
|
|
}
|
|
],
|
|
"duration_seconds": 1440.5,
|
|
"is_video": true
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔐 Autenticación
|
|
|
|
> **TODO**: Implementar autenticación con JWT tokens
|
|
|
|
---
|
|
|
|
## 📊 Códigos de Error
|
|
|
|
- `200 OK`: Éxito
|
|
- `201 Created`: Recurso creado
|
|
- `400 Bad Request`: Parámetros inválidos
|
|
- `404 Not Found`: Recurso no encontrado
|
|
- `409 Conflict`: Conflicto (ej: duplicado)
|
|
- `500 Internal Server Error`: Error del servidor
|
|
|
|
---
|
|
|
|
## 🧪 Testing
|
|
|
|
### cURL Examples
|
|
|
|
```bash
|
|
# Get system status
|
|
curl http://localhost:8000/api/status
|
|
|
|
# Create a job
|
|
curl -X POST http://localhost:8000/api/jobs \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"file_path": "/media/anime/episode.mkv",
|
|
"file_name": "episode.mkv",
|
|
"target_lang": "es",
|
|
"quality_preset": "fast"
|
|
}'
|
|
|
|
# Add a GPU worker
|
|
curl -X POST http://localhost:8000/api/workers \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"worker_type": "gpu",
|
|
"device_id": 0
|
|
}'
|
|
```
|
|
|
|
### Python Example
|
|
|
|
```python
|
|
import requests
|
|
|
|
# Base URL
|
|
BASE_URL = "http://localhost:8000"
|
|
|
|
# Create a job
|
|
response = requests.post(
|
|
f"{BASE_URL}/api/jobs",
|
|
json={
|
|
"file_path": "/media/anime/episode.mkv",
|
|
"file_name": "episode.mkv",
|
|
"target_lang": "es",
|
|
"quality_preset": "fast"
|
|
}
|
|
)
|
|
|
|
job = response.json()
|
|
print(f"Job created: {job['id']}")
|
|
|
|
# Check job status
|
|
response = requests.get(f"{BASE_URL}/api/jobs/{job['id']}")
|
|
status = response.json()
|
|
print(f"Job status: {status['status']} - {status['progress']}%")
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 Notas
|
|
|
|
- Todas las fechas están en formato ISO 8601 UTC
|
|
- Los idiomas usan códigos ISO 639-1 (2 letras: ja, en, es, fr, etc.)
|
|
- La paginación usa índices base-1 (primera página = 1)
|
|
- Los workers se identifican por ID único generado automáticamente
|
|
|