CommunitySchreiben & Editierengithub.com

aedis/Subtitlarr

Übersetze manuell ein Subtitlarr-Element, bei dem keine Cloud-Engine geeignet ist (Inhaltsfilterablehnungen, tote Anmeldedaten, erschöpftes Kontingent) oder wenn die Übersetzungsqualität von Claude spezifisch gewünscht wird.

Was ist Subtitlarr?

Dieses Skill ermöglicht einem KI-Agenten (wie Claude Code), ein einzelnes Subtitlarr-Element manuell zu übersetzen, indem es die Quelle abruft, die Übersetzung in kontextbewussten Abschnitten durchführt und das Ergebnis über die dedizierte API von Subtitlarr wieder einreicht. Es ist primär für Elemente gedacht, die von anderen Anbietern aufgrund von Inhaltsfilterablehnungen (z. B. von Gemini) blockiert werden, toten Anmeldedaten oder erschöpftem Kontingent – eine Situation, die ein standardgemäßes erneutes Senden nicht beheben kann. Das Skill arbeitet vollständig über HTTP mit einer Subtitlarr-Instanz (lokal oder remote) und benötigt keinen direkten Dateisystem- oder Datenbankzugriff. Es enthält detaillierte Anweisungen zur Klassifizierung fehlgeschlagener Elemente, zur sequenziellen Verarbeitung eines Elements pro Agent (um Kontextaufblähung und Token-Kosten zu vermeiden) und zur Sicherstellung der Indexparität vor dem Absenden. Darüber hinaus bietet es projektspezifische Übersetzungskonventionen wie den Umgang mit rassistischen Begriffen und historischer Sprache, basierend auf den Vorgaben des Benutzers.

Funktioniert mit✓Claude Code~Codex CLI~Cursor✓Gemini CLI
npx skills add https://github.com/aedis/Subtitlarr/tree/HEAD/.claude/skills/manual-translate

Installed? Explore more Schreiben & Editieren skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

In Ihrer bevorzugten KI fragen

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

Dokumentation

Manuelle Übersetzung über Subtitlarr's API

Es gibt zwei Endpunkte speziell dafür (hinzugefügt, weil Geminis Inhaltsfilter legitime historische/dramatische Dialoge blockiert – z. B. ein Film über Sklaverei, der zeitgenössische rassistische Begriffe verwendet –, die Claude kontextgetreu übersetzen kann):

  • GET /api/queue/{item_id}/manual-translation/source – löst den Quell-Untertitel des Elements auf die normale Weise auf und gibt {item_id, source_language, target_language, cue_count, dialogue_text} zurück. dialogue_text sind Cue-Blöcke im Format "<index>\n<content>", getrennt durch Leerzeilen – genau das Format, mit dem ein echter Anbieter angestoßen wird.
  • POST /api/queue/{item_id}/manual-translation – Body {"translated_text": "...", "model_name": "claude-code"}. Führt die Übersetzung durch dieselbe Wiederzusammenfassungs-/Integritätsprüfungs-/Disclaimer-/Upload-/DB-Tracking-Pipeline durch, die auch die Ausgabe eines echten Anbieters durchläuft. Zeichnet engine_used="manual", model_used=<model_name> auf – in der Warteschlange, im Verlauf und in der Disclaimer-Zeile, die in den Untertitel selbst eingebettet ist, ehrlich von einer echten API-gesteuerten Übersetzung unterscheidbar.

Auf der Serverseite erfolgt überhaupt kein LLM-Aufruf – der Server akzeptiert lediglich den geposteten translated_text und setzt ihn auf das ursprüngliche Cue-Timing wieder zusammen.

All dies funktioniert rein über HTTP gegen die Basis-URL der jeweiligen Subtitlarr-Instanz (lokale Entwicklung oder eine remote/NAS-Instanz) – es erfordert weder lokalen Dateisystem- noch Datenbankzugriff. Wenn Sie gegen eine Remote-Instanz arbeiten, bestätigen Sie vor dem Start, dass die Basis-URL erreichbar ist (ein einfacher curl auf GET /api/run/current ist ein guter Rauchtest).

Kompletten fehlgeschlagenen Rückstand durcharbeiten

Siehe EXAMPLE_PROMPT.md für einen echten Prompt, der diesen Workflow aufruft, plus Anmerkungen, was ihn zum Funktionieren gebracht hat.

Es gibt keinen dedizierten Endpunkt "Für manuelle Übersetzung geeignete Elemente auflisten" – erstellen Sie die Liste aus der vorhandenen Warteschlangenauflistung, rein über HTTP:

  1. Fehlgeschlagene Elemente auflisten, paginiert. GET /api/queue?status=failed&page=1&page_size=100, page erhöhen, bis data kürzer als page_size (oder leer) zurückkommt – total in der Antwort sagt Ihnen, wie viele Seiten zu erwarten sind. Jede Zeile enthält id, title, error_message.
  2. Vor jeder Aktion nach error_message klassifizieren – übersetzen Sie nicht wahllos jedes fehlgeschlagene Element manuell:
    • Inhaltsblockiert ("blocked its own response", "blocked this request", "prohibited content" usw.) – das eigentliche Ziel dieses Workflows. Nichts anderes behebt diese; der gleiche Inhalt wird auf die gleiche Weise gegen die gleiche Engine jedes Mal fehlschlagen.
    • Ratenbegrenzt ("rate limit hit (429)") – vorübergehend, KEIN Inhaltsproblem. Diese normal erneut versuchen über POST /api/queue/run-by-ids mit den Element-IDs, nicht manuelle Übersetzung.
    • Auth/Anmeldedatenfehler (401, "service account is deleted/disabled" usw.) – erfordert, dass der Benutzer den tatsächlichen API-Schlüssel/die Anmeldedaten korrigiert; das manuelle Übersetzen um einen toten Schlüssel herum hilft nicht bei zukünftigen Läufen, die dieselbe Engine verwenden.
    • Alles andere (5xx-Serverfehler, Timeouts, fehlerhafte Antworten) – normalerweise auch vorübergehend; ein einfacher erneuter Versuch ist besser geeignet als eine manuelle Übersetzung.
  3. Bestätigen Sie die Klassifizierung/Zielliste mit dem Benutzer, bevor Sie eine große Charge übersetzen – besonders für einen ersten Durchlauf gegen eine bestimmte Instanz, da "wie viele tatsächlich inhaltsblockiert sind" erst nach Ausführung der Schritte 1–2 bekannt ist.
  4. Arbeiten Sie die Liste der inhaltsblockierten Elemente einzeln mit dem unten stehenden Workflow für einzelne Elemente durch.

EINEN Hintergrund-Agenten PRO ELEMENT verwenden – niemals mehrere Elemente in einem Agenten bündeln

Dies ist keine nebensächliche Präferenz – das Bündeln mehrerer Elemente in einem einzigen Agenten vervielfacht die Token-Kosten, es spart sie nicht. Innerhalb eines Agenten-Gesprächs bleibt jeder Tool-Aufruf (Quellabruf, jeder Block-Schreibvorgang, jede Überprüfung) dauerhaft im Kontext dieses Agenten. Wenn ein Agent 5 Filme nacheinander übersetzt, findet die Arbeit an Film 2 zusätzlich zum gesamten akkumulierten Kontext von Film 1 statt (Blöcke, Quell-JSON, Tool-Ausgabe), Film 3 bezahlt für Filme 1+2 und so weiter – das 5. Element in einer Charge ist viel teurer als das 1., und die Charge als Ganzes kostet deutlich mehr als 5 unabhängige Agenten. Dies wurde empirisch bestätigt: Zwei 5-Film-Batch-Agenten verbrauchten jeweils ~520K Tokens (ungefähr 100K/Film, frontgeladen auf spätere Elemente in der Charge) – ein Einzelelement-Agent, der Inhalte vergleichbarer Länge verarbeitet, kostet einen Bruchteil davon, weil er niemals den übrig gebliebenen Kontext anderer Elemente mit sich führt.

Die festen "Kaltstart"-Kosten eines Agenten, der diese Skill-Datei und den Konventionenabschnitt liest, sind im Vergleich gering und sind KEIN Grund zum Bündeln. Rationalisieren Sie das Bündeln nicht als Kostenoptimierung – das ist es nicht. Wenn Sie gebeten werden, dies zu überdenken, tun Sie es nicht – der Bündelungsansatz wurde ausprobiert, gemessen und als schlechter bestätigt.

Also: Sobald die Klassifizierung und der Pilot-/Bestätigungsschritt (oben) abgeschlossen sind, starten Sie genau einen Hintergrund-Agenten pro Element-ID, egal ob es sich um einen Film oder eine einzelne TV-Episode handelt. Bei einer TV-Serie mit mehreren ausstehenden Episoden starten Sie dennoch einen Agenten pro Episode (nicht einen Agenten für die ganze Serie) – die Episoden benötigen auch nicht den Kontext der jeweils anderen.

Jeder Agent-Prompt muss in sich geschlossen sein – der Agent hat keine Erinnerung an dieses Gespräch – und sollte enthalten:

  1. Einen Link/Verweis auf diese Skill-Datei und ihren Workflow für einzelne Elemente.
  2. Die einzelne zu bearbeitende Element-ID (mit Titel, zur Plausibilitätsprüfung gegen das, was GET /api/queue/{id} zurückgibt).
  3. Die Basis-URL der Subtitlarr-Instanz.
  4. Eine Erinnerung, das Element intern zu chunkieren (~250-350 Cues pro Block, an eine einzige laufende lokale Datei angehängt, anstatt eine separate Scratch-Datei pro Block) und die Indexparität einmal am Ende zu überprüfen, bevor es eingereicht wird – dies ist ein anderes Anliegen als "keine Elemente bündeln" und beide Regeln gelten zusammen. Beachten Sie auch, dass Node.js die verfügbare Skripting-Laufzeitumgebung für den Überprüfungsschritt ist (nicht python3 – es ist nicht installiert; lassen Sie den Agenten keine Runden damit verschwenden, das selbst herauszufinden).
  5. Die Übersetzungskonventionen des Projekts (siehe unten), falls für den Inhalt relevant (z. B. historische rassistische Begriffe).
  6. Was zurückgemeldet werden soll: Erfolg/Misserfolg und Cue_Count, damit die Ergebnisse mit der masterhaften klassifizierten Liste abgeglichen werden können.

Agenten EINEN NACH DEM ANDEREN dispatchieren, streng sequenziell – niemals zwei parallel starten

"Ein Agent pro Element" (oben) bezieht sich darauf, was innerhalb eines Agenten-Gesprächs passiert – niemals darauf, wie viele Agenten gleichzeitig laufen. Verwechseln Sie die beiden nicht. Auch wenn jedes Element seinen eigenen unabhängigen Agenten bekommt, müssen Agenten dennoch einer nach dem anderen, in Reihe gestartet werden: einen einzelnen Agenten starten, warten, bis er fertig ist und zurückmeldet, DANN den nächsten starten. Geben Sie niemals zwei oder mehr Agent-Tool-Aufrufe in derselben Nachricht aus, und lassen Sie niemals mehr als einen manuellen Übersetzungsagenten gleichzeitig laufen – nicht zwei, nicht eine "kleine kontrollierte Welle", nicht zehn. Der Benutzer hat dies explizit und wiederholt gesagt: ein Agent nach dem anderen, einer nach dem anderen, niemals parallel.

Konkret: Starten Sie den Agenten für Element N im Hintergrund, stoppen Sie dann und warten Sie auf seine Fertigstellungsbenachrichtigung, bevor Sie den Agenten für Element N+1 starten. Starten Sie nicht mehrere Agenten vorab, "um die Pipeline in Bewegung zu halten" – das IST paralleles Dispatchen und ist genau das, was hier verboten ist, unabhängig davon, ob jeder Agent intern nur ein Element bearbeitet.

Workflow für einzelne Elemente

Hinweis zu Token-Kosten: Die dominierenden Kosten hier sind I/O-Overhead, nicht die Übersetzung selbst. Ein erster Live-Lauf auf einem Film mit 1188 Cues verbrauchte ~110K Tokens, weil er die Übersetzung jedes Blocks in eine Scratch-Datei schrieb und vor dem Kombinieren wieder las – 8 zusätzliche Schreib-+Lese-Roundtrips, plus Umgebungsentdeckungs-Overhead (fehlendes python3, Pfadinterpolationsprobleme). Die Schritte 2–5 unten sind so geschrieben, dass dies vermieden wird: Behalten Sie Blöcke in Ihrer eigenen Arbeitsausgabe, schicken Sie sie nicht durch das Dateisystem hin und her, und entdecken Sie die Umgebung nicht jedes Element neu.

  1. Bestätigen Sie das Element. GET /api/queue/{item_id} – überprüfen Sie status und error_message auf Übereinstimmung mit Ihren Erwartungen, bevor Sie Aufwand investieren.
  2. Rufen Sie die Quelle ab. GET /api/queue/{item_id}/manual-translation/source. Notieren Sie cue_count.
  3. Übersetzen Sie in Blöcken von etwa 250–350 Cues, nicht die gesamte Datei in einem Durchgang und auch keine 100–150-Cue-Ausschnitte – sehr lange Einzeldurchgänge sind die Ursache für Indexausrichtungsfehler (die Aufmerksamkeit driftet über eine lange kontinuierliche Ausgabe), aber jeder Block trägt auch festen Overhead (erneutes Darlegen von Format/Konventionen, ein Tool-Roundtrip), daher kosten weniger/größere Blöcke innerhalb dieses sicheren Bereichs insgesamt weniger. Schreiben Sie die übersetzte Ausgabe jedes Blocks direkt in eine laufende lokale Datei, benannt mit der ID dieses Elements (z. B. translated_<item_id>.txt im Scratchpad, nicht ein generisches translated.txt) während Sie fortfahren – erstellen Sie keine separate Scratch-Datei pro Block und lesen Sie einen Block nach dem Schreiben nicht zurück, um ihn isoliert erneut zu überprüfen. Eine Datei, angehängt, einmal am Ende für die Paritätsprüfung unten gelesen. Das Element-ID-Suffix ist wichtig, selbst wenn das Dispatchen sequenziell sein sollte (siehe unten): Ein generischer Dateiname hat bereits dazu geführt, dass die laufenden Blöcke eines Agenten von einem anderen gleichzeitig laufenden manuellen Übersetzungsagenten, der dasselbe Scratchpad-Verzeichnis verwendet, stillschweigend überschrieben wurden – benennen Sie jede Scratch-Datei (Quell-JSON, Blöcke, kombinierte Ausgabe) nach der Element-ID, damit ein Dispatch-Fehler nicht die Arbeit eines anderen Elements beschädigen kann.
  4. Bewahren Sie jeden Index exakt und überspringen Sie keine Cues. Jeder <index> in der Quelle muss genau einmal in Ihrer übersetzten Ausgabe erscheinen, mit derselben Indexnummer – reassemble() gleicht nach Index ab, nicht nach Position, und ein nicht eingeschlossener Cue fällt auf seinen ursprünglichen Text zurück (Qualität wird stillschweigend gemindert, kein Fehler). Behalten Sie Formatierungsauszeichnungen bei (z. B. <i>...</i> Kursiv für Liedtexte/inneren Monolog) – sie sind Teil des Inhalts und werden vor der Wiederzusammenstellung nicht entfernt.
  5. Überprüfen Sie die Indexparität vor dem Absenden, einmal, an der vollständig zusammengestellten Datei – nicht pro Block. Extrahieren Sie jede ^\d+$-Zeile und differieren Sie die resultierende Menge gegen 1..cue_count. Beachten Sie, dass dies nur fehlende/doppelte Indexnummern erfasst – es erfasst NICHT einen Block, in dem Cues zusammengeführt oder aufgeteilt wurden, sodass die Indizes immer noch 1-zu-1 laufen, aber der falsche Dialog unter einer bestimmten Nummer landet (z. B. das versehentliche Zusammenführen von zwei Quell-Cues in einem Ausgabeblock verschiebt den Inhalt jedes nachfolgenden Cues um eins, unsichtbar für diese Prüfung). Überprüfen Sie auch punktuell den tatsächlichen Inhalt gegen die Quelle an einigen Stellen (Blockgrenzen, ein Cue in der Mitte eines Blocks), nicht nur das Index-Skript. Verwenden Sie diesen exakten Node-Einzeiler für die Indexprüfung (Node ist nachweislich verfügbar; verschwenden Sie keine Zeit damit, ein fehlendes python3 zu entdecken oder zu umgehen – verwenden Sie einfach dies):
    node -e "const fs=require('fs');const t=fs.readFileSync('translated.txt','utf8');const idx=[...t.matchAll(/^(\d+)$/gm)].map(m=>+m[1]);const want=new Set(Array.from({length:<CUE_COUNT>},(_,i)=>i+1));const got=new Set(idx);const missing=[...want].filter(x=>!got.has(x));const extra=idx.filter((x,i)=>idx.indexOf(x)!==i);console.log('count',idx.length,'missing',missing,'dupes',[...new Set(extra)])"
    
    Beheben Sie jede Lücke/Dopplung, die dies meldet, vor dem Absenden.
  6. Absenden. POST /api/queue/{item_id}/manual-translation mit dem vollständig kombinierten translated_text (alle Blöcke konkateniert – der Endpunkt akzeptiert eine vollständige Einreichung pro Element, nicht einen Aufruf pro Block) und model_name (Standard "claude-code"; ändern Sie es nur, wenn Sie gebeten werden, die Arbeit anders zuzuschreiben).
  7. Bestätigen. GET /api/queue/{item_id} sollte status: "done", engine_used: "manual" anzeigen. Optional können Sie die hochgeladene Datei punktuell über GET /api/debug/{movie|episode}/{bazarr_id}/subtitle?lang=<code> überprüfen – der erste Cue sollte die Disclaimer-Zeile sein, die auf [<model_name>] endet, und das Timing von Cue 2 sollte exakt mit dem ersten echten Cue der Originalquelle übereinstimmen.

Übersetzungskonventionen für dieses Projekt

  • Rassistische Begriffe / zeitgenössisch akkurate anstößige Sprache: getreu übersetzen, Register bewahren – nicht abschwächen oder weglassen. Für Englisch "nigger" → Katalanisch, die Anweisung des Benutzers: "negrata" (die pejorative Form) verwenden, wenn ein Sklavenhändler/Aufseher es verächtlich verwendet, und "negre"/"negres" für neutrale/beschreibende Verwendungen. Dies spiegelt die eigene Unterscheidung des englischen Originals zwischen neutraler und beleidigender Verwendung wider.
  • Historisches/zeitgenössisches Register: formelle Anrede, Dialektmarker und zeitgenössische Sprechmuster beibehalten (Kontraktionen, fallen gelassene Grammatik in Dialogen versklavter Figuren, formelle Diktion in Dialogen der Oberschicht) – nicht alle auf modernes neutrales Katalanisch einebnen.
  • Liedtexte / Spirituals (z. B. "Roll, Jordan, Roll"): die Bedeutung übersetzen, nicht unbedingt eine singbare Entsprechung – dies sind Untertitel, kein Synchronskript.

Warum dies existiert, anstatt einfach die konfigurierte Engine erneut zu versuchen

Geminis Inhaltsfilter blockiert ganze Kategorien legitimer narrativer Inhalte (explizite Gewalt, historische rassistische Begriffe, erwachsene Themen in einem ernsten dramatischen Kontext), die ein Mensch oder Claude, der dieselbe Szene im Kontext liest, ohne Probleme übersetzen würde. Das wiederholte erneute Senden desselben Inhalts gegen dieselbe Engine hilft nicht – es ist ein Urteil über die Inhaltspolitik, kein vorübergehender Fehler – und riskiert die eigene Missbrauchsdurchsetzung des Anbieters gegen den API-Schlüssel. Dieser Workflow existiert genau für diese Klasse von feststeckenden Elementen, nicht als allgemeiner Ersatz für die normale Engine-Kaskade.

Verwandte Skills