Community라이팅 & 에디팅github.com

aedis/Subtitlarr

Subtitlarr 항목을 구성된 LLM 제공자가 아닌 Claude가 직접 수동 번역합니다. — 클라우드 엔진이 처리할 수 없는 항목(콘텐츠 필터 거부, 자격 증명 만료, 할당량 소진)이나 Claude의 자체 번역 품질이 특별히 필요한 경우에 사용합니다.

Subtitlarr란 무엇인가요?

이 스킬은 Subtitlarr API를 통해 사용자가 수동 번역을 요청한 자막 항목을 Claude가 직접 번역할 수 있게 해줍니다. 주로 Gemini와 같은 다른 엔진이 콘텐츠 필터로 거부하거나, 자격 증명이 만료되었거나, 할당량이 소진된 항목을 대상으로 합니다. 작동 방식: 먼저 GET /api/queue/{item_id}/manual-translation/source 엔드포인트로 원본 자막 데이터를 가져온 후, Claude가 이를 250~350개 큐 단위로 나누어 번역합니다. 번역이 완료되면 POST /api/queue/{item_id}/manual-translation 엔드포인트로 제출하며, 서버 측에서는 LLM 호출 없이 원본 큐 타이밍에 번역된 텍스트를 재조립합니다. 모든 과정은 HTTP를 통해 이루어지며 로컬 파일 시스템이나 데이터베이스 접근이 필요 없습니다. 중요한 제약 사항: 하나의 에이전트가 여러 항목을 일괄 처리하면 토큰 비용이 기하급수적으로 증가하므로, 반드시 항목당 하나의 에이전트를 순차적으로 실행해야 합니다. 또한 병렬 실행이 금지되며, 한 번에 하나의 에이전트만 실행하고 완료를 기다린 후 다음 에이전트를 시작해야 합니다. 이 스킬은 Cursor, Claude Code, Codex 등 모든 AI 에이전트 플랫폼에서 사용할 수 있습니다.

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

Installed? Explore more 라이팅 & 에디팅 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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"}. 실제 제공자의 출력이 거치는 동일한 재조립/무결성 검사/고지사항/업로드/DB 추적 파이프라인을 통해 번역을 처리합니다. engine_used="manual", model_used=<model_name>을 기록하며, 대기열, 기록 및 자막 자체에 포함된 고지문에서 실제 API 기반 번역과 구별할 수 있습니다.

서버 측에서는 LLM 호출이 전혀 발생하지 않습니다 — 서버는 전달된 translated_text를 그대로 받아 원본 큐 타이밍에 재조립하기만 합니다.

이 모든 작업은 주어진 Subtitlarr 인스턴스의 기본 URL에 대해 순수하게 HTTP로 작동합니다(로컬 개발 환경 또는 원격/NAS 인스턴스) — 로컬 파일 시스템이나 데이터베이스 접근이 필요하지 않습니다. 원격 인스턴스에서 작업하는 경우, 기본 URL에 접근 가능한지 확인하십시오(GET /api/run/current에 대한 일반 curl이 좋은 사전 테스트입니다).

전체 실패 백로그 작업 처리

이 워크플로우를 호출하는 실제 프롬프트와 작동하게 만든 참고 사항은 EXAMPLE_PROMPT.md를 참조하십시오.

