Skip to main content

sttx

sttx is a standalone Linux CPU transcription command. It normalizes one audio/video file with ffmpeg, runs NVIDIA Parakeet TDT 0.6B v3 through the sherpa-onnx Python bindings, and writes timestamped JSON and plain text transcripts.

Installation

sttx officially supports Linux and requires Python 3.11, 3.12, or 3.13. Its native dependencies also publish macOS and Windows packages, but sttx uses Unix process-group APIs and its process lifecycle is currently tested only on Linux.

Install ffmpeg

Install ffmpeg separately and make sure it is available on PATH. The application invokes it for every input, including WAV files that already have the target format.

Install sttx from PyPI

Install the published sttx package from PyPI:

python -m pip install sttx
sttx --version

This installs the sttx console command and its Python runtime dependencies.

Quickstart

sttx recording.mp4

The default output directory is ./transcriptions. For recording.mp4, a successful run prints these two paths to stdout and writes both files:

transcriptions/recording.json
transcriptions/recording.txt

Existing output files are replaced atomically.

Command-line interface

The complete public surface is:

sttx [-h] [-o STEM] [-d DIR] [--model-dir DIR] [-v] [--debug] [--log-format {text,json}] [--version] media
  • media — required positional path to one local audio/video file.
  • -h, --help — show usage and exit.
  • -o STEM, --output-name STEM — output filename stem. The program adds .json and .txt; a path or an existing suffix is rejected.
  • -d DIR, --outdir DIR — output directory. Relative paths are resolved from the current directory; the default is ./transcriptions.
  • --model-dir DIR — use a local, offline model bundle instead of acquiring model assets.
  • -v, --verbose — write elapsed-time pipeline stage messages and live transcription progress to stderr. Text logs include a 20-character ASCII bar, processed audio position, real-time factor, estimated time remaining, and a final duration/real-time-factor summary. In an interactive terminal, an elapsed-time prefix and progress bar redraw in place ten times per second; redirected stderr receives readable line-oriented progress snapshots.
  • --debug — enable pipeline progress plus diagnostic details on stderr: ffmpeg arguments, the normalized WAV path, model source and asset paths/sizes, stage timings, live ASR scan/VAD/decode activity, reported language, word counts, and an aggregate ASR summary. It includes a traceback for unexpected failures and never prints transcript content.
  • --log-format {text,json} — format enabled stderr diagnostics as human text (the default) or JSON Lines. JSON records have stable event and elapsed_seconds fields plus event-specific data such as stage, audio_seconds, percent, and eta_seconds; use it with -v or --debug for machine-consumable pipeline telemetry.
  • --version — print sttx 0.1.2 and exit.

There is no language option: the model reports a language when available and the JSON writer falls back to "auto".

Streams and exit codes

On a successful transcription, stdout contains exactly two newline-separated paths, JSON first and TXT second. The application writes diagnostics to stderr: argument usage/errors, environment or runtime errors, the no-speech warning, optional verbose stage and live-progress messages, and debug diagnostics. --help and --version are the usual argparse exceptions: their informational text is printed to stdout and they exit successfully.

With --log-format json, enabled diagnostic records are one JSON object per stderr line. Successful invocations still print only the two output paths to stdout. Argument usage, --help, --version, and signal cancellation retain their normal argparse or cancellation text behavior.

The process exits with:

  • 0 — transcription completed, including a silence/no-speech input.
  • 1 — input or environment failure (for example an unreadable file, unavailable ffmpeg, missing model asset, or output filesystem failure).
  • 2 — argument/usage failure or transcription/decode failure.
  • 130 — clean cancellation after SIGINT.
  • 143 — clean cancellation after SIGTERM.

Errors are emitted on stderr with an error: prefix. A cancellation also prints a short cancellation diagnostic on stderr. Temporary normalized audio is removed during success, failure, and signal cleanup.

Model acquisition and cache

Without --model-dir, sttx resolves csukuangfj/sherpa-onnx-nemo-parakeet-tdt-0.6b-v3-int8 at the pinned revision 2bda32ec70b097a55adaa07d9a7173915b43cc78. Both local-only lookup and network acquisition request that exact revision, not the repository's current head. Missing Parakeet assets are downloaded into the Hugging Face cache.

Acquisition is local-first: a complete local snapshot of the pinned revision is reused without a refresh request. Network acquisition is attempted only when that snapshot is missing or incomplete. Runtime checks require readable, nonempty Parakeet assets; the large model files are not rehashed on every run. Qualification separately checks their declared SHA-256 identities.

Silero VAD is stored at ~/.cache/sttx/silero_vad.onnx. Its SHA-256 is checked before reuse. A missing, unreadable, empty, or hash-mismatched cache entry triggers reacquisition. Downloaded bytes are staged and verified against the backend contract before atomic replacement. A bad download fails rather than replacing an existing final file; its staging file is cleaned up. A valid warm Silero cache needs no download.

For an offline run, --model-dir must point to a flat directory containing the complete five-file bundle below. These exact filenames are required and each file must be readable and non-empty; extra files are not consumed:

encoder.int8.onnx
decoder.int8.onnx
joiner.int8.onnx
tokens.txt
silero_vad.onnx

Explicit bundles remain zero-network and use the same filename, readability, and nonempty checks as before. They are not subjected to the default model's revision or checksum policy and do not participate in automatic cache repair. Arbitrary offline bundles and explicit dependency overrides are outside the qualification guarantees.

Output files

The JSON file is UTF-8, uses two-space indentation, and keeps non-ASCII text unescaped. Its text field is the segment text joined with single spaces. duration, start, and end are rounded to two decimal places; segment IDs start at zero.

For example, one short transcript may be:

{
  "task": "transcribe",
  "language": "auto",
  "duration": 3.2,
  "text": "Hello world.",
  "segments": [
    {
      "id": 0,
      "start": 0.0,
      "end": 3.2,
      "text": "Hello world."
    }
  ]
}

The matching TXT file contains exactly the value of JSON text, with no additional newline or other bytes:

Hello world.

Silence/no speech is a successful result with text: "", an empty segments array, and warning: no speech detected on stderr.

Development

From a source checkout, Poetry creates the project's virtual environment in .venv/:

poetry install
poetry env info --path
poetry check
poetry run basedpyright
poetry run pytest -q -m "not integration"
poetry run python -m compileall -q src tests
poetry run sttx --help
poetry run sttx --version
poetry build

The offline suite uses fakes for native inference and does not download models. An unfiltered poetry run pytest also runs the three real integration gates; these require native packages, models/network, ffmpeg, strace, and working Linux namespace support. Keep their evidence and build outputs in scratch directories, not tracked fixtures. poetry.lock remains ignored local state. The repository contract requires the approved qualification sources, tests, scripts, baseline JSON, and this gap history; the packaging contract still requires an exact pure wheel inventory and exact dependency metadata. Models, media, run evidence, wheels, caches, and lockfiles are not repository deliverables.

Backend qualification identity

Normal fresh installations resolve the four exact direct runtime pins in pyproject.toml: sherpa-onnx==1.13.6, sherpa-onnx-bin==1.13.6, huggingface-hub==1.29.0, and numpy==2.4.6. This is not a lock of every transitive dependency or a promise of identical numerics on every CPU.

The qualification fingerprint includes sorted direct dependencies, the backend contract payload (settings, asset and independently pinned fixture identities, timing constants, probe version, and approved-run anchor), and the complete bytes of exactly these 26 required sources:

scripts/compute_backend_fingerprint.py
scripts/backend_change_detector.py
tests/conftest.py
src/sttx/asr.py
src/sttx/cli.py
src/sttx/model.py
src/sttx/audio.py
src/sttx/asr_events.py
tests/integration/timing_lattice.py
tests/integration/test_timing_qualification.py
tests/integration/qualification_schema.py
tests/integration/qualification_validation.py
tests/integration/qualification_evaluation.py
tests/integration/qualification_recording.py
tests/integration/qualification_probes.py
tests/integration/test_binding_contract.py
tests/integration/test_real_pipeline.py
tests/integration/real_pipeline_artifacts.py
tests/integration/real_pipeline_checks.py
tests/integration/real_pipeline_evidence_contract.py
tests/integration/real_pipeline_media.py
tests/integration/real_pipeline_observability.py
tests/integration/real_pipeline_runner.py
tests/integration/real_pipeline_signal_process.py
tests/integration/real_pipeline_signals.py
tests/integration/real_pipeline_trace.py

Any bound-byte change, including comments, invalidates the old fingerprint. All 26 sources must be present for qualification and release. New executable qualification logic must join the binding before use, not escape into an unhashed helper. Ownership is deliberate:

