clipper

module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: MIT

README

clipper

Zieht mit ffmpeg eine gewünschte Anzahl Screenshots über die Gesamtlaufzeit eines Videos.

clipper -n 10 -i holiday.mp4

Installation

Voraussetzung sind ffmpeg und ffprobe im PATH. Auf macOS:

brew install ffmpeg

Bauen:

make build      # erzeugt bin/clipper
make install    # nach $GOPATH/bin

Verwendung

clipper -n <anzahl> [flags] -i <video> [weitere videos ...]
Flag Bedeutung
-n Anzahl Screenshots pro Video (Pflicht, > 0)
-i Videodateien; wiederholbar oder mehrere Dateien hintereinander
-o Ausgabeverzeichnis, wird bei Bedarf angelegt (Default: aktuelles Verzeichnis)
-prefix Dateinamen-Prefix (Default: shot, bei mehreren Videos der jeweilige Videoname)
-format png (Default), jpg oder webp
-quality 1–100, nur für jpg und webp (Default: 90)
-jobs parallele ffmpeg-Prozesse (Default: min(GOMAXPROCS, 4))
-random zufällige Position je Abschnitt statt gleichmäßigem Raster
-seed Seed für -random; ohne Angabe wird einer erzeugt und ausgegeben
-version Version ausgeben

Wasserzeichen, Verlauf und Vignette sind drei optionale Nachbearbeitungsschritte, alle standardmäßig aus:

Flag Default Bedeutung
-watermark aus PNG-Logo in eine Ecke legen; leerer Wert sucht ./logo.png
-watermark-size 10 Prozent der Bildgröße; lange Logokante, in jedem Format gleich
-watermark-margin 2 Prozent der Bildgröße Abstand zu den Rändern
-watermark-opacity 100 1–100, wirkt zusätzlich zur Transparenz des PNG
-watermark-position top-right top-right, top-left, bottom-right, bottom-left
-gradient aus Farbverlauf am unteren Bildrand einblenden
-gradient-height 33 Prozent der Bildhöhe, 1 bis 100
-gradient-color #000000 Hex-Farbe, auch kurz (#000) und ohne #
-gradient-opacity 95 1–100, Deckkraft in der untersten Bildzeile
-vignette aus Bildränder abdunkeln, immer schwarz
-vignette-radius 65 0–100, freie Zone in Prozent des Weges von der Mitte zur Ecke
-vignette-softness 50 0–100 in derselben Einheit, Breite des Übergangs; 0 ergibt eine harte Kante
-vignette-opacity 40 1–100, Deckkraft am Ende des Übergangs

Beispiele:

clipper -n 10 -i holiday.mp4
clipper -n 5 -o shots -format jpg -quality 85 -i a.mp4 b.mp4
clipper -n 8 -random -seed 42 -i holiday.mp4
clipper -n 6 -watermark assets/logo.png -gradient -i holiday.mp4
clipper -n 6 -vignette -i holiday.mp4              # dezente Abdunklung der Ecken
clipper -n 6 -vignette -vignette-radius 40 -vignette-opacity 70 -i holiday.mp4
clipper -n 6 -watermark "" -i holiday.mp4          # nimmt ./logo.png

Zwei Eigenheiten der Overlay-Flags: Der Wert von -watermark ist ein Pfad, ein leerer Wert (-watermark "") heißt „nimm logo.png aus dem Verzeichnis, in dem clipper aufgerufen wurde". Und Unterflags ohne ihr Hauptflag werden ignoriert — -gradient-height 20 ohne -gradient tut also nichts.

Ein Hinweis zur Argumentreihenfolge: -i sammelt alle folgenden Argumente als Videos ein. Nach dem zweiten Video kann daher kein Flag mehr folgen — clipper -n 2 -i a.mp4 b.mp4 -format jpg bricht mit einer entsprechenden Meldung ab. Direkt hinter einem einzelnen -i-Wert sind Flags dagegen erlaubt.

