feat(packaging): ship as a tool install or a self-contained directory
Two ways to get `ccn-transcribe` on PATH. `uv tool install .` is the light one. scripts/build-binary.sh produces a ~450 MB directory that needs neither Python nor uv, which turned out to work despite OpenVINO resolving 47 plugins by dlopen at runtime -- the frozen build reports CPU, GPU and transcribes on the GPU to a byte-identical transcript. Two things that build needs. PyInstaller points sys.prefix at the bundle, so deno goes in bin/ and the runtime lookup finds it with no frozen-app special case in the package. And a frozen executable is re-invoked to start a multiprocessing child, which parsed the interpreter's own -B flag as a CLI option and printed "No such option '-B'" twice per run before dying; freeze_support() in the entry point makes that child do its job and exit. onedir, not onefile: onefile would extract 450 MB to /tmp on every launch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,3 +21,7 @@ htmlcov/
|
||||
# Tool caches
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
|
||||
# PyInstaller output
|
||||
/dist/
|
||||
/build/
|
||||
|
||||
@@ -161,6 +161,31 @@ the retry loop lives here instead.
|
||||
yt-dlp defaults to deno only, and installing it as a dependency means it works
|
||||
under cron without any PATH setup.
|
||||
|
||||
## Installing
|
||||
|
||||
Either route puts `ccn-transcribe` on your PATH; `ccn-transcribe doctor` tells you
|
||||
whether the machine can actually run it.
|
||||
|
||||
**As a tool** (needs uv; tracks nothing but what it installed):
|
||||
|
||||
```bash
|
||||
uv tool install . # then: ccn-transcribe doctor
|
||||
uv tool install . --reinstall # pick up later commits
|
||||
uv tool install . --editable # or track the checkout instead
|
||||
```
|
||||
|
||||
**As a self-contained directory** (needs neither Python nor uv at runtime):
|
||||
|
||||
```bash
|
||||
scripts/build-binary.sh # then: dist/ccn-transcribe/ccn-transcribe doctor
|
||||
```
|
||||
|
||||
~450 MB, most of it OpenVINO and its 47 runtime-loaded plugins. It is `onedir`
|
||||
rather than `onefile` on purpose: `onefile` extracts the whole bundle to `/tmp`
|
||||
on every launch. Move or copy the whole `ccn-transcribe/` directory, not just
|
||||
the executable inside it. `ffmpeg`, `ffprobe` and `aria2c` are still expected on
|
||||
the system either way — they are not bundled.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build a self-contained ccn-transcribe directory that runs without Python or uv.
|
||||
#
|
||||
# onedir, never onefile: onefile extracts ~450 MB to /tmp on every launch.
|
||||
# deno lands in bin/ because PyInstaller points sys.prefix at the bundle, which
|
||||
# is where the package already looks for the JS runtime it ships with.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
DIST=${1:-dist}
|
||||
|
||||
uv run --locked --with pyinstaller pyinstaller \
|
||||
--noconfirm --onedir --name ccn-transcribe \
|
||||
--distpath "$DIST" --workpath "build/pyinstaller" --specpath "build" \
|
||||
--collect-all openvino \
|
||||
--collect-all openvino_genai \
|
||||
--collect-all openvino_tokenizers \
|
||||
--collect-all yt_dlp \
|
||||
--collect-all yt_dlp_ejs \
|
||||
--add-binary "$PWD/.venv/bin/deno:bin" \
|
||||
src/ccn_transcribe/__main__.py
|
||||
|
||||
echo
|
||||
echo "built $DIST/ccn-transcribe ($(du -sh "$DIST/ccn-transcribe" | cut -f1))"
|
||||
echo "verify it with: $DIST/ccn-transcribe/ccn-transcribe doctor"
|
||||
@@ -9,4 +9,10 @@ __all__ = ["main"]
|
||||
# Python 3.14 uses forkserver, which re-imports __main__; without this guard a
|
||||
# dependency touching multiprocessing runs a second copy of the program.
|
||||
if __name__ == "__main__":
|
||||
# A frozen build re-invokes this executable to start a multiprocessing child.
|
||||
# Without this the child parses the interpreter's own flags as CLI options and
|
||||
# prints "No such option '-B'" before failing to start.
|
||||
import multiprocessing
|
||||
|
||||
multiprocessing.freeze_support()
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user