Owner Responsibility
scripts/compute_backend_fingerprint.py Read-only checker CLI and promotion policy
scripts/backend_change_detector.py Base/head AST and diff decisions, without executing base code
tests/integration/test_timing_qualification.py Native gate lifecycle and failure envelopes
qualification_schema.py, qualification_validation.py, qualification_evaluation.py Typed serialization, parsing/validation, and pure comparison respectively
qualification_recording.py, qualification_probes.py Passive production recording and real probe collection respectively
src/sttx/model.py, src/sttx/audio.py, src/sttx/asr_events.py Asset acquisition and checksums, ffmpeg normalization of the observed samples, and the activity-event union the probes consume
tests/integration/test_binding_contract.py, test_real_pipeline.py, real_pipeline_*.py Native binding and real pipeline gates, their fixtures, checks, and evidence
tests/conftest.py Pytest options and atomic evidence writer
tests/backend_qualification_helpers.py Offline synthetic fixtures only; never a checker/gate runtime import

Bare qualification_*.py names refer to files under tests/integration/. The signed observation payload has three separate collections: raw lattice runs, VAD/chunk-level production_runs, and final production_transcripts. Actual production builders and transcribe() supply nonempty speech and multichunk coverage. This finite fixture and its timing tolerances are an acceptance envelope, not proof against every numerical or transcription change. See KNOWN-GAPS.md for historical context and residual limits.

Read-only checks from the repository root:

poetry run python scripts/compute_backend_fingerprint.py --help
poetry run python scripts/compute_backend_fingerprint.py
poetry run python scripts/compute_backend_fingerprint.py --check tests/integration/qualified_backend.json

Print mode emits an identity, not a qualification verdict. --check validates the baseline, signature/anchor, provenance, and current source identity; it does not run inference or certify a clean Git worktree. Neither mode writes a record.

Qualifying a backend update locally

Complete and review all contract, dependency, and bound-source changes before collecting candidates. A changed bound probe source requires an increasing PROBE_VERSION and a changed qualified record. Preserve the previous baseline and anchor together before changing either. Keep C1/C2/C3 in distinct files in one persistent local package environment; CI candidates are diagnostic only and must never be promoted directly.

  1. Allocate a fresh evidence directory, for example QUALIFICATION_DIR=$(mktemp -d /tmp/sttx-qualification.XXXXXX). Measure C1 against the previous baseline and anchor with the command below. Keep C1 unchanged, even if the final aggregate assertion is nonzero. Inspect the complete candidate, not merely that exit status: every candidate must match the current contract. For routine updates C1 must be comparable with empty integrity diagnostics. Only reviewed stale fingerprint, probe-version, model-repo/revision/assets, dependencies, or quantum identity diagnostics are eligible; verify each delta against the intended change.

    poetry run pytest tests/integration/test_timing_qualification.py -m integration -q --qualification-output="$QUALIFICATION_DIR/C1.json"
    
  2. Review all behavioral differences. Routine promotion requires empty behavior diagnostics; accepting a known difference is a deliberate maintainer decision using --accept-behavior-drift, with the reason recorded in review. Never use it to bypass invalid observations, missing coverage, fixture/platform mismatch, integrity failures, or unknown diagnostic codes.

  3. Rotate QUALIFIED_RUNS_SHA256 to C1's signed-payload signature and rerun the same gate into "$QUALIFICATION_DIR/C2.json". C2 must have the current fingerprint and anchor and exactly the same canonical three-collection payload and signature as C1. Only the permitted anchor-transition diagnostic may be added to the reviewed identity diagnostics. Stop on instability. Run the read-only promotion check before writing any baseline:

    poetry run pytest tests/integration/test_timing_qualification.py -m integration -q --qualification-output="$QUALIFICATION_DIR/C2.json"
    poetry run python scripts/compute_backend_fingerprint.py --check-promotion "$QUALIFICATION_DIR/C1.json" "$QUALIFICATION_DIR/C2.json"
    
  4. After checker success, promote C2's observations and identity into tests/integration/qualified_backend.json with record_kind=qualified_backend. Replace its top-level baseline_comparison with C1's comparison and add promotion={previous_fingerprint,previous_anchor,comparison_to_previous,decision}. The provenance comparison must equal the top-level comparison. Use routine or accepted_behavior as appropriate; preserve the actual previous identity. The checker does not perform this edit for you.

  5. Run --check tests/integration/qualified_backend.json, then the native gate again into "$QUALIFICATION_DIR/C3.json". C3 must pass with empty diagnostics. Land the reviewed baseline and anchor together, never an intermediate state. On any failed rotation, restore the saved baseline/anchor pair and retain candidate evidence for diagnosis. Any subsequent bound-source edit requires reviewed regeneration and the full sequence again.

    poetry run python scripts/compute_backend_fingerprint.py --check tests/integration/qualified_backend.json
    poetry run pytest tests/integration/test_timing_qualification.py -m integration -q --qualification-output="$QUALIFICATION_DIR/C3.json"
    

Bootstrap is only for a genuinely absent baseline: C1/C2 may report only baseline.missing, with otherwise valid observations. Use --bootstrap on the pair check, then decision=bootstrap with null previous fingerprint/anchor. It is incompatible with --accept-behavior-drift and refuses an existing baseline; deleting a baseline is not an upgrade bypass. Failure envelopes are never promotable. Unsafe output aliases or filesystem failures can prevent artifact creation and still fail the gate.

CI, dependency updates, and releases

The CI workflow retains the Python 3.11-3.13 offline matrix. On unrelated PRs, the backend job explicitly succeeds without model downloads, native gates, or artifact uploads. The detector requires qualification when the fingerprint mismatches the committed baseline or the record changed; metadata-only pyproject.toml edits are not automatically expensive. Missing base history follows a strict first-introduction policy, not silent fast-pass.

The slow path runs qualification, binding, and the full pipeline separately, retains each outcome, uploads every required evidence file individually, and fails the aggregate on any failed/skipped gate, missing upload, or stale baseline. The stable checks job requires both the matrix and backend job. On fresh jobs, qualification acquires an ambient bundle, binding reuses it, and the full pipeline acquires an independently isolated cold bundle with real signal/cache checks. No Actions model cache persists across jobs or runs.

Dependabot proposes weekly grouped speech-backend updates. There is no auto-merge, automatic baseline commit, or auto-promotion.

An authorized v* tag starts the release workflow. The tag must match the package version. Build runs once; qualification downloads that exact artifact ID and installs its wheel into a fresh non-editable venv, proves import origin, then runs all three native gates before publication. Publish and GitHub release download that same artifact through the dependency chain build -> qualify -> publish -> github-release; there is no rebuild. OIDC id-token: write is confined to publish under the pypi environment, and contents: write to the GitHub release job with GH_TOKEN bound to the workflow token. Failed qualification blocks publication. These are implemented workflow contracts, not evidence of hosted execution. Local verification is complete, including bootstrap C1/C2/C3 and the offline plain wheel release mirror. Final F1 through F4 verification and authorized hosted PR/tag execution remain outstanding. No tag or publication is part of local qualification.

Releasing from a validated branch