Ausgabe und Exit-Codes

Fortschritt, Warnungen und Fehler gehen auf stderr, die Zusammenfassung auf stdout. Die Fortschrittszeilen erscheinen bei paralleler Ausführung in beliebiger Reihenfolge und nennen deshalb immer Video und Index.

Code Bedeutung
0 alles geschrieben
1 mindestens ein Screenshot oder Video fehlgeschlagen, oder Umgebung unbrauchbar
2 Kommandozeile falsch
130 mit Strg+C abgebrochen (bereits erzeugte Bilder bleiben erhalten)

Verhalten im Detail

Verteilung. Standardmäßig liegt Screenshot i bei (i+1)·Dauer/(n+1). Das ist symmetrisch und vermeidet den allerersten und allerletzten Frame, die häufig schwarz sind. Mit -random wird das Video in n gleich große Abschnitte geteilt und in jedem eine zufällige Position gezogen (stratified sampling); der verwendete Seed wird ausgegeben, damit ein Lauf wiederholbar ist.

Seeking. -ss steht vor -i, also Input-Seeking: ffmpeg springt zum Keyframe und dekodiert von dort bis zur exakten Position. Das ist auf langen Videos um Größenordnungen schneller als Output-Seeking und trotzdem framegenau.

Schwarzbild-Erkennung. Jeder geschriebene Screenshot wird in Go analysiert (mittlere Helligkeit und Standardabweichung der Luminanz). Ist das Bild praktisch schwarz oder völlig einfarbig, wird eine Ersatzposition im selben Abschnitt genommen — insgesamt bis zu drei Versuche. Bleibt es dabei, wird das letzte Bild behalten und eine Warnung ausgegeben.

Wasserzeichen, Verlauf und Vignette. Alle drei sind Nachbearbeitungsschritte in reinem Go, keine ffmpeg-Filter. Ist einer davon aktiv, ändert sich die Reihenfolge pro Screenshot:

  1. ffmpeg extrahiert den Frame verlustfrei als PNG in eine versteckte Zwischendatei neben dem Ziel (.shot_001.jpg.4711-8f2ca931.raw.png; Prozess-ID und Zufallsanteil im Namen verhindern, dass sich zwei gleichzeitige Läufe im selben Verzeichnis die Datei wegziehen),
  2. die Schwarzbild-Erkennung prüft diesen rohen Frame,
  3. Vignette, dann Verlauf, dann Logo werden aufgelegt,
  4. Go schreibt das Endbild im gewünschten Format und löscht die Zwischendatei.

Die Ebenenfolge in Schritt 3 ist bewusst so: die Vignette ahmt ein Objektiv nach und liegt deshalb unten, ein farbiger Verlauf gehört darüber, und das Logo kommt zuletzt, damit es in seiner Ecke nicht mitabgedunkelt wird und lesbar bleibt. Weil es getrennte Durchgänge über dasselbe Bild sind, addiert sich die Wirkung dort, wo sich zwei Ebenen überlappen — mit -vignette und -gradient-height 100 wird der untere Bildrand also von beiden abgedunkelt.

Die Zwischendatei gehört dem Nachbearbeitungsschritt: sie wird auch dann entfernt, wenn das Kodieren scheitert oder ein Wiederholungsversuch nach einem bereits erfolgreichen Versuch fehlschlägt. Nach einem Lauf bleibt nichts liegen.

Die Prüfung läuft also bewusst vor den Overlays. Andernfalls würde der Verlauf die Statistik verfälschen: schon ein Verlauf über das voreingestellte Drittel der Höhe erzeugt allein eine Standardabweichung von etwa 0,16, und mit -gradient-height 100 wird kein Bild mehr als schwarz erkannt. Frames, die nach drei Versuchen einfarbig bleiben, werden unverändert durchgeschrieben und bekommen keine Overlays.

