Local CLI for extracting subtitles and aligned timestamps from audio and video.
Project description
echoalign-asr-mlx
easr is a local CLI for extracting subtitles and aligned timestamps from audio/video files.
It currently targets macOS + Apple Silicon and uses an MLX-based provider (Qwen3-ASR + Qwen3-ForcedAligner) behind a stable CLI interface.
Current Status
The project is implemented and runnable.
Current capabilities:
- local file and directory processing
- audio/video normalization to mono 16 kHz WAV
- sentence-level and token-level export views
- subtitle export to
srtandvtt - rich JSON export (segments, tokens, provider metadata)
- windowed transcription/alignment pipeline with diagnostics
Environment Requirements
- OS:
macOSonApple Silicon - Python:
>=3.14,<3.15 - package installer:
piporuv pip - build tooling:
uv(recommended) - system dependencies on
PATH:ffmpegffprobe
- network access on first run (model download from Hugging Face)
Default provider models:
Installation
- Install
ffmpegandffprobewith your package manager. - Install package dependencies with MLX extra.
python3.14 -m pip install ".[mlx]"
Alternative (uv-managed environment in project root):
uv sync --extra mlx
- Verify CLI is available.
easr --help
If you are using uv sync in the project checkout without installing into an active shell
environment, run through uv:
uv run --python 3.14 --extra mlx easr --help
Python Package Distribution
Build source + wheel artifacts:
uv build
The package version is derived from Git tags. A clean release build from tag
v0.2.1 produces 0.2.1 distributions.
Install wheel in a target environment:
python3.14 -m pip install dist/echoalign_asr_mlx-<version>-py3-none-any.whl
For full transcription runtime, install with MLX extra:
python3.14 -m pip install ".[mlx]"
After publishing to an index (for example PyPI), end users can install with:
python3.14 -m pip install "echoalign-asr-mlx[mlx]"
GitHub Release to PyPI
This repository includes a publish workflow at:
.github/workflows/publish-pypi.yml
Release flow:
- Configure a Trusted Publisher in PyPI for this project:
- project:
echoalign-asr-mlx - owner/repo: your GitHub repository
- workflow:
publish-pypi.yml - environment:
pypi
- project:
- Merge release-ready code to
main. - Create and publish a GitHub Release tagged
vX.Y.Z, for examplev0.2.1. - GitHub Actions runs tests, builds distributions with the tag-derived version, checks them with Twine, and publishes to PyPI.
The workflow also supports manual trigger with workflow_dispatch, but manual
publishing must run from a release tag such as v0.2.1.
Quick Start
The examples below use the installed easr command directly. If you are running from a
project checkout with uv sync, prefix commands with:
uv run --python 3.14 --extra mlx easr ...
Single file
easr ./demo.mp4 --verbose
No input path (defaults to current directory)
easr
Directory (non-recursive by default)
easr ./media
Directory (recursive)
easr ./media --recursive --verbose
Glob pattern input
easr "./media/**/*.mp4" --recursive --verbose
CLI Reference
Help output:
usage: easr [-h] [--recursive] [--output-dir OUTPUT_DIR]
[--granularity {sentence,token}] [--no-vad] [--verbose]
[inputs ...]
Arguments and flags:
inputs: file, directory, or glob pattern; defaults to current directory if omitted--recursive: recurse when scanning directory inputs--output-dir: override default output root--granularity {sentence,token}:sentence: subtitle entries come from segment boundariestoken: subtitle/JSONitemsare generated from tokens
--no-vad: disable voice activity detection preprocessing--verbose: print detailed per-step timing and export<name>.metrics.json
VAD preprocessing
VAD preprocessing is enabled by default. easr first scans prepared audio with
Silero VAD to find high-recall speech candidates, merges them into padded
super-chunks, and asks the provider to process only those ranges. Final subtitle
timestamps remain on the original media timeline.
Use --no-vad to restore full-duration provider processing:
uv run --python 3.14 --extra mlx easr ./demo.mp4 --no-vad
If VAD fails, transcription falls back to the full-duration provider path. If VAD
successfully finds no speech, easr writes successful empty subtitle outputs.
Shell Completion (fish)
Generate fish completion script:
easr completion fish
Install fish completion script:
easr completion install fish
Install target path:
~/.config/fish/completions/easr.fish- existing file is overwritten on install
Supported Input Formats
Audio:
wavmp3m4aflacaac
Video:
mp4movm4vmkvwebm
Output Files and Layout
For each input media file, the CLI writes:
<name>.srt<name>.vtt<name>.json<name>.metrics.json(only when--verboseis enabled)
Default output directory name: outputs
Layout rules:
- input is a directory/current directory:
- output root defaults to
<input_root>/outputs
- output root defaults to
- input is a single file:
- output root defaults to
<file_parent>/outputs
- output root defaults to
- recursive batch keeps relative directory structure
--output-diroverrides output root
Example:
/project/media/
a.mp4
nested/b.wav
/project/media/outputs/
a.srt
a.vtt
a.json
nested/b.srt
nested/b.vtt
nested/b.json
JSON Contract (Practical)
json output includes:
- top-level transcription document:
source_pathprovider_namedetected_languagesegmentssource_media
- export view:
granularityitems
source_media currently includes:
prepared_audio_pathprovider_metadata:processing_strategywindow_countduration_secquality_pass_countfailed_window_countwindow_diagnostics
Real E2E Example (Current Repo)
Command used in this repository:
uv run --python 3.14 --extra mlx easr tests/e2e/test1.mov --verbose
Observed output files:
tests/e2e/outputs/test1.srttests/e2e/outputs/test1.vtttests/e2e/outputs/test1.json
Observed sample stats from test1.json:
window_count:3failed_window_count:0segments: ~49(depends on current alignment behavior/model output)
Sample subtitle excerpt:
00:00:09,600 --> 00:00:17,640
But despite all the buzz and hype, one of the things that's still underestimated by many people is their power as a developer too.
Common Commands
Run unit tests
PYTHONPATH=src uv run --python 3.14 python -m unittest discover -s tests -p 'test_*.py'
Run a single focused test
PYTHONPATH=src uv run --python 3.14 python -m unittest tests.test_authority
Dry-check CLI parsing/help
uv run --python 3.14 easr --help
Token-level subtitle export
uv run --python 3.14 --extra mlx easr ./demo.mp4 --granularity token --verbose
Export verbose metrics JSON for optimization
uv run --python 3.14 --extra mlx easr ./demo.mp4 --verbose
Runtime Flow (What Happens Internally)
For each media file:
- CLI discovers supported inputs (file/dir/glob).
- Environment preflight validates:
ffmpegandffprobeavailability- MLX/Metal basic runtime check
- progress starts rendering in terminal (single-line by default)
- Media is normalized to mono 16 kHz WAV.
- Provider runs windowed ASR + alignment.
- Results are exported to
srt,vtt, andjson. - When
--verboseis enabled,<name>.metrics.jsonis also exported.
Exit Codes and Runtime Behavior
0: all discovered files processed successfully1: no supported input found, preflight failed, or at least one file failed in batch
Batch behavior:
- files are processed one-by-one
- failures are reported per file to stderr
- other files continue processing
Troubleshooting
[easr] environment check failed: Missing required media dependency...
Cause: ffmpeg and/or ffprobe not found on PATH.
Fix:
- install both binaries
- ensure they are visible in the shell used to run
easr
[easr] environment check failed: MLX/Metal preflight failed...
Cause: MLX runtime could not initialize Metal backend (or crashed during preflight).
Fix:
- verify Apple Silicon + supported macOS runtime
- re-check Python/venv and
mlxdependency installation - retry from a clean shell/session
First run is slow
Cause: model download and cache warm-up.
Fix:
- expected on first run
- later runs should be faster after cache is populated
Notes and Scope
- translation is out of scope in current phase
- speaker diarization is not implemented
- subtitle segmentation quality depends on model + alignment behavior
- provider abstraction is in place for future backend extension
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file echoalign_asr_mlx-0.4.0.tar.gz.
File metadata
- Download URL: echoalign_asr_mlx-0.4.0.tar.gz
- Upload date:
- Size: 174.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
547d301092def8bdec419ed981fa30f28420b727ac825f62ae48938ac2d39998
|
|
| MD5 |
f3a0a372572f0a9590106129c029e32c
|
|
| BLAKE2b-256 |
c24441c5d5b9d00640409857d780696efbb156cb5c829dc7e2c8b8ac0d3b4d9f
|
Provenance
The following attestation bundles were made for echoalign_asr_mlx-0.4.0.tar.gz:
Publisher:
publish-pypi.yml on morehardy/ASR
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
echoalign_asr_mlx-0.4.0.tar.gz -
Subject digest:
547d301092def8bdec419ed981fa30f28420b727ac825f62ae48938ac2d39998 - Sigstore transparency entry: 1535541704
- Sigstore integration time:
-
Permalink:
morehardy/ASR@4c3f369fb48d55e64836b3f61f011447a24c9e49 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/morehardy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@4c3f369fb48d55e64836b3f61f011447a24c9e49 -
Trigger Event:
release
-
Statement type:
File details
Details for the file echoalign_asr_mlx-0.4.0-py3-none-any.whl.
File metadata
- Download URL: echoalign_asr_mlx-0.4.0-py3-none-any.whl
- Upload date:
- Size: 45.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc4898c8ee783d7040f353d0d37d1543ccafa0c64f8c6fcc2387c03c35440405
|
|
| MD5 |
1243a9e42ed95d3675438b06ff003b33
|
|
| BLAKE2b-256 |
96d04cf5d81a8e9fa83dec63d9a7e48126efe9402e144361ba91458592d51ef4
|
Provenance
The following attestation bundles were made for echoalign_asr_mlx-0.4.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on morehardy/ASR
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
echoalign_asr_mlx-0.4.0-py3-none-any.whl -
Subject digest:
bc4898c8ee783d7040f353d0d37d1543ccafa0c64f8c6fcc2387c03c35440405 - Sigstore transparency entry: 1535541804
- Sigstore integration time:
-
Permalink:
morehardy/ASR@4c3f369fb48d55e64836b3f61f011447a24c9e49 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/morehardy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@4c3f369fb48d55e64836b3f61f011447a24c9e49 -
Trigger Event:
release
-
Statement type: