API de vídeos y tráilers

Los endpoints de vídeos listan los tráilers, teasers, clips y reportajes asociados a una película o serie. Cada entrada trae el servidor, una clave de vídeo, un tipo, una marca oficial, un tamaño y una fecha de publicación. Las rutas de temporada y episodio existen pero no devuelven vídeos. Filtra por idioma y tipo en tu código.

Actualizado el Ver en Markdown

Obtén los vídeos de una película

GET /3/movie/{movie_id}/videos devuelve los registros de vídeo asociados a una película. Un registro son solo metadatos: un nombre, el servidor que sirve el vídeo, la clave del vídeo en ese servidor, un tipo y unas pocas marcas. amodb no devuelve archivos de vídeo.

El ejemplo imprime el primer tráiler oficial en inglés y, si no existe, cualquier tráiler en inglés.

bashjspyphpgo
curl -s "https://api.amodb.dev/3/movie/550/videos" \
  -H "Authorization: Bearer $AMODB_KEY"
const url = 'https://api.amodb.dev/3/movie/550/videos';
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` } });
if (!res.ok) throw new Error(`amodb returned ${res.status}`);
const { results } = await res.json();
const trailers = results.filter((v) => v.type === 'Trailer' && v.iso_639_1 === 'en');
const pick = trailers.find((v) => v.official) ?? trailers[0];
console.log(pick ? `${pick.name} (${pick.site}, key ${pick.key})` : 'no trailer');
import os
import requests

r = requests.get(
    "https://api.amodb.dev/3/movie/550/videos",
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
r.raise_for_status()
trailers = [v for v in r.json()["results"] if v["type"] == "Trailer" and v["iso_639_1"] == "en"]
official = [v for v in trailers if v["official"]]
pick = (official or trailers or [None])[0]
print(f"{pick['name']} ({pick['site']}, key {pick['key']})" if pick else "no trailer")
<?php
$url = 'https://api.amodb.dev/3/movie/550/videos';
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 10,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('AMODB_KEY')],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
    throw new RuntimeException("amodb returned $status");
}
$trailers = array_values(array_filter(
    json_decode($body, true)['results'],
    fn($v) => $v['type'] === 'Trailer' && $v['iso_639_1'] === 'en'
));
$official = array_values(array_filter($trailers, fn($v) => $v['official']));
$pick = $official[0] ?? $trailers[0] ?? null;
echo $pick ? "{$pick['name']} ({$pick['site']}, key {$pick['key']})" : 'no trailer', PHP_EOL;
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

type Videos struct {
	Results []struct {
		Name     string `json:"name"`
		Key      string `json:"key"`
		Site     string `json:"site"`
		Type     string `json:"type"`
		Language string `json:"iso_639_1"`
		Official bool   `json:"official"`
	} `json:"results"`
}

func main() {
	url := "https://api.amodb.dev/3/movie/550/videos"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("AMODB_KEY"))
	client := &http.Client{Timeout: 10 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		panic(res.Status)
	}
	var v Videos
	if err := json.NewDecoder(res.Body).Decode(&v); err != nil {
		panic(err)
	}
	for _, r := range v.Results {
		if r.Type == "Trailer" && r.Language == "en" && r.Official {
			fmt.Printf("%s (%s, key %s)\n", r.Name, r.Site, r.Key)
			return
		}
	}
	fmt.Println("no official trailer")
}

Parámetros

  • movie_idrutaobligatorio
    ID numérico de la película, por ejemplo 550.
  • api_keyconsultauno de los dos
    Tu clave. Usa esta o el encabezado Authorization: Bearer.
  • language, include_video_languageconsulta
    Se aceptan pero se ignoran. La respuesta siempre contiene todos los vídeos del título. Filtra por iso_639_1 en tu código.

Campos de la respuesta

CampoTipoNotas
identeroEl ID de la película.
resultsarrayRegistros de vídeo, sin ordenar por tipo ni por fecha. Vacío cuando no se conoce ninguno.
results[].iso_639_1, results[].iso_3166_1textoIdioma y país del vídeo, por ejemplo en y US.
results[].nametextoTítulo del vídeo, por ejemplo #TBT Trailer.
results[].keytextoID del vídeo en ese servidor.
results[].sitetextoNombre del servidor de vídeo. Hoy siempre YouTube.
results[].sizeenteroResolución vertical. Hoy siempre 1080.
results[].typetextoTrailer, Teaser, Clip, Featurette, Behind the Scenes, Bloopers, Opening Credits o Recap.
results[].officialbooleanoTrue cuando lo publicó el estudio o el titular de los derechos.
results[].published_attextoMarca de tiempo ISO 8601, con o sin milisegundos.
results[].idtextoID del registro de vídeo. Igual a key.

Ejemplo de respuesta

Se muestran tres de los 19 registros de la película 550.

JSON
{
  "id": 550,
  "results": [
    {
      "iso_639_1": "en",
      "iso_3166_1": "US",
      "name": "Theatrical Trailer (HD Fan Remaster)",
      "key": "6JnN1DmbqoU",
      "site": "YouTube",
      "size": 1080,
      "type": "Trailer",
      "official": false,
      "published_at": "2015-02-26T03:19:25Z",
      "id": "6JnN1DmbqoU"
    },
    {
      "iso_639_1": "en",
      "iso_3166_1": "US",
      "name": "#TBT Trailer",
      "key": "BdJKm16Co6M",
      "site": "YouTube",
      "size": 1080,
      "type": "Trailer",
      "official": true,
      "published_at": "2014-10-02T19:20:22Z",
      "id": "BdJKm16Co6M"
    },
    {
      "iso_639_1": "en",
      "iso_3166_1": "US",
      "name": "Mark Kermode introduces Fight Club | Film4 Interview",
      "key": "tiWCNtGDGEY",
      "site": "YouTube",
      "size": 1080,
      "type": "Featurette",
      "official": true,
      "published_at": "2015-05-08T15:59:05Z",
      "id": "tiWCNtGDGEY"
    }
  ]
}

Tipos de vídeo

El campo type te dice qué es un vídeo. Compáralo de forma exacta, con las mayúsculas indicadas.

TipoPara qué usarlo
TrailerLa promoción principal. Muéstralo en una página de detalle.
TeaserUn anuncio corto que llega antes del tráiler.
ClipUna escena tomada de la película o del episodio terminado.
FeaturetteUna pieza breve sobre cómo se hizo el título.
Behind the ScenesImágenes del rodaje y entrevistas.
BloopersTomas falsas y recopilaciones de gags.
Opening CreditsLa secuencia de títulos de una serie.
RecapUn resumen de episodios anteriores.

Con el tiempo pueden aparecer otros valores, así que trata un type desconocido como un extra genérico y no falles por él.

Elige el vídeo adecuado

Usa el mismo orden para todos los títulos. Prueba tus idiomas en orden. Dentro de un idioma, primero van los tráilers oficiales, luego cualquier tráiler, después los teasers y por último los clips. Dentro de un grupo ordena por published_at: el orden ascendente da el primer tráiler y el descendente da el más reciente.

JavaScript
function pickVideo(results, langs = ['en']) {
  const order = ['Trailer', 'Teaser', 'Clip'];
  for (const lang of langs) {
    for (const type of order) {
      const group = results
        .filter((v) => v.iso_639_1 === lang && v.type === type)
        .sort((a, b) => Number(b.official) - Number(a.official) || Date.parse(a.published_at) - Date.parse(b.published_at));
      if (group.length > 0) return group[0];
    }
  }
  return null;
}

// Russian first, English as the fallback.
// const video = pickVideo(results, ['ru', 'en']);

Idiomas

language y include_video_language no cambian la respuesta, así que una sola llamada te da todos los vídeos del título. Cada registro trae iso_639_1 e iso_3166_1. La película 550 tiene vídeos etiquetados como fr, en, es, pt, it, cs, pl y ru. Filtra por iso_639_1 en tu código como se muestra arriba, y luego indica al usuario en qué idioma va a escuchar el vídeo. Un título puede no tener ningún vídeo en el idioma que quieres, así que conserva un respaldo en inglés o en cualquier idioma. Los 14 idiomas que se sirven están en idiomas.

Otros endpoints de vídeo

Las rutas siguientes devuelven la misma estructura de id y results.

Método y rutaDevuelve
GET /tv/{series_id}/videosVídeos de una serie.
GET /tv/{series_id}/season/{season_number}/videosSiempre un array results vacío.
GET /tv/{series_id}/season/{season_number}/episode/{episode_number}/videosSiempre un array results vacío.

También puedes añadir append_to_response=videos a una llamada de detalles de película o serie. El bloque añadido contiene results y no id. Las páginas de series y temporadas se describen en series de TV y temporadas y episodios.

Errores

HTTPstatus_codeCuándo
4017Falta la clave o es incorrecta.
40236El periodo del plan ha terminado.
40434No existe la temporada o el episodio con ese id, o el id no es un número.
40537Usaste un método de escritura en una ruta de vídeos.
42925Superaste tus solicitudes por minuto. Espera los segundos indicados en Retry-After.

Un id de película o serie que no existe no es un error aquí: la ruta de vídeos responde 200 con "results": [], igual que un título conocido sin vídeos. La ruta de detalles responde 404 ante un id desconocido. Más información en errores y límites de solicitudes.

Notas prácticas

  • Guarda con el título el site y la key elegidos, no una dirección completa, para poder cambiar el reproductor más adelante.
  • Las listas de vídeos siguen la caché de detalles de 6 horas en la caché perimetral. Una caché de cliente de unas pocas horas encaja con ella.
  • size es 1080 en todos los registros hoy, así que no te dice qué clip es ligero. Deja que el reproductor elija la calidad.
  • No des por hecho que existe un tráiler. Oculta el botón de reproducir cuando tu función de selección devuelva null.
  • El orden de results no significa nada. Ordena en tu código.
  • Para los archivos de póster y de imagen de fondo, consulta imágenes. Para la vista del caso de uso, consulta la API de tráilers de películas.