User story
As a developer running the video pipeline on an M3 Mac, I want all pipeline scripts to run natively on macOS rather than inside Docker, so that Metal, VideoToolbox, and Core ML are accessible and Phase 1 GPU acceleration (Issues #22, #23) actually takes effect.
Background
Phase 1 of the production refactor plan adds hardware detection (scripts/config/hardware.ts) and typed FFmpeg commands that branch on M3/NVIDIA/CPU. But this is currently meaningless: all pipeline scripts run inside Docker via docker-compose, and Docker on macOS runs in a Linux VM with no access to Metal, VideoToolbox, or Core ML. The hardware detection will always report CPU-only inside a Docker container on macOS.
The existing Dockerfile was built for the NVIDIA/Linux path and the public deployment. It should stay — but it should not be the default local runtime on macOS.
What runs in Docker today (pipeline services in docker-compose.yml):
wizard — main pipeline orchestrator
transcribe — whisper.cpp transcription
diarize — pyannote speaker diarization (CPU PyTorch in Docker, could use MPS natively)
setup-camera — face detection (CPU MediaPipe in Docker, could use Core ML natively)
color-match — colour correction
hook-qa — hook quality review
thumbnail — thumbnail generation (rembg background removal)
remotion — Remotion Studio preview
What already works natively: The Python scripts (run_diarize.py, align-transcript.js) already accept a --python flag pointing to a local binary. The wizard prompts for a Python path interactively. So native execution is partially supported — the missing piece is removing Docker as the assumed default and providing a native setup path.
Relationship to Phase 1: This issue is a prerequisite for Phase 1's hardware acceleration to work on M3. Issues #22 (hardware detection) and #23 (FFmpeg command builder) should be implemented against natively-running scripts, not Docker. The Phase 1 status check npm run video:optimize must contain h264_videotoolbox — that is only possible if the script runs outside Docker.
Acceptance criteria
Happy path
Given the native environment is set up on an M3 Mac (per the setup script/guide)
When a developer runs npm run video:wizard
Then the wizard runs natively on macOS (not via docker-compose) and the hardware profile detects appleSilicon
Given the native environment is set up
When the diarization step runs
Then PyTorch uses the MPS backend (not CPU) on M3 — observable via device: mps in diarize output logs
Given the native environment is set up
When npm run video:optimize runs an FFmpeg encode (after Phase 1 #23 is implemented)
Then the ffmpeg command contains h264_videotoolbox, not libx264
Given a developer on an NVIDIA Linux machine wants to use Docker
When they reference docker-compose.yml
Then the pipeline services still exist and work (Docker is kept as the NVIDIA/Linux path, not deleted)
Edge case
Given a developer has not set up the native Python environment
When they run a script that requires Python (diarize, align)
Then they receive a clear error message pointing to the setup guide, not a silent failure
Out of scope
Technical context
npm script cleanup
12 :docker`` variants exist in package.json. These should be:
- Renamed to
:linux`` to signal they are for the NVIDIA/Linux path, not the default
- Or removed if the non-suffixed native script is sufficient for all environments
Affected scripts:
camera:color-match:docker → :linux
camera:setup:docker → :linux
carousel:pdf:docker → :linux (carousel is for public app, may be removed anyway)
carousel:wizard:docker → :linux
shorts:camera-setup:docker → :linux
shorts:wizard:docker → :linux
review:hooks:docker → :linux
thumbnail:candidates:extract:docker → :linux
thumbnail:cutouts:generate:docker → :linux
thumbnail:frames:select:docker → :linux
thumbnail:simple:docker → :linux
thumbnail:wizard:docker → :linux
video:wizard:docker → :linux
Python environment
The Python scripts already accept --python <path> (see wizard.js:561, Diarizer.js:10, align-transcript.js:44). Native setup requires:
- conda or venv with Python 3.11/3.12
- PyTorch with MPS backend:
pip install torch torchvision torchaudio (macOS wheels include MPS)
- whisperx, pyannote-audio, faster-whisper (same as
requirements.txt but without CUDA variants)
- rembg, mediapipe (thumbnail + camera)
- Set
PYTHON_PATH=.venv/bin/python in .env.local so wizard reads it without interactive prompt
PYTHON_PATH in wizard
wizard.js:561 currently prompts interactively for the Python path. Add a fallback: read PYTHON_PATH from the environment before prompting. This makes CI and scripted runs non-interactive.
docker-compose.yml
Keep docker-compose.yml but document it as the NVIDIA/Linux path. Remove or rename pipeline services on the macOS-native path. Consider adding a comment header to docker-compose.yml:
# This file is for NVIDIA Linux deployment. Run scripts natively on macOS — see docs/NATIVE_SETUP.md
The app service (Next.js dev server) can stay or be removed from docker-compose — Next.js already runs fine natively via npm run dev.
requirements.txt
The current requirements.txt should split into:
requirements.txt — platform-agnostic (used for native macOS; PyTorch uses MPS automatically on Apple Silicon)
requirements.nvidia.txt — CUDA variants (torch+cu124, faster-whisper with CUDA) for the Docker/Linux path
Implementation details
-
Create docs/NATIVE_SETUP.md — step-by-step guide: install conda/homebrew, create venv, install Python deps, set PYTHON_PATH in .env.local, verify MPS with a one-liner (python3 -c "import torch; print(torch.backends.mps.is_available())")
-
Create scripts/setup-native-env.sh — automates the venv creation and dep install for macOS; reads requirements.txt (not the CUDA variant)
-
In wizard.js: before the interactive Python path prompt, check process.env.PYTHON_PATH; use it if set; only prompt if unset
-
In package.json: rename all :docker script variants to `:`linux
-
In docker-compose.yml: add a header comment scoping it to NVIDIA/Linux; optionally remove pipeline services that are now run natively (keep the file for the Linux path)
-
Split requirements.txt → requirements.txt + requirements.nvidia.txt; update Dockerfile to use requirements.nvidia.txt
-
Add PYTHON_PATH to .env.example with a comment: # Path to the native Python binary — set to .venv/bin/python after running scripts/setup-native-env.sh
Additional test scenarios
python3 -c "import torch; print(torch.backends.mps.is_available())" returns True after running the setup script on M3
- Diarization log output shows
device: mps (or equivalent) when running natively on M3
npm run diarize (non-docker) completes successfully with the native venv Python
- A developer on Linux running
npm run diarize:linux via Docker still works
Hard constraints
- Do not delete
Dockerfile or docker-compose.yml — they are the NVIDIA/Linux deployment path
- The setup script must be idempotent (running it twice does not break the environment)
PYTHON_PATH must never be hardcoded in any script — always read from env or CLI flag
Dependency issues
User story
As a developer running the video pipeline on an M3 Mac, I want all pipeline scripts to run natively on macOS rather than inside Docker, so that Metal, VideoToolbox, and Core ML are accessible and Phase 1 GPU acceleration (Issues #22, #23) actually takes effect.
Background
Phase 1 of the production refactor plan adds hardware detection (
scripts/config/hardware.ts) and typed FFmpeg commands that branch on M3/NVIDIA/CPU. But this is currently meaningless: all pipeline scripts run inside Docker viadocker-compose, and Docker on macOS runs in a Linux VM with no access to Metal, VideoToolbox, or Core ML. The hardware detection will always report CPU-only inside a Docker container on macOS.The existing Dockerfile was built for the NVIDIA/Linux path and the public deployment. It should stay — but it should not be the default local runtime on macOS.
What runs in Docker today (pipeline services in
docker-compose.yml):wizard— main pipeline orchestratortranscribe— whisper.cpp transcriptiondiarize— pyannote speaker diarization (CPU PyTorch in Docker, could use MPS natively)setup-camera— face detection (CPU MediaPipe in Docker, could use Core ML natively)color-match— colour correctionhook-qa— hook quality reviewthumbnail— thumbnail generation (rembg background removal)remotion— Remotion Studio previewWhat already works natively: The Python scripts (
run_diarize.py,align-transcript.js) already accept a--pythonflag pointing to a local binary. The wizard prompts for a Python path interactively. So native execution is partially supported — the missing piece is removing Docker as the assumed default and providing a native setup path.Relationship to Phase 1: This issue is a prerequisite for Phase 1's hardware acceleration to work on M3. Issues #22 (hardware detection) and #23 (FFmpeg command builder) should be implemented against natively-running scripts, not Docker. The Phase 1 status check
npm run video:optimizemust containh264_videotoolbox— that is only possible if the script runs outside Docker.Acceptance criteria
Happy path
Given the native environment is set up on an M3 Mac (per the setup script/guide)
When a developer runs
npm run video:wizardThen the wizard runs natively on macOS (not via docker-compose) and the hardware profile detects
appleSiliconGiven the native environment is set up
When the diarization step runs
Then PyTorch uses the MPS backend (not CPU) on M3 — observable via
device: mpsin diarize output logsGiven the native environment is set up
When
npm run video:optimizeruns an FFmpeg encode (after Phase 1 #23 is implemented)Then the ffmpeg command contains
h264_videotoolbox, notlibx264Given a developer on an NVIDIA Linux machine wants to use Docker
When they reference
docker-compose.ymlThen the pipeline services still exist and work (Docker is kept as the NVIDIA/Linux path, not deleted)
Edge case
Given a developer has not set up the native Python environment
When they run a script that requires Python (diarize, align)
Then they receive a clear error message pointing to the setup guide, not a silent failure
Out of scope
Dockerfileanddocker-compose.ymlas the NVIDIA/Linux pathDockerfile.public, tracked in Issue feat(deploy): write deployment platform ADR and deploy public app #82)Technical context
npm script cleanup
12
:docker`` variants exist inpackage.json. These should be::linux`` to signal they are for the NVIDIA/Linux path, not the defaultAffected scripts:
Python environment
The Python scripts already accept
--python <path>(seewizard.js:561,Diarizer.js:10,align-transcript.js:44). Native setup requires:pip install torch torchvision torchaudio(macOS wheels include MPS)requirements.txtbut without CUDA variants)PYTHON_PATH=.venv/bin/pythonin.env.localso wizard reads it without interactive promptPYTHON_PATH in wizard
wizard.js:561currently prompts interactively for the Python path. Add a fallback: readPYTHON_PATHfrom the environment before prompting. This makes CI and scripted runs non-interactive.docker-compose.yml
Keep
docker-compose.ymlbut document it as the NVIDIA/Linux path. Remove or rename pipeline services on the macOS-native path. Consider adding a comment header todocker-compose.yml:# This file is for NVIDIA Linux deployment. Run scripts natively on macOS — see docs/NATIVE_SETUP.mdThe
appservice (Next.js dev server) can stay or be removed from docker-compose — Next.js already runs fine natively vianpm run dev.requirements.txt
The current
requirements.txtshould split into:requirements.txt— platform-agnostic (used for native macOS; PyTorch uses MPS automatically on Apple Silicon)requirements.nvidia.txt— CUDA variants (torch+cu124,faster-whisperwith CUDA) for the Docker/Linux pathImplementation details
Create
docs/NATIVE_SETUP.md— step-by-step guide: install conda/homebrew, create venv, install Python deps, setPYTHON_PATHin.env.local, verify MPS with a one-liner (python3 -c "import torch; print(torch.backends.mps.is_available())")Create
scripts/setup-native-env.sh— automates the venv creation and dep install for macOS; readsrequirements.txt(not the CUDA variant)In
wizard.js: before the interactive Python path prompt, checkprocess.env.PYTHON_PATH; use it if set; only prompt if unsetIn
package.json: rename all:dockerscript variants to `:`linuxIn
docker-compose.yml: add a header comment scoping it to NVIDIA/Linux; optionally remove pipeline services that are now run natively (keep the file for the Linux path)Split
requirements.txt→requirements.txt+requirements.nvidia.txt; updateDockerfileto userequirements.nvidia.txtAdd
PYTHON_PATHto.env.examplewith a comment:# Path to the native Python binary — set to .venv/bin/python after running scripts/setup-native-env.shAdditional test scenarios
python3 -c "import torch; print(torch.backends.mps.is_available())"returnsTrueafter running the setup script on M3device: mps(or equivalent) when running natively on M3npm run diarize(non-docker) completes successfully with the native venv Pythonnpm run diarize:linuxvia Docker still worksHard constraints
Dockerfileordocker-compose.yml— they are the NVIDIA/Linux deployment pathPYTHON_PATHmust never be hardcoded in any script — always read from env or CLI flagDependency issues
h264_videotoolboxin ffmpeg output) can be verified on M3 natively