API obrazów i host obrazów

Endpointy obrazów wypisują plakaty, tła, loga, profile i kadry tytułu jako ścieżki plików. Każdy plik ładujesz potem z img.amodb.dev w stałym rozmiarze, bez klucza i z cache na edge przez 30 dni. Ścieżki i JSON odpowiadają TMDB v3, więc istniejący kod do obrazów działa po zmianie bazowego URL.

Zaktualizowano Zobacz jako Markdown

Pobieranie obrazów filmu

GET /3/movie/{movie_id}/images wypisuje wszystkie znane obrazy jednego filmu jako trzy tablice: backdrops, logos i posters. Każda pozycja ma file_path, rozmiar w pikselach i tag języka. Samego pliku w JSON nie ma. Ładujesz go z hosta obrazów w drugim kroku.

Przykład prosi o obrazy angielskie i bez tekstu, wybiera pierwszy angielski plakat, a potem pobiera ten plik o szerokości 500 pikseli.

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)
}

Parametry

  • movie_idpathwymagany
    Numeryczne ID filmu, na przykład 550.
  • api_keyqueryjeden z dwóch
    Twój klucz. Użyj go albo nagłówka Authorization: Bearer.
  • include_image_languagequery
    Kody ISO 639-1 oddzielone przecinkami. Dodaj null, aby uwzględnić obrazy bez tekstu, na przykład en,null. Wracają tylko wymienione języki. Bez niego dostajesz każdy język.
  • languagequery
    Przyjmowany, ale ignorowany w tym endpoincie. Do wyboru języków użyj include_image_language.

Pola odpowiedzi

PoleTypUwagi
idintegerID filmu.
backdrops, logos, postersarrayJedna tablica na rodzaj obrazu, w tej kolejności kluczy. Każda może być pusta.
file_pathstringZaczyna się od ukośnika. Dołącz go do rozmiaru na hoście obrazów. Plakaty i tła to .jpg, loga to .png lub .svg.
width, heightintegerRozmiar oryginalnego pliku w pikselach. 0, gdy rozmiar nie jest znany.
aspect_rationumberSzerokość podzielona przez wysokość. Plakaty mają około 0.667, tła około 1.778. 0, gdy rozmiar nie jest znany.
iso_639_1string lub nullJęzyk tekstu na obrazie. null oznacza brak tekstu.
vote_average, vote_countnumber, integerZawsze 0 dla obrazów filmów i seriali, więc nie da się nimi porządkować obrazów. Zachowaj kolejność zwracaną przez API.

Przykładowa odpowiedź

Pokazano po dwie pozycje w każdej tablicy.

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
    }
  ]
}

Drugi plakat pokazuje pozycję, której rozmiar nie jest znany. Plik mimo to się ładuje.

Budowanie URL obrazu

Połącz trzy części: https://img.amodb.dev/t/p/, segment rozmiaru i file_path. Ścieżka już zaczyna się od ukośnika, więc https://img.amodb.dev/t/p/w500 plus /jSziioSwPVrOy9Yow3XhWIBDjq1.jpg to kompletny URL.

Rozmiar to w plus szerokość w pikselach, h plus wysokość w pikselach albo original dla zapisanego pliku. Host obsługuje dziesięć rozmiarów: w92, w154, w185, w300, w342, w500, w780, w1280, h632 i original. Każdy inny rozmiar, na przykład w400, odpowiada 404. /3/configuration wymienia rozmiary pasujące do każdego rodzaju obrazu.

RodzajPole konfiguracjiRozmiary
Plakatposter_sizesw92, w154, w185, w342, w500, w780, original
Tłobackdrop_sizesw300, w780, w1280, original
Logologo_sizesw92, w154, w185, w300, w500, original
Profilprofile_sizesw185, h632, original
Kadrstill_sizesw92, w185, w300, original

Host nie sprawdza rodzaju, więc plakat ładuje się też w w1280. Trzymaj się rozmiarów wymienionych dla danego rodzaju, aby twój kod zgadzał się z konfiguracją i każdym klientem TMDB.