"수동 번역에 적격한 항목 목록"을 위한 전용 엔드포인트는 없습니다 — 기존 대기열 목록에서 HTTP를 통해 목록을 작성하십시오:

  1. 실패한 항목을 페이지별로 나열합니다. GET /api/queue?status=failed&page=1&page_size=100, data가 page_size보다 짧아지거나 빈 배열이 될 때까지 page를 증가시킵니다 — 응답의 total이 예상 페이지 수를 알려줍니다. 각 행에는 id, title, error_message가 포함됩니다.
  2. 어떤 항목이든 건드리기 전에 error_message로 분류합니다 — 모든 실패한 항목을 무차별적으로 수동 번역하지 마십시오:
    • 콘텐츠 차단됨 ("blocked its own response", "blocked this request", "prohibited content" 등) — 이 워크플로우의 실제 대상입니다. 다른 방법으로는 해결되지 않습니다. 동일한 콘텐츠는 동일한 엔진에 대해 매번 같은 방식으로 실패합니다.
    • 속도 제한됨 ("rate limit hit (429)") — 일시적이며, 콘텐츠 문제가 아닙니다. 수동 번역이 아닌 POST /api/queue/run-by-ids를 통해 해당 항목 ID로 일반 재시도를 하십시오.
    • 인증/자격 증명 오류 (401, "service account is deleted/disabled" 등) — 사용자가 실제 API 키/자격 증명을 수정해야 합니다. 유효하지 않은 키를 우회하여 수동 번역해도 동일한 엔진을 사용하는 향후 실행에 도움이 되지 않습니다.
    • 기타 모든 오류 (5xx 서버 오류, 타임아웃, 잘못된 형식의 응답) — 일반적으로 일시적입니다. 수동 번역보다는 일반 재시도가 더 적절합니다.
  3. 대량 번역 전에 사용자에게 분류/대상 목록을 확인합니다 — 특히 특정 인스턴스에 대한 첫 번째 시도인 경우, "실제로 콘텐츠 차단된 항목이 몇 개인지"는 1~2단계를 실행한 후에야 알 수 있습니다.
  4. 아래 단일 항목 워크플로우를 사용하여 콘텐츠 차단 목록을 항목별로 하나씩 처리합니다.

항목당 하나의 백그라운드 에이전트 사용 — 여러 항목을 하나의 에이전트에 일괄 처리하지 마십시오

이는 사소한 선호도가 아닙니다. 여러 항목을 단일 에이전트에 일괄 처리하면 토큰 비용이 절약되는 것이 아니라 증가합니다. 하나의 에이전트 대화 내에서 모든 도구 호출(소스 가져오기, 각 청크 쓰기, 모든 검증 실행)은 해당 에이전트의 컨텍스트에 영구적으로 남습니다. 하나의 에이전트가 5개의 영화를 순차적으로 번역하면, 영화 2의 작업은 영화 1의 전체 누적 컨텍스트(청크, 소스 JSON, 도구 출력) 위에서 이루어지고, 영화 3은 영화 1+2의 비용을 부담하며, 이와 같이 계속됩니다 — 배치의 5번째 항목은 첫 번째 항목보다 훨씬 더 비싸며, 배치 전체는 5개의 독립적인 에이전트보다 현저히 더 많은 비용이 듭니다. 이는 경험적으로 확인되었습니다: 두 개의 5개 영화 배치 에이전트 각각이 약 520K 토큰을 소모했습니다(영화당 약 100K, 배치 내 후반 항목에 더 많이 부과됨) — 유사한 길이의 콘텐츠를 처리하는 단일 항목 에이전트는 다른 항목의 남은 컨텍스트를 전혀 이월하지 않기 때문에 그 비용의 극히 일부만 소모합니다.

이 스킬 파일과 규칙 섹션을 읽는 고정된 "콜드 스타트" 비용은 이에 비해 작으며, 일괄 처리를 정당화하는 이유가 되지 않습니다. 일괄 처리를 비용 최적화로 합리화하지 마십시오 — 그렇지 않습니다. 재고를 요청받더라도 하지 마십시오 — 일괄 처리 접근 방식은 시도되었고, 측정되었으며, 더 나쁘다는 것이 확인되었습니다.

따라서: 위의 분류 및 파일럿/확인 단계가 완료되면, 영화든 단일 TV 에피소드든 항목 ID당 정확히 하나의 백그라운드 에이전트를 생성하십시오. 여러 대기 중인 에피소드가 있는 TV 쇼의 경우에도 여전히 에피소드당 하나의 에이전트를 생성하십시오(전체 쇼에 대해 하나의 에이전트가 아님) — 에피소드는 서로의 컨텍스트가 필요하지 않습니다.

