API зображень і хост зображень

Ендпоінти зображень повертають шляхи до постерів, фонів, логотипів, фото персон і кадрів проєкту. Кожен файл ви завантажуєте з img.amodb.dev у фіксованому розмірі, без ключа і з кешем CDN на 30 днів. Шляхи та JSON збігаються з TMDB v3, тож наявний код для зображень працює після заміни базового URL.

Оновлено Відкрити в Markdown

Зображення фільму

GET /3/movie/{movie_id}/images перелічує всі відомі зображення одного фільму у вигляді трьох масивів: backdrops, logos та posters. Кожен запис має file_path, розмір у пікселях і мовний тег. Самого файлу в JSON немає. Ви завантажуєте його з хоста зображень другим кроком.

Приклад запитує англомовні зображення та зображення без тексту, бере перший англійський постер, а потім завантажує цей файл завширшки 500 пікселів.

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

Параметри

  • movie_idшляхобов'язковий
    Числовий id фільму, наприклад 550.
  • api_keyqueryодне з двох
    Ваш ключ. Використовуйте його або заголовок Authorization: Bearer.
  • include_image_languagequery
    Коди ISO 639-1 через кому. Додайте null, щоб включити зображення без тексту, наприклад en,null. Повертаються лише вказані мови. Без нього ви отримаєте всі мови.
  • languagequery
    Приймається, але на цьому ендпоінті ігнорується. Мови вибирайте через include_image_language.

Поля відповіді

ПолеТипПримітки
idintegerid фільму.
backdrops, logos, postersarrayОдин масив для кожного виду зображень, у такому порядку ключів. Кожен може бути порожнім.
file_pathstringПочинається зі скісної риски. Допишіть його до розміру на хості зображень. Постери й фони мають розширення .jpg, логотипи .png або .svg.
width, heightintegerРозмір оригінального файлу в пікселях. 0, якщо розмір невідомий.
aspect_rationumberШирина, поділена на висоту. Постери близькі до 0.667, фони до 1.778. 0, якщо розмір невідомий.
iso_639_1string або nullМова тексту на зображенні. null означає, що тексту немає.
vote_average, vote_countnumber, integerДля зображень фільмів і серіалів завжди 0, тож за ними не можна ранжувати зображення. Зберігайте порядок, у якому їх повертає API.

Приклад відповіді

Показано по два записи в кожному масиві.

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

Другий постер показує запис із невідомим розміром. Файл усе одно завантажується.

Побудова URL зображення

З'єднайте три частини: https://img.amodb.dev/t/p/, сегмент розміру та file_path. Шлях уже починається зі скісної риски, тож https://img.amodb.dev/t/p/w500 плюс /jSziioSwPVrOy9Yow3XhWIBDjq1.jpg дає повну URL-адресу.

Розмір це w і ширина в пікселях, h і висота в пікселях або original для збереженого файлу. Хост віддає десять розмірів: w92, w154, w185, w300, w342, w500, w780, w1280, h632 та original. Будь-який інший розмір, наприклад w400, повертає 404. /3/configuration перелічує розміри, які підходять для кожного виду зображень.

ВидПоле конфігураціїРозміри
Постерposter_sizesw92, w154, w185, w342, w500, w780, original
Фон (backdrop)backdrop_sizesw300, w780, w1280, original
Логотипlogo_sizesw92, w154, w185, w300, w500, original
Профільprofile_sizesw185, h632, original
Кадрstill_sizesw92, w185, w300, original

Хост не перевіряє вид зображення, тож постер також завантажується у w1280. Тримайтеся розмірів, наведених для кожного виду, щоб ваш код відповідав конфігурації та будь-якому клієнту TMDB.

Хост зображень не потребує ключа, а CDN зберігає кожен файл 30 днів. Оскільки ключ не надсилається, завантаження зображень не витрачають ваш ліміт запитів на хвилину. Відповіді містять Access-Control-Allow-Origin: *, тож браузер може завантажувати їх з будь-якої сторінки. Невідомий розмір або невідомий файл повертає 404 з тілом JSON.

Адаптивні зображення з srcset

Вибирайте найменший розмір, який усе ще покриває ширину відображення, помножену на щільність екрана. Постеру завширшки 171 CSS-піксель на екрані 2x потрібен w342. Цей помічник будує srcset зі збереженого шляху.

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

Інші ендпоінти зображень

Усі шляхи нижче використовують основу https://api.amodb.dev/3 і повертають масиви, названі в другому стовпці.

Метод і шляхЯкі масиви повертаєПараметри
GET /tv/{series_id}/imagesbackdrops, logos, postersinclude_image_language
GET /tv/{series_id}/season/{season_number}/imagesposters, завжди порожнійнемає
GET /tv/{series_id}/season/{season_number}/episode/{episode_number}/imagesstills, один запис з width і height, що дорівнюють 0немає
GET /person/{person_id}/imagesprofilesнемає

Для фільмів і серіалів можна також додати append_to_response=images до виклику деталей. include_image_language там теж працює. Доданий блок не має власного id.

Профілі персон це єдине місце, де vote_average містить справжню оцінку. id у відповіді для сезону чи серії це id сезону чи серії. Батьківські записи дивіться на сторінках персони та серіали.

amodb не надає шляхів зображень для колекцій, компаній і мереж. Їхні записи вже містять один poster_path, backdrop_path або logo_path, описаний на сторінках колекції та компанії та телемережі.

Ендпоінт конфігурації

GET /3/configuration повертає блок images з secure_base_url, що дорівнює https://img.amodb.dev/t/p/, base_url (та сама адреса з простим http) і списки розмірів backdrop_sizes, logo_sizes, poster_sizes, profile_sizes та still_sizes. Прочитайте його один раз під час запуску, використовуйте захищену форму й будуйте URL з нього, а не зашивайте розміри в код. CDN кешує його на 24 години.

Помилки

HTTPstatus_codeКоли
4017Ключ відсутній або неправильний у JSON-виклику.
40236Термін дії тарифу закінчився.
40434Немає персони, сезону чи серії з таким id, або невідомий розмір чи файл на хості зображень.
40537Ви використали метод запису на шляху зображень.
42925Ви перевищили ліміт запитів на хвилину. Зачекайте стільки секунд, скільки вказано в Retry-After.

Неіснуючий id фільму чи серіалу тут не є помилкою: шлях зображень повертає 200 з порожніми масивами, а шлях деталей повертає 404. Повний список дивіться на сторінці помилки. Запити файлів зображень це прості HTTP-завантаження, тому перевіряйте код статусу відповіді, а не розбирайте тіло JSON.

Практичні поради

  • Зберігайте file_path у базі даних і будуйте URL під час відображення. Тоді новий розмір чи хост коштуватиме однієї зміни коду.
  • Передавайте include_image_language з мовою відображення плюс null, наприклад ru,null, щоб не з'являлися постери з іноземним текстом.
  • Порожній масив це нормальна відповідь 200. Використайте запасний варіант: poster_path із запису деталей фільму.
  • Використовуйте w1280 для фонів на всю ширину, а w780 для карток. original це збережений файл у повній роздільній здатності, і він набагато більший за w780.
  • Копії на CDN живуть 30 днів і є спільними, тож файл, який запитав один клієнт, наступному віддається з кешу.