# 视频与预告片 API

> 视频接口列出电影或剧集的预告片、先导预告、片段和花絮。每条记录给出视频平台名称、视频 key、类型、是否官方、分辨率和发布日期。季与单集路径存在但不返回视频。请在你自己的代码里按语言和类型筛选。

## 接口

`GET /3/movie/{movie_id}/videos`

```bash
curl "https://api.amodb.dev/3/movie/550/videos" \
  -H "Authorization: Bearer $AMODB_KEY"
```

响应:

```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": "20th Anniversary Trailer",
      "key": "dfeUzm6KF4g",
      "site": "YouTube",
      "size": 1080,
      "type": "Trailer",
      "official": true,
      "published_at": "2019-10-15T18:59:47Z",
      "id": "dfeUzm6KF4g"
    }
  ]
}
```

## 获取电影的视频

`GET /3/movie/{movie_id}/videos` 返回挂在一部电影上的视频记录。记录只有元数据：名称、提供该视频的平台、视频在该平台上的 key、类型和几个标记。amodb 不返回视频文件。

示例打印第一条英文官方预告片，找不到时回退到任意英文预告片。

```bash
curl -s "https://api.amodb.dev/3/movie/550/videos" \
  -H "Authorization: Bearer $AMODB_KEY"
```

```js
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');
```

```python
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
<?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;
```

```go
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_key`                           | 查询 | 二选一 | 你的密钥。使用它，或使用 `Authorization: Bearer` 请求头。      |
| `language`、`include_video_language` | 查询 | 否   | 接受但被忽略。响应始终包含该作品的全部视频。请在你的代码里按 `iso_639_1` 筛选。 |

### 响应字段

| 字段                                           | 类型      | 说明                                                                                                 |
| -------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------- |
| `id`                                         | integer | 电影 ID。                                                                                             |
| `results`                                    | array   | 视频记录，不按类型或日期排序。没有已知视频时为空。                                                                          |
| `results[].iso_639_1`、`results[].iso_3166_1` | string  | 视频的语言和国家，例如 `en` 和 `US`。                                                                           |
| `results[].name`                             | string  | 视频标题，例如 `#TBT Trailer`。                                                                            |
| `results[].key`                              | string  | 视频在该平台上的 ID。                                                                                       |
| `results[].site`                             | string  | 视频平台名称。目前始终是 `YouTube`。                                                                            |
| `results[].size`                             | integer | 垂直分辨率。目前始终为 `1080`。                                                                                |
| `results[].type`                             | string  | `Trailer`、`Teaser`、`Clip`、`Featurette`、`Behind the Scenes`、`Bloopers`、`Opening Credits` 或 `Recap`。 |
| `results[].official`                         | boolean | 由制片方或版权方发布时为 true。                                                                                 |
| `results[].published_at`                     | string  | ISO 8601 时间戳，带或不带毫秒。                                                                               |
| `results[].id`                               | string  | 视频记录的 ID。与 `key` 相同。                                                                               |

### 示例响应

这里显示了电影 550 的 19 条记录中的 3 条。

```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` 排序：升序得到第一条预告片，降序得到最新的一条。

```js
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 种提供语言见[语言](https://amodb.dev/zh/languages/)。

## 其他视频接口

下面的路径返回相同的 `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`。剧集和季的页面分别在[电视剧](https://amodb.dev/zh/tv-series/)和[季与单集](https://amodb.dev/zh/tv-seasons-episodes/)中说明。

## 错误

| HTTP | status_code | 何时出现                           |
| ---- | ----------- | ------------------------------ |
| 401  | 7           | 密钥缺失或错误。                       |
| 402  | 36          | 套餐周期已结束。                       |
| 404  | 34          | 没有该 ID 的季或单集，或 ID 不是数字。        |
| 405  | 37          | 你对视频路径使用了写入方法。                 |
| 429  | 25          | 超过了每分钟请求数。请等待 `Retry-After` 秒。 |

不存在的电影或剧集 ID 在这里不算错误：视频路径返回 200 和 `"results": []`，与没有视频的已知作品相同。详情路径对未知 ID 返回 404。更多见[错误](https://amodb.dev/zh/errors/)和[速率限制](https://amodb.dev/zh/rate-limits/)。

## 实用说明

- 把选中的 `site` 和 `key` 与作品一起保存，而不是完整地址，这样以后可以更换播放器。
- 视频列表遵循边缘 6 小时的详情缓存。几小时的客户端缓存与之匹配。
- 目前每条记录的 `size` 都是 1080，所以它无法告诉你哪个片段更轻。让播放器自己选择画质。
- 不要指望一定存在某条预告片。当你的挑选函数返回 null 时，隐藏播放按钮。
- `results` 的顺序没有意义。请在你的代码里排序。
- 海报和背景图文件见[图片](https://amodb.dev/zh/images-api/)。用例视角见[电影预告片 API](https://amodb.dev/zh/movie-trailer-api/)。

## 常见问题

### 如何只获取官方预告片？

在 results 数组中筛选 type 等于 Trailer 且 official 为 true 的条目。如果还剩多条，按 published_at 排序，取最早或最新的一条。

### 如何获取我所用语言的视频？

该接口返回作品的全部视频，并忽略 language 和 include_video_language。请在你的代码里按 iso_639_1 筛选 results，例如先取 ru，再以 en 作为回退。许多作品在某种语言下没有视频。

### 如何播放响应中的视频？

用 site 字段选择播放器，用 key 字段作为该平台上的视频 ID。目前返回的数据中 site 始终是 YouTube。请按该平台自己的规则构建嵌入或观看地址。amodb 返回的是元数据，不是视频文件。

### 单集和季有自己的视频吗？

没有。季和单集的视频路径返回 200 和空的 results 数组。只有电影和电视剧带有视频记录。

## 相关页面

- [电影详情 API](https://amodb.dev/zh/movies/): 路径和 JSON 与 TMDB v3 相同的电影详情 API：按 ID 取电影，用 append_to_response 加入演职员和评分，支持 14 种语言。
- [电视剧 API](https://amodb.dev/zh/tv-series/): 路径和 JSON 与 TMDB v3 相同的电视剧 API。按 ID 获取剧集的季、电视网和下一集，以及内容分级和观看平台。
- [电视剧季与单集 API](https://amodb.dev/zh/tv-seasons-episodes/): 路径和 JSON 与 TMDB v3 相同的电视剧季与单集 API。读取一季及其全部单集，或读取单集的播出日期、时长和剧照。
- [电影预告片 API](https://amodb.dev/zh/movie-trailer-api/): 基于 TMDB v3 videos 接口的电影预告片 API。按类型、官方标记和语言代码对预告片、先导片和片段排序，每部作品只需一次请求。
- [图片 API 与图片主机](https://amodb.dev/zh/images-api/): 采用 TMDB v3 路径的电影图片 API：列出作品的海报、背景图、标志和剧照，再从 img.amodb.dev 以 10 种尺寸无密钥加载。
- [快速开始](https://amodb.dev/zh/quickstart/): 发出第一次 amodb 调用：获取密钥，用 curl、JavaScript、Python、PHP 或 Go 请求电影详情，再加载海报。

更新于: 2026-10-18. Source: https://amodb.dev/zh/videos-trailers/