각 에이전트 프롬프트는 자체적으로 완결되어야 합니다 — 에이전트는 이 대화에 대한 기억이 없습니다 — 그리고 다음을 포함해야 합니다:

  1. 이 스킬 파일 및 단일 항목 워크플로우에 대한 링크/참조.
  2. 작업할 단일 항목 ID(제목 포함, GET /api/queue/{id}가 반환하는 내용과 일치하는지 확인하기 위해).
  3. Subtitlarr 인스턴스의 기본 URL.
  4. 항목 내 청크 분할(큐 약 250~350개, 청크당 별도의 스크래치 파일이 아닌 하나의 단일 로컬 파일에 추가) 및 제출 전 마지막에 한 번 인덱스 일치를 확인하라는 알림 — 이는 "항목 일괄 처리 금지"와는 다른 문제이며 두 규칙이 함께 적용됩니다. 또한 Node.js가 확인 단계에 사용 가능한 스크립팅 런타임입니다(python3가 아닙니다 — 설치되어 있지 않습니다. 에이전트가 직접 발견하는 데 시간을 낭비하지 않도록 하십시오).
  5. 콘텐츠와 관련된 경우 프로젝트의 번역 규칙(아래 참조)(예: 시대 비속어).
  6. 보고할 내용: 성공/실패 여부 및 cue_count, 마스터 분류 목록과 결과를 대조할 수 있도록.

에이전트를 하나씩, 엄격히 순차적으로 발송 — 절대 두 개를 병렬로 실행하지 마십시오

"항목당 하나의 에이전트"(위)는 하나의 에이전트 대화 내부에 있는 것에 관한 것입니다 — 한 번에 실행되는 에이전트 수와는 전혀 관련이 없습니다. 둘을 혼동하지 마십시오. 각 항목이 고유한 독립적인 에이전트를 가지더라도, 에이전트는 한 번에 하나씩, 순차적으로 발송되어야 합니다: 단일 에이전트를 시작하고, 완료되어 보고할 때까지 기다린 후, 그런 다음 다음 에이전트를 시작하십시오. 동일한 메시지에서 두 개 이상의 Agent 도구 호출을 절대 발행하지 말고, 한 번에 둘 이상의 수동 번역 에이전트가 실행 중이어서는 안 됩니다 — 둘도 안 되고, "작은 통제된 파동"도 안 되고, 열 개도 안 됩니다. 사용자는 명시적으로 그리고 반복적으로 다음과 같이 말했습니다: 하나씩, 한 번에 하나씩, 절대 병렬로 하지 않습니다.

구체적으로: 항목 N의 에이전트를 백그라운드에서 시작한 다음, 항목 N+1의 에이전트를 시작하기 전에 완료 알림을 위해 중지하고 기다리십시오. "파이프라인을 계속 움직이기 위해" 여러 에이전트를 미리 시작하지 마십시오 — 그것은 병렬 발송이며, 각 에이전트가 내부적으로 하나의 항목만 건드리더라도 여기서 정확히 금지된 것입니다.

단일 항목 워크플로우

