Ansichten eines Informatikers

Videos verlustfrei schneiden

Hadmut
11.2.2022 15:05

Mal ein kurzer technischer Hinweis zur Asynchronität zwischen Bild und Ton.

Der ein oder andere Leser wird sich erinnern, dass vor einiger Zeit schon mal was zum Problem des verlustfreien Videoschneidens geschrieben habe.

Häufig steht man vor dem Problem, dass man ein langes Video hat, dass dann auch gerne mal ein oder mehrere GByte auf der Platte belebt. Entweder, weil man selbst irgendwas aufgenommen hat und das erst in der fünften Wiederholung saß, oder weil man ein Video irgendeiner Fernsehsendung hat, aus dem man eine Szene zitieren will.

Nun ist es zwar einfach, mit irgendeinem x-beliebigen Videoschnittprogramm einfach ein Stück rauszuschneiden und als Video zu zu rendern, aber jede Neukompression bedeutet bei Bild und Ton Qualitätsverlust (und ab und zu auch mal Parameterfehler, es ist nämlich alles andere als trivial, das richtig zu wählen). Deshalb ist es immer wichtig, die Ur-Daten in der Ur-Qualität aufzubewahren. Manche Schnittprogramme, wie kdenlive, bieten es auch an, einen Ausschnitt eines Videostücks nicht nur in das produzierte Video (timeline) zu pappen, sondern auch es als separate Datei abzuspeichern. Ich habe aber noch nicht verstanden, ob und bei welchen Programmen das dann auch verlustfrei passiert.

Verlustfrei heißt, das Video nicht zu dekodieren und dekomprimieren, und dann neu zu komprimieren, wie man es beim Schneiden macht, sondern die entsprechende Stelle aus dem Quellvideo unverändert herauszukopieren.

Ich habe mir dazu vor Jahren ein Skript geschrieben, das mit gegebenen Parametern ffmpeg aufruft. ffmpeg ist das Standardprogramm unter Linux zur Videobearbeitung, und zwar sehr fähig, aber nur ein Kommandozeilenprogramm ohne graphische Benutzerschnittstelle. Manche graphischen Schnittprogramme sind nur ein Frontend für ffmpeg oder seine Programmbibliothek.

Und ffmpeg kann nicht nur komprimieren, Spuren mischen, konvertieren und sowas alles, sondern – theoretisch – eben auch verlustfrei ausschneiden. Was neben dem Vermeiden eines Verlustes auch noch den Vorteil hat, dass es blitzschnell geht. Manchmal fragt man sich, ob das Programm überhaupt gelaufen ist, weil man das Kommando eingibt, Return drückt und es schon fertig ist.

Wenn man also eine Szene aus einer Fernsehsendung im Blog zitieren will, ist es nicht nur Platz, sondern auch sehr zeitsparend, wenn man die Stelle zuerst mal verlustfrei herausschneidet, und erst dann runterskaliert und komprimiert.

Der Haken daran:

Es geht eigentlich nicht (so wohne weiteres).

Normalerweise würde man denken, dass das doch einfach ist, weil man das Video einfach ab Frame (Bild) Nr. soundso kopiert und den Ton ab Sekunde x,y. Der Haken daran ist, dass sowohl Videos, als auch deren Tonspuren nicht Bildweise gespeichert werden, sondern die Kompressionsverfahren immer längere Strecken zusammenfassen. Beim Ton ist das noch relativ unkritisch, weil auch die Stücke, die zusammengehören, relativ kurz sind, und sich leicht zerlegen lassen, aber beim Video ist das anders. Videos werden bei vielen Kompressionsverfahren in einer sogenannten GOP, einer Group of Pictures, zusammengefasst. Das erste Bild ein einer GOP ist ein Vollbild, das für sich alleine vollständig gespeichert ist, die Folgebilder enthalten aber nur Änderungen gegenüber dieses ersten oder des jeweils vorangegangenen Bildes. Weil man zum Beispiel bei einer Aufnahme von einer sprechenden Person nur den bewegten Mund oder den etwas wackelnden Kopf neu kodieren muss, aber nicht den Hintergrund, der sich nicht bewegt. Oder weil bei einem Kameraschwenk die Information reicht, das ganze Bild oder einen Teil davon erst mal ein paar Pixel zu verschieben, und man dann viel weniger Differenzen draufpacken muss, etwa weil ein Auto durchs Bild fährt.

Das heißt, dass man jedes Bild in einer Group of Pictures – außer dem ersten – nur verstehen kann, wenn man alle Bilder der GOP hat, weil vielleicht das Bild Nr. 7 sagt, dass es eignetlich aussieht wie das erste Bild, nur der Mund der Person anders dargestellt werden muss, weil sie spricht. Wie genau das ist und wie präzise man die Änderungen alle erfasst, ist eine Frage der Kompressionsstärke.

