FFmpegで特定のタイムスタンプの前に最も近いキーフレームのタイムスタンプを取得する方法は?


18

高速かつ正確なFFmpegシークコマンドが必要です。これを見つけまし

解決策は、-ss入力(高速シーク)と出力(正確なシーク)の両方に適用することです。しかし、入力シークが正確でない場合、シーク位置が正確であることをどのようにして確認できますか?


例:00:03:00にシークしたい場合、コマンドは次のようになります。

ffmpeg -ss 00:02:30 -i <INPUT> ... -ss 00:00:30 <OUTPUT>

最初の人-ssはそうではなく00:02:30、他の場所を探します00:02:31。そして、2番目のシークを適用した後、最終結果は00:03:01- 私たちが望むものではありません。あれは正しいですか?

最初の人はどこ-ssを目指しますか?最も近いキーフレームを探し00:02:30ますか?

もしそうなら、ここに私の考えがあります-私が間違っているなら私を修正してください:最初のシークの後、結果のタイムスタンプを取得し(この例00:02:31では:)、適切な時間でこのシークで2番目のシークを適用します00:00:29

質問:最初のシーク結果のタイムスタンプを取得するにはどうすればよいですか?

回答:


18

タイトルの質問に文字通り答えるには、次のコマンドでIフレームのリストを取得できます。

ffprobe -select_streams v -show_frames <INPUT> 

を追加して、これを必要な出力にさらに制限でき-show_entries frame=pkt_pts_time,pict_typeます。

特定のタイムスタンプに最も近い(後の)フレームを確認するには、キーフレームのすべてのタイムスタンプを見つける必要があります(例:)awk

最初に、検索する時間を定義します。たとえば、2:30mは150秒です。

ffprobe -select_streams v -show_frames -show_entries frame=pkt_pts_time,pict_type -v quiet in.mp4 | 
awk -F= ' 
  /pict_type=/ { if (index($2, "I")) { i=1; } else { i=0; } } 
  /pkt_pts_time/ { if (i && ($2 >= 150)) print $2; }  
' | head -n 1

たとえば、これはを返し150.400000ます。


-ssbefore を使用する場合-i、FFmpegはシークポイントののキーフレームを見つけ、シークポイントに到達するまで後続のすべてのフレームに負のPTS値を割り当てます。プレーヤーはデコードする必要がありますが、負のPTSを持つフレームは表示せず、ビデオは正確に開始する必要があります。

一部のプレーヤーはこれを適切に尊重せず、黒いビデオまたはゴミを表示します。この場合、上記のスクリプトを使用して、シークポイントののキーフレームのPTSを見つけ、それを使用してキーフレームからシークを開始できます。ただし、これは正確ではありません。

検索中に非常に正確になり、多くのプレーヤーとの互換性を維持したい場合は、ビデオを任意のポイントでカットしてから再エンコードできるロスレスのイントラ専用フォーマットに変換する必要があります。しかし、これは高速ではありません。


1
おかげで、私はビデオエディタを作成していませんが、ギャップを0.5秒未満にする必要がある正確なビデオシークが必要です。
ジャッケード

1
おそらくPTSを手探りすることができffprobeます。そうでない場合は、たとえばProRes 422、DNxHDなど、視覚的に無損失でイントラフレームのみの中間フォーマットが使用されます。または、HuffYUVなどのようなものを使用します。しかし、その後、もちろん「高速」の側面を失います。
slhck

私が言ったので、コマンドにはどのバージョンのffprobeを使用しましたかUnrecognized option 'select_streams'
jackode

2
あなたは近くにいました、select_streamsオプションは2012年10月追加されました。:)それなしでもできますが、オーディオフレームの情報も取得できます。
slhck

2
あなたの代わりにはawkで捨てます多くのもので、それは出力のみ必要な2つのフィールドをできるように、このffmpegの行を追加することができます注意:フレーム= pkt_pts_time、pict_type -show_entries
Jannes

7

この質問は数年前のものですが、ffprobeの最新バージョンにはフレームスキップする機能があります。-skip_frame nokeyキーフレーム(Iフレーム)の情報のみをレポートに渡すことができます。これにより多くの時間を節約できます!2GB 1080p MP4ファイルでは、スキップフレームなしで4分かかりました。スキップパラメータを追加すると、20秒しかかかりません。

コマンド:

ffprobe -select_streams v -skip_frame nokey -show_frames -show_entries frame = pkt_pts_time、pict_type D:\ test.mp4

結果:

[FRAME]
pkt_pts_time=0.000000
pict_type=I
[/FRAME]
[FRAME]
pkt_pts_time=3.753750
pict_type=I
[/FRAME]
[FRAME]
pkt_pts_time=7.507500
pict_type=I
[/FRAME]
[FRAME]
pkt_pts_time=11.261250
pict_type=I
[/FRAME]
[FRAME]
pkt_pts_time=15.015000
pict_type=I
[/FRAME]

そのため、結果にはキーフレームに関する情報のみが含まれます。


1

slhckの答え基づいてN秒前に発生する最も近いキーフレームを返すbash関数を次に示します。

これにより-read_intervals、ffprobeが25秒前にキーフレームの検索のみを開始することも保証されますN。タイムスタンプが見つかったときにこのトリックとawkが終了すると、処理速度が大幅に向上します。

function ffnearest() {
  STIME=$2; export STIME;
  ffprobe -read_intervals $[$STIME-25]% -select_streams v -show_frames -show_entries frame=pkt_pts_time,pict_type -v quiet "$1" |
  awk -F= '
    /pict_type=/ { if (index($2, "I")) { i=1; } else { i=0; } }
    /pkt_pts_time/ { if (i && ($2 <= ENVIRON["STIME"])) print $2; }
    /pkt_pts_time/ { if (i && ($2 > ENVIRON["STIME"])) exit 0; }
  ' | tail -n 1
}

使用例:

➜ ffnearest input.mkv 30
23.941000

これを使用して、ビデオファイルを再エンコードせずにトリミングします。再エンコードせずに新しいキーフレームを追加することはできないffnearestため、カットする前にキーフレームをシークするために使用します。以下に例を示します。

ffmpeg  -i input.mkv -ss 00:00:$(echo "$(ffnearest input.mkv 30) - 0.5" | bc)  -c copy -y output.mkv;

この例では-ss、最初の60秒よりも遠くを探している場合、paramで渡されるものの形式を変更する必要があることに注意してください。

(うるさく、正確にキーフレームのタイムスタンプにまでシークするのffmpegを伝えることはffmpegの出力にそのキーフレームを除外作るようですが、キーフレームの実際のタイムスタンプは、トリックを行いますから、0.5秒を差し引く。bashのためにあなたが使用する必要がbc小数で式を評価します、しかしzshでは-ss 00:00:$[$(ffnearest input.mkv 28)-0.5]動作します。)


これにより、Iフレーム後の次のフレーム時間が与えられます。
エーサンチャヴォシ

0

Iフレームの情報を取得したい場合は、次を使用できます。

ffprobe -i input.mp4 -v quiet -select_streams v -show_entries frame=pkt_pts_time,pict_type|grep -B 1 'pict_type=I'
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.