Wie die Logogröße rechnet. Bezugspunkt ist nicht eine Bildkante, sondern die Bildgröße als solche: das geometrische Mittel der beiden Kanten, also √(Breite·Höhe). -watermark-size ist der Anteil davon, den die lange Kante des Logos einnimmt, und das Logo wird unter Beibehaltung seines Seitenverhältnisses in eine quadratische Box dieser Kantenlänge eingepasst.

Der Grund ist Vorhersagbarkeit über Bildformate. Bezogen auf die Breite ergäbe -watermark-size 10 auf 1920x1080 ein 192 px breites Logo, auf 1080x1080 aber nur 108 px — derselbe Wert würde in jedem Format anders wirken. Mit dem geometrischen Mittel belegt das Logo in 1:1, 9:16 und 16:9 denselben Anteil des Bildes, und zwar für jeden Wert: der Flächenanteil ist (Prozent/100)² · Logohöhe/Logobreite und enthält Breite, Höhe und Seitenverhältnis des Bildes überhaupt nicht. -watermark-margin folgt demselben Bezug, sonst wäre die Größe formatunabhängig, der Abstand zur Ecke aber nicht.

Eine Ausnahme gibt es am oberen Ende: die Box wird auf die kürzere Bildkante gedeckelt, damit ein Logo nie größer als das Bild wird. Das greift erst weit jenseits sinnvoller Wasserzeichengrößen, auf 16:9 etwa ab 75 Prozent.

Das Logo wird pro Lauf einmal dekodiert und pro Bildauflösung einmal skaliert (Seitenverhältnis bleibt, nie hochskaliert). Die Quelldatei wird ausschließlich gelesen. Wäre das skalierte Logo kleiner als 8 px, wird es weggelassen und einmal pro Auflösung gewarnt — bei -watermark-size 5 und einem 320x240-Video passiert genau das, dort bleiben nur 13x5 Pixel übrig.

Wie die Vignette rechnet. Der Abstand eines Pixels zur Bildmitte wird pro Achse auf die halbe Bildbreite beziehungsweise Bildhöhe bezogen. Dadurch sind die Linien gleicher Abdunklung Ellipsen in der Form des Bildes: ein Kreis würde auf 16:9 die linken und rechten Ränder viel stärker abdunkeln als oben und unten. In dieser Einheit liegt eine Kantenmitte bei 1 und eine Ecke bei √2, weshalb die Ecken von sich aus stärker abdunkeln — das ist der Effekt, den man von einem Objektiv kennt.

-vignette-radius ist die freie Zone, gemessen als Anteil des Weges von der Mitte zur Ecke. Bei 100 reicht sie bis in die Ecke, es passiert also nichts; das ist kein Fehler, sondern ein stilles Nichtstun, und clipper überspringt dann auch die Nachbearbeitung. -vignette-softness ist die Breite des Übergangs in derselben Einheit. Beide dürfen 0 sein, und ihre Summe darf 100 überschreiten — dann ist der Abfall in der Ecke noch nicht fertig und die Vignette bleibt entsprechend dezent. Genau das tun die Voreinstellungen: 65 und 50 ergeben in den Ecken etwa 31 Prozent Abdunklung und an den Kantenmitten praktisch nichts.

Der Übergang ist eine Smoothstep-Kurve und keine Gerade. Das ist der Unterschied zu einem radialen Verlauf: eine lineare Rampe hat am Beginn des Abfalls einen Knick, und den sieht das Auge als Ring, obwohl der Sprung zwischen zwei Nachbarpixeln weit unter einer der 255 Stufen liegt. Bei Smoothstep ist die Steigung an beiden Enden null, der Anfang des Abfalls ist damit unsichtbar.

Die Maske hängt nur von der Bildgröße ab, nie vom Bildinhalt, und wird deshalb pro Auflösung einmal berechnet und für alle Screenshots wiederverwendet — genau wie das skalierte Logo, im selben Cache. Das ist die teure Hälfte des Effekts: auf 1080p kostet der Aufbau der Maske rund 13 ms, das Auflegen danach etwa 4 ms (go test -bench Vignette ./internal/overlay/).

