API de imágenes y host de imágenes

Los endpoints de imágenes listan los pósters, fondos, logos, perfiles y fotogramas de un título como rutas de archivo. Luego cargas cada archivo desde img.amodb.dev en un tamaño fijo, sin clave y con 30 días de caché perimetral. Las rutas y el JSON coinciden con TMDB v3, así que tu código de imágenes funciona tras cambiar la URL base.

Actualizado el Ver en Markdown

Obtener las imágenes de una película

GET /3/movie/{movie_id}/images lista todas las imágenes conocidas de una película en tres arrays: backdrops, logos y posters. Cada entrada tiene un file_path, su tamaño en píxeles y una etiqueta de idioma. El archivo en sí no está en el JSON. Lo cargas desde el host de imágenes en un segundo paso.

El ejemplo pide imágenes en inglés y sin texto, elige el primer póster en inglés y luego obtiene ese archivo con 500 píxeles de ancho.

bashjspyphpgo
curl -s "https://api.amodb.dev/3/movie/550/images?include_image_language=en,null" \
  -H "Authorization: Bearer $AMODB_KEY"

curl -s -o poster.jpg -w "%{http_code}\n" \
  "https://img.amodb.dev/t/p/w500/jSziioSwPVrOy9Yow3XhWIBDjq1.jpg"
const url = 'https://api.amodb.dev/3/movie/550/images?include_image_language=en,null';
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` } });
if (!res.ok) throw new Error(`amodb returned ${res.status}`);
const { posters } = await res.json();
if (posters.length === 0) throw new Error('no posters');
const poster = posters.find((p) => p.iso_639_1 === 'en') ?? posters[0];
const posterUrl = `https://img.amodb.dev/t/p/w500${poster.file_path}`;
const img = await fetch(posterUrl);
console.log(img.status, img.headers.get('content-type'), posterUrl);
import os
import requests

r = requests.get(
    "https://api.amodb.dev/3/movie/550/images",
    params={"include_image_language": "en,null"},
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
r.raise_for_status()
posters = r.json()["posters"]
if not posters:
    raise SystemExit("no posters")
poster = next((p for p in posters if p["iso_639_1"] == "en"), posters[0])
poster_url = "https://img.amodb.dev/t/p/w500" + poster["file_path"]
img = requests.get(poster_url, timeout=10)
print(img.status_code, img.headers.get("content-type"), poster_url)
<?php
$url = 'https://api.amodb.dev/3/movie/550/images?include_image_language=en,null';
$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");
}
$posters = json_decode($body, true)['posters'];
if (!$posters) {
    throw new RuntimeException('no posters');
}
$poster = $posters[0];
foreach ($posters as $p) {
    if ($p['iso_639_1'] === 'en') {
        $poster = $p;
        break;
    }
}
$posterUrl = 'https://img.amodb.dev/t/p/w500' . $poster['file_path'];
$img = curl_init($posterUrl);
curl_setopt_array($img, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10]);
curl_exec($img);
echo curl_getinfo($img, CURLINFO_HTTP_CODE), ' ', curl_getinfo($img, CURLINFO_CONTENT_TYPE), ' ', $posterUrl, PHP_EOL;
curl_close($img);
package main

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

type Images struct {
	Posters []struct {
		FilePath string `json:"file_path"`
		Language string `json:"iso_639_1"`
	} `json:"posters"`
}

func main() {
	url := "https://api.amodb.dev/3/movie/550/images?include_image_language=en,null"
	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 images Images
	if err := json.NewDecoder(res.Body).Decode(&images); err != nil || len(images.Posters) == 0 {
		panic("no posters")
	}
	poster := images.Posters[0]
	for _, p := range images.Posters {
		if p.Language == "en" {
			poster = p
			break
		}
	}
	posterURL := "https://img.amodb.dev/t/p/w500" + poster.FilePath
	img, err := client.Get(posterURL)
	if err != nil {
		panic(err)
	}
	defer img.Body.Close()
	fmt.Println(img.StatusCode, img.Header.Get("Content-Type"), posterURL)
}

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.
  • include_image_languageconsulta
    Códigos ISO 639-1 separados por comas. Añade null para incluir imágenes sin texto, por ejemplo en,null. Solo llegan los idiomas indicados. Sin él obtienes todos los idiomas.
  • languageconsulta
    Se acepta pero se ignora en este endpoint. Usa include_image_language para elegir idiomas.