Die Effekte daraus habt Ihr bestimmt alle schon mal bei Internet-Videos oder im Digitalfernsehen (vor allem DVB-T und DVB-T2) gesehen: Manchmal kommt es zu einem Übertragungsfehler und demolierten bunten Kästchen mittem im Bild, die da eine Weile, teils ein, zwei Sekunden da bleiben, bis das Bild plötzlich wieder in Ordnung ist. Das war dann so eine GOP, in der ein Fehler passierte. Wenn das erste Bild demoliert übertragen wird, können die Folgebilder, die ja nur die Änderungen dazu darstellen, natürlich auch nicht in Ordnung sein. Erst wenn eine neue GOP mit einem neuen Startbild kommt ist wieder alles gut. Und sicherlich werdet Ihr auch schon mal den Effekt gesehen haben, dass da ein Gesicht spricht, der Hintergrund aber seltsam tot aussieht, weil er sich nicht bewegt. Weil eben nur die Änderungen des Gesichts übertragen werden und der Hintergrund immer eine ganze GOP lang unverändert aussieht. Kann man auch bei stark komprimierten Videos oder im Fernsehen bei DVB-T und DVB-T2 (die stärker komprimieren als DVB-C und DVB-S) sehen, wenn man einen großen Fernseher hat und nahe dran geht. Der Hintergrund kann mitunter recht schlecht aussehen, wie aus kleinen Kästchen zusammengesetzt, die wie Flicken aussehen, und die dann immer so für eine Sekunde oder so fest stehen, bevor sie sich ändern. Auch da merkt man, dass das erste Bild der GOP ein Vollbild ist und die Folgebilder für diese Stelle keine Änderung enthalten.

Und das führt zu einem Problem beim verlustfreien Schneiden.

Weil man nämlich das Video eben nicht exakt bei Bild Nr. 734 schneiden kann, sondern schauen muss, in welche GOP das Bild fällt, und dann die ganze GOP kopieren muss, um es eben verlustfrei zu bekommen. Wenn man also mit Bild Nr. 734 des Videos nicht zufällig das erste Bild einer GOP erwischt, muss man mehr Bilder kopieren, früher anfangen, als man eignetlich will. Am hinteren Ende tritte das Problem nicht auf, weil man da ja die Änderungen abschneiden kann.

So eine GOP kann aber, je nach Parametern und Verfahren, mehrere Sekunden lang sein, man kann also ungewollt ganze Szenen mitkopieren, die man eigentlich nicht haben will.

Als ich vor einigen Jahren dieses Skript geschrieben hatte, waren die Ergebnisse fehlerhaft. Weil Bild und Ton deutlich, teils mehr als eine Sekunde, gegeneinander verschoben waren. Das Problem war klar: Ich geben eine Zeitangabe oder Frameangabe an, wo der Ausschnitt anfangen soll, und der Ton fängt dann exakt da an, die Bilder aber früher. Wenn ich also beispielsweise ab Bild 734 ausschneiden will, kann es sein, dass das Video schon bei Bild 703 anfängt, weil da eben die GOP anfängt, in der Bild 734 liegt.

Also hatte ich einen Bug-Report aufgemacht und berichtet, dass das nicht sauber funktioniert, und vorgeschlagen

  • nur die angeschnittene GOP neu zu codieren, so dass sie am gewünschten Bild anfängt, oder
  • wenigstens den Tonschnitt anzupassen, damit ich, wenn ich schon mehr Bild bekomme, als ich wollte, auch der Ton früher anfängt, damit es wieder zusammenpasst.

Die Antwort war, dass ich da was nicht richtig verstanden hätte und das Problem gar nicht existiere. Das sei kein Fehler, das gehöre so.

Man würde den Ton exakt richtig schneiden, zum gewünschten Zeitpunkt, und ihn dann im ausgeschnittenen Video exakt beim Zeitpunkt 0 anfangen lassen.

Bei den Bildern dagegen würde man die GOP unverändert einkopieren, sie aber mit einem negativen Offset versehen. Wenn ich also ein Video mit 50 Bildern pro Sekunde hätte, und das Video ab Bild 734 ausschneiden will, die GOP aber schon bei Bild 703 anfängt, setzt man dann für das Video einen negativen Offset von (703-734)/50.0 Sekunden, also -0.62 Sekunden setzen. Auf diese Weise würden im Video zwar die Bilder ab 730 abgelegt, um die GOP unverändert reinzukopieren, weil der Player aber beim Abspielen bei exakt Sekunde 0 anfängt (sofern nicht ausdrücklich anders gewünscht und angegeben), würde er also bei Bild 31, eben dem ursprünglichen Bild 734 anfangen und das gwünschte Ergebnis sei verlustfrei erreicht.

