FFmpeg で普通にメディアファイルを扱うと副音声などの stream が欠落

## 要約

- 手軽に対策するには `-map 0` を指定して全ての stream を出力
- 動画ファイルは program と stream で構成される構造
- 現時点 (2024-09-02) では、FFmpeg は入力ファイルの program 構造を完全に複製不能
- stream に含まれる次についてはこの記事では扱わないので、またの機会に解説:
  - metadata
  - packet, frame の PTS time

### 手軽な対策のコマンド

```bash
ffmpeg \
  -i <入力ファイル名> \
  -map 0 \
  -c copy \
  <出力ファイル名>
```

## 前提知識: 動画ファイルの構造

- 1 つの動画ファイルは複数の program を含むことが可能
- 1 つの program は複数の stream を含むことが可能
- program は stream を含まないことが可能
- 1 つの動画ファイルは program に含まれない stream を含むことが可能
- 1 つの動画ファイル内の stream はファイル内の全ての stream 間で順番を保持

### 確認方法

次のコマンドで出力される JSON データを解析することで確認可能:

```bash
ffprobe \
  -show_entries program=program_num:stream=index \
  -output_format json \
  -loglevel repeat+fatal \
  <動画ファイル名>
```

<details><summary>出力される JSON の例</summary>

```json
{
    "programs": [
        {
            "program_num": 23656,
            "streams": [
                {
                    "index": 1,
                    "side_data_list": [
                        {

                        }
                    ]
                },
                {
                    "index": 2
                },
                {
                    "index": 3
                },
                {
                    "index": 4
                },
                {
                    "index": 5
                },
                {
                    "index": 6
                }
            ]
        },
        {
            "program_num": 0,
            "streams": [

            ]
        }
    ],
    "stream_groups": [

    ],
    "streams": [
        {
            "index": 0
        },
        {
            "index": 1,
            "side_data_list": [
                {

                }
            ]
        },
        {
            "index": 2
        },
        {
            "index": 3
        },
        {
            "index": 4
        },
        {
            "index": 5
        },
        {
            "index": 6
        }
    ]
}
```

</details>

JSON の root キーの `streams` には、
program に含まれる stream と program に含まれない stream の両方が含まれることに注意

上記の JSON の場合、ファイル内の program と stream の構造は次の通り:

```plaintext
動画ファイル
  ├─ stream 0
  ├─ program 23656
  │    ├─ stream 1
  │    ├─ stream 2
  │    ├─ stream 3
  │    ├─ stream 4
  │    ├─ stream 5
  │    └─ stream 6
  └─ program 0
```

## FFmpeg はデフォルトでは入力ファイルの一部の stream のみ出力

FFmpeg はデフォルトでは:

- video, audio, 字幕の stream は入力ファイルから 1 つの stream のみ出力
- データや添付ファイルの stream は出力せず

[4.1.1 Automatic stream selection | ffmpeg Documentation]
> ビデオ: 最高解像度の stream
> オーディオ: 最も多くのチャンネルを持つ stream
> 字幕: 最初に見つかった字幕 stream ですが、注意点があります
>   出力形式のデフォルトの字幕エンコーダは
>   テキ​​ストベースまたは画像ベースのいずれかであり、
>   同じタイプの字幕ストリームのみが選択されます
>   同じタイプの複数のストリームが均等にレートする場合、
>   インデックスが最も低いストリームが選択されます
> データまたは添付ファイルのストリームは自動的に選択されず、
> -map を使用してのみ含めることができます

### 手軽な対策: `-map` オプションの使用

入力ファイルが 1 つの場合は `-map 0` で全ての stream を出力

[Simple examples | Map – FFmpeg]
> `-map 0` From input index #0 (the 1st input) select all streams.

## FFmpeg は program と stream の構造を完全に複製不能

単純に `-map 0` を指定すると、FFmpeg は入力ファイルの program 構造を維持せず、
新たに `program_num`: 1 で program を作成し、
すべての stream をその program に含めるように動作する模様:

[#4525 (Multi-program structure of input is not preserved in MPEG-TS output) – FFmpeg]

先ほどの JSON の例で説明すると、
`-map 0` を指定した場合の出力ファイルの program と stream の構造は次の通り:

```plaintext
入力ファイル            出力ファイル
    ├─ stream 0            └─ program 1
    ├─ program 23656            ├─ 入力ファイルの stream 0
    │    ├─ stream 1            ├─ 入力ファイルの stream 1
    │    ├─ stream 2            ├─ 入力ファイルの stream 2
    │    ├─ stream 3            ├─ 入力ファイルの stream 3
    │    ├─ stream 4            ├─ 入力ファイルの stream 4
    │    ├─ stream 5            ├─ 入力ファイルの stream 5
    │    └─ stream 6            └─ 入力ファイルの stream 6
    └─ program 0
```

program の構造を維持するためには `-program` オプションを使い、
`program_num` と、その program に含める stream の index を指定する必要あり

参考: 
[FFMPEG - build transport stream with multiple programs - Video Production Stack Exchange]

コマンドの例:

```bash
ffmpeg \
  -i <入力ファイル名> \
  -map 0:0 \
  -map p:23656 \
  -program program_num=23656:st=1:st=2:st=3:st=4:st=5:st=6 \
  -c copy \
  <出力ファイル名>
```

上記のコマンドを実行した場合の出力ファイルの program と stream の構造は次の通り:

```plaintext
入力ファイル            出力ファイル
    ├─ stream 0            ├─ program 23656
    ├─ program 23656       │    ├─ 入力ファイルの stream 1
    │    ├─ stream 1       │    ├─ 入力ファイルの stream 2
    │    ├─ stream 2       │    ├─ 入力ファイルの stream 3
    │    ├─ stream 3       │    ├─ 入力ファイルの stream 4
    │    ├─ stream 4       │    ├─ 入力ファイルの stream 5
    │    ├─ stream 5       │    └─ 入力ファイルの stream 6
    │    └─ stream 6       └─ 入力ファイルの stream 0
    └─ program 0
```

上記の対応を行っても、次の内容は維持することは不可能:

- stream を含まない program
- stream の順序

なお、この他に metadata という情報や、
stream 内の各 packet, frame の PTS time に関しても見ておく必要があるが、
この記事では扱わないので、またの機会に解説

### stream を含まない program

FFmpeg の `-program` オプションで作成不能であるため

### stream の順序

FFmpeg の `-program` オプションを使用した場合、
stream の順序はコマンドのオプションで指定した順にならないため

#### `-program` オプションを使用しなければ stream の順序は -map で指定した順

[Order | Map – FFmpeg]
> Order
> The order of -map options is important:
> 
> - -map order determines the stream order in the output(s).
> - Mapping is applied in order. This is useful when using negative mapping.
> 
> Examples
> 
> Tip:
> You can add -c copy to many of these examples to enable stream copy mode.
> This is useful if you want to mux only and avoid encoding.

## `-map 0` を設定した場合 `-copy_unknown` は不要

検証したところ、`-map 0` を設定した場合、
`-copy_unknown` を設定しなくても
FFMpeg がサポートしていない `codec` の stream を含む全ての stream が出力されることを確認済

念のため、`-map 0 -copy_unknown` を設定しても動作に問題なし

[5.11 Advanced options | ffmpeg Documentation]
> -copy_unknown
> Allow input streams with unknown type to be copied instead of failing
> if copying such streams is attempted.

[4.1.1 Automatic stream selection | ffmpeg Documentation]: https://ffmpeg.org/ffmpeg.html#Automatic-stream-selection
[Simple examples | Map – FFmpeg]: https://trac.ffmpeg.org/wiki/Map#Simpleexamples
[#4525 (Multi-program structure of input is not preserved in MPEG-TS output) – FFmpeg]: https://trac.ffmpeg.org/ticket/4525
[FFMPEG - build transport stream with multiple programs - Video Production Stack Exchange]: https://video.stackexchange.com/a/23978
[Order | Map – FFmpeg]: https://trac.ffmpeg.org/wiki/Map#Order
[5.11 Advanced options | ffmpeg Documentation]: https://ffmpeg.org/ffmpeg-all.html#Advanced-options