Campos de la respuesta

CampoTipoNotas
identeroEl id de la película.
backdrops, logos, postersarrayUn array por tipo de imagen, en ese orden de claves. Cada uno puede estar vacío.
file_pathtextoEmpieza con una barra. Añádelo a un tamaño en el host de imágenes. Los pósters y las imágenes de fondo son .jpg, los logos son .png o .svg.
width, heightenteroTamaño en píxeles del archivo original. 0 cuando el tamaño no se conoce.
aspect_rationúmeroAncho dividido por alto. Los pósters rondan 0.667 y las imágenes de fondo 1.778. 0 cuando el tamaño no se conoce.
iso_639_1texto o nullIdioma del texto de la imagen. null significa sin texto.
vote_average, vote_countnúmero, enteroSiempre 0 en las imágenes de películas y series, así que no sirven para ordenar imágenes. Conserva el orden que devuelve la API.

Ejemplo de respuesta

Se muestran dos entradas por array.

JSON
{
  "id": 550,
  "backdrops": [
    {
      "aspect_ratio": 1.778,
      "height": 1440,
      "iso_639_1": null,
      "file_path": "/c6OLXfKAk5BKeR6broC8pYiCquX.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 2560
    },
    {
      "aspect_ratio": 1.778,
      "height": 1080,
      "iso_639_1": "en",
      "file_path": "/b9HyPoxwxjxkWEUL5ErZdhApQe2.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1920
    }
  ],
  "logos": [
    {
      "aspect_ratio": 4.638,
      "height": 389,
      "iso_639_1": "en",
      "file_path": "/7Uqhv24pGJs4Ns31NoOPWFJGWNG.png",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1804
    },
    {
      "aspect_ratio": 0.848,
      "height": 1295,
      "iso_639_1": "en",
      "file_path": "/4XkF0Rf7gvSfea8fYLFbU5tmuJw.png",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1098
    }
  ],
  "posters": [
    {
      "aspect_ratio": 0.667,
      "height": 3000,
      "iso_639_1": "en",
      "file_path": "/jSziioSwPVrOy9Yow3XhWIBDjq1.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 2000
    },
    {
      "aspect_ratio": 0,
      "height": 0,
      "iso_639_1": null,
      "file_path": "/qkrH7rjYKU4KOwwIl6wF6H3ISvm.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 0
    }
  ]
}

El segundo póster es una entrada cuyo tamaño no se conoce. El archivo se carga igualmente.

Construir la URL de una imagen

Une tres partes: https://img.amodb.dev/t/p/, un segmento de tamaño y el file_path. La ruta ya empieza con una barra, así que https://img.amodb.dev/t/p/w500 más /jSziioSwPVrOy9Yow3XhWIBDjq1.jpg es una URL completa.

Un tamaño es w más un ancho en píxeles, h más un alto en píxeles, o original para el archivo guardado. El host sirve diez tamaños: w92, w154, w185, w300, w342, w500, w780, w1280, h632 y original. Cualquier otro tamaño, como w400, responde 404. /3/configuration lista los tamaños que encajan con cada tipo de imagen.

TipoCampo de configuraciónTamaños
Pósterposter_sizesw92, w154, w185, w342, w500, w780, original
Imagen de fondobackdrop_sizesw300, w780, w1280, original
Logologo_sizesw92, w154, w185, w300, w500, original
Perfilprofile_sizesw185, h632, original
Fotogramastill_sizesw92, w185, w300, original

El host no comprueba el tipo, así que un póster también se carga en w1280. Quédate con los tamaños listados para cada tipo, para que tu código coincida con la configuración y con cualquier cliente de TMDB.