Host obrazów nie wymaga klucza, a edge trzyma każdy plik przez 30 dni. Ponieważ klucz nie jest wysyłany, ładowanie obrazów nie zużywa twoich żądań na minutę. Odpowiedzi niosą Access-Control-Allow-Origin: *, więc przeglądarka może je ładować z dowolnej strony. Nieznany rozmiar lub nieznany plik odpowiada 404 z treścią JSON.

Responsywne obrazy ze srcset

Wybierz najmniejszy rozmiar, który nadal pokrywa wyświetlaną szerokość razy gęstość ekranu. Plakat wyświetlany na szerokość 171 pikseli CSS potrzebuje w342 na ekranie 2x. Ta funkcja pomocnicza buduje srcset z zapisanej ścieżki.

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">`);

Inne endpointy obrazów

Wszystkie poniższe ścieżki używają bazy https://api.amodb.dev/3 i zwracają tablice wymienione w drugiej kolumnie.

Metoda i ścieżkaZwracane tabliceParametry
GET /tv/{series_id}/imagesbackdrops, logos, postersinclude_image_language
GET /tv/{series_id}/season/{season_number}/imagesposters, zawsze pustabrak
GET /tv/{series_id}/season/{season_number}/episode/{episode_number}/imagesstills, jedna pozycja z width i height ustawionymi na 0brak
GET /person/{person_id}/imagesprofilesbrak

W filmach i serialach możesz też dodać append_to_response=images do wywołania szczegółów. include_image_language działa tam również. Dołączony blok nie ma własnego id.

Profile osób to jedyne miejsce, w którym vote_average niesie prawdziwą ocenę. id w odpowiedzi sezonu lub odcinka to ID sezonu lub odcinka. Rekordy nadrzędne opisują strony osoby i seriale.

amodb nie udostępnia ścieżek images dla kolekcji, firm i sieci. Ich rekordy zawierają już jedno poster_path, backdrop_path lub logo_path, opisane na stronach kolekcje i firmy i sieci telewizyjne.

Endpoint konfiguracji

GET /3/configuration zwraca blok images z secure_base_url ustawionym na https://img.amodb.dev/t/p/, base_url (zwykła forma http) oraz listami rozmiarów backdrop_sizes, logo_sizes, poster_sizes, profile_sizes i still_sizes. Odczytaj go raz przy starcie, użyj formy bezpiecznej i buduj z niego URL-e zamiast wpisywać rozmiary na stałe. Edge trzyma go w cache przez 24 godziny.

Błędy

HTTPstatus_codeKiedy
4017Brakuje klucza lub jest błędny w wywołaniu JSON.
40236Okres planu dobiegł końca.
40434Brak osoby, sezonu lub odcinka o tym ID albo nieznany rozmiar lub plik na hoście obrazów.
40537Użyto metody zapisu na ścieżce images.
42925Przekroczono limit żądań na minutę. Poczekaj tyle sekund, ile podaje Retry-After.

Nieistniejące ID filmu lub serialu nie jest tu błędem: ścieżka images odpowiada 200 z pustymi tablicami, podczas gdy ścieżka szczegółów odpowiada 404. Pełną listę zobacz w błędy. Żądania plików obrazów to zwykłe ładowanie HTTP, więc sprawdzaj kod statusu odpowiedzi zamiast parsować treść JSON.

Praktyczne uwagi

  • Zapisuj file_path w bazie danych i buduj URL przy renderowaniu. Nowy rozmiar lub host kosztuje wtedy jedną zmianę kodu.
  • Przekaż include_image_language z językiem wyświetlania plus null, na przykład ru,null, by nie pojawiały się plakaty z obcym tekstem.
  • Pusta tablica to zwykłe 200. Użyj zapasowego poster_path z rekordu szczegółów filmu.
  • Użyj w1280 dla teł hero na całą szerokość i w780 dla kart. original to zapisany plik w pełnej rozdzielczości i jest dużo większy niż w780.
  • Kopie na edge żyją 30 dni i są współdzielone, więc plik zażądany przez jednego klienta jest serwowany z cache następnemu.