Communitygithub.com

eternova/long-video-course-notes

Claude Code Skill: Erstellt automatisch bebilderte Feishu-Dokumentnotizen aus langen Videokursen. Video→Canvas-Frames + KI-Erkennung + lark-cli-Push.

Was ist long-video-course-notes?

Dieses Skill für Claude Code automatisiert die Extraktion von Schlüsselmomenten aus langen Videokursen (Vorträge, Webinare, Live-Aufzeichnungen > 30 Minuten) und erstellt daraus strukturierte, bebilderte Notizen. Es verwendet einen cleveren Umweg: Anstatt das Video direkt herunterzuladen (was aufgrund von DRM und Token-Authentifizierung meist fehlschlägt), greift es mit Playwright auf das `<video>`-Element im Browser zu, zeichnet einzelne Frames mit `canvas.drawImage` auf und erstellt daraus eine visuelle Zusammenfassung. KI-gestützt klassifiziert es die Frames (Folien vs. Sprecher vs. Code-Editor) und priorisiert relevante Inhalte. Das Ergebnis ist ein Markdown-Artikel mit Zeitstempeln und Bildern, der standardmäßig automatisch als Feishu-Dokument (über lark-cli) veröffentlicht oder lokal als `.md`-Datei abgelegt wird. Das Skill unterstützt die meisten HTML5-Video-Websites (z. B. Bilibili, XuetangX, Youzan) und erfordert je nach Plattform eine vorherige Anmeldung des Benutzers.

Funktioniert mit✓Claude Code~Codex CLI✓Cursor
npx skills add eternova/long-video-course-notes

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Vorschau

Aus dem Skill-README

placeholder-frame-XXXXs.jpg

Dokumentation

Skill für bebilderte Notizen aus langen Videokursen

Wann verwenden

  • Der Benutzer gibt eine lange Video-URL (> 30 Minuten) und wünscht eine bebilderte Zusammenfassung, anstatt das Video komplett ansehen zu müssen.
  • Das Video ist ein Live-Stream-Recording / Online-Kurs / Vortrag, bei dem der Inhalt hauptsächlich aus Folien und Erklärungen des Dozenten besteht (keine Unterhaltungsvideos).
  • Das Ziel ist ein Feishu-Dokument (Standard) oder ein lokales Markdown-Dokument.
  • Die Videoplattform unterstützt <video> HTML5-Elemente (z. B. Xiaoe-tech, Shengcaiyoushu, Bilibili, NetEase Cloud Classroom, Douyin Education usw.).

Wann nicht verwenden

  • Das Video verwendet Flash / Silverlight / einen proprietären Player (kein HTML5 <video>-Element).
  • Das Video ist kürzer als 15 Minuten (dann ist direktes Ansehen schneller).
  • Der Videoinhalt ist reine Unterhaltung (Musik, Vlog, Animation) – das Erstellen von Frames ist nicht sinnvoll.
  • Der Benutzer wünscht ein transkribiertes Wortprotokoll – dieses Skill führt keine ASR durch; dafür sind separate Tools wie Whisper oder Feishu-Miaji erforderlich.

Kernprinzip (Warum dieser Ansatz funktioniert)

Videostreams verwenden oft HLS / MPEG-DASH mit Token-Authentifizierung – ein direkter Download mit ffmpeg würde zu einem 403 Fehler führen. Aber das <video>-Element im Browser hat den Stream bereits entschlüsselt und in einen Framebuffer umgewandelt. Mit canvas.drawImage(video, ...) kann man zu jedem beliebigen Zeitpunkt einen PNG-Schnappschuss machen und so die gesamte DRM-Kette umgehen.

Video-Element (nach DRM-Entschlüsselung) → canvas.drawImage → toDataURL → lokales jpg

Prozessübersicht

Schritt 1: Video-Zugänglichkeit prüfen (Login-Status / Vorhandensein von HTML5 video)
Schritt 2: Sonde – Dauer / Auflösung / Quelle ermitteln
Schritt 3: Grobe Abtastung (alle 8-10 Minuten ein Frame)
Schritt 4: KI klassifiziert Frames (Folie vs. Kopf vs. Editor vs. Q&A)
Schritt 5: Nachabtastung in Bereichen mit vielen Folien
Schritt 6: Markdown-Artikel schreiben
Schritt 7: In Feishu veröffentlichen (Ordner erstellen + Dokument erstellen + Bilder einfügen)

Schritt 1: Login / Zugänglichkeitsprüfung

Öffne die Video-URL mit dem Playwright MCP Browser:

mcp__playwright__browser_navigate(url=VideoURL)

Prüfung:

  • Wird zur Login-Seite weitergeleitet → siehe „Login-Strategie“ unten.
  • Wird die Kursseite direkt angezeigt → fahre mit Schritt 2 fort.

Login-Strategie (nach Priorität)

A. Plattform bereits eingeloggt (häufigster Fall, keine Aktion nötig) – Der Playwright MCP Browser behält Cookies innerhalb derselben Sitzung. Wenn der Benutzer kürzlich mit Playwright auf dieselbe Domain zugegriffen hat und eingeloggt war, sind die Cookies noch vorhanden. Einfach direkt navigieren.

B. Benutzer ist in seinem eigenen Chrome eingeloggt → Cookie-Import – Verwende ~/.claude/skills/gstack/browse und cookie-import-browser:

cd ~/.claude/skills/gstack/browse
bun run src/cli.ts goto "https://target.com/"  # Zuerst die Ziel-Domain aufrufen
bun run src/cli.ts cookie-import-browser chrome --domain target.com

⚠️ Häufige Fehlerursache: Die Cookies im Benutzer-Chrome sind abgelaufen (prüfe das Feld expires_utc).

C. Unbeaufsichtigter Login nicht möglich → Benutzer einbeziehen – Captchas / Slider / WeChat-QR-Codes sind nie automatisierbar. Bitte den Benutzer höflich, sich selbst einzuloggen:

  • Gib eine klare, einzeilige Anweisung: „Öffne X in Chrome, logge dich ein und sag mir Bescheid.“
  • Keine Textblöcke länger als 2 Absätze, keine 4 Optionen.

Vermeide unbedingt: Eine Endlosschleife aus Login-Versuchen → Cookie abgelaufen → Tool wechseln → Tool funktioniert nicht → wieder Tool wechseln. Maximal 2 Versuche, dann direkt zu Plan B wechseln (Benutzer macht Screenshots und schickt sie mir).

Schritt 2: Videoelement-Sonde

Führe nach dem Öffnen der Videoseite Folgendes aus:

() => {
  const v = document.querySelector('video');
  if (!v) return {error: 'no video element'};
  return {
    src: v.src || v.currentSrc,
    duration: v.duration,
    width: v.videoWidth,
    height: v.videoHeight,
    paused: v.paused,
    readyState: v.readyState,
    title: document.title,
    url: location.href
  };
}

Beurteilung:

  • duration > 0 und readyState >= 3 → Frame-Erfassung möglich
  • src ist eine blob:... URL → MSE wird verwendet, genau das, was wir brauchen
  • videoWidth >= 720 → Bildqualität ausreichend
  • readyState < 3 → Video nicht geladen, möglicherweise muss zuerst die Play-Taste gedrückt werden (stummgeschaltetes Autoplay überspringen)

Schritt 3: Grobe Abtastung – Schlüsselzeitpunkte

Bei einem N-minütigen Video werden zunächst ceil(N/8) Frames aufgenommen (alle 8 Minuten ein Frame), um den Gesamtrhythmus abzudecken:

async () => {
  const v = document.querySelector('video');
  v.pause(); v.muted = true;
  
  const duration = v.duration;
  const interval = 480; // 8 Minuten
  const targets = [];
  for (let t = 30; t < duration - 30; t += interval) targets.push(Math.floor(t));
  
  const seekTo = (t) => new Promise((resolve) => {
    const onSeeked = () => { v.removeEventListener('seeked', onSeeked); resolve(); };
    v.addEventListener('seeked', onSeeked);
    v.currentTime = t;
    setTimeout(() => { v.removeEventListener('seeked', onSeeked); resolve(); }, 8000);
  });
  
  const canvas = document.createElement('canvas');
  canvas.width = v.videoWidth;
  canvas.height = v.videoHeight;
  const ctx = canvas.getContext('2d');
  
  window.__frames = window.__frames || {};
  const out = [];
  for (const t of targets) {
    await seekTo(t);
    v.pause();
    await new Promise(r => setTimeout(r, 800)); // Auf Framebuffer warten
    ctx.drawImage(v, 0, 0, canvas.width, canvas.height);
    window.__frames[t] = canvas.toDataURL('image/jpeg', 0.8);
    out.push({t: v.currentTime, target: t, kb: (window.__frames[t].length / 1024) | 0});
  }
  return out;
}

Wichtige Details:

  • v.muted = true: Verhindert das Auslösen der Autoplay-Erkennung.
  • v.pause() wird nach jedem seek erneut aufgerufen (manchen Player setzen den Stream nach seek automatisch fort).
  • 800 ms Wartezeit: Das seeked-Event bedeutet nicht, dass der Frame bereits gerendert ist. Dem GPU-Puffer etwas Zeit geben, sonst wird der Frame des vorherigen Zeitpunkts erfasst.
  • Alle Frames werden in window.__frames zwischengespeichert, um zu vermeiden, dass 30 MB große Base64-Daten auf einmal zurückgegeben werden.

Schritt 4: Base64 auslesen und auf Platte speichern

Jeder evaluate-Aufruf kann maximal ~5 MB JSON zurückgeben. In Batches:

() => {
  const keys = Object.keys(window.__frames).sort((a,b) => +a - +b);
  const out = {};
  for (const k of keys.slice(START, START+4)) {
    out[k] = window.__frames[k].split(',')[1]; // Remove data:image/jpeg;base64,
  }
  return out;
}

Der Parameter filename von mcp__playwright__browser_evaluate schreibt das JSON in ~/.playwright-mcp/ oder ~/. Nicht direkt in /tmp (Sandbox-Einschränkung).

Skript zum Speichern:

import json, base64, os
os.makedirs(os.path.expanduser("~/course-frames"), exist_ok=True)
for batch in glob("~/course-frames/batch*.json"):
    d = json.load(open(batch))
    for t, b64 in d.items():
        secs = int(t)
        fn = f"frame-{secs:04d}s-{secs//60:03d}m{secs%60:02d}s.jpg"
        open(f"~/course-frames/{fn}", "wb").write(base64.b64decode(b64))

Schritt 5: KI klassifiziert Frames + Nachabtastung

Verwende das Read-Tool, um die Frames nacheinander zu betrachten (Bilder werden dir automatisch angezeigt). Jeden Frame taggen:

  • slide – Enthält Folientext, lesbar → Muss erfasst werden
  • editor – Cursor / VS Code / Terminal → Erfassen (für Demo-Szenarien nützlich)
  • talking_head – Nur das Gesicht des Dozenten → Überspringen
  • qa / chat – Kommentare und Interaktionen → 1-2 als Q&A-Repräsentanten auswählen

Dichte Nachabtastung: Wenn zwischen zwei aufeinanderfolgenden Frames ein Abstand von 8 Minuten liegt, der Inhalt aber völlig unterschiedlich ist (z. B. von Prinzip 01 zu Prinzip 05), deutet dies auf häufige Folienwechsel in diesem Abschnitt hin. Kehre zurück und nimm zusätzliche Frames auf:

// Im Bereich [start, end] alle 1-2 Minuten nachabtasten
const targets = [];
for (let t = START; t <= END; t += 90) targets.push(t);

Schritt 6: Markdown-Artikel schreiben

Artikelstruktur (je nach Kurstyp anpassen):

# Kurstitel · Vollständiger Überblick

> Kursinformationen: Dozent / Datum / Dauer / Plattform / Link zum Originalvideo

## Für wen ist dieser Artikel
(1 Absatz, direkte Beschreibung der Zielgruppe und was gelernt wird)

## Gesamtstruktur des Kurses

00:00 - 08:00 Einleitung 08:00 - 25:00 Teil 1 ... ...


## Teil 1 · ...
> ⏱ Zeitstempel: **16:00 – 25:00**

### Unterkapitel 1
> ⏱ **16:00** · Folie 9/38
![placeholder-frame-XXXXs.jpg](placeholder-frame-XXXXs.jpg)
(Schlüsselaussagen + Merksätze + Checkliste)

...

## Umsetzbare nächste Schritte für Anfänger
(Auf Wochen aufgeteilte Aktionspunkte)

## Anhang · Wichtige Ressourcen-Links

Schreibregeln:

  • Jeder Bildplatzhalter verwendet einen eindeutigen Dateinamen placeholder-frame-{seconds}s.jpg für die spätere Positionierung mit selection-with-ellipsis.
  • Jede Folie muss einen exakten Zeitstempel (hh:mm oder mm:ss) + Foliennummer enthalten.
  • Der Folieninhalt wird direkt in eine Tabelle übernommen, nicht zusammengefasst (der Benutzer will den Originaltext sehen).
  • Strukturierte Inhalte wie Gegenbeispiele/Beispiele / Checklisten werden in Markdown-Tabellen oder -Listen dargestellt.
  • Am Ende muss es „Umsetzbare nächste Schritte für Anfänger“ geben, aufgeteilt nach Wochen, sonst ist der Artikel nur lesend.

Schritt 7: In Feishu veröffentlichen

7.1 lark-cli Autorisierungsstatus prüfen

lark-cli auth status

Beim ersten Ausführen dieses Skills müssen folgende Scopes autorisiert werden (nacheinander, einen nach dem anderen):

  • space:folder:create – Ordner erstellen
  • docx:document:create – Dokument erstellen
  • docx:document:write_only + docx:document:readonly – Dokument bearbeiten (für Medieneinfügung)
  • docs:document.media:upload – Bilder in das Dokument hochladen

Autorisierungsbefehl:

nohup lark-cli auth login --scope "<scope>" > /tmp/lark-auth.log 2>&1 &
sleep 5
cat /tmp/lark-auth.log  # Device-Flow-URL abrufen
open "<URL>"  # Benutzer im Browser autorisieren lassen
# Warten, bis der Benutzer fertig ist
until grep -q "Autorisierung erfolgreich" /tmp/lark-auth.log; do sleep 3; done

⚠️ Scopes müssen einzeln hinzugefügt werden – mehrere Scopes gleichzeitig führen zu einem „invalid scope“-Fehler.

7.2 Ordner erstellen (Standardmäßig im persönlichen Stammverzeichnis des Benutzers)

lark-cli drive +create-folder --name "<Ordnername>"
# Gibt folder_token zurück, notieren

7.3 Bereinigte Version des Markdowns vorbereiten (ohne Bildplatzhalter)

sed '/!\[.*\](placeholder-frame/d' article.md > article-clean.md

7.4 Dokument erstellen

cd ~/course-frames/article  # Markdown-Pfad: mit cd hineinwechseln (CLI-Einschränkung)
lark-cli docs +create \
  --markdown @article-clean.md \
  --folder-token "<folder_token>" \
  --title "<Dokumenttitel>" \
  > /tmp/doc-create.json 2>&1
# doc_id extrahieren

7.5 Bilder nacheinander einfügen

Jedes Bild wird an einem Anker (Kapitelüberschrift oder eindeutiger Text) platziert:

lark-cli docs +media-insert \
  --doc "<doc_id>" \
  --file "frame-XXXXs-MMMmSSs.jpg" \
  --type image \
  --selection-with-ellipsis "<eindeutiger Kapitelüberschriftstext>" \
  --align center \
  --caption "<Zeitstempel · Beschreibung>"

Anchor-Regeln:

  • Die ersten 15 Zeichen der Kapitelüberschrift verwenden (eindeutig genug).
  • Bei mehreren Bildern im selben Kapitel → unterschiedliche eindeutige Textphrasen aus verschiedenen Absätzen.
  • Bei Fehlern das Format start...end verwenden: --selection-with-ellipsis "Erster Absatz...Letzter Absatz"

7.6 Fehlerbehandlung

  • Server-Timeout → 1x wiederholen. Die Dokumenterstellung bei mehr als 16k Zeichen neigt zu Timeouts. Den Artikel notfalls in zwei Teile aufteilen.
  • Fehler „Muss im Root-Ordner erstellt werden“ → folder_token falsch oder nicht übergeben.
  • Bildplatzhalter sind noch vorhanden → Rückstände von placeholder-frame-XXX im Markdown. Zuerst das Dokument mit --mode overwrite mit der bereinigten Version überschreiben, dann die Bilder einfügen.

Antipatterns (Fehler vermeiden)

❌ Antipattern 1: Versuchen, das Video direkt mit ffmpeg herunterzuladen

HLS + Token führt fast immer zu einem 403-Fehler. Selbst wenn es funktioniert, dauert der Download des gesamten Videos 20 Minuten, gefolgt von 5 Minuten für das ffmpeg-Frame-Extraktion. video → canvas benötigt 5 Sekunden für 16 Frames.

❌ Antipattern 2: Claude das ganze Video ansehen lassen

Du hast diese Fähigkeit nicht. Selbst wenn (mit einer multimodalen Video-API), wären die Kosten 50-mal höher als bei der Frame-Erfassung. Frames erfassen + Bilder ansehen + Zeitstempel verwenden ist der Hebel.

❌ Antipattern 3: drawImage aufrufen, ohne nach seek ausreichend zu warten

Das seeked-Event bedeutet nicht, dass das Frame rendern abgeschlossen ist. Der GPU-Puffer hat eine Verzögerung von 200-500ms. Mindestens 800ms, 1 Sekunde ist sicherer. Symptom: „Die ersten beiden Frames sind OK, ab dem dritten ist alles dasselbe Bild.“

❌ Antipattern 4: Scopes auf einmal anfordern

lark-cli auth login --scope "scope1 scope2 scope3" führt zu einem „invalid scope“-Fehler. Einen nach dem anderen.

❌ Antipattern 5: Versuchen, gleichzeitig Video- und Feishu-Operationen mit gstack browse auszuführen

gstack browse Daemon ist instabil beim Wechsel von Bash-Sitzungen. Playwright MCP für Video + lark-cli für Feishu-Operationen ist der sauberste Weg.

❌ Antipattern 6: Trotz abgelaufener Cookies weiter versuchen

Prüfen: expires_utc < aktuelle Zeit → den Benutzer direkt auffordern: „Bitte neu anmelden“, nicht 5 verschiedene Lösungen ausprobieren.

Häufige Benutzerfragen und Antworten

„Warum gibst du mir nicht direkt die Video-Untertitel?“ Antwort: Möglich, aber dafür muss separat Whisper ausgeführt werden (zusätzliche 5-10 Minuten). Wenn der Benutzer explizit das Transkript möchte, führe ich es aus. Ansonsten sind bebilderte Notizen der Standard.

„Warum nicht mehr Frames erfassen?“ Antwort: Jeder Frame ist ~200KB groß. Feishu-Dokumente haben eine Größenbeschränkung, und zu viele Bilder führen zu visueller Ermüdung. 12-18 Frames sind der Sweet Spot für eine 2-stündige Vorlesung.

„Kann ich auf Feishu verzichten?“ Antwort: Standardmäßig wird ~/course-frames/article/article.md + alle *.jpg im selben Verzeichnis ausgegeben. Der Benutzer kann diese manuell an jeden beliebigen Ort versenden.

Ausgabeformat

Nach jedem erfolgreichen Durchlauf muss die endgültige Antwort Folgendes enthalten:

  1. Feishu-Dokument-URL (direkt klickbar)
  2. Gesamtanzahl der erfassten Frames + Gesamtdauer des Kurses
  3. Inhaltsverzeichnis der Kapitel (damit der Benutzer einen Überblick erhält)
  4. Ein Satz mit „Nächster Schritt“ (z. B. „Sag mir in den Kommentaren, welchen Abschnitt ich ausführlicher beschreiben soll“)

Begleitskripte

scripts/capture-frames.js – Playwright-evaluate-Funktionen für Sonde und Frame-Erfassung (in mcp__playwright__browser_evaluate einfügen)

scripts/decode-batches.py – Batch-Dekodierung von Base64 zu JPG

scripts/insert-images.sh – Vorlage für das batchweise Einfügen von Bildern mit lark-cli

Siehe einen vollständig durchgeführten Fall: examples/deepsea-2026-04-23.md

Verwandte Skills