Translates SRT subtitle files using LLM APIs (OpenRouter/Llama). Use when user wants to translate subtitles, SRT files, or needs batch subtitle translation for movies/series. Handles HTML tags, batch processing, retries, and cost estimation.
Translates SRT subtitle files using LLM APIs with batch processing and cost estimation.
/plugin marketplace add theflysurfer/claude-skills-marketplace/plugin install theflysurfer-claude-skills-marketplace@theflysurfer/claude-skills-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/api-providers.mdscripts/translate_srt.pyTranslate SRT subtitle files efficiently using LLM APIs with proper cost management.
.srt subtitle filesAlways ask the user which provider to use:
Which API provider do you want to use?
1. **OpenRouter** (Recommended) - Best price/quality, many models
2. **OpenAI** - GPT-4o-mini, reliable but needs credit card
3. **Ollama Local** - Free, runs on your machine (needs GPU)
4. **Ollama Hostinger** - Free, runs on your VPS
For large batches (100+ episodes): OpenRouter or Ollama Hostinger
For small batches (<20 episodes): Any option works
| Provider | API URL | Auth |
|---|---|---|
| OpenRouter | https://openrouter.ai/api/v1/chat/completions | Bearer token |
| OpenAI | https://api.openai.com/v1/chat/completions | Bearer token |
| Ollama Local | http://localhost:11434/api/chat | None |
| Ollama Hostinger | http://YOUR_VPS_IP:11434/api/chat | None |
Ask user about subtitle complexity:
What type of content are you translating?
A) Simple dialogue (sitcoms, slice-of-life) → Fast/cheap model
B) Standard content (action, drama) → Balanced model
C) Complex content (technical, poetry, wordplay) → Quality model
| Complexity | OpenRouter | OpenAI | Ollama |
|---|---|---|---|
| Simple | mistral-7b-instruct ($0.03/M) | - | mistral:7b |
| Standard | llama-3.3-70b-instruct ($0.11/M in, $0.34/M out) | gpt-4o-mini ($0.15/M in, $0.60/M out) | llama3.1:70b |
| Complex | claude-3.5-sonnet ($3/M in, $15/M out) | gpt-4o ($2.50/M in, $10/M out) | llama3.1:70b |
For servers with limited RAM, use lightweight multilingual models:
| Model | RAM Required | Quality | Speed | Best For |
|---|---|---|---|---|
aya:8b | ~6GB | Good | Fast | Multilingual - 100+ languages native |
mistral:7b | ~5GB | OK | Fast | European languages |
gemma2:9b | ~7GB | Good | Medium | General purpose |
Aya 8B is recommended for subtitle translation on VPS because:
# Install on VPS
ollama pull aya:8b
# Test
ollama run aya:8b "Translate to French: Hello, how are you?"
def search_models(min_context=8000):
"""Search OpenRouter for available models with pricing"""
r = requests.get("https://openrouter.ai/api/v1/models")
models = r.json().get("data", [])
# Filter and sort by price
suitable = []
for m in models:
ctx = m.get("context_length", 0)
if ctx >= min_context:
price_in = m.get("pricing", {}).get("prompt", 0)
price_out = m.get("pricing", {}).get("completion", 0)
suitable.append({
"id": m["id"],
"name": m.get("name", m["id"]),
"context": ctx,
"price_in": float(price_in) * 1_000_000, # Per M tokens
"price_out": float(price_out) * 1_000_000
})
return sorted(suitable, key=lambda x: x["price_in"])[:10]
Always multiply naive estimates by 1.5x due to:
| Model | Input | Output | Real Cost/Episode |
|---|---|---|---|
| Llama 3.3 70B | $0.11/M | $0.34/M | ~$0.007 |
| GPT-4o-mini | $0.15/M | $0.60/M | ~$0.012 |
BATCH_SIZE = 25 # Subtitles per API request (sweet spot)
DELAY_BETWEEN_REQUESTS = 0.5 # Avoid rate limits
MAX_RETRIES = 3 # Per batch
REQUEST_TIMEOUT = 90 # Seconds
def parse_srt(content):
"""Parse SRT into list of (index, timing, text) tuples"""
blocks = []
current = []
for line in content.strip().split('\n'):
if line.strip() == '':
if current:
idx = current[0]
timing = current[1]
text = '\n'.join(current[2:])
blocks.append((idx, timing, text))
current = []
else:
current.append(line)
if current:
idx = current[0]
timing = current[1]
text = '\n'.join(current[2:])
blocks.append((idx, timing, text))
return blocks
Never send HTML tags to the LLM - they get corrupted. Strip before, reapply after.
import re
def extract_formatting(text):
"""Extract HTML tags with positions for later restoration"""
tags = []
for match in re.finditer(r'<[^>]+>', text):
tags.append((match.start(), match.end(), match.group()))
return tags
def strip_html_tags(text):
"""Remove HTML tags for clean translation"""
return re.sub(r'<[^>]+>', '', text)
def apply_formatting(translated, original_tags):
"""Reapply original HTML structure to translation"""
if not original_tags:
return translated
# Preserve italic tags at start/end
result = translated
for start, end, tag in original_tags:
if tag == '<i>' and not result.startswith('<i>'):
result = '<i>' + result
elif tag == '</i>' and not result.endswith('</i>'):
result = result + '</i>'
return result
def translate_batch(texts, source='English', target='French'):
"""Translate batch of texts, return list or None on failure"""
prompt = f"""Translate these {source} subtitles to {target}.
Return ONLY a JSON array of translated strings, same order.
Keep it natural and conversational.
{json.dumps(texts, ensure_ascii=False)}"""
# API call with retries
for attempt in range(MAX_RETRIES):
try:
response = call_api(prompt)
return json.loads(response)
except:
time.sleep(2 ** attempt) # Exponential backoff
return None
Store failed batches for later retry:
def add_failed_batch(file_path, batch_index, blocks_data):
"""Queue failed batch for retry"""
failed_data = load_failed_batches()
failed_data["batches"].append({
"fr_srt": str(file_path),
"batch_index": batch_index,
"blocks": blocks_data,
"retry_count": 0,
"timestamp": datetime.now().isoformat()
})
save_failed_batches(failed_data)
Always wrap main() in auto-restart:
def run_resilient():
"""Auto-restart on errors (up to 50 times)"""
max_restarts = 50
restart_count = 0
while restart_count < max_restarts:
try:
main()
break
except KeyboardInterrupt:
log("Manual stop (Ctrl+C)")
break
except Exception as e:
restart_count += 1
log(f"ERROR: {e}")
log(f"Auto-restart {restart_count}/{max_restarts} in 30s...")
time.sleep(30)
PROGRESS_FILE = "translation_progress.json"
def load_progress():
try:
with open(PROGRESS_FILE) as f:
return json.load(f)
except:
return {"completed": [], "failed": [], "total_cost": 0.0}
def save_progress(state):
state["last_update"] = datetime.now().isoformat()
with open(PROGRESS_FILE, 'w') as f:
json.dump(state, f, indent=2)
OPENROUTER_API_KEY = "sk-or-v1-..."
MODEL = "meta-llama/llama-3.3-70b-instruct"
API_URL = "https://openrouter.ai/api/v1/chat/completions"
headers = {
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json"
}
def get_credits():
r = requests.get("https://openrouter.ai/api/v1/credits",
headers={"Authorization": f"Bearer {API_KEY}"})
data = r.json().get("data", {})
return data.get("total_credits", 0) - data.get("total_usage", 0)
| Input | Output |
|---|---|
movie.eng.srt | movie.fr.srt |
episode.en.srt | episode.fr.srt |
episodes × 250 subtitles × 50 tokens × 2 (in+out) × price/token × 1.5 (overhead)
Example: 600 episodes EN→FR with Llama 3.3 70B:
| Issue | Solution |
|---|---|
| HTML tags corrupted | Strip before translation, reapply after |
| Rate limit errors | Add 0.5s delay between requests |
| Parsing failures | Retry with exponential backoff |
| Script stops randomly | Use run_resilient() wrapper |
| Cost higher than expected | Multiply estimates by 1.5x |
.srt files.srt files with language suffixtranslation_progress.json - tracks completed filesfailed_batches.json - retry queuetranslation.log - detailed logsWrite (create translation script)Bash (run translation)Read (check progress/logs)AskUserQuestion (provider/model selection)WebFetch (live model pricing)User: "Translate my subtitles to French"
↓
[Ask] Which API provider?
├─► OpenRouter (recommended)
├─► OpenAI
├─► Ollama Local
└─► Ollama Hostinger
↓
[Ask] Content complexity?
├─► Simple → mistral-7b ($0.03/M)
├─► Standard → llama-3.3-70b ($0.20/M)
└─► Complex → claude-3.5-sonnet ($9/M)
↓
[Calculate] Cost estimate × 1.5
↓
[Confirm] "~$X for Y episodes. Proceed?"
↓
[Execute] translate_srt.py
├─► Parse SRT → Strip HTML → Batch (25/req)
├─► Translate → Reapply formatting
└─► Track progress → Queue failures
↓
[Done] X.fr.srt files created
Scenario: Translate 600 One Piece episodes EN→FR
Interaction:
.fr.srt files, actual cost ~$4-7Output structure:
/anime/onepiece/
├── Episode.001.eng.srt (original)
├── Episode.001.fr.srt (NEW)
├── translation_progress.json
├── failed_batches.json
└── translation.log
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.