El host de imágenes no necesita clave, y la caché perimetral guarda cada archivo 30 días. Como no se envía clave, las cargas de imágenes no cuentan para tus solicitudes por minuto. Las respuestas llevan Access-Control-Allow-Origin: *, así que un navegador puede cargarlas desde cualquier página. Un tamaño desconocido o un archivo desconocido responde 404 con un cuerpo JSON.

Imágenes responsivas con srcset

Elige el tamaño más pequeño que aún cubra el ancho mostrado multiplicado por la densidad de pantalla. Un póster que se muestra con 171 píxeles CSS de ancho necesita w342 en una pantalla 2x. Esta función auxiliar construye un srcset a partir de una ruta guardada.

JavaScript
const WIDTHS = [185, 342, 500, 780];

function posterSrcset(filePath) {
  return WIDTHS.map((w) => `https://img.amodb.dev/t/p/w${w}${filePath} ${w}w`).join(', ');
}

const path = '/jSziioSwPVrOy9Yow3XhWIBDjq1.jpg';
console.log(`<img src="https://img.amodb.dev/t/p/w342${path}" srcset="${posterSrcset(path)}" sizes="(max-width: 600px) 50vw, 342px" alt="Fight Club poster">`);

Otros endpoints de imágenes

Todas las rutas de abajo usan la base https://api.amodb.dev/3 y devuelven los arrays que indica la segunda columna.

Método y rutaArrays devueltosParámetros
GET /tv/{series_id}/imagesbackdrops, logos, postersinclude_image_language
GET /tv/{series_id}/season/{season_number}/imagesposters, siempre vacíoninguno
GET /tv/{series_id}/season/{season_number}/episode/{episode_number}/imagesstills, una entrada con width y height en 0ninguno
GET /person/{person_id}/imagesprofilesninguno

Para películas y series también puedes añadir append_to_response=images a una llamada de detalles. include_image_language funciona ahí también. El bloque añadido no tiene un id propio.

Los perfiles de personas son el único lugar donde vote_average lleva una puntuación real. El id de una respuesta de temporada o de episodio es el id de la temporada o del episodio. Consulta personas y series de TV para los registros principales.

amodb no ofrece rutas de imágenes para colecciones, productoras y cadenas. Sus registros ya contienen un poster_path, backdrop_path o logo_path, descritos en colecciones y productoras y cadenas de TV.

El endpoint de configuración

GET /3/configuration devuelve un bloque images con secure_base_url fijado en https://img.amodb.dev/t/p/, base_url (la forma http simple) y las listas de tamaños backdrop_sizes, logo_sizes, poster_sizes, profile_sizes y still_sizes. Léelo una vez al iniciar, usa la forma segura y construye las URL a partir de él en lugar de fijar los tamaños en el código. La caché perimetral lo guarda 24 horas.

Errores

HTTPstatus_codeCuándo
4017La clave falta o es incorrecta en una llamada JSON.
40236El periodo del plan ha terminado.
40434No hay ninguna persona, temporada o episodio con ese id, o hay un tamaño o un archivo desconocido en el host de imágenes.
40537Usaste un método de escritura en una ruta de imágenes.
42925Superaste tus solicitudes por minuto. Espera los segundos de Retry-After.

Un id de película o serie que no existe no es un error aquí: la ruta de imágenes responde 200 con arrays vacíos, mientras que la ruta de detalles responde 404. Consulta errores para ver la lista completa. Las solicitudes de archivos de imagen son cargas HTTP simples, así que comprueba el código de estado de la respuesta en lugar de analizar un cuerpo JSON.

Notas prácticas

  • Guarda file_path en tu base de datos y construye la URL al mostrar la imagen. Así, un tamaño o un host nuevo cuesta un solo cambio de código.
  • Pasa include_image_language con tu idioma de visualización más null, por ejemplo ru,null, para que no aparezcan pósters con texto en otros idiomas.
  • Un array vacío es un 200 normal. Recurre al poster_path del registro de detalles de película.
  • Usa w1280 para imágenes de fondo a todo el ancho y w780 para tarjetas. original es el archivo guardado a resolución completa y es mucho más grande que w780.
  • Las copias de la caché perimetral viven 30 días y se comparten, así que un archivo que pidió un cliente se sirve desde la caché al siguiente.