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.0 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 does NOT -- see "Transcription needs a key" 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 needs a key (or the VideoCaptioner CLI)

There is no working key-free speech-to-text path. This is measured, not theoretical — as of 2026-09-22, on a real 10-minute video:

ASR backend Status
Whisper API Needs OPENAI_API_KEY (or a compatible endpoint)
VideoCaptioner CLI Needs the videocaptioner package installed separately
Bcut Host answers, but returns zero utterances
Google Web ([stt]) Returns the empty result {"result":[]} for every request

Both key-free endpoints are reverse-engineered and neither transcribes any more. Google Web's response is also 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 a bare uvx porter-workflow "<URL>" will download and standardise the video and then fail at the transcription phase with every speech-to-text backend failed. To get subtitles, do one of:

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

# Option B: 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 needs credentials.

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

Optional extras

Extra Adds Enables
[llm] openai, json-repair LLM translation + Whisper API ASR
[stt] SpeechRecognition Google Web STT — measured non-functional, see below
[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

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.0

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.0
File Size Uploaded
porter_workflow-0.2.0.tar.gz 259.3 kB Details

Built distribution (wheel)

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

Total release size: 559.3 kB

Release files / porter_workflow-0.2.0.tar.gz

Download URL porter_workflow-0.2.0.tar.gz
Size 259.3 kB
Tags Source
SHA-256 checksum
How to use checksums
644baf19ca074d67993a354e575d1eee92f2e025ff75977dd3cd7df7837838b3
BLAKE2b-256 checksum
How to use checksums
89ec58246ed9f3b1cff85c1b3b6d5638448cb71049dc3f12a3865a8f8a8bc50c
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.0-py3-none-any.whl

Download URL porter_workflow-0.2.0-py3-none-any.whl
Size 299.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
aeced48087b1ca452a718b8946280ae1e17751a69488f001ccdfe1fd23aa6030
BLAKE2b-256 checksum
How to use checksums
f0c050c4beb7ada3057dd18d57f28e0ac81f75ff1249e74b1a077b9bbd91ccaf
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

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

This release

0.2.0 This release

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