read-video – Video byteweise nach Bedarf lesen
Welches Problem löst diese Fähigkeit?
Ein Nutzer schickt einen Videolink und möchte den Inhalt verstehen. Der naive Ansatz ist, das gesamte Video herunterzuladen – langsam, speicherintensiv, urheberrechtlich bedenklich und meist nur für ein paar Sekunden nötig. Diese Fähigkeit geht anders vor: Sie ruft nur die Bytes ab, die in der Nähe des gewünschten Zeitpunkts liegen. Die Audiospur wird lokal offline in Text umgewandelt (Hauptmethode für „Worum geht es?“), und die Videospur gibt Schlüsselbilder an ein Vision-Modell weiter, um den visuellen Kontext zu liefern.
Tests mit einer 249 MB / 9 Min 56 Sek MP4 (nicht faststart):
| Aktion | Abgerufene Bytes | Anteil am gesamten Video |
|---|---|---|
| Bild bei 30 Sek | 2,0 MB | 0,839 % |
| Bild bei 8 Min | 1,7 MB | 0,707 % |
| Audio ab 480 s für 20 Sek | 6,6 MB | 2,784 % |
Wann verwenden / wann nicht
Verwenden: Der Benutzer gibt einen direkten Videolink oder einen Weblink mit Video (Plattformen wie Bilibili werden automatisch von resolve.py analysiert – kein manueller API-Aufruf nötig).
Nicht verwenden: Lokale Dateien (direkt lesen), lange Videos, die vollständig verstanden werden müssen (Kosten zu hoch – zuerst nach Zeitpunkten fragen).
Harte Prinzipien
- Kein vollständiger Download. Kein Schritt darf
wget/curl -Ofür die gesamte Datei verwenden. - Erst prüfen, dann handeln. Ohne Prüfung zu seeken, wartet man auf einem Server ohne Range-Unterstützung vergeblich.
-ssmuss vor-istehen. Dahinter ist Output-Seeking und entspricht einem vollständigen sequentiellen Lesen. Vor-iist Input-Seeking, der Demuxer springt direkt.- Messung ist an. Standardmäßig aktiviert: Der lokale Proxy zählt die abgerufenen Bytes – der einzige harte Beweis, dass wirklich nur Byteweise gelesen wird, und Nachweis, dass kein heimlicher Download stattfand.
- Weblinks an Skripte übergeben. Seiten-URL ≠ direkter Link, und direkte Links laufen mit Signatur ab.
resolve.pynutzen oder den Link direkt angrab.py/transcribe.pyübergeben (die erkennen und ergänzen automatisch die Header). Keine manuellen API-Aufrufe der Plattform im Chat schreiben – der Code variiert immer und ist ungeprüft.
Workflow
Schritt 0: Eingabe unterscheiden – Weblink oder Direktlink
- Weblink (Bilibili, YouTube, TikTok, Weibo, Vimeo … insgesamt 1700+ Sites) → erst in direkten Link auflösen. Kann auch direkt an
grab.py/transcribe.pyübergeben werden, die erkennen und parsen automatisch und fügen die erforderlichen Header hinzu. - Direktlink (
.mp4/.m3u8usw. Medienerweiterungen) → Auflösung überspringbar.
Die Entscheidungslogik in resolvers.is_page_url(): URLs mit Medienerweiterung gelten als Direktlinks; alle anderen HTTP-Links werden zur Auflösung geschickt.
Schritt 1: Abrufbaren Stream erhalten
python scripts/resolve.py "<Weblink>"
Ausgabe: video_url (für Bilder), audio_url (für Transkription – nicht identisch), headers (vom CDN geforderte Header), plus Titel, Dauer und eine Liste empfohlener Zeitpunkte für Bilder.
Die Zeile plattform zeigt das verwendete Backend: native (eigener Adapter, derzeit Bilibili) oder yt-dlp (allgemein, 1700+ Sites). Erzwingbar mit --backend native|yt-dlp|auto.
Ein Befehl für alles (Auflösung und Abruf automatisch verknüpft – empfohlen):
python scripts/transcribe.py --url "<Weblink>" --seconds 90
python scripts/grab.py "<Weblink>" --times 14 29 44 58 73 --prefix shot --outdir ./out
Schritt 2: Überprüfung (bei Direktlinks empfohlen, bei Weblinks überspringbar)
python scripts/probe.py <Direktlink> --json
Wenn der Direktlink vom CDN stammt, den Ursprung angeben: --header 'Referer: https://www.bilibili.com'.
Drei Felder beachten:
seekable– beifalseabbrechen. Der Server ignoriert Range, punktgenaues Bilderlesen degeneriert zu sequentiellem Streaming. Alternativ: „Während des Downloads dekodieren“ (-i <url>ohne-ss, nimm das erste Bild) oder dem Benutzer mitteilen, dass es nicht möglich ist.container.moov_offset/faststart– Index am Anfang (faststart): Fixkosten einige Dutzend KB. Am Ende: zuerst das Dateiende abrufen (gemessen ca. 441 KB). Dies sind die Fixkosten für „ein Bild holen“, unvermeidbar.content_length– Nenner für den später berechneten Prozentsatz „Anteil am gesamten Video“.
Schritt 3: Nach Benutzerabsicht Daten abrufen
Benutzer gibt konkreten Zeitpunkt an → punktgenau Bild holen:
python scripts/grab.py <url> --time 183.5 --out shot.jpg
Benutzer gibt keinen Zeitpunkt an → ein paar Bilder für Überblick:
python scripts/grab.py <url> --times 5 60 180 420 --prefix shot --outdir /tmp/rv
Tipp zur Auswahl: 4–6 Bilder nach Inhaltsdichte, nicht gleichmäßig über die gesamte Länge verteilen (das entspricht dem Dekodieren des gesamten Videos). Bei Unsicherheit erst 3 Bilder holen, dann entscheiden, ob ein bestimmter Abschnitt genauer betrachtet werden soll.
Um zu verstehen, worum es geht → direkt in Text umwandeln (empfohlen zuerst):
python scripts/transcribe.py --url <url> --segments 0:90 320:90 630:97
Ein Befehl erledigt: „Audio extrahieren → lokal erkennen“. Ausgabe ist chinesischer Text mit Zeitstempeln. Keine externe API, keine Online-Inferenz nötig. Beim ersten Start wird das SenseVoice-Modell automatisch heruntergeladen (ca. 240 MB, von hf-mirror, nur einmal).
Bei langen Videos 3–4 Segmente zu je 90 Sekunden reichen, um die Haupthandlung abzudecken – nicht das gesamte Video transkribieren. Das ist viel schneller als jedes Bild zu analysieren und besser geeignet für Fragen wie „Worum geht es?“. Zuerst Text, dann Bilder zur visuellen Ergänzung – diese Reihenfolge.
Nach der Transkription zuerst die Ausbeute prüfen. Normale Sprechgeschwindigkeit ca. 4–6 Zeichen/Sek. Die Skriptausgabe enthält chars_per_second. Liegt sie unter 1 Zeichen/Sek, wird aktiv darauf hingewiesen. In diesem Fall (reine Musik, Gameplay, stumme Demo) die wenigen Zeichen nicht als Inhalt werten, sondern auf dichtere Bilder (8–10) für die visuelle Analyse umsteigen – die Antwort liegt nur im Bild.
Nur die Audiodatei selbst benötigt (z. B. für externe Transkription):
python scripts/grab.py <url> --start 0 --seconds 60 --audio clip.wav
Bei eingebetteten Untertiteln zuerst diese lesen (ffmpeg -i <url> prüfen, ob subtitle-Stream vorhanden) – genauer und schneller als ASR.
Schritt 4: Bilder an das Vision-Modell übergeben
grab.py holt nur die Pixel lokal, versteht den Inhalt nicht. Den Pfad des ausgegebenen jpg an das Vision-Modell übergeben mit einer Frage („Was ist in diesem Bild?“, „Worum geht es hier?“). Bei mehreren Bildern in zeitlicher Reihenfolge zusammen übergeben, damit das Modell eine zusammenhängende Erzählung bilden kann.
Ausgabe lesen
grab.py gibt JSON aus, jeder Eintrag mit Byte-Buchhaltung:
{
"ok": true,
"actions": [{
"kind": "frame", "time": 30,
"out_bytes": 48211,
"upstream_bytes": 2097152,
"requests": 4,
"wall_seconds": 2.317
}],
"total_upstream_bytes": 2097152
}
upstream_bytes / content_lengthist der prozentuale Anteil am gesamten Video. Über 5 % bedeutet: Stopp und Überprüfung – meist falsche-ss-Position oder Server erkennt Range nicht.requests> 1 ist normal: ffmpeg holt in mehreren Schritten (moov, zwischen Keyframe und Ziel, Audio-Header).- Bei
ok: falseinerr_tailnachsehen – häufig Timeout oder 5XX. Das Skript hat bereits einen Wiederholungsmechanismus; bei Misserfolg anderen Zeitpunkt versuchen.
Ausgabe von transcribe.py:
{
"ok": true,
"text": "Dies ist ein von Flüssen und Bergen umgebenes Land…",
"segments": [{"start": 0, "end": 25, "text": "…"}],
"audio_seconds": 276.7,
"model": "SenseVoiceSmall-int8"
}
text ist der zusammengesetzte gesamte Text; segments enthalten Zeitstempel – nützlich für die Frage „Was wurde in Sekunde X gesagt?“.
Umgebung
- Interpreter: Lokal gehostetes venv-python. Unter Windows Pfad ähnlich
~/.workbuddy/binaries/python/envs/default/Scripts/python.exe. Achtung:Scripts/, nicht Unix-typischbin/(beibin/pythonkommt „Datei nicht gefunden“). - Abhängigkeiten:
httpx,imageio-ffmpeg(Abruf),sherpa-onnx,numpy(Transkription),yt-dlp(allgemeine Plattformauflösung) – alle im selben venv installiert. Fehlt eine, meldet das Skript genau, welche. Fehlt yt-dlp, funktioniert Bilibili weiterhin (eigener Adapter), aber andere Websites scheitern. - ffmpeg: Wird von
imageio-ffmpegmit statischem Build mitgeliefert, keine Systeminstallation nötig. Umleitung mit UmgebungsvariableRV_FFMPEGoder--ffmpeg. - ASR-Modell-Cache:
~/.workbuddy/cache/read-video/models(ca. 240 MB, erster Download automatisch). Bei vorhandener Datei und passender Byteanzahl wird Download übersprungen.
Bekannte Grenzen
- CDN prüft häufig den Ursprung: CDN von Bilibili etc. verlangt
Referer, sonst 403. Mitresolve.pyoder--pageautomatisch gesetzt; bei manuell eingegebenem Direktlink--header 'Referer: https://www.bilibili.com'nicht vergessen (alle drei Skripte unterstützen dies, wiederholbar). - Direktlinks laufen ab: Die Signatur und Deadline der Plattform-Links sind zeitlich begrenzt. Einmal 403 erhalten →
resolve.pyerneut ausführen. - Plattformabdeckung = Abdeckung von yt-dlp (1700+ Sites), nicht selbst gepflegt. Bei fehlschlagender Auflösung zuerst
--backend yt-dlpverwenden, um die Rohfehler zu sehen; Sites, die Login/Cookies benötigen, scheitern – dann nur aufgeben oder Cookie manuell bereitstellen. yt-dlp selbst muss regelmäßig aktualisiert werden (nach Website-Änderungen) –pip install -U yt-dlp. - „Auflösung okay“ ≠ „Abruf möglich“: Die Auflösung liefert nur die URL. Bei Abruffehler zuerst unterscheiden, ob Auflösung oder Netzwerk nicht erreichbar war – getestet: Lokal zu YouTube / Vimeo nicht erreichbar (Proxy 502), aber TikTok / Weibo / Xigua erreichbar.
- HLS / m3u8: Keine Einzeldatei, daher schlägt die box-Analyse von
probe.pyfehl (enthälterror-Feld).grab.pyfunktioniert trotzdem; ffmpeg holt die benötigten Segmente selbst;content_lengthist leer, prozentualer Anteil nicht berechenbar. - DASH mit getrenntem Audio/Video: Viele Websites (Bilibili usw.) haben separate URLs für Video- und Audiostreams. Bilder vom Video-Stream, Transkription vom Audio-Stream – falsch verwendet gibt es leere Ergebnisse.
-ss-Genauigkeit durch GOP begrenzt: Für ein Bild zu einer bestimmten Sekunde muss vom nächsten Keyframe aus dekodiert werden. Die kleinste abrufbare Einheit ist daher „Keyframe bis Zielbild“, nicht „ein Byte-Intervall“.- ASR-Homophone: SenseVoiceSmall arbeitet gut mit Chinesisch, aber bei Eigennamen, Ortsnamen und alten Wörtern häufig falsche Zeichen (getestet: „阪泉之野“ → „版圈之也“, „涿鹿“ → „涿陆“). Vor dem Zitieren von Eigennamen kontextuell korrigieren, den transkribierten Text nicht als Tatsache übernehmen.
- Abhängigkeiten:
httpx+imageio-ffmpeg(Abruf) +sherpa-onnx+numpy(Transkription). Fehlt eine, meldet das Skript genau, welche installiert werden muss.
Ausführlichere Details, Fallstricke und Testdaten finden sich in references/methodology.md.
Das Verhältnis dieser Fähigkeit zum dsh-Plugin dsh-readvideo ist in references/dsh-dual-form.md beschrieben.