Wie erhalte ich mit FFmpeg den Zeitstempel des nächsten Keyframes vor einem bestimmten Zeitstempel?

18

Ich möchte einen FFmpeg-Suchbefehl, der so schnell und genau ist. Ich fand diese .

Die Lösung ist, dass wir uns sowohl -ssfür die Eingabe (schnelle Suche) als auch für die Ausgabe (genaue Suche) bewerben . Aber: Wenn die Eingabesuche nicht genau ist, wie können wir dann sicher sein, dass die Suchposition genau ist?


Zum Beispiel: Wenn wir nach 00:03:00 suchen wollten, ist der Befehl ungefähr so:

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

Der erste -sswird sich irgendwo anders umsehen 00:02:30, sagen wir nicht 00:02:31. Und nach der zweiten Suche wäre das Endergebnis 00:03:01- nicht das, was wir wollen. Ist das korrekt?

Wohin strebt der Erste -ss? Sucht es nach dem Keyframe, der am nächsten ist 00:02:30?

Wenn ja, hier ist mein Gedanke - korrigieren Sie mich, wenn ich mich irre: Nach der ersten Suche erhalten wir den Zeitstempel des Ergebnisses (in diesem Beispiel:) 00:02:31, dann wenden wir in diesem Fall die zweite Suche mit angemessener Zeit an 00:00:29.

Die Frage ist: Wie erhalten wir den Zeitstempel des ersten Suchergebnisses?

Jackode
quelle

Antworten:

18

Um die Frage Ihres Titels buchstäblich zu beantworten: Mit können Sie eine Liste von I-Frames abrufen

ffprobe -select_streams v -show_frames <INPUT> 

Sie können dies durch Hinzufügen weiter auf die erforderliche Ausgabe beschränken -show_entries frame=pkt_pts_time,pict_type.

Um zu sehen, welcher Frame einem bestimmten Zeitstempel am nächsten kommt (nach ihm kommt), müssen Sie zuerst alle Zeitstempel der Keyframes herausfinden, zum Beispiel mit awk.

Definieren Sie zunächst die Zeit, nach der Sie suchen möchten, z. B. 2: 30m, was 150s entspricht.

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

Dies würde zum Beispiel zurückkehren 150.400000.


Beachten Sie, dass bei der Verwendung -ssvor -i, wird lokalisieren FFmpeg die Keyframe vorherige zu dem Punkt sucht, dann assign negative PTS - Werte für alle folgenden Frames , bis sie den Punkt suchen , erreicht. Ein Player sollte Frames mit negativem PTS dekodieren, aber nicht anzeigen, und das Video sollte korrekt starten.

Einige Player respektieren dies nicht richtig und zeigen schwarze Videos oder Müll an. In diesem Fall kann das obige Skript verwendet werden, um den PTS des Keyframes nach Ihrem Suchpunkt zu finden und den Suchvorgang vom Keyframe aus zu starten. Dies wird jedoch nicht genau sein.

Beachten Sie, dass Sie das Video in ein verlustfreies Intra-Only-Format konvertieren sollten, wenn Sie bei der Suche sehr genau sein und die Kompatibilität mit vielen Playern beibehalten möchten. Sie können es dann jederzeit ausschneiden und neu codieren. Aber das wird nicht schnell gehen.

slhck
quelle
1
danke, ich mache keinen Video-Editor, aber ich möchte eine präzise Videosuche, bei der die Lücke weniger als 0,5 Sekunden betragen sollte.
Jackode
1
Sie können wahrscheinlich mit dem PTS aus jonglieren ffprobe. Andernfalls würde ein Zwischenformat ausreichen, z. B. ProRes 422, DNxHD, die visuell verlustfrei sind und nur innerhalb eines Frames angezeigt werden. Oder du verwendest so etwas wie HuffYUV usw. Aber dann verlierst du natürlich wieder den "schnellen" Aspekt.
Slhck
welche version von ffprobe hast du für den befehl benutzt, weil meins sagteUnrecognized option 'select_streams'
jackode
2
Sie standen kurz davor, die select_streamsOption wurde im Oktober 2012 hinzugefügt . :) Sie könnten darauf verzichten, aber dann würden Sie auch Informationen für Audio-Frames erhalten, gemischt dazwischen.
Slhck
2
Beachten Sie, dass Sie die Zeile ffmpeg hinzufügen können, um nur die erforderlichen 2 Felder auszugeben, anstatt viele Dinge, die awk wegwirft: -show_entries frame = pkt_pts_time, pict_type
Jannes
7

Ich verstehe, dass diese Frage einige Jahre alt ist, aber die neueste Version von ffprobe kann Frames überspringen . Sie können -skip_frame nokeynur Informationen zu den Keyframes (I-Frames) melden. Das kann Ihnen viel Zeit sparen! Bei einer 2 GB 1080p MP4-Datei dauerte es ohne die überspringenden Frames 4 Minuten. Das Hinzufügen des Skip-Parameters dauert nur 20 Sekunden.

Befehl:

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

Ergebnisse:

[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]

Die Ergebnisse enthalten also nur Informationen zu den Keyframes.

Hinter-D
quelle
1

Aufbauend auf der Antwort von slhck ist hier eine Bash-Funktion, die den nächsten Keyframe zurückgibt , der VOR NSekunden auftritt .

Dadurch wird auch -read_intervalssichergestellt, dass ffprobe erst 25 Sekunden vor NSekunden nach Ihrem Keyframe sucht . Dieser Trick und das Beenden von awk, wenn der Zeitstempel gefunden wird, beschleunigen die Dinge erheblich.

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
}

Beispielverwendung:

➜ ffnearest input.mkv 30
23.941000

Ich benutze dies, um Videodateien zu schneiden, ohne sie neu zu kodieren. Da Sie keine neuen Keyframes hinzufügen können, ohne sie neu zu codieren, ffnearestsuche ich vor dem Ausschneiden nach dem Keyframe. Hier ist ein Beispiel:

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

Beachten Sie, dass Sie in diesem Beispiel möglicherweise das Format der -ssÜbergabe im Parameter ändern müssen, wenn Sie länger als die ersten 60 Sekunden suchen.

(Es ist ärgerlich, wenn ffmpeg angewiesen wird, genau nach dem Zeitstempel des Keyframes zu suchen, um diesen Keyframe in der Ausgabe auszuschließen, aber 0,5 Sekunden vom tatsächlichen Zeitstempel des Keyframes abzuziehen, ist der Trick. Für bash müssen Sie bcAusdrücke mit Dezimalstellen auswerten , aber in zsh -ss 00:00:$[$(ffnearest input.mkv 28)-0.5]funktioniert.)

Chris
quelle
Dies gibt die nächste Bildzeit, nachdem ich ein Bild aufgenommen habe.
Ehsan Chavoshi
0

Wenn Sie Informationen über die I-Frames erhalten möchten, können Sie verwenden

ffprobe -i input.mp4 -v quiet -select_streams v -show_entries frame=pkt_pts_time,pict_type|grep -B 1 'pict_type=I'
shuaihanhungry
quelle