Zwei Nebenwirkungen: Mit Overlays kodiert Go das Bild statt ffmpeg. Bei PNG ist das Ergebnis pixelgleich, aber die Datei kann eine andere Größe haben; bei JPEG bedeutet -quality dann die Skala von image/jpeg statt der von ffmpeg — gleiche Qualitätsstufe, nicht bitgleiches Ergebnis. Und -format webp lässt sich nicht mit Overlays kombinieren, weil Go keinen webp-Encoder hat; der Preflight lehnt das mit einer klaren Meldung ab.

Fehler. Ein kaputtes Video oder ein fehlgeschlagener Screenshot bricht den Lauf nicht ab. Alles wird gesammelt und am Ende als Liste ausgegeben, der Exit-Code ist dann 1.

Überschreiben. Vorhandene Dateien werden ohne Rückfrage überschrieben — aber erst, wenn das neue Bild vollständig ist: ffmpeg schreibt in eine temporäre Datei neben dem Ziel, die nur bei Erfolg an ihren Platz verschoben wird. Eine fehlgeschlagene Extraktion zerstört also keinen vorhandenen Screenshot und hinterlässt keine halben Dateien. Tragen mehrere Videos den gleichen Basisnamen, würden sich ihre Screenshots gegenseitig überschreiben; das lehnt der Preflight ab.

Preflight. Vor dem ersten ffmpeg-Aufruf werden geprüft: ffmpeg und ffprobe im PATH, Existenz und Art der Eingabedateien, Basisnamen-Kollisionen, Beschreibbarkeit des Ausgabeverzeichnisses (per Probedatei, auch für das aktuelle Verzeichnis) und ob der lokale ffmpeg-Build den gewünschten Encoder überhaupt hat. Der Homebrew-Build kann zum Beispiel oft kein webp — das sagt clipper dann klar, statt ffmpeg kryptisch scheitern zu lassen.

Bei der Art der Eingabedatei besteht clipper auf einer regulären Datei. Eine Named Pipe, ein Socket oder eine Gerätedatei ist lesbar und kein Verzeichnis, würde also durchrutschen — aber ffprobe blockiert darauf endlos, und der Lauf hängt ohne jede Ausgabe. Das ist das unangenehmste Fehlerbild überhaupt, deshalb wird es vorher abgelehnt und die Meldung nennt die Art der Datei.

Als zweite Sicherung ist jeder ffprobe-Aufruf zeitlich begrenzt, voreingestellt auf 60 Sekunden. Metadaten zu lesen dauert auch bei sehr langen Videos nur Bruchteile davon; die Grenze existiert allein, damit ein Sonderfall, den die Preflight-Prüfung nicht kennt — etwa eine Datei auf einem toten Netzlaufwerk — nicht denselben stummen Hänger erzeugt. Das Extrahieren selbst bekommt bewusst keine Grenze, weil dort lange Laufzeiten auf großen Dateien und langsamen Medien legitim sind.

Ebenfalls vorab geprüft wird die Länge der Dateinamen. Ein Dateiname darf 255 Bytes haben, und mit aktiven Overlays gehen davon 25 Bytes für die Zwischendatei ab. Ein sehr langer -prefix kann daher ohne Overlays funktionieren und mit ihnen nicht — das sagt clipper jetzt vorher und in einem Satz, statt bei jedem Screenshot einzeln mit dem Pfad der versteckten Zwischendatei zu scheitern.

Aufbau

cmd/clipper           main: Signalbehandlung, Exit-Code
internal/cli          Flags, Preflight, Wiring, Bericht
internal/clipper      Domäne: Planung, Orchestrierung, Ports
internal/ffmpeg       Adapter für ffmpeg und ffprobe
internal/imgstat      Schwarzbild-Erkennung (reines Go)
internal/overlay      Vignette, Verlauf und Wasserzeichen (reines Go)
assets/logo.png       Beispiellogo für -watermark

