# 原声配乐

> GET /3/movie/{movie_id}/soundtracks 返回电影或剧集的原声专辑，含来源、名称、艺人、发行日期、链接和曲目列表。用 append_to_response=soundtracks 并入详情。

## 接口

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

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

响应:

```json
{
  "id": 155,
  "results": [
    {
      "source": "musicbrainz",
      "album": "The Dark Knight",
      "artist": "Hans Zimmer & James Newton Howard",
      "released": "2008-07-15",
      "url": "https://musicbrainz.org/release/814b2f7b-c27c-451e-abac-a316d2e6cea9",
      "track_count": 14,
      "tracks": [
        {
          "disc": 1,
          "number": 1,
          "title": "Why So Serious?",
          "artist": "Hans Zimmer",
          "duration_ms": 554000
        },
        {
          "disc": 1,
          "number": 2,
          "title": "I'm Not a Hero",
          "artist": "Hans Zimmer",
          "duration_ms": 395000
        },
        {
          "disc": 1,
          "number": 3,
          "title": "Harvey Two-Face",
          "artist": "James Newton Howard",
          "duration_ms": 377000
        }
      ]
    }
  ]
}
```

## 获取电影的原声配乐

`GET /3/movie/{movie_id}/soundtracks` 返回一部电影的原声专辑。响应带有电影 `id` 和 `results` 数组。每个条目是一张专辑，包含其详情和 `tracks` 列表。

示例加载《黑暗骑士》（ID 155），打印专辑信息和每首曲目一行。

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

```js
const res = await fetch('https://api.amodb.dev/3/movie/155/soundtracks', {
  headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` },
});
if (!res.ok) throw new Error(`amodb returned ${res.status}`);
const { results } = await res.json();
if (results.length === 0) console.log('no soundtrack album known');
for (const album of results) {
  console.log(`${album.album} by ${album.artist} (${album.track_count} tracks)`);
  for (const t of album.tracks) {
    console.log(`${t.number}. ${t.title} by ${t.artist}`);
  }
}
```

```python
import os
import requests

r = requests.get(
    "https://api.amodb.dev/3/movie/155/soundtracks",
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
r.raise_for_status()
results = r.json()["results"]
if not results:
    print("no soundtrack album known")
for album in results:
    print(f"{album['album']} by {album['artist']} ({album['track_count']} tracks)")
    for t in album["tracks"]:
        print(f"{t['number']}. {t['title']} by {t['artist']}")
```

```php
<?php
$ch = curl_init('https://api.amodb.dev/3/movie/155/soundtracks');
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");
}
$results = json_decode($body, true)['results'];
if (!$results) {
    echo 'no soundtrack album known', PHP_EOL;
}
foreach ($results as $album) {
    echo $album['album'], ' by ', $album['artist'] ?? 'unknown', ' (', $album['track_count'], ' tracks)', PHP_EOL;
    foreach ($album['tracks'] as $t) {
        echo $t['number'], '. ', $t['title'], ' by ', $t['artist'], PHP_EOL;
    }
}
```

```go
package main

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

type Track struct {
	Disc       int    `json:"disc"`
	Number     int    `json:"number"`
	Title      string `json:"title"`
	Artist     string `json:"artist"`
	DurationMs int    `json:"duration_ms"`
}

type Album struct {
	Source     string  `json:"source"`
	Album      string  `json:"album"`
	Artist     string  `json:"artist"`
	Released   string  `json:"released"`
	URL        string  `json:"url"`
	TrackCount int     `json:"track_count"`
	Tracks     []Track `json:"tracks"`
}

