Generate a Media Overlay EPUB from an audiobook file and a source .epub.
This project provides a command-line pipeline that automatically resumes prior work when possible and:
- prepares a working EPUB copy
- splits audiobook input into audio chunks
- transcribes each chunk with a platform-appropriate backend
- matches transcript chunks to EPUB HTML files
- injects stable segment ids into HTML
- generates SMIL overlays
- packages the final Media Overlay EPUB
- validates the generated result
- Python 3.10 or later, subject to the selected backend's supported Python versions.
- FFmpeg, including both
ffmpegandffprobe, installed onPATH. - Internet access on first use to download transcription/alignment models and NLTK data.
- Enough disk space for models, split audio, temporary WAV conversion, and the output EPUB.
All three backends are integrated in transcription_backend.py:
| Backend option | Transcription | Word alignment | Selection |
|---|---|---|---|
whisperx |
NVIDIA CUDA when available, otherwise CPU | WhisperX on the selected device | Default outside Apple Silicon |
mlx |
Apple Silicon using MLX | mlx-whisperx | Default on Apple Silicon |
whispercpp |
Vulkan GPU, including AMD | WhisperX on CPU | Select explicitly |
For the pip-installed whispercpp runtime, use Windows x64, an AVX2/FMA/F16C-capable CPU, a Vulkan-capable GPU with its graphics driver installed, and the Microsoft Visual C++ v14 x64 Redistributable. CUDA, a compiler, and the Vulkan SDK are not needed to run it. Automatic runtime installation is Windows x64 only; other systems require a compatible Vulkan executable through WHISPERCPP_BINARY and the WhisperX alignment dependency.
Install Python dependencies into your virtual environment:
python -m pip install -r requirements.txtFor a fresh Windows checkout, from the repository directory (PowerShell):
py -3.10 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
ffmpeg -version
ffprobe -versionReuse an existing virtual environment if you already have one. requirements.txt includes huggingface-hub for whispercpp model downloads and the Windows x64 whispercpp-vulkan-runtime wheel for the executable and DLLs. Keep WhisperX installed when using whispercpp: it supplies word alignment on CPU. FFmpeg and GPU drivers are system requirements, not Python packages.
Pip installs the whispercpp executable and DLLs into the active environment and creates Scripts/whispercpp.exe. Models download separately into the Hugging Face cache on first use. No vendor archive or separate runtime installer is required. See AMD GPU setup and storage below.
The pipeline downloads the required NLTK tokenizer data automatically on first run and caches it under ~/.cache/epub-media-overlay/nltk_data.
Basic run:
python generate_epub_overlay.py \
--audio /path/to/book.m4b \
--epub /path/to/book.epubParameter template with every CLI option shown:
python generate_epub_overlay.py \
--audio /path/to/book.m4b \
--epub /path/to/book.epub \
--backend mlx \
--output-dir /path/to \
--work-dir /path/to/.book.epubmo \
--model mlx-community/whisper-small-mlx \
--language en \
--audio-extension .m4a \
--audio-codec aac \
--audio-bitrate 64k \
--audio-sample-rate 24000 \
--audio-channels 1 \
--split-jobs 4 \
--chunk-seconds 600 \
--batch-size 1 \
--mlx-cache-gb 8- transcription backend:
- Apple Silicon macOS:
mlx-whisperx - other platforms:
whisperx
- Apple Silicon macOS:
- available backend overrides:
mlxwhisperxwhispercpp(Vulkan GPU; opt-in)
- transcription model:
- Apple Silicon macOS:
mlx-community/whisper-large-v3-mlx - other platforms:
small
- Apple Silicon macOS:
- language:
en - audio extension:
.m4a - split audio codec:
copy - AAC split audio bitrate:
64kwhen--audio-codec aac - AAC split audio sample rate:
24000when--audio-codec aac - AAC split audio channels:
1(mono) when--audio-codec aac - split jobs with
--audio-codec copy:1 - split jobs with
--audio-codec aac:max(1, cpu_count - 2), capped at CPU count - transcription batch size:
1
With --audio-codec copy:
- split audio preserves the source audio stream without re-encoding
- packaged Media Overlay playback keeps source-quality audio
- splitting is usually faster than AAC re-encoding
- final EPUB size may increase because the source-quality audio is packaged
The --language setting is used for both transcription and HTML sentence segmentation.
--audio
- Required.
- Path to the source audiobook file.
- Any audio extension is accepted as long as
ffprobeandffmpegcan read it.
--epub
- Required.
- Path to the source ebook file.
- Must point to an
.epubfile.
--output-dir
- Optional.
- Directory where the final
<book-stem>.media-overlay.epubfile is written. - Default: the source EPUB directory.
--work-dir
- Optional.
- Directory used for persistent state, logs, transcripts, split audio, and intermediate EPUB artifacts.
- Default:
<output-dir>/.<book-stem>.epubmo
--model
- Optional.
- Transcription model identifier.
- Default:
- Apple Silicon macOS:
mlx-community/whisper-large-v3-mlx - other platforms:
small
- Apple Silicon macOS:
- Valid model names depend on the active backend.
- Common Apple Silicon macOS values:
mlx-community/whisper-tiny-mlxmlx-community/whisper-tiny.en-mlxmlx-community/whisper-base-mlxmlx-community/whisper-base.en-mlxmlx-community/whisper-small-mlxmlx-community/whisper-small.en-mlxmlx-community/whisper-medium-mlxmlx-community/whisper-medium.en-mlxmlx-community/whisper-large-mlxmlx-community/whisper-large-v1-mlxmlx-community/whisper-large-v2-mlxmlx-community/whisper-large-v3-mlxmlx-community/whisper-large-v3-turbo
- Common values on other platforms:
tinytiny.enbasebase.ensmallsmall.enmediummedium.enlargelarge-v1large-v2large-v3turbo
--modelcan also point to a compatible local model path instead of one of the common identifiers above.
--backend
- Optional.
- Overrides automatic backend detection.
- Supported values:
mlx,whisperx,whispercpp - Default:
- Apple Silicon macOS:
mlx - other platforms:
whisperx
- Apple Silicon macOS:
--language
- Optional.
- Language code used for transcription and HTML sentence segmentation.
- Default:
en
--audio-extension
- Optional.
- Filename extension used for split audio chunks.
- Default:
.m4a
--audio-codec
- Optional.
- Split audio codec mode.
- Supported values:
copy,aac - Default:
copy copypreserves the source audio stream.aacre-encodes split audio and enables the quality controls below.- With
--audio-codec aac, the default bitrate is64kand the default sample rate is24000unless overridden.
--audio-bitrate
- Optional.
- AAC bitrate for split audio chunks, such as
64k,96k, or128k. - Default:
64kwhen--audio-codec aacis used. - Only valid when
--audio-codec aacis used.
--audio-sample-rate
- Optional.
- AAC sample rate in Hz, such as
24000or44100. - Default:
24000when--audio-codec aacis used. - Only valid when
--audio-codec aacis used.
--audio-channels
- Optional.
- AAC channel count, such as
1for mono or2for stereo. - Default:
1(mono) when--audio-codec aacis used. - Only valid when
--audio-codec aacis used.
--chunk-seconds
- Optional.
- Fixed chunk length, in seconds, used only when the source audio has no chapter metadata.
- Default:
600
--split-jobs
- Optional.
- Number of parallel
ffmpegjobs used during the split stage. - Default with
--audio-codec copy:1 - Default with
--audio-codec aac:max(1, cpu_count - 2) - The effective value is capped at the machine CPU count.
- Higher values may help AAC splitting, but can be slower on external drives or with
--audio-codec copy.
--batch-size
- Optional.
- Number of audio windows decoded together per transcription batch.
- Default:
1 - Higher values can increase transcription speed but raise peak memory (notably GPU/unified memory on the
mlxbackend). - Ignored by
whispercpp, which processes one audio chunk per invocation.
--mlx-cache-gb
- Optional.
- Cap, in GB, for the
mlxMetal buffer cache during transcription, limiting how much freed GPU memorymlxretains across chunks. - Default: unset (no cap).
- Ignored for non-
mlxbackends.
- if compatible work already exists, the pipeline resumes automatically
- if the inputs or transcription-relevant options changed, derived artifacts (match, segmentation, SMIL, packaged and final EPUB) are rebuilt; split audio chunks and transcripts are kept and reused whenever their recorded configuration still matches
- if no work exists yet, the pipeline starts from the beginning
- if
--output-diris omitted, the final EPUB is written next to the source EPUB
Final output:
<book-stem>.media-overlay.epub- default location: the source EPUB folder
Working directory default:
<resolved-output-dir>/.<book-stem>.epubmo
Important working artifacts:
state.jsonmatched_list.jsonsegmented.epub3validation.jsonlogs/pipeline.log
- The source
.epubis not modified directly. - If the source audio has chapter metadata, the pipeline splits by chapter.
- If the source audio has no chapter metadata, the pipeline splits into fixed-size chunks.
- Split audio defaults to stream copy so packaged playback preserves source quality.
- Validation checks packaging, SMIL clip quality, transcript coverage, and OPF media-overlay wiring.
- The console output and
logs/pipeline.loginclude detailed stage-by-stage progress and timings.
generate_epub_overlay.py: CLI wrapper and state manager with automatic resumepipeline_core.py: EPUB/audio matching, SMIL generation, packaging, and validation logicmark_sentence.py: HTML segmentation logic used for overlay targetsepub_reference_index.py: builds the set of referencedclass/idnames so tag cleanup only removes provably-unused markuptranscription_backend.py: MLX, WhisperX, and whispercpp adapters, shared word alignment, and whispercpp runtime/model setuprequirements.txt: Python dependencies for the pipeline and backends- whisper.cpp runtime repository: runtime source packaging, build script, releases, and licenses
If ffmpeg or ffprobe are missing, install them and ensure they are on PATH.
If the first run cannot download NLTK data automatically, make sure the machine has network access and write permission for ~/.cache/epub-media-overlay/nltk_data.
If a run is interrupted, rerun the same command with the same work directory. Completed compatible chunk transcripts are reused; an unfinished chunk restarts from its beginning. With whispercpp, the progress bar advances when a chunk finishes, so a long chapter can remain at 0% while transcription and CPU alignment are running.
For whispercpp GPU detection errors, check your graphics driver and Vulkan support. The backend reports an error when GPU execution cannot be confirmed. For a missing runtime package, run python -m pip install -r requirements.txt in the same Python environment used to run the program. The pipeline log contains per-chunk GPU diagnostics.
Select --backend whispercpp --model small for Vulkan GPU transcription followed
by WhisperX forced word alignment on CPU. The remaining matching, SMIL, packaging,
and validation stages use the existing pipeline. Default backend selection is
unchanged; this backend is opt-in. --batch-size does not affect whisper.cpp.
Install the normal Python requirements with python -m pip install -r requirements.txt.
On Windows x64, pip installs the whispercpp-vulkan-runtime wheel named in requirements.txt from the latest standalone runtime release. The URL currently requires the 1.8.5.1 wheel asset; update the filename when changing package versions.
- Runtime and DLLs:
.venv/Lib/site-packages/whispercpp_runtime/bin/. - Command launcher:
.venv/Scripts/whispercpp.exe. - GGML models: standard Hugging Face cache, normally
~/.cache/huggingface/hub/models--ggerganov--whisper.cpp/. - WhisperX CPU alignment models: downloaded when needed and reused from their existing cache.
Package revision 1.8.5.1 contains an MSVC/shared-library build of upstream commit f24588a272ae8e23280d9c220536437164e6ed28 (reported version 1.8.5). This is a community build, not an executable published by OpenAI or ggml-org. Build provenance and third-party licenses are included in the installed package; build instructions live in the separate runtime repository.
No compiler, Vulkan SDK, or manual model download is required for end users. A Vulkan-capable graphics driver, FFmpeg, and the Microsoft Visual C++ v14 x64 Redistributable are required. Pip installation and initial model downloads need network access; installed runtimes and cached models work offline. Other platforms can supply their own compatible Vulkan CLI.
Storage overrides:
HF_HOME/HF_HUB_CACHE: standard Hugging Face model cache controls.WHISPERCPP_BINARY: an explicit compatible Vulkanwhisper-cliexecutable.WHISPERCPP_MODEL_DIR: a manually populated model directory.--model: an existing GGML.binfile or a supported Whisper model name.
WHISPERCPP_CACHE_DIR is no longer used. Upgrading from the extracted runtime changes the executable path, so existing transcript stamps may trigger retranscription. Keep a separate work directory if you want to preserve old results for comparison. The old runtime cache is not used or automatically deleted.
GGML models are downloaded from https://huggingface.co/ggerganov/whisper.cpp,
pinned at revision 5359861c739e955e79d9a303bcbc70fb988958b1. Existing faster-whisper
model files cannot be used as GGML models. The adapter checks for a Vulkan GPU
before prepare and requires confirmation of GPU execution for each chunk; it
will report an error instead of silently accepting CPU fallback.
Example from the repository directory (PowerShell):
.\.venv\Scripts\python.exe generate_epub_overlay.py `
--audio "..\Audiobooks\Example.m4b" `
--epub "..\EPUB\Example.epub" `
--backend whispercpp --model small --language en `
--work-dir "..\Overlay Work\Example-amd" `
--output-dir "..\Media Overlays AMD"Use a separate work/output folder when comparing backends to retain both sets of results. Cache stamps distinguish the backend, model, and local executable/model file identities. An identical rerun can reuse completed AMD transcripts. Replacing the executable or model with a file of different size or modification time invalidates those transcript stamps. Runtime DLL changes alone are not detected: use a new work folder when replacing only companion DLLs.
Audio chunks are converted to temporary 16 kHz mono PCM WAV files for whisper.cpp. Its JSON millisecond offsets become second-based segments, then the existing WhisperX alignment model supplies word timestamps. Alignment models are cached across chunks; whisper.cpp currently starts a new process and loads its model for each chunk. All temporary conversion files are cleaned up on success or failure. GPU selection is shown once at startup. Per-chunk GPU diagnostics and timings go only to the pipeline log, so the console progress bar updates in place.
Different transcription backends can produce different text and segment boundaries with the same model size.