This procedure records local validation. It does not provide hosted evidence. The committed baseline was produced locally on CPython 3.12.3. The PR backend-compat slow path reruns qualification on CPython 3.13, as .github/workflows/ci.yml:89-93 specifies. Identity or behavior drift between interpreters fails the PR and requires a fresh local cycle. The hosted evidence contract also requires unshare to work on the runner.

  1. Run the local gate from the repository root before pushing. It matches the todo 6 release mirror commands. Both jq checks must print true, and every command must succeed.

    poetry run python -I -S -B scripts/backend_change_detector.py --base origin/main --head HEAD --root "$PWD" | jq -e '.first_introduction==true and .head_version==1 and .record_changed==true and .needs_qualification==true and .fingerprint_mismatch==false'
    poetry run python -I -S -B scripts/backend_change_detector.py --base "$(git rev-parse HEAD)" --head "$(git rev-parse HEAD)" --root "$PWD" | jq -e '.needs_qualification==false and .fingerprint_mismatch==false and .first_introduction==false'
    poetry run pytest -q -m "not integration"
    poetry check
    poetry run python -m compileall -q src tests
    poetry build
    poetry run pytest tests/test_backend_fingerprint.py -q -k "existing_source_changes and (model or audio or asr_events)"
    BUILD_OUT=$(mktemp -d /tmp/sttx-build.XXXXXX)
    RELEASE_TMP=$(mktemp -d /tmp/sttx-release.XXXXXX)
    export WHEEL_VENV="$RELEASE_TMP/wheel-venv"
    EVIDENCE="$RELEASE_TMP/evidence"
    mkdir -p "$EVIDENCE"
    test ! -e "$WHEEL_VENV"
    poetry build --output "$BUILD_OUT"
    wheels=("$BUILD_OUT"/*.whl); test "${#wheels[@]}" = 1
    test -f "${wheels[0]}"
    poetry run python -m venv "$WHEEL_VENV"
    "$WHEEL_VENV/bin/python" -m pip install "${wheels[0]}" pytest
    "$WHEEL_VENV/bin/python" -c 'import importlib.metadata, os, sys, sttx; from pathlib import Path; assert sys.prefix == os.environ["WHEEL_VENV"]; assert Path(sttx.__file__).resolve().is_relative_to(Path(sys.prefix).resolve()); assert importlib.metadata.version("sttx") == "0.1.2"; print(sttx.__file__); print(importlib.metadata.version("sttx"))'
    "$WHEEL_VENV/bin/python" --version
    "$WHEEL_VENV/bin/python" -m pytest tests/integration/test_timing_qualification.py -m integration -q --qualification-output="$EVIDENCE/release-candidate.json"
    "$WHEEL_VENV/bin/python" -m pytest tests/integration/test_binding_contract.py -m integration -q --identity-output="$EVIDENCE/release-binding.json"
    "$WHEEL_VENV/bin/python" -m pytest tests/integration/test_real_pipeline.py -m integration -q --identity-output="$EVIDENCE/release-pipeline.json"
    jq -e --arg venv "$WHEEL_VENV" --arg executable "$WHEEL_VENV/bin/python" --arg fingerprint "$(poetry run python -I -S -B scripts/compute_backend_fingerprint.py)" '.venv == $venv and .executable == $executable and .fingerprint == $fingerprint and .gate.verdict == "pass"' "$EVIDENCE/release-pipeline.json"
    find dist -maxdepth 1 -type f -printf '%f\n' | LC_ALL=C sort
    rm -rf "$BUILD_OUT" "$RELEASE_TMP" dist
    git status --porcelain --untracked-files=no
    git diff --quiet && git diff --cached --quiet
    
  2. Push the validated branch.

    git push -u origin token-out-of-chunk
    
  3. Open a PR from token-out-of-chunk against main. Because the base has no contract, the PR backend-compat job takes the first introduction slow path with real model downloads under a 60 minute timeout.

  4. Wait for test (3.11|3.12|3.13), backend-compat, and the stable checks job to succeed.

  5. Merge the PR after those jobs succeed.

  6. Tag the merge commit and push that tag. Replace <merge-commit> with the merge commit SHA. The tag must equal v$(poetry version --short). The v0.1.1 tag already exists on the remote and cannot be reused.

    git tag v0.1.2 <merge-commit> && git push origin v0.1.2
    
  7. The tag starts build -> qualify -> publish -> github-release. The publish job runs under the pypi environment with id-token: write. A failed qualification blocks publication.

  8. PyPI never permits reusing a version number. A failed publish requires a new patch version and a new tag.

Attribution and licensing

sttx source code is licensed under the GNU General Public License v3.0 only. Third-party software and model assets remain subject to their own terms; see NOTICE.md for attribution and primary upstream license links.

Metadata

Release files for sttx 0.1.2

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

Source distribution (sdist)

Source distribution for sttx 0.1.2
File Size Uploaded
sttx-0.1.2.tar.gz 49.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sttx 0.1.2
File Interpreter ABI Platform
sttx-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 95.7 kB

Release files / sttx-0.1.2.tar.gz

Download URL sttx-0.1.2.tar.gz
Size 49.9 kB
Tags Source
SHA-256 checksum
How to use checksums
dd5582f95b591e76b6f63fea3e7dc78fe2039733622aff195411e40d97a8cbf1
BLAKE2b-256 checksum
How to use checksums
d986aaaaeafa50cd14fb3d4bcae75625f1d080d8b572881817dd731ebbabea2f
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 12, 2026.

Transparency log

Release files / sttx-0.1.2-py3-none-any.whl

Download URL sttx-0.1.2-py3-none-any.whl
Size 45.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bbceab14bd95ab53f5993adb08f5c3f7c9ff2b67cb3c5e9073294becfcbac394
BLAKE2b-256 checksum
How to use checksums
cc1843dd6a326db00c02b3044718326d4e474c1abd7f3039f25e46f8a08b9cb2
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 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

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