func main() {
	req, _ := http.NewRequest("GET", "https://api.amodb.dev/3/movie/155/soundtracks", 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 body struct {
		Results []Album `json:"results"`
	}
	if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
		panic(err)
	}
	if len(body.Results) == 0 {
		fmt.Println("no soundtrack album known")
	}
	for _, a := range body.Results {
		fmt.Printf("%s by %s (%d tracks)\n", a.Album, a.Artist, a.TrackCount)
		for _, t := range a.Tracks {
			fmt.Printf("%d. %s by %s\n", t.Number, t.Title, t.Artist)
		}
	}
}
```

### 参数

| 参数         | 位置 | 必填  | 说明                                        |
| ---------- | -- | --- | ----------------------------------------- |
| `movie_id` | 路径 | 是   | 数字形式的电影 ID，例如 `155`。                      |
| `api_key`  | 查询 | 二选一 | 你的密钥。使用它，或使用 `Authorization: Bearer` 请求头。 |

### 响应字段

| 字段                               | 类型            | 说明                                           |
| -------------------------------- | ------------- | -------------------------------------------- |
| `id`                             | integer       | 电影 ID。使用 `append_to_response` 时不存在。          |
| `results`                        | array         | 专辑。没有已知专辑时为空。目前最多一张。                         |
| `results[].source`               | string        | 专辑的来源：`musicbrainz` 或 `deezer`。              |
| `results[].album`                | string        | 专辑标题。                                        |
| `results[].artist`               | string 或 null | 专辑艺人，例如 `Hans Zimmer & James Newton Howard`。 |
| `results[].released`             | string 或 null | 发行日期，格式为 `YYYY-MM-DD`。                       |
| `results[].url`                  | string 或 null | 指向来源处专辑页面的链接。                                |
| `results[].track_count`          | integer       | 专辑的曲目数。                                      |
| `results[].tracks`               | array         | 按专辑顺序排列的曲目。                                  |
| `results[].tracks[].disc`        | integer       | 碟号，从 1 开始。                                   |
| `results[].tracks[].number`      | integer       | 该碟上的曲目序号。                                    |
| `results[].tracks[].title`       | string        | 曲目标题。                                        |
| `results[].tracks[].artist`      | string        | 曲目艺人。                                        |
| `results[].tracks[].duration_ms` | integer       | 时长，单位为毫秒。                                    |

请按名称读取你用到的键，并跳过其他键。

### 示例响应

```json
{
  "id": 155,
  "results": [
    {
      "source": "musicbrainz",
      "album": "The Dark Knight",
      "artist": "Hans Zimmer & James Newton Howard",
      "released": "2008-07-15",
      "url": "https://musicbrainz.org/release/814b2f7b-c27c-451e-abac-a316d2e6cea9",
      "track_count": 14,
      "tracks": [
        { "disc": 1, "number": 1, "title": "Why So Serious?", "artist": "Hans Zimmer", "duration_ms": 554000 },
        { "disc": 1, "number": 2, "title": "I'm Not a Hero", "artist": "Hans Zimmer", "duration_ms": 395000 },
        { "disc": 1, "number": 3, "title": "Harvey Two-Face", "artist": "James Newton Howard", "duration_ms": 377000 }
      ]
    }
  ]
}
```

曲目列表缩减为 14 首中的 3 首。没有已知专辑的作品，例如《搏击俱乐部》（ID 550），会返回空列表：

```json
{
  "id": 550,
  "results": []
}
```

## 获取剧集的原声配乐

`GET /3/tv/{tv_id}/soundtracks` 对剧集返回同样的结构。传入剧集 ID，例如 `1396`。《绝命毒师》没有已知专辑，所以其 `results` 数组为空。

| 方法和路径                               | 返回内容                         | 参数         |
| ----------------------------------- | ---------------------------- | ---------- |
| `GET /movie/{movie_id}/soundtracks` | `id` 和 `results`             | `movie_id` |
| `GET /tv/{tv_id}/soundtracks`       | `id` 和 `results`             | `tv_id`    |
| `append_to_response=soundtracks`    | `soundtracks` 下相同的 `results` | 添加到电影或剧集详情 |

要在加载电影页面时一并获得专辑，请把它追加到详情中：

```bash
curl -sf "https://api.amodb.dev/3/movie/155?append_to_response=credits,soundtracks" \
  -H "Authorization: Bearer $AMODB_KEY"
```

追加的数据块位于记录的 `soundtracks` 键下。它包含 `results`，自己没有 `id`。它只算一个请求。

## 制作电影中的音乐列表

按 `disc` 和 `number` 排序，显示标题和艺人，当 `results` 为空时隐藏该区块。下面的函数返回纯文本行，可以放进列表元素。

```js
function clock(ms) {
  const s = Math.round(ms / 1000);
  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
}

