FFmpeg でカット編集する際、カット位置のズレまたは再エンコードどちらかが必ず発生


- FFmpeg でカット編集をする際、カット位置のズレまたは再エンコードが必ず発生 (仕様)
- 多くの場合問題ないはず
  - どちらを回避するかは FFmpeg に指定されたオプションの内容から
    ユーザーの意図に沿った方を適切に決定するような仕様になっているため
- この記事は厳密な部分が気になる人向け
- この記事は上の方ほど要約的な内容で、下に行くほど根拠・エビデンスを示す内容

なお、この記事は `-ss` を入力パラメーター (`-i` の前) として指定している前提で解説
`-ss` の指定位置と FFmpeg の振る舞いについては次の記事を参照:

[FFmpeg で keyframe でカットする方法 #ffmpeg - Qiita]

## 要約

### なぜカット編集するとカット位置のズレまたは再エンコードが発生するのか

FFmpeg の `-ss` オプションで `position` を指定してカット編集をする際に
`position` から最初の `keyframe` までの間をどのように処理するかを決定する必要があるため

#### 図解

```plaintext
■: keyframe
□: non-keyframe
-: no frame
                    keyframe から始まらないこの区間をどうするか
                    |<---------------------------------->|
□--□--□--□--■--□--□--□--□--□--□--□--□--□--□--□--□--□--□--■--□--□--□--□--□--□--□--□--□--□--□--□--□--
                    ^ position
```



### 「やりたいこと」から「何をすればよいのか」の逆引き表

やりたいこと|方法|FFmpeg での<br/>振る舞いの呼び方
---|---|---
`-ss` で指定した `position` より前の<br/>`keyframe` からになってもよいので<br/>stream の再 encode を避けたい場合|stream の `codec` を `copy` に設定<br/>または<br/>`-noaccurate_seek` を設定|`no accurate`<br/>または<br/>`inaccurate`
再 encode が発生してもよいので<br/>`-ss` で指定した `position` 丁度から<br/>カットしたい場合|`-accurate_seek` を設定|`accurate`

- 上記のいずれの方法も取らなかった場合は `accurate` の振る舞い
  - (FFmpeg 2.1 以降のデフォルトの振る舞いは `accurate` のため)
- stream の `codec` を `copy` に設定しつつ `-accurate_seek` を設定すると
  `no accurate` の振る舞いになる模様 (根拠は後述)

#### 図解

##### `no accurate` の場合

```plaintext
■: keyframe
□: non-keyframe
-: no frame

            v seek point
            |<-------
□--□--□--□--■--□--□--□--□--□--□--□--□--□--□--□--□--□--□--■--□--□--□--□--□--□--□--□--□--□--□--□--□--
                    ^ position
```

##### `accurate` の場合

```plaintext
■: keyframe
□: non-keyframe
-: no frame

                              1. decode
            |<----------------------------------------->|
                    v seek point
              2.破棄            3. 再 encode
            |<----->|<--------------------------------->|
□--□--□--□--■--□--□--□--□--□--□--□--□--□--□--□--□--□--□--■--□--□--□--□--□--□--□--□--□--□--□--□--□--
                    ^ position
```

## 用語の説明

### `position` と `seek point` の意味

公式ドキュメントなどに明確な解説はないので、自分が FFmpeg を使った経験からの解釈で解説

次の通り:

用語|日本語|意味
---|---|---
`position`|`位置`|ffmpeg の `-ss` オプションなどで自由に指定できる timestamp
`seek point`|`シークポイント`|動画ファイルの各フレームの離散的な timestamp

### `position` と `seek point` の違い

`position` は自由に設定可能
一方で `seek point` は入力ストリームの中でフレームが存在する時刻のみ
そのため、FFmpeg は `position` で指定された直前の `seek point` から実際の処理を実行

### ファイルの形式によっては `seek point` は `keyframe` という情報も

[Answer: command line - FFmpeg -ss option and keyframes - Super User]

一部を和訳すると次の通り:

> `seek point` は `keyframe` を意味しますか？
> 
> 一般的には。
> 一部の形式 (MP4 など) には、各 stream の `seek point` のリストが含まれています。
> FFmpeg はその packet header を解析し、
> `keyframe` の index を組み立てようとします。
> その他の形式では使用可能な metadata がないので、FFmpeg は seek に失敗します。

### seek における `accurate` (正確) とは「時間的に正確な位置に seek すること」

seek に関して `accurate`, `inaccurate`, `noaccurate` という用語が使われるので、
そちらも解説
ソースは公式ドキュメントの `-ss position (input/output)` の解説:
[ffmpeg Documentation]

用語|日本語|意味|`seek point` ~ `position`<br/>の間の処理
---|---|---|---
`accurate`|`正確`|時間的に正確な位置に seek すること|デコードされ破棄
`inaccurate`<br/>`noaccurate`|`不正確`|時間的に不正確な位置に seek すること<br/>(直前の `keyframe` に対してのみ seek)|保持

### オプション: `-accurate_seek (input)`, `-noaccurate_seek (input)` の解説

次の 2 つのドキュメントを要約して解説:

[ffmpeg Documentation]

> This option enables or disables accurate seeking in input files with the `-ss` option.
> It is enabled by default, so seeking is accurate when transcoding.
> Use `-noaccurate_seek` to disable it, which may be useful
> e.g. when copying some streams and transcoding the others.

[Seeking – FFmpeg]
> Previous behavior
> (seek only to nearest preceding `keyframe`, despite inaccuracy)
> can be restored with `-noaccurate_seek`.

#### `-noaccurate_seek (input)`

- FFmpeg 2.1 より前のデフォルト動作
- 不正確であっても、直前の `keyframe` に対してのみ seek する
- 一部の stream を `copy` し、他の stream をトランスコードするときに便利


#### `-accurate_seek (input)`

- `-ss` オプションを使用した入力ファイルの正確なシークを有効化
- デフォルトで有効になっているため、トランスコード時のシークは正確
- `-noaccurate_seek` を使用して無効化可能

## 公式ドキュメントの記述

[ffmpeg Documentation]

> `-ss position (input/output)`
> 
> When used as an input option (before `-i`), seeks in this input file to `position`. 
> Note that in most formats it is not possible to seek exactly,
> so `ffmpeg` will seek to the closest `seek point` before `position`.
> When transcoding and `-accurate_seek` is enabled (the default),
> this extra segment between the `seek point` and `position` will be decoded and discarded.
> When doing stream copy or when `-noaccurate_seek` is used, it will be preserved.

日本語に翻訳すると概ね次の通り:

> 入力オプションとして (`-i` の前で) 使用する場合、この入力ファイル内で位置を seek します。
> ほとんどの形式では正確に seek できないため、
> `ffmpeg` は `position` の直前の `seek point` に seek します。
> トランスコーディング時に `-accurate_seek` が有効になっている場合 (デフォルト)、
> `seek point` と `position` の間のこの余分なセグメントはデコードされ、破棄されます。
> stream の `copy` を実行する場合、または `-noaccurate_seek` が使用されている場合は、
> 保持されます。

## stream の `codec` を `copy` に設定しつつ `-accurate_seek` を設定した場合、`no accurate` の振る舞いになる根拠

実際に手元で何度か条件を変えて検証した結果より

何度条件を変えてカット編集しても、
FFprobe でカット編集後の動画ファイルの冒頭のフレームが次のようになることを確認:

- `keyframe` から開始
- 次の `keyframe` までの frame 数がその次の `keyframe` までの frame 数と一致

[FFmpeg で keyframe でカットする方法 #ffmpeg - Qiita]: https://qiita.com/road-master/items/290f3fd83810d45cd4f7
[ffmpeg Documentation]: https://www.ffmpeg.org/ffmpeg-all.html
[Answer: command line - FFmpeg -ss option and keyframes - Super User]: https://superuser.com/a/1847657/2166172
[Seeking – FFmpeg]: https://trac.ffmpeg.org/wiki/Seeking