Skip to main content

Porter Workflow

English | 简体中文

CI PyPI Python 3.11 - 3.13 License: MIT

Porter Workflow is an automated video localization pipeline: give it a video URL, get back a standardised asset bundle plus hard-subbed release videos with bilingual and Chinese-only subtitles.

It ships as one engine with three frontends:

Frontend Entry point For
porter (CLI) porter "<URL>" Humans and shell scripts
porter-mcp uvx --from "porter-workflow[mcp]" porter-mcp AI agents via Model Context Protocol
porter-skill npx skills add RolinShmily/porter-workflow Agents that use the Agent Skills spec

All three call the same porter library. The engine contains no argument parsing and never writes to stdout — a requirement, because in an MCP stdio server stdout is the JSON-RPC channel.

Status: v0.2.x is the current development line on main. The v0.2 rewrite — one engine, three frontends — has landed. The v0.1 implementation is preserved in git history at commit b5fd577.


Installation

Requires Python ≥ 3.11.

# CLI, minimal install (pure-Python; translation works without a key, but
# transcription needs the [asr-local] extra or an API key -- see below)
uvx porter-workflow "<URL>"

# CLI with every optional backend
uvx --from "porter-workflow[all]" porter "<URL>"

# Persistent install
pipx install "porter-workflow[all]"

System dependencies

These are not Python packages and must be on PATH:

Dependency Needed for Notes
FFmpeg + ffprobe Everything Must be built with libass for hardsubbing. Verify: ffmpeg -filters | grep subtitles
Deno (recommended) YouTube downloads yt-dlp ≥ 2025.11.12 needs an external JS runtime to solve YouTube's JS challenges. Node ≥ 20 also works.

Check everything at once:

porter doctor

Transcription is key-free and offline, or an API key

Local Whisper runs on your own machine: no key, no network after the first run. Measured 2026-09-24 on real speech -- 18 s of audio became 3 cues in 7.2 s on CPU with the default small model:

ASR backend Needs Status
whisper-local the [asr-local] extra Works. Key-free; offline once the model is cached
Whisper API OPENAI_API_KEY (or a compatible endpoint) Works
VideoCaptioner CLI the videocaptioner package, installed separately Works
Bcut nothing Host answers, but returns zero utterances
Google Web ([stt]) nothing Returns the empty result {"result":[]} for every request

whisper-local is tried first, so it is what a run uses whenever it is installed. It sits behind an extra because faster-whisper and ctranslate2 are heavy, and CTranslate2-backed rather than PyTorch:

# Key-free and offline. This is the recommended install.
uvx --from "porter-workflow[asr-local]" porter "<URL>"

# Or everything at once -- [all] includes [asr-local]
uvx --from "porter-workflow[all]" porter "<URL>"

The first run downloads the model from Hugging Face (464 MB for the default small); after that it is fully offline. Device and precision are chosen for you -- CUDA if it loads, CPU otherwise -- and can be pinned:

porter config set asr.whisper_local_model=medium
porter config set asr.whisper_local_device=cuda
porter config set asr.whisper_local_compute_type=float16

A bare uvx porter-workflow "<URL>" still cannot transcribe, because no ASR backend is in a minimal install: it downloads and standardises the video and then fails with every speech-to-text backend failed. The two key-free endpoints are the ones that are dead -- both are reverse-engineered, and Google Web's response is malformed at the HTTP level, so the read fails outright rather than merely returning nothing. Bcut's failure is the more ambiguous of the two -- the host answers, so it could be a quota or a changed field rather than a dead endpoint -- which is why it keeps an "unverified" label in the source.

So to get subtitles, do one of:

# Option A (recommended): local, key-free, offline
uvx --from "porter-workflow[asr-local]" porter "<URL>" --burn skip

# Option B: an LLM key (also improves translation quality a lot)
export OPENAI_API_KEY=sk-...
porter "<URL>" --burn skip

# Option C: install VideoCaptioner and use its engines
pip install videocaptioner
porter "<URL>" --asr-engine bijian --burn skip

Translation is unaffected. Bing, Google and MyMemory all still work without a key; each was probed against its live endpoint and returned real Chinese. Only transcription ever needed credentials.

porter doctor reports which route a job will actually take before you start it.

Optional extras

Extra Adds Enables
[asr-local] faster-whisper Local Whisper ASR — key-free and offline, see above
[llm] openai, json-repair LLM translation + Whisper API ASR
[stt] SpeechRecognition Google Web STT — measured non-functional, see above
[images] pillow Cover image handling
[mcp] fastmcp The porter-mcp server
[all] all of the above —

videocaptioner is deliberately not a dependency. It is GPL-3.0 (this project is MIT) and pins python<3.13. Porter detects it at runtime and, if present, uses it as an extra ASR/translation backend across a process boundary. Install it yourself if you want those engines:

pip install videocaptioner

Slow or blocked? Mirrors for China

Both of porter's downloads default to endpoints that are painful from mainland China: the packages come from PyPI, and the Whisper weights from Hugging Face. On a machine that looks Chinese -- by timezone, by a UTC+8 offset, or by a zh locale -- porter switches to domestic mirrors on its own:

What Default elsewhere Default in China Change it with
Python packages PyPI USTC UV_DEFAULT_INDEX
Whisper weights Hugging Face ModelScope HF_ENDPOINT

The faster-whisper-* repositories on ModelScope hold the official CTranslate2 weights rather than a re-conversion: their config.json is byte-identical to Systran/faster-whisper-small's. Each model size is downloaded from there once.

To force a direction, or to use a mirror of your own choosing:

PORTER_MIRROR=cn     # always prefer the mirrors
PORTER_MIRROR=off    # never; always use the official sources

An index you set yourself always wins and is never overwritten. Setting UV_DEFAULT_INDEX (or the older UV_INDEX_URL) is enough on its own. PIP_INDEX_URL is honoured as well, and is copied across to uv -- because uv ignores PIP_INDEX_URL by itself, so pip config set global.index-url <mirror> alone would silently do nothing at all.


Usage

# Inspect a link without downloading anything
porter inspect "<URL>"

# Full pipeline: download → transcribe → translate → burn
porter "<URL>" -o ./porter_output

# Subtitles only, no video encoding
porter "<URL>" --burn skip

# Diagnose the environment
porter doctor

# Show resolved configuration (secrets masked)
porter config list

Output layout

<output_dir>/<video_id>_<safe_title>/
├── raw/
│   ├── video.mp4                 H.264 + AAC + faststart master
│   ├── audio.wav                 16 kHz mono reference track
│   ├── audio_enhanced.wav        denoised track used only for ASR
│   ├── transcript.json/.txt      reconstructed sentence script book
│   ├── subtitle.srt              platform-provided source subtitles, if any
│   ├── cover.jpg
│   └── metadata.json
└── cooked/
    ├── subtitle_bilingual.{srt,ass}
    ├── subtitle_zh.{srt,ass}
    ├── video_bilingual.mp4
    └── video_zh.mp4

Configuration

Resolved in this order, highest priority first:

  1. --config <path>
  2. $PORTER_CONFIG
  3. Environment variables (OPENAI_API_KEY, PORTER_ASR_ENGINE, …)
  4. Project-level ./porter.json
  5. User-level config directory (platformdirs)
  6. Built-in defaults

MCP server

{
  "mcpServers": {
    "porter": {
      "command": "uvx",
      "args": ["--from", "porter-workflow[mcp]", "porter-mcp"]
    }
  }
}

Long jobs are exposed as a start/status/result/cancel API rather than one blocking call, because encoding a 1080p video can take tens of minutes.


Development

uv sync --extra all --extra dev      # installs exactly what uv.lock pins

ruff check src          # includes T20: the engine must not print()
mypy src
lint-imports            # enforces the engine/frontend boundary
pytest

CONTRIBUTING.md documents the full gate, the architecture rules those tools enforce, and the licence rules for new dependencies.

Repository layout:

src/porter/        engine (library) — all business logic lives here
src/porter_cli/    CLI frontend
src/porter_mcp/    MCP frontend
skills/porter-skill/   Agent Skill assets (SKILL.md, scripts, references)
tests/{unit,regression,integration}/

License

MIT. See LICENSE.


Acknowledgements

Porter is built on other people's work, and the debt is worth stating plainly:

  • yt-dlp — downloading from five platforms is a solved problem rather than five scrapers, because of this.
  • VideoCaptioner (GPL-3.0) — its sentence segmentation, alignment and subtitle-processing design shaped the approach in porter.subtitles and the ASR chain. Ideas only: no code was copied, and it is never imported — only run as an external process, which is why porter can stay MIT.
  • FFmpeg and libass — the entire media layer: standardisation, denoising and professional ASS rendering.

THIRD_PARTY_NOTICES.md records every component, its licence, and whether porter depends on it, optionally installs it, or only spawns it as a subprocess.


Contributing

Contributions are welcome. CONTRIBUTING.md covers the development setup and the gate every change must pass; SECURITY.md explains how to report a vulnerability privately; release history lives in CHANGELOG.md.

This project follows the Contributor Covenant.

Release files for porter-workflow 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for porter-workflow 0.2.3
File Size Uploaded
porter_workflow-0.2.3.tar.gz 268.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for porter-workflow 0.2.3
File Interpreter ABI Platform
porter_workflow-0.2.3-py3-none-any.whl Python 3 none any Details

Total release size: 575.1 kB

Release files / porter_workflow-0.2.3.tar.gz

Download URL porter_workflow-0.2.3.tar.gz
Size 268.4 kB
Tags Source
SHA-256 checksum
How to use checksums
c0149c6fb49cf9a7b1ba128b6db70b9b7063eb84f58cf601e0e6be1e0f1603db
BLAKE2b-256 checksum
How to use checksums
c28998520eef33d395524ad564f746f7f71ccb5ae9829746d014f41e292cb842
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / porter_workflow-0.2.3-py3-none-any.whl

Download URL porter_workflow-0.2.3-py3-none-any.whl
Size 306.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d0b7bcc4e318d2b5ceea7f1f2651510d2816707142c839117bd235885c227e5b
BLAKE2b-256 checksum
How to use checksums
fdf7ad8b90599eb0b54f913f50a9318e056a00645756309d9171515028c8a5a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

This release

0.2.3 This release

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page