function musicLines(results) {
  const album = results[0];
  if (!album) return [];
  return [...album.tracks]
    .sort((a, b) => a.disc - b.disc || a.number - b.number)
    .map((t) => `${t.title} by ${t.artist} (${clock(t.duration_ms)})`);
}
```

一些不需要额外数据的界面想法：

| 想法   | 做法                                               |
| ---- | ------------------------------------------------ |
| 编号列表 | 用 `number` 作为列表序号，有多张碟时按 `disc` 分组。              |
| 专辑链接 | `url` 不为 null 时，把专辑名称链接到它。                       |
| 总时长  | 把各曲目的 `duration_ms` 相加。                          |
| 搭配情绪 | 把列表放在同一作品的[情绪指数](https://amodb.dev/zh/moods/)旁边。 |

## 错误

每次失败都像 TMDB 一样返回 `success` 为 false 和数字 `status_code`。

| HTTP | status_code | 何时出现                          |
| ---- | ----------- | ----------------------------- |
| 401  | 7           | 密钥缺失或错误。                      |
| 402  | 36          | 套餐周期已结束。                      |
| 429  | 25          | 超过每分钟请求数。请等待 `Retry-After` 秒。 |

原声配乐接口对未知 ID 不返回 404，而是返回 200 和空的 `results` 数组。追加到详情时，未知 ID 由详情调用返回 404。所有代码见[错误](https://amodb.dev/zh/errors/)，响应头见[速率限制](https://amodb.dev/zh/rate-limits/)。

## 实用说明

- 与其他详情数据一样，原声配乐在边缘缓存 6 小时。更短的客户端缓存没有好处。
- 在你本来就要加载的详情页上使用 `append_to_response=soundtracks`。它能为你每分钟 20、40 或 120 个请求节省一个请求。
- 不要要求专辑必须存在。`results` 为空时隐藏音乐区块。
- 有些专辑来自 MusicBrainz，其他来自 Deezer。电影 27205（《盗梦空间》）有一张 Deezer 专辑。结构相同，所以只有要展示来源时才需要检查 `source`。
- 曲目和艺人名称是普通字符串，可以包含任何字符。请以 UTF-8 存储，并在放进 HTML 时转义。
- 同一作品的预告片见[视频与预告片](https://amodb.dev/zh/videos-trailers/)。

## 常见问题

### 一条原声记录包含什么？

一个专辑对象，含来源、专辑名称、艺人、发行日期、链接和 track_count。其 tracks 数组列出每首曲目的 disc、number、title、artist 和 duration_ms。响应还带有作品 ID。

### 我能得到音频文件或流媒体链接吗？

没有音频。url 字段链接到 MusicBrainz 或 Deezer 上的专辑页面，也可能为 null。要让用户收听，请在你已在用的音乐服务中搜索该专辑，并从你自己的界面打开。

### 每部作品都有原声配乐吗？

没有。许多作品（包括《搏击俱乐部》）没有已知专辑，响应是空的 results 数组。目前一部作品最多有一张专辑。请把音乐区块设计成没有内容时自动隐藏。

### 能获取剧集的原声配乐吗？

可以。用剧集 ID 调用 GET /3/tv/{tv_id}/soundtracks。响应结构与电影接口相同，所以一个解析器即可覆盖两者。

## 相关页面

- [电影详情 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 获取剧集的季、电视网和下一集，以及内容分级和观看平台。
- [情绪指数](https://amodb.dev/zh/moods/): 电影情绪 API，每部作品提供 0 到 10 的情绪指数。读取电影或剧集的情绪，用 append_to_response=moods 合并，构建徽章或筛选。
- [视频与预告片 API](https://amodb.dev/zh/videos-trailers/): 兼容 TMDB v3 路径的电影视频 API：列出电影或剧集的预告片、先导预告、片段和花絮，再在你的代码里按类型和语言筛选。
- [电影与电视剧评分](https://amodb.dev/zh/ratings/): 一次调用返回作品的 IMDb、Kinopoisk、Metacritic、Rotten Tomatoes 和 TMDB 评分，来源未知时为 null。
- [快速开始](https://amodb.dev/zh/quickstart/): 发出第一次 amodb 调用：获取密钥，用 curl、JavaScript、Python、PHP 或 Go 请求电影详情，再加载海报。

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