Die Domäne startet keine Prozesse und dekodiert keine Bilder. Sie definiert fünf Ports — Prober, Extractor, Analyzer, Compositor, Reporter — und wird im Test gegen handgeschriebene Doubles in internal/clipper/clippertest gefahren (TDD im London Style). Sie kennt die Formatnamen und deren ffmpeg-Encoder (Format.Encoder()), damit der Preflight vorab prüfen kann, ob der lokale Build sie schreiben kann; alles andere über ffmpeg liegt im Adapter. Ist kein Compositor verdrahtet, schreibt ffmpeg das Endbild direkt und es findet keine Nachbearbeitung statt — die Domäne selbst weiß nichts über Logos oder Farben, die Overlay-Konfiguration steckt im Adapter.

Die Adapter werden gegen ein schmales Runner-Interface getestet, sodass kein Test der Adapterlogik einen echten Prozess startet — nur der Runner selbst wird gegen echo geprüft. Die ffprobe-Fixtures in internal/ffmpeg/testdata sind echte Ausgaben.

Weil Mocks nicht beweisen, dass die zusammengebauten ffmpeg-Argumente funktionieren, gibt es zusätzlich Integrationstests hinter dem Build-Tag integration. Sie erzeugen ihre Videos selbst per ffmpeg -f lavfi — inklusive eines Videos mit zwei Sekunden schwarzem Vorspann, um die Schwarzbild-Erkennung im Zusammenspiel zu prüfen. Für die Overlays vergleichen sie zwei Läufe des gleichen Frames pixelweise: außerhalb von Verlaufsband und Logo-Rechteck muss das Bild identisch bleiben, und bei der Vignette muss das mittlere Pixel unverändert sein, während die Ecken messbar dunkler werden und die Kantenmitten es nicht sind.

Entwicklung

make test               # Unit-Tests
make test-race          # mit Race Detector
make test-integration   # echtes ffmpeg
make check              # fmt + vet + race + integration

Lizenz

MIT, siehe LICENSE.

ffmpeg wird nur als eigener Prozess aufgerufen und nicht eingebunden, seine Lizenz gilt also nur für die von Ihnen installierte ffmpeg-Version, nicht für clipper.

Directories

Path Synopsis
cmd
clipper command
Command clipper takes screenshots out of videos with ffmpeg.
Command clipper takes screenshots out of videos with ffmpeg.
internal
cli
Package cli parses the command line, checks the environment and wires the domain to its adapters.
Package cli parses the command line, checks the environment and wires the domain to its adapters.
clipper
Package clipper contains the domain logic of the clipper tool: it decides which frames of a video are captured and drives the extraction through a set of small ports (Prober, Extractor, Analyzer, Reporter).
Package clipper contains the domain logic of the clipper tool: it decides which frames of a video are captured and drives the extraction through a set of small ports (Prober, Extractor, Analyzer, Reporter).
clipper/clippertest
Package clippertest provides hand written test doubles for the ports of package clipper.
Package clippertest provides hand written test doubles for the ports of package clipper.
ffmpeg
Package ffmpeg adapts the external ffmpeg and ffprobe binaries to the ports of package clipper.
Package ffmpeg adapts the external ffmpeg and ffprobe binaries to the ports of package clipper.
imgstat
Package imgstat inspects extracted screenshots and reports whether they carry any usable content.
Package imgstat inspects extracted screenshots and reports whether they carry any usable content.
overlay
Package overlay draws the post processing overlays on an extracted screenshot: a colour gradient at the bottom edge and a watermark in one of the corners.
Package overlay draws the post processing overlays on an extracted screenshot: a colour gradient at the bottom edge and a watermark in one of the corners.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL