透過 Subtitlarr API 進行手動翻譯
為此目的存在兩個端點(之所以新增,是因為 Gemini 的內容過濾器會封鎖合法的歷史/戲劇對話——例如,一部關於奴隸制度的電影使用了符合時代背景的歧視用語——而 Claude 能在上下文中忠實地翻譯這類內容):
GET /api/queue/{item_id}/manual-translation/source— 以正常方式解析項目的來源字幕,並返回{item_id, source_language, target_language, cue_count, dialogue_text}。dialogue_text是以"<index>\n<content>"格式呈現的提示區塊,區塊之間以空行分隔——這正是真實提供者被提示的格式。POST /api/queue/{item_id}/manual-translation— 請求主體{"translated_text": "...", "model_name": "claude-code"}。將翻譯結果通過真實提供者輸出所經歷的相同重新組裝/完整性檢查/免責聲明/上傳/資料庫追蹤管道進行處理。在佇列、歷史記錄以及嵌入字幕本身的免責聲明行中,記錄engine_used="manual"、model_used=<model_name>——這與真實的 API 驅動翻譯在這些記錄中無法區分。
伺服器端完全不執行 LLM 呼叫——伺服器僅接受提交的 translated_text,並將其重新組裝到原始提示時間上。
所有這些都僅透過 HTTP 對你指定的 Subtitlarr 實例的基本 URL 進行(無論是本地開發實例,還是遠端/NAS 實例)——無需本地檔案系統或資料庫存取權限。如果針對遠端實例進行操作,請在開始前確認基本 URL 可達(對 GET /api/run/current 執行一個簡單的 curl 是一個良好的連通性測試)。
處理整個失敗待辦事項
請參閱 EXAMPLE_PROMPT.md 以獲取一個實際調用此工作流程的提示範例,以及使其運作的相關說明。
沒有專門的「列出符合手動翻譯條件的項目」端點——請從現有的佇列清單中構建列表,僅透過 HTTP 進行:
- 列出失敗項目,並進行分頁。
GET /api/queue?status=failed&page=1&page_size=100,遞增page直到data返回的長度小於page_size(或為空)——回應中的total字段告訴你預期有多少頁。每一行都包含id、title、error_message。 - 在執行任何操作之前,先根據
error_message進行分類——不要不加區別地手動翻譯每個失敗項目:- 內容被封鎖(
"blocked its own response"、"blocked this request"、"prohibited content"等)——此工作流程的實際目標。其他方法無法解決這些問題;相同的內容對相同的引擎會每次都相同地失敗。 - 速率限制(
"rate limit hit (429)")——暫時性的,不是內容問題。透過POST /api/queue/run-by-ids正常重試這些項目(附上項目 ID),不要進行手動翻譯。 - 身份驗證/憑證錯誤(401、「服務帳戶已被刪除/停用」等)——需要用戶修復實際的 API 金鑰/憑證;繞過失效金鑰進行手動翻譯對未來使用相同引擎的運行沒有幫助。
- 其他所有錯誤(5xx 伺服器錯誤、超時、格式錯誤的回應)——通常也是暫時性的;普通重試比手動翻譯更合適。
- 內容被封鎖(
- 在翻譯大量批次之前,先與用戶確認分類/目標清單——特別是針對某個實例的第一次操作,因為只有執行了步驟 1-2 之後,才能知道「實際有多少項目是內容被封鎖的」。
- 使用下面的單一項目工作流程,逐項處理內容被封鎖的清單。
每個項目使用一個後台代理——絕不將多個項目批次處理到一個代理中
這不是一個次要偏好——將多個項目批次處理到一個代理中會倍乘令牌成本,而不是節省成本。在一個代理的對話中,每次工具呼叫(獲取來源、每次區塊寫入、每次驗證運行)都會永久保留在該代理的上下文中。如果一個代理順序翻譯 5 部電影,電影 2 的工作是在電影 1 的整個累積上下文(區塊、來源 JSON、工具輸出)之上進行的,電影 3 需要為電影 1+2 的上下文付費,以此類推——批次中的第 5 個項目的成本遠高於第 1 個,而且整個批次的總成本顯著高於 5 個獨立代理的成本。這已通過實證確認:兩個各處理 5 部電影的批次代理,每個都消耗了約 520K 個令牌(大約每部電影 100K,且集中在批次中的後續項目)——而一個處理類似長度內容的單一項目代理,其成本僅為此的一小部分,因為它永遠不會攜帶其他項目的殘留上下文。
代理閱讀此技能文件和慣例部分的固定「冷啟動」成本相對較小,並且不是批次處理的理由。不要將批次處理合理化為成本優化——它並非如此。如果被要求重新考慮這一點,不要這樣做——批次處理方法已被嘗試、測量並確認效果更差。
因此:一旦完成了上述的分類和試點/確認步驟,就為每個項目 ID 創建一個後台代理,無論是一部電影還是一個單集的電視劇。對於一個有多個待處理劇集的電視節目,仍然需要為每個劇集創建一個代理(而不是為整個節目創建一個代理)——這些劇集也不需要彼此的上下文。
每個代理提示必須是自包含的——該代理不記得本次對話——並且應包含:
- 指向此技能文件及其單一項目工作流程的鏈接/參考。
- 要處理的單一項目 ID(附上標題,以便與
GET /api/queue/{id}返回的內容進行合理性檢查)。 - Subtitlarr 實例的基本 URL。
- 關於在項目內部進行分塊(大約每塊 250-350 條提示,追加到一個正在運行的本地文件中,而不是為每個區塊創建一個暫存文件),以及最後在提交前一次性驗證索引一致性的提醒——這與「不要批次處理項目」是不同的問題,兩個規則都必須同時遵守。同時注意,Node.js 是可用於驗證步驟的腳本運行時(而不是
python3——它沒有安裝;不要讓代理浪費時間自己發現這一點)。 - 項目的翻譯慣例(見下文),如果與內容相關(例如時代背景的歧視用語)。
- 要回報的內容:成功/失敗和提示計數(cue_count),以便結果可以與主分類清單進行核對。
嚴格按順序、一次一個地調度代理——絕不並行啟動兩個
「每個項目一個代理」(上文)是關於一個代理的對話內部的事務——它絕不涉及有多少代理同時運行。不要將兩者混淆。即使每個項目都有自己的獨立代理,代理仍然必須一次一個、按順序啟動:啟動一個代理,等待它完成並回報結果,然後再啟動下一個。切勿在同一條訊息中發出兩個或更多的 Agent 工具呼叫,並且永遠不要同時有多個手動翻譯代理在運行——不是兩個,不是「小規模受控波次」,也不是十個。用戶已明確且反覆說明:一個接一個代理,一次一個,絕不並行。
具體來說:在後台啟動項目 N 的代理,然後停止並等待其完成通知,然後再啟動項目 N+1 的代理。不要預先啟動多個代理「以保持管道順暢」——那就是並行調度,並且這裡明確禁止這樣做,無論每個代理內部是否只處理一個項目。
單一項目工作流程
令牌成本注意: 此處的主要成本是 I/O 開銷,而不是翻譯本身。在對一個包含 1188 條提示的電影進行首次實際運行時,消耗了約 110K 個令牌,因為它將每個區塊的翻譯寫入一個暫存文件,然後在合併之前讀回它——總共 8 次額外的寫入+讀取往返,加上環境發現開銷(缺少 python3、路徑插值問題)。下面的步驟 2-5 旨在避免這種情況:將區塊保留在你自己的工作輸出中,不要通過檔案系統進行往返,並且不要為每個項目重新發現環境。
- 確認項目。
GET /api/queue/{item_id}——檢查status和error_message是否符合預期,然後再投入精力。 - 獲取來源。
GET /api/queue/{item_id}/manual-translation/source。注意cue_count。 - 以約 250-350 條提示為一個區塊進行翻譯,不要一次性翻譯整個文件,也不要使用 100-150 條提示的切片——非常長的單次傳遞是索引對齊錯誤發生的地方(注意力在長時間連續輸出中會漂移),但每個區塊也帶有固定的開銷(重新陳述格式/慣例、一次工具往返),因此在安全範圍內使用更少/更大的區塊總體成本更低。將每個區塊的翻譯輸出直接寫入一個正在運行的本地文件中,以該項目的 id 命名(例如存在暫存區的
translated_<item_id>.txt,而不是通用的translated.txt)——不要為每個區塊創建單獨的暫存文件,並且不要為了隔離驗證而讀回一個已寫入的區塊。一個文件,追加寫入,最後讀取一次以進行下面的一致性檢查。即使調度應該是順序的(見下文),項目 id 後綴也很重要:通用文件名已經導致一個代理正在進行的區塊被另一個共享相同暫存目錄的並行運行的手動翻譯代理靜默覆寫——為所有暫存文件(來源 JSON、區塊、合併輸出)使用項目 id 命名,以便調度失誤不會破壞另一個項目的工作。 - 精確保留每個索引,不要跳過提示。 來源中的每個
<index>必須在你的翻譯輸出中恰好出現一次,並使用相同的索引號——reassemble()按索引匹配,而不是按位置,並且你未包含的提示將回退到其原始語言文本(靜默地降低質量,而不是報錯)。保留格式標記(例如,歌詞/內心獨白上的<i>...</i>斜體)——它是內容的一部分,不會在重新組裝前被去除。 - 在提交前一次性驗證索引一致性,針對完整組裝好的文件——而不是每個區塊。提取所有
^\d+$行,並將結果集合與1..cue_count進行比對。請注意,這只能捕獲缺少/重複的索引號碼——它無法捕獲一個區塊中提示被合併或拆分的情況,使得索引仍然以 1 對 1 的方式運行,但錯誤的對話最終出現在給定的號碼下(例如,意外合併兩個來源提示到一個輸出區塊中,會使每個後續提示的內容都偏移一個,而此檢查無法發現)。除了索引腳本之外,還應在幾個點上(區塊邊界、一個區塊中間的提示)對實際內容與來源進行抽樣檢查。對於索引檢查,請使用以下精確的 Node 單行命令(確認 Node 可用;不要花費時間去發現或繞過缺失的python3——直接使用此命令):
在提交前修復此命令報告的任何間隙或重複。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)])" - 提交。
POST /api/queue/{item_id}/manual-translation,包含完整的合併translated_text(所有區塊的串聯——該端點每個項目只接受一次完整的提交,而不是每個區塊一次呼叫)和model_name(默認為"claude-code";僅當要求以不同方式歸屬工作時才更改它)。 - 確認。
GET /api/queue/{item_id}應顯示status: "done"、engine_used: "manual"。可選地,透過GET /api/debug/{movie|episode}/{bazarr_id}/subtitle?lang=<code>抽樣檢查已上傳的文件——第一條提示應是免責聲明行,結尾是[<model_name>],而提示 2 的時間應與原始來源的第一個真實提示完全匹配。
此項目的翻譯慣例
- 種族歧視用語 / 符合時代背景的冒犯性語言:忠實翻譯,保留語氣——不要淡化或省略。對於英語 "nigger" 到加泰隆語,用戶的指導:當奴隸主/監工角色以鄙視的語氣使用時,使用 "negrata"(貶義形式);對於中性/描述性的使用,使用 "negre"/"negres"。這反映了英語原文本身在中性用法和歧視用語之間的區別。
- 歷史/時代語氣:保持正式稱呼、方言標記和時代語言模式(縮略語、被奴役角色對話中的語法缺失、上層階級角色對話中的正式措辭)——不要將所有角色都簡化為現代中性的加泰隆語。
- 歌詞 / 靈歌(例如 "Roll, Jordan, Roll"):翻譯其意義,不一定要翻譯成可唱的版本——這些是字幕,不是配音腳本。
為什麼存在此功能而不是僅僅重試已設定的引擎
Gemini 的內容安全過濾器會封鎖一整類合法的敘事內容(明顯的暴力、時代歧視用語、嚴肅戲劇背景下的成人主題),而人類或 Claude 在閱讀相同場景的上下文時,可以毫無問題地翻譯這些內容。對相同的引擎重複重試相同的內容沒有幫助——這是一個內容政策的判斷,而不是暫時性失敗——並且有可能導致提供者對 API 金鑰採取自身的濫用執法措施。此工作流程正是為了解決這類卡住的項目而存在,而不是作為正常引擎級聯的通用替代方案。