Porter Skill (Streaming Media Porter)
Turn a video link into a finished video with Chinese hard subtitles. The workflow executes in four sequential stages: Fetch → Transcribe → Translate → Burn. Each stage writes its output to disk, so a failure mid-way won't lose completed work.
⚠️ Prerequisite: Transcription Needs a Key
You must confirm this first, otherwise the task will fail at the transcription stage.
| Capability | Key Required? |
|---|---|
| Download, Translate, Burn | No |
| Speech Transcription (ASR) | Requires OPENAI_API_KEY (or WHISPER_API_KEY), or a locally installed VideoCaptioner CLI |
| Platform Native Subtitle Track (YouTube / Bilibili, etc.) | No – if a native track exists, ASR is skipped |
The v0.1 claim of a "pure Python zero-key closed loop" is no longer valid: all key-free speech recognition endpoints have been decommissioned (tested 2026-09-22; use the [asr-local] extra for key-free local recognition). Without a key, and if the video has no platform subtitle track, the task will end at the transcribe stage with every speech-to-text backend failed.
So: run plan first – it will tell you directly whether the video uses a native subtitle track or ASR, and whether it's feasible.
Calling the Entry Point
The CLI is invoked via uvx. On Windows PowerShell, use the native .ps1 script; do not assume Git Bash / WSL is available:
| Script | Purpose |
|---|---|
scripts/porter.sh | Bash main entry, forwards to the porter CLI. |
scripts/porter.ps1 | Windows PowerShell main entry, also supports subcommands like inspect / plan / run / jobs. |
scripts/inspect.sh | Bash pre-check, equivalent to porter.sh inspect. |
scripts/bootstrap.sh | Alternative entry to create a .venv from a repo checkout; does not install Python source code with the skill. |
# Pre-check
<SKILL_DIR>/scripts/inspect.sh "https://youtu.be/dQw4w9WgXcQ"
# Plan
<SKILL_DIR>/scripts/porter.sh plan "https://youtu.be/dQw4w9WgXcQ"
# Execute
<SKILL_DIR>/scripts/porter.sh run "https://youtu.be/dQw4w9WgXcQ" -o ./porter_output
Windows PowerShell uses & '<SKILL_DIR>\scripts\porter.ps1' inspect '<URL>' --json; plan / run / jobs also call this same entry. The inspect.sh and the nohup/grep commands below are Bash examples and cannot be pasted directly into PowerShell.
uvx needs network access on first run to resolve dependencies. By default, only porter-workflow[llm] is installed (the base package includes yt-dlp); when local ASR is needed, set PORTER_SKILL_SPEC=porter-workflow[llm,asr-local] after user consent. For offline installation, prepare local dependencies inside the repo checkout, not by relying on the skill directory having source code.
On a host that only allows writing to a workspace, first confirm the allowed absolute directory, set PORTER_SKILL_STATE_DIR to a .state subdirectory within it, then run porter.sh --host-check or porter.ps1 --host-check; this will redirect uv, yt-dlp, and the job registry cache there (overriding any cache paths already set by the host). The output -o directory must also be within the allowed directory. When not set, default host paths are used. See references/HOSTS.md for commands, restrictions, and Windows examples.
Workflow Closed Loop
Execute in order. Each step is cheaper than the next, designed to weed out bad inputs before spending significant compute time.
1. Pre-check – inspect
<SKILL_DIR>/scripts/inspect.sh "<URL>" --json
Returns platform, title, duration, resolution, aspect ratio, and whether a subtitle track exists. Completes before downloading any media, in seconds.
Interpretation: is_valid indicates "the link is usable", while ok only means "the call succeeded". If the link is dead, is_valid=false – do not retry; retrying a dead link is pointless.
Exit code 1 means the link is not usable.
2. Plan – plan
<SKILL_DIR>/scripts/porter.sh plan "<URL>" --json
This is the most time-saving step – it answers "how will this video be processed, and can it be completed?":
subtitles.route:platform(use the platform's native subtitle track) orasr(run speech recognition)translation.needed:falseif a Chinese track already exists, allowing the entire translation phase to be skippedfeasible/blocking_issues: Iffalse, stop – do not open a jobnotes: Must read. The two most important notes:- The platform subtitle track is "requested, not guaranteed" – downloading may fail (platform rate limiting, HTTP 429) and fall back to speech recognition
- If all available recognition backends are untested reverse-engineered endpoints, the note will explicitly state this
Exit code 1 means not feasible.
3. Confirm with the User
A job may download hundreds of MB and run for minutes. Report the plan conclusion to the user and get their consent before starting (which track will be used, which phases will run, whether to burn subtitles). If feasible=false, report the blocking issue and do not open a job.
4. Execute – run
<SKILL_DIR>/scripts/porter.sh run "<URL>" -o ./porter_output
For long videos, do not use a foreground call and wait for it to finish. Start it in the background, then poll the job registry:
nohup <SKILL_DIR>/scripts/porter.sh run "<URL>" -o ./porter_output > run.log 2>&1 &
# Poll (job records are written to a shared registry file, visible across processes)
<SKILL_DIR>/scripts/porter.sh jobs list
<SKILL_DIR>/scripts/porter.sh jobs status <job_id>
On PowerShell, use the host's background job tool to start & '<SKILL_DIR>\scripts\porter.ps1' run ... asynchronously, and continuously call jobs status using the same PORTER_SKILL_STATE_DIR. Do not use Out-String to accumulate progress from lengthy commands until they finish.
Do not use the approach of "set the tool timeout to 1200 seconds" to brute-force long tasks (as in v0.1). That still fails for longer videos, and you get no progress on failure. The job registry is designed exactly for this.
5. Quality Check (Mandatory)
Do not assume completion based solely on exit code. At minimum, verify:
# 1. Chinese subtitles exist and contain CJK characters (prevent "untranslated fake finished product")
grep -qP '[\x{4e00}-\x{9fff}]' ./porter_output/<task>/cooked/subtitle_zh.srt \
&& echo "OK: Contains Chinese" || echo "FAIL: No Chinese"
# 2. The finished video is decodable and the moov atom is intact (prevent publishing truncated files)
ffprobe -v error -show_entries format=duration -of csv=p=0 ./porter_output/<task>/cooked/video_zh.mp4
An unusually small finished file usually indicates a burn failure, not that the video is short.
6. On Error
<SKILL_DIR>/scripts/porter.sh doctor # Check if this machine can work: ffmpeg/libass, JS runtime, fonts, encoder
<SKILL_DIR>/scripts/porter.sh config list # Display parsed configuration (keys are masked)
Common Parameters
# Download subtitles only, no burning
run "<URL>" --burn skip
# Output only a pure Chinese finished video (no bilingual)
run "<URL>" --burn zh_only
# Specify output directory / target language
run "<URL>" -o /path/to/out --target-lang zh-Hans
# Prioritize one backend (others remain as fallbacks, not "use only this one")
run "<URL>" --translator google
run "<URL>" --asr-engine whisper-api
# Use a different LLM model for this job (without changing config)
run "<URL>" --llm-model deepseek-reasoner
# Use an existing source subtitle file directly, skip recognition (.srt / .vtt)
run "<URL>" --subtitle-file ./my_subtitles.srt
# For videos that require login to view
run "<URL>" --cookies-from-browser chrome
run "<URL>" --cookies ./cookies.txt
# Rerun (ignore cached stage results)
run "<URL>" --force
# For script consumption
run "<URL>" --json
The semantics of --only-phase is "up to this phase", not "run only this phase" – each phase consumes the output of the previous one, so earlier phases still execute.
Output Structure
<task_dir>/
├── raw/ # Master and intermediate files
│ ├── video.mp4 # Standardized master (lossless stream copy, no re-encoding)
│ ├── audio.wav # Reference audio track
│ ├── audio_enhanced.wav # Enhanced audio track for ASR only (denoising + vocal EQ), not used in the final video
│ ├── subtitle.srt # Source language subtitle (platform track or ASR result)
│ ├── transcript.json/.txt # Structured transcript
│ ├── cover.jpg
│ └── metadata.json
└── cooked/ # Finished products
├── subtitle.srt / .ass # Bilingual
├── subtitle_zh.srt / .ass # Pure Chinese
├── video_bilingual.mp4 # Bilingual hard-subtitled video
└── video_zh.mp4 # Pure Chinese hard-subtitled video
The audio in the final video is directly copied (-c:a copy) from the original master; the enhanced audio track is only used for recognition – achieving both recognition accuracy and final video quality.
Configuration
Configuration search order (later overrides earlier):
- Built-in defaults
- Environment variables:
OPENAI_API_KEY,OPENAI_BASE_URL,OPENAI_MODEL,WHISPER_API_KEY,PORTER_ASR_ENGINE - User configuration (
porter config listwill print the actual path) - Project configuration:
porter.json/porter.toml/config.jsonin the current directory --config <path>
See references/CONFIG.md for the full key table and assets/config.example.json for a template.
Using via MCP (Optional, but Preferred)
If the host already has a porter MCP server configured, prefer it over shell:
- Structured results, no need to parse stdout (also avoids shell escaping and encoding issues)
porter_job_startreturns immediately +porter_job_statuspolling, long tasks won't hit tool timeouts- The only way to get "zero-key LLM-level translation":
§8.3 samplinguses the host model for translation; the CLI process cannot access the host model
See references/MCP.md for tool name mapping and configuration.
Note:
npx skills addonly installs this skill's directory, not the MCP server. For a fresh install, use the CLI entry point.
Reference Files
| File | Content |
|---|---|
references/ARCHITECTURE.md | Four-stage pipeline, platform and backend fallback chain, design constraints |
references/CONFIG.md | All configuration keys, environment variables, priorities |
references/MCP.md | MCP tool mapping and configuration |
references/HOSTS.md | Windows native entry and restricted host pre-checks |
assets/config.example.json | Configuration template |