Das Bild und Ton nicht zusammenpasse läge an mir, weil ich noch den klassischen Videoabspieler unter Linux, den mplayer, verwendete. Der sei veraltet und würde nicht mehr gepflegt, und könne nicht mit diesen negativen Offsets umgehen. Da gebe es eine Fork, der weiter gepflegt werde und das enthalte, der heiße mpv statt mplayer.

Also gut, nehmen wir mpv. Und damit sah es dann auch besser aus, der zeigte auch im Informationstext an, dass er ein Video mit negativem Offset detektiert hat.

Nun, dachte ich, das ist ja schön und gut, aber das funktioniert ja nur bei mir. Ich kann ja nicht Videos ins Blog pappen und dranschreiben, dass das jetzt nur mit Programm X funktioniert, weil die Leser ja mit dem Browser draufgehen und das mit den Browsern funktionieren soll.

Aber, so dachte ich mir dann, das spielt ja eigentlich keine Rolle. Weil ich nämlich diese verlustfreien Ausschnitte ja nur für mich erstellte und bei mir speichere, und die dann nochmal mit ffmpeg auf Blog-Zitatformat runterskaliere und damit neu komprimiere, und ffmpeg seine selbst erzeugten Videos ja auch mit dem negativen Offset ordentlich verarbeiten können sollte.

Sah auch gut aus.

Gelegentlich, erstaunlich selten, aber eben doch kamen Leserbeschwerden rein, dass Bild und Ton manchmal nicht synchron seien. Was mich überrascht hatte, weil ich in den dann runterskalierten und dazu neu komprimierten Videos eigentlich keinen negativen Offset mehr gesehen habe, der sollte ja auch durch die Neukompresssion rausfallen, weil die die überflüssigen Frames überspringt und neue GOPs erzeugt.

Irgendwie war also im neukomprimierten Video doch wieder der Wurm drin. Verschiebung zwischen Bild und Ton, die aber browserabhängig zu sein scheint, weil sich nur einige wenige Leser beschwerten. Weil ich aber nicht die Zeit hatte, das näher zu debuggen, und die ffmpeg-Version unter der Linux-Version, die ich zum Blogbearbeiten verwende (Ubuntu 20.04), veraltet ist, dachte ich mir, verzichte ich halt darauf, bis ich eine neuere Version habe, und probiere es dann nochmal neu, ob das noch ein Bug in ffmpeg war, und ob das dann besser funktioniert. Mir fehlt die Zeit, das jetzt völlig auszudebuggen.

Dazu kommt nämlich auch noch, dass es bei ffmpeg (oder auch seinem zwischenzeitlichen Ableger avconv) darauf ankommt, ob man die Zeitangabe, ab wo man das Bild haben will, vor oder nach der Quellenangabe als Parameter angibt. Bei einer Variante weiß die Eingaberoutine, dass sie im Quellvideo – formatabhängig – direkt zu der gewünschten Stelle gehen soll, was – je nach Format – extrem schnell geht, weil er alles, was man nicht will, überspringt, während bei der anderen Variante alles von Anfang an gelesen und dann nur die entsprechende Menge Material in der Verarbeitungskette ignoriert wird. Wenn man also etwas ab 1:00:00 rauskopieren will, dauert das dann trotzdem, weil der dann erst mal eine Stunde Video einliest und dekodiert, um es ignorieren zu können. Die Reihenfolge der Parameter auf der Kommandozeile spielt also auch noch eine Rolle.

Einen möglichen Fehler, den ich noch prüfen wollte, war, dass ich die Start- und End-Zeiten nicht auf die Bildrate runde, sondern mit drei Stellen hinter dem Komma (also Tausendstel Sekunden) an ffmpeg durchreiche, weil ich davon ausgegangen bin, dass ffmpeg die auch beim Ton automatisch auf die Bidrate anpasst. Kann natürlich sein, dass das doch nicht so ist, und dann, wenn die Zeitangabe nicht exakt zur Framerate passt, der Ton anders angeschnitten ist als das Bild. Das aber würde im Versatz unter der Bildrate bleiben und eine Ansynchronität, die damit höchstens 1/50 bzw 1/25 Sekunde beträgt, unterhalb der Wahrnehmungsschwelle bleiben.

Eben kam die neue Ausgabe der c’t.

Was sehen meine leidgeprüften, alterstrüben Augen?

„Verlustfreier Videoschnitt mit LosslessCut“.

Oh. Kannte ich noch gar nicht. Gibt es sogar als snap. Ein graphisches Frontend für ffmpeg zum verlustfreien Schneiden.

Muss ich mal ausprobieren, ob das dann fehlerfrei funktioniert. Was auch bedeutet, dass ich mal was ins Blog stellen und abwarten muss, ob sich wieder Leser beschweren, weil ich das ja nicht mit allen Browsern testen kann.

Und falls es funktioniert, muss ich mal rausfinden, was die beim Aufruf von ffmpeg eigentlich anders machen als ich in meinem Skript.