API видео и трейлеров

Эндпоинты видео перечисляют трейлеры, тизеры, клипы и фичереты, привязанные к фильму или сериалу. Каждая запись содержит название хостинга, ключ видео, тип, признак официальности, размер и дату публикации. Пути сезонов и серий существуют, но видео не возвращают. Фильтруйте по языку и типу в собственном коде.

Обновлено Открыть в Markdown

Видео фильма

GET /3/movie/{movie_id}/videos возвращает видеозаписи, привязанные к одному фильму. Запись содержит только метаданные: название, хостинг, на котором лежит видео, ключ видео на этом хостинге, тип и несколько флагов. amodb не возвращает видеофайлы.

Пример выводит первый официальный английский трейлер, а при его отсутствии любой английский трейлер.

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

Параметры

  • movie_idпутьобязателен
    Числовой id фильма, например 550.
  • api_keyqueryодно из двух
    Ваш ключ. Используйте его или заголовок Authorization: Bearer.
  • language, include_video_languagequery
    Принимаются, но игнорируются. Ответ всегда содержит все видео названия. Фильтруйте по iso_639_1 в своем коде.

Поля ответа

ПолеТипПримечания
idцелое числоId фильма.
resultsмассивВидеозаписи без сортировки по типу и дате. Пуст, если видео неизвестны.
results[].iso_639_1, results[].iso_3166_1строкаЯзык и страна видео, например en и US.
results[].nameстрокаНазвание видео, например #TBT Trailer.
results[].keyстрокаId видео на этом хостинге.
results[].siteстрокаНазвание видеохостинга. Сегодня всегда YouTube.
results[].sizeцелое числоВертикальное разрешение. Сегодня всегда 1080.
results[].typeстрокаTrailer, Teaser, Clip, Featurette, Behind the Scenes, Bloopers, Opening Credits или Recap.
results[].officialлогическое значениеРавно true, если видео опубликовала студия или правообладатель.
results[].published_atстрокаВременная метка ISO 8601, с миллисекундами или без них.
results[].idстрокаId видеозаписи. Равен key.

Пример ответа

Показаны три из 19 записей фильма 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"
    }
  ]
}

Типы видео

Поле type говорит, что это за видео. Сравнивайте его точно, с показанными заглавными буквами.

ТипДля чего использовать
TrailerОсновной промо-ролик. Показывайте его на странице деталей.
TeaserКороткий анонс, выходящий перед трейлером.
ClipСцена из готового фильма или серии.
FeaturetteКороткий материал о том, как создавался фильм.
Behind the ScenesКадры со съемочной площадки и интервью.
BloopersНеудачные дубли и подборки курьезов.
Opening CreditsЗаставка сериала.
RecapКраткое содержание предыдущих серий.

Со временем могут появиться другие значения, поэтому неизвестный type считайте обычным дополнительным материалом и не падайте с ошибкой.

Выбор подходящего видео

Используйте один и тот же порядок для всех названий. Пробуйте свои языки по очереди. Внутри одного языка сначала идут официальные трейлеры, затем любые трейлеры, затем тизеры, затем клипы. Внутри группы сортируйте по published_at: по возрастанию получите первый трейлер, по убыванию самый свежий.

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']);

Языки

language и include_video_language не меняют ответ, поэтому один вызов дает все видео названия. Каждая запись содержит iso_639_1 и iso_3166_1. У фильма 550 есть видео с метками fr, en, es, pt, it, cs, pl и ru. Фильтруйте по iso_639_1 в своем коде, как показано выше, и сообщите пользователю, на каком языке он услышит видео. У названия может не быть видео на нужном языке, поэтому держите запасной вариант: английский или любой язык. 14 обслуживаемых языков перечислены на странице языки.

Другие эндпоинты видео

Пути ниже возвращают те же id и results.

Метод и путьЧто возвращает
GET /tv/{series_id}/videosВидео сериала.
GET /tv/{series_id}/season/{season_number}/videosВсегда пустой массив results.
GET /tv/{series_id}/season/{season_number}/episode/{episode_number}/videosВсегда пустой массив results.

Можно также добавить append_to_response=videos к вызову деталей фильма или сериала. Добавленный блок содержит results и не содержит id. Сериалы и сезоны описаны в разделах сериалы и сезоны и серии.

Ошибки

HTTPstatus_codeКогда
4017Ключ отсутствует или неверен.
40236Срок действия тарифа истек.
40434Нет сезона или серии с таким id, либо id не число.
40537Вы использовали метод записи на пути видео.
42925Вы превысили число запросов в минуту. Подождите число секунд из Retry-After.

Несуществующий id фильма или сериала здесь не ошибка: путь видео отвечает 200 с "results": [], так же как для известного названия без видео. Путь деталей отвечает 404 на неизвестный id. Подробнее: ошибки и лимиты запросов.

Практические заметки

  • Храните выбранные site и key вместе с названием, а не полный адрес, чтобы позже можно было сменить плеер.
  • Списки видео следуют кэшу деталей на CDN в 6 часов. Клиентский кэш в несколько часов с ним согласуется.
  • Сегодня size равен 1080 у каждой записи, поэтому по нему нельзя понять, какой ролик легче. Пусть качество выбирает плеер.
  • Не рассчитывайте, что трейлер обязательно есть. Скрывайте кнопку воспроизведения, когда функция выбора возвращает null.
  • Порядок в results ничего не означает. Сортируйте в своем коде.
  • Файлы постеров и фонов (backdrop) смотрите в разделе API изображений. Взгляд с точки зрения сценария использования смотрите в API трейлеров фильмов.