Descargar Subtítulo¶
Descarga el archivo de subtítulo actual para un subtítulo específico.
Endpoint¶
GET /api/subtitles/{subtitle_id}/download
Autenticación¶
✅ Autenticación requerida - Incluye tu clave de API en el header Authorization.
Descripción¶
Este endpoint descarga el archivo de subtítulo actual (típicamente formato .srt, .sub, o .zip). El archivo se sirve desde caché si fue descargado previamente, o se obtiene de la fuente y se cachea para solicitudes futuras.
Características Clave: - Caché automático para descargas subsecuentes más rápidas - Descarga desde la fuente si no está cacheado - Incrementa contador de descargas - Devuelve el archivo con headers content-disposition apropiados
Parámetros de Path¶
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
subtitle_id |
string (UUID) | Sí | Identificador único del subtítulo |
Ejemplos de Solicitud¶
=== "cURL"
# Download and save to file
curl -X GET "https://subx-api.duckdns.org/api/subtitles/550e8400-e29b-41d4-a716-446655440000/download" \
-H "Authorization: Bearer {TU_CLAVE_API}" \
-o subtitle.srt
# Download with original filename
curl -X GET "https://subx-api.duckdns.org/api/subtitles/550e8400-e29b-41d4-a716-446655440000/download" \
-H "Authorization: Bearer {TU_CLAVE_API}" \
-O -J
import requests
subtitle_id = "550e8400-e29b-41d4-a716-446655440000"
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
response = requests.get(
f"https://subx-api.duckdns.org/api/subtitles/{subtitle_id}/download",
headers=headers
)
# Save with original filename from headers
filename = "subtitle.srt"
if 'content-disposition' in response.headers:
# Extract filename from Content-Disposition header
import re
cd = response.headers['content-disposition']
filename_match = re.findall('filename="(.+)"', cd)
if filename_match:
filename = filename_match[0]
with open(filename, "wb") as f:
f.write(response.content)
print(f"Downloaded: {filename}")
package main
import (
"fmt"
"io"
"net/http"
"os"
"regexp"
)
func main() {
apiKey := "{TU_CLAVE_API}"
subtitleID := "550e8400-e29b-41d4-a716-446655440000"
url := fmt.Sprintf("https://subx-api.duckdns.org/api/subtitles/%s/download", subtitleID)
// Create request
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
// Send request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Extract filename from Content-Disposition
filename := "subtitle.srt"
cd := resp.Header.Get("Content-Disposition")
if cd != "" {
re := regexp.MustCompile(`filename="(.+)"`)
matches := re.FindStringSubmatch(cd)
if len(matches) > 1 {
filename = matches[1]
}
}
// Save to file
file, _ := os.Create(filename)
defer file.Close()
io.Copy(file, resp.Body)
fmt.Printf("Downloaded: %s\n", filename)
}
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
var subtitleId = "550e8400-e29b-41d4-a716-446655440000";
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "{TU_CLAVE_API}");
var url = $"https://subx-api.duckdns.org/api/subtitles/{subtitleId}/download";
var response = await client.GetAsync(url);
// Extract filename from Content-Disposition
var filename = "subtitle.srt";
if (response.Content.Headers.ContentDisposition?.FileName != null)
{
filename = response.Content.Headers.ContentDisposition.FileName.Trim('"');
}
var content = await response.Content.ReadAsByteArrayAsync();
await File.WriteAlleBytesAsync(filename, content);
Console.WriteLine($"Downloaded: {filename}");
const subtitleId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
`https://subx-api.duckdns.org/api/subtitles/${subtitleId}/download`,
{
headers: {
'Authorization': 'Bearer {TU_CLAVE_API}'
}
}
);
if (!response.ok) {
throw new Error(`Download failed: ${response.status}`);
}
// Extract filename from Content-Disposition
let filename = 'subtitle.srt';
const cd = response.headers.get('content-disposition');
if (cd) {
const match = cd.match(/filename="(.+)"/);
if (match) filename = match[1];
}
// Save to file (Node.js)
const fs = require('fs').promises;
const buffer = await response.arrayBuffer();
await fs.writeFile(filename, Buffer.from(buffer));
console.log(`Downloaded: ${filename}`);
Respuesta¶
Respuesta Exitosa (200 OK)¶
La respuesta es el contenido del archivo de subtítulo binario con headers apropiados:
Headers:
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="Dexter.S01E01.HDTV.XviD-LOL.srt"
Content-Length: 45678
Body: Contenido del archivo binario (archivo de subtítulo)
Extensiones de Archivo Comunes¶
| Extensión | Descripción |
|---|---|
.srt |
Formato SubRip (más común) |
.sub |
Formato MicroDVD o SubViewer |
.ssa / .ass |
SubStation Alpha / Advanced SubStation Alpha |
.zip |
Archivo comprimido (puede contener múltiples archivos) |
.rar |
Archivo comprimido |
Manejo de Archivos ZIP
Si el archivo descargado es un archivo .zip, extráelo para encontrar el archivo de subtítulo dentro. El archivo puede contener múltiples archivos de subtítulos o metadatos adicionales.
:::
Códigos de Estado¶
| Código | Descripción |
|---|---|
| 200 | Éxito - Archivo descargado |
| 400 | Solicitud Incorrecta - ID de fuente faltante o descarga fallida |
| 401 | No Autorizado - Clave de API inválida o faltante |
| 404 | No Encontrado - El subtítulo no existe |
| 429 | Demasiadas Solicitudes - Límite de tasa excedido |
| 500 | Error Interno del Servidor |
| 502 | Bad Gateway - Fallo al obtener de la fuente |
Respuestas de Error¶
404 No Encontrado¶
400 Solicitud Incorrecta - ID de Fuente Faltante¶
Esto ocurre cuando el registro del subtítulo no tiene los metadatos necesarios para descargar de la fuente.
502 Bad Gateway - Fallo al Obtener de la Fuente¶
Esto puede ocurrir cuando: - El sitio web de la fuente está temporalmente no disponible - El archivo de la fuente ha sido eliminado - Problemas de conectividad de red
Flujo de Descarga¶
graph TD
A[Client requests download] --> B{File cached?}
B -->|Yes| C[Return cached file]
B -->|No| D[Fetch from source]
D --> E{Fetch successful?}
E -->|Yes| F[Save to cache]
F --> G[Return file]
E -->|No| H[Return 502 error]
C --> I[Increment download counter]
G --> I
Ejemplos Avanzados¶
1. Descarga con Seguimiento de Progreso¶
import requests
from tqdm import tqdm
def download_with_progress(subtitle_id, output_path):
"""Download subtitle with progress bar."""
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
url = f"https://subx-api.duckdns.org/api/subtitles/{subtitle_id}/download"
response = requests.get(url, headers=headers, stream=True)
response.raise_for_status()
total_size = int(response.headers.get('content-length', 0))
with open(output_path, 'wb') as f:
with tqdm(total=total_size, unit='B', unit_scale=True) as pbar:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
pbar.update(len(chunk))
print(f"✓ Downloaded: {output_path}")
# Usage
download_with_progress(
"550e8400-e29b-41d4-a716-446655440000",
"dexter_s01e01.srt"
)
2. Descarga por Lotes de Múltiples Subtítulos¶
import requests
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
def download_subtitle(subtitle_id, output_dir, headers):
"""Download a single subtitle."""
try:
url = f"https://subx-api.duckdns.org/api/subtitles/{subtitle_id}/download"
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
# Extract filename
filename = f"{subtitle_id}.srt"
cd = response.headers.get('content-disposition', '')
if 'filename=' in cd:
import re
match = re.search(r'filename="(.+)"', cd)
if match:
filename = match.group(1)
# Save file
filepath = os.path.join(output_dir, filename)
with open(filepath, 'wb') as f:
f.write(response.content)
return {'id': subtitle_id, 'success': True, 'filename': filename}
except Exception as e:
return {'id': subtitle_id, 'success': False, 'error': str(e)}
def batch_download(subtitle_ids, output_dir="subtitles", max_workers=3):
"""Download multiple subtitles concurrently."""
os.makedirs(output_dir, exist_ok=True)
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
results = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {
executor.submit(download_subtitle, sub_id, output_dir, headers): sub_id
for sub_id in subtitle_ids
}
for future in as_completed(futures):
result = future.result()
results.append(result)
if result['success']:
print(f"✓ Downloaded: {result['filename']}")
else:
print(f"✗ Failed {result['id']}: {result['error']}")
success_count = sum(1 for r in results if r['success'])
print(f"\nCompleted: {success_count}/{len(subtitle_ids)} successful")
return results
# Usage
ids = [
"550e8400-e29b-41d4-a716-446655440000",
"660e9500-f39c-52e5-b827-557766551111"
]
batch_download(ids)
3. Descarga con Reintento Automático¶
import requests
import time
def download_with_retry(subtitle_id, output_path, max_retries=3):
"""Download subtitle with automatic retry on failure."""
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
url = f"https://subx-api.duckdns.org/api/subtitles/{subtitle_id}/download"
for attempt in range(max_retries):
try:
response = requests.get(url, headers=headers, timeout=30)
if response.status_code == 502:
# Source fetch failed - retry with backoff
if attempt < max_retries - 1:
wait_time = 60 * (attempt + 1)
print(f"Source unavailable. Retrying in {wait_time}s...")
time.sleep(wait_time)
continue
else:
raise Exception("Failed to fetch from source after retries")
response.raise_for_status()
# Success - save file
with open(output_path, 'wb') as f:
f.write(response.content)
print(f"✓ Downloaded: {output_path}")
return True
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
print(f"✗ Download failed: {e}")
return False
wait_time = 60 * (attempt + 1)
print(f"Attempt {attempt + 1} failed. Retrying in {wait_time}s...")
time.sleep(wait_time)
return False
# Usage
success = download_with_retry(
"550e8400-e29b-41d4-a716-446655440000",
"subtitle.srt"
)
4. Descarga y Extracción de Archivos ZIP¶
import requests
import zipfile
import io
import os
def download_and_extract(subtitle_id, output_dir="subtitles"):
"""Download subtitle and extract if it's a ZIP archive."""
headers = {"Authorization": "Bearer {TU_CLAVE_API}"}
url = f"https://subx-api.duckdns.org/api/subtitles/{subtitle_id}/download"
response = requests.get(url, headers=headers)
response.raise_for_status()
# Get filename
filename = "subtitle.srt"
cd = response.headers.get('content-disposition', '')
if 'filename=' in cd:
import re
match = re.search(r'filename="(.+)"', cd)
if match:
filename = match.group(1)
os.makedirs(output_dir, exist_ok=True)
# Check if ZIP file
if filename.endswith('.zip'):
print(f"Extracting ZIP archive: {filename}")
with zipfile.ZipFile(io.BytesIO(response.content)) as zf:
# Extract all files
zf.extractall(output_dir)
extracted = zf.namelist()
print(f"✓ Extracted {len(extracted)} files:")
for file in extracted:
print(f" - {file}")
return extracted
else:
# Not a ZIP - save directly
filepath = os.path.join(output_dir, filename)
with open(filepath, 'wb') as f:
f.write(response.content)
print(f"✓ Downloaded: {filename}")
return [filename]
# Usage
files = download_and_extract("550e8400-e29b-41d4-a716-446655440000")
Límite de Tasa y Seguimiento de Uso¶
- Cada descarga incrementa tu contador de uso de la API
- Las descargas se rastrean por clave de API para monitoreo
- Se aplican límites de tasa estándar (ver Guía de Límites de Tasa)
- Las descargas cacheadas aún cuentan hacia tu uso
Mejores Prácticas¶
✅ Hacer¶
- Cachear descargas localmente para evitar llamadas repetidas a la API
- Verificar extensión de archivo antes de procesar (manejar archivos ZIP apropiadamente)
- Implementar lógica de reintento para errores 502 (fuente temporalmente no disponible)
- Usar seguimiento de progreso para mejor experiencia de usuario con archivos grandes
- Verificar integridad del archivo después de descargar (verificar tamaño de archivo, intentar parsear)
❌ No Hacer¶
- No descargar el mismo archivo repetidamente - cachealo localmente
- No ignorar errores - maneja 404, 400, y 502 apropiadamente
- No asumir formato - siempre verifica la extensión del archivo
- No omitir configuración de timeout - archivos grandes pueden tomar tiempo
Consideraciones de Tamaño de Archivo¶
Tamaños típicos de archivos de subtítulos: - Archivos SRT: 50-200 KB - Archivos ASS/SSA: 100-500 KB - Archivos ZIP: Varía (100 KB - 5 MB)
Configura timeouts apropiados basados en el tamaño del archivo y velocidad de conexión:
# For typical subtitle files
response = requests.get(url, headers=headers, timeout=30)
# For potentially large ZIP files
response = requests.get(url, headers=headers, timeout=60)
Notas¶
- El contador de descargas se incrementa en cada solicitud, incluso para archivos cacheados
- Los archivos se almacenan con deduplicación SHA256 (archivos idénticos comparten almacenamiento)
- La API maneja automáticamente la compresión (gzip, brotli) para transferencias más rápidas
- El nombre de archivo original se preserva en el header
Content-Disposition - Las descargas cuentan tanto hacia el uso de la API como hacia las estadísticas de descarga
Próximos Pasos¶
- Buscar Subtítulos - Encuentra subtítulos para descargar
- Obtener Subtítulo - Verifica detalles del subtítulo antes de descargar
- Manejo de Errores - Maneja errores de descarga con gracia