토큰 비용 참고: 여기서 지배적인 비용은 번역 자체가 아니라 I/O 오버헤드입니다. 1188개 큐 영화에 대한 첫 번째 라이브 실행은 각 청크의 번역을 스크래치 파일에 쓰고 결합하기 전에 다시 읽기 때문에 약 110K 토큰을 소모했습니다 — 8번의 추가 쓰기+읽기 왕복, 그리고 환경 발견 오버헤드(python3 누락, 경로 보간 문제). 아래 2~5단계는 이를 방지하기 위해 작성되었습니다: 청크를 자신의 작업 출력에 유지하고, 파일 시스템을 통해 왕복시키지 말며, 각 항목마다 환경을 다시 발견하지 마십시오.

  1. 항목을 확인합니다. GET /api/queue/{item_id} — status와 error_message가 예상과 일치하는지 확인한 후 작업을 시작합니다.
  2. 소스를 가져옵니다. GET /api/queue/{item_id}/manual-translation/source. cue_count를 기록합니다.
  3. 약 250~350개 큐 단위로 청크를 번역합니다. 전체 파일을 한 번에 번역하지 말고 100~150개 큐 조각으로도 하지 마십시오 — 매우 긴 단일 패스는 인덱스 정렬 실수가 발생하는 곳입니다(긴 연속 출력에서 주의가 분산됨). 그러나 각 청크는 또한 고정된 오버헤드(형식/규칙 재진술, 도구 왕복)를 수반하므로, 안전한 범위 내에서 더 적고 더 큰 청크가 전체적으로 비용이 덜 듭니다. 각 청크의 번역된 출력을 이 항목의 ID로 명명된 하나의 단일 로컬 파일에 직접 추가합니다(예: 스크래치패드의 translated_<item_id>.txt, 일반적인 translated.txt가 아님) — 청크당 별도의 스크래치 파일을 생성하지 말고, 청크를 쓴 후 다시 읽어 격리된 상태로 재확인하지 마십시오. 하나의 파일, 추가 전용, 마지막에 한 번 읽어 아래 일치 검사를 수행합니다. 발송이 순차적이어야 함에도 항목 ID 접미사가 중요한 이유(아래 참조): 일반적인 파일 이름은 이미 동일한 스크래치패드 디렉토리를 공유하는 동시에 실행되는 다른 수동 번역 에이전트에 의해 진행 중인 청크가 자동으로 덮어쓰여지는 문제를 일으켰습니다 — 따라서 소스 JSON, 청크, 결합된 출력 등 모든 스크래치 파일을 항목 ID로 명명하여 발송 실수로 인해 다른 항목의 작업이 손상되지 않도록 하십시오.
  4. 모든 인덱스를 정확히 유지하고, 큐를 건너뛰지 마십시오. 소스의 모든 <index>는 번역된 출력에 동일한 인덱스 번호로 정확히 한 번 나타나야 합니다 — reassemble()은 위치가 아닌 인덱스로 일치시키며, 포함하지 않은 큐는 원래 언어 텍스트로 대체됩니다(오류 없이 자동으로 품질이 저하됨). 형식 마크업(예: 가사/내적 독백의 <i>...</i>)을 유지하십시오 — 내용의 일부이며, 재조립 전에 제거되지 않습니다.
  5. 제출 전에 인덱스 일치를 확인합니다. 한 번, 완전히 조립된 파일에 대해 — 청크별로 확인하지 마십시오. 모든 ^\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)])"
    
    이 검사에서 보고하는 모든 간격이나 중복을 제출 전에 수정하십시오.
  6. 제출합니다. POST /api/queue/{item_id}/manual-translation에 전체 결합된 translated_text(모든 청크가 연결됨 — 엔드포인트는 항목당 하나의 완전한 제출을 받으며, 청크당 한 번의 호출이 아님)와 model_name(기본값 "claude-code", 작업을 다르게 귀속시키도록 요청받은 경우에만 변경)을 포함합니다.
  7. 확인합니다. 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의 콘텐츠 안전 필터는 합법적인 서사 콘텐츠의 전체 범주(명시적 폭력, 시대 비속어, 심각한 극적 맥락의 성인 주제)를 차단합니다. 동일한 콘텐츠를 동일한 엔진에 반복적으로 재시도해도 도움이 되지 않습니다 — 이는 콘텐츠 정책 판단이지 일시적인 실패가 아니며, API 키에 대한 제공자의 자체 남용 방지 조치가 발동될 위험이 있습니다. 이 워크플로우는 정확히 그러한 종류의 막힌 항목을 위해 존재하며, 일반 엔진 계단식의 대체품이 아닙니다.

관련 스킬