readalign 1.0.0__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {readalign-1.0.0 → readalign-1.2.0}/.gitignore +20 -20
- readalign-1.2.0/CHANGELOG.md +94 -0
- {readalign-1.0.0 → readalign-1.2.0}/PKG-INFO +28 -4
- {readalign-1.0.0 → readalign-1.2.0}/README.md +27 -3
- {readalign-1.0.0 → readalign-1.2.0}/pyproject.toml +1 -1
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/__init__.py +1 -1
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/align.py +13 -1
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/asr.py +303 -277
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/audio.py +39 -15
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/cli.py +628 -495
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/clock.py +15 -9
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/epub.py +674 -671
- readalign-1.2.0/src/readalign/fs.py +64 -0
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/members.py +72 -72
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/package.py +91 -46
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/sentences.py +3 -1
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/sidecar.py +10 -5
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/smil.py +4 -3
- readalign-1.2.0/src/readalign/verify.py +75 -0
- {readalign-1.0.0 → readalign-1.2.0}/tests/test_align.py +28 -0
- {readalign-1.0.0 → readalign-1.2.0}/tests/test_asr.py +72 -0
- readalign-1.2.0/tests/test_audio.py +77 -0
- readalign-1.2.0/tests/test_cli.py +292 -0
- readalign-1.2.0/tests/test_fs.py +51 -0
- {readalign-1.0.0 → readalign-1.2.0}/tests/test_output.py +160 -0
- {readalign-1.0.0 → readalign-1.2.0}/tests/test_sidecar_report.py +33 -0
- {readalign-1.0.0 → readalign-1.2.0}/tests/test_text.py +13 -0
- readalign-1.2.0/tests/test_verify.py +128 -0
- readalign-1.0.0/CHANGELOG.md +0 -34
- readalign-1.0.0/src/readalign/fs.py +0 -22
- readalign-1.0.0/tests/test_cli.py +0 -100
- {readalign-1.0.0 → readalign-1.2.0}/LICENSE +0 -0
- {readalign-1.0.0 → readalign-1.2.0}/scripts/fetch_samples.py +0 -0
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/__main__.py +0 -0
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/errors.py +0 -0
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/refine.py +0 -0
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/report.py +0 -0
- {readalign-1.0.0 → readalign-1.2.0}/src/readalign/textnorm.py +0 -0
- {readalign-1.0.0 → readalign-1.2.0}/tests/conftest.py +0 -0
- {readalign-1.0.0 → readalign-1.2.0}/tests/test_epub.py +0 -0
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
__pycache__/
|
|
2
|
-
*.py[cod]
|
|
3
|
-
*.egg-info/
|
|
4
|
-
.eggs/
|
|
5
|
-
build/
|
|
6
|
-
dist/
|
|
7
|
-
.venv/
|
|
8
|
-
venv/
|
|
9
|
-
.pytest_cache/
|
|
10
|
-
.ruff_cache/
|
|
11
|
-
.coverage
|
|
12
|
-
htmlcov/
|
|
13
|
-
*.epub
|
|
14
|
-
!examples/aesop/*.epub
|
|
15
|
-
readalign-report.json
|
|
16
|
-
*-sidecars/
|
|
17
|
-
.readalign-cache/
|
|
18
|
-
examples/output/
|
|
19
|
-
.DS_Store
|
|
20
|
-
Thumbs.db
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.eggs/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.coverage
|
|
12
|
+
htmlcov/
|
|
13
|
+
*.epub
|
|
14
|
+
!examples/aesop/*.epub
|
|
15
|
+
readalign-report.json
|
|
16
|
+
*-sidecars/
|
|
17
|
+
.readalign-cache/
|
|
18
|
+
examples/output/
|
|
19
|
+
.DS_Store
|
|
20
|
+
Thumbs.db
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.0 - 2026-10-22
|
|
4
|
+
|
|
5
|
+
- New: `--report PATH` selects a separate JSON report for each batch build, with guards
|
|
6
|
+
against overwriting input audio, the input/output EPUB or generated sidecars.
|
|
7
|
+
- Fixed: colliding overlay names and URL-special characters in EPUB resource paths.
|
|
8
|
+
Generated audio and CSS also avoid overwriting unrelated existing resources.
|
|
9
|
+
- Fixed: transcript cache identity now hashes prepared audio bytes and includes chapter
|
|
10
|
+
boundaries and the effective language. Malformed cached words are ignored instead of crashing.
|
|
11
|
+
- Fixed: EPUB, report and cache writes replace their destinations only after a complete write.
|
|
12
|
+
- Fixed: work-directory audio aliases, invalid durations, chapter marks and beam sizes are
|
|
13
|
+
rejected; missing epubcheck paths fail before transcription.
|
|
14
|
+
- Fixed: `check` rejects unmanifested audio, overlays pointing outside their associated document,
|
|
15
|
+
empty overlays, duplicate manifest ids and mismatched durations. Corrupt archives produce
|
|
16
|
+
readable errors; audio-probe scratch filenames never come from archive member names.
|
|
17
|
+
- New: `--min-token-match FRACTION` optionally gates builds on the fraction of book tokens
|
|
18
|
+
matched to narration. Values range from 0 to 1; the default 0 preserves existing behaviour.
|
|
19
|
+
A failed gate exits 5 after writing the EPUB, report and enabled sidecars. Its threshold and
|
|
20
|
+
verdict are recorded under `token_match_gate`; drift and epubcheck failures retain precedence.
|
|
21
|
+
- Fixed: chapters with identical basenames in different EPUB directories could overwrite each
|
|
22
|
+
other's WebVTT sidecars. Colliding names now gain a numeric suffix, including case-only
|
|
23
|
+
collisions on Windows, without changing filenames for non-colliding exports.
|
|
24
|
+
- Fixed: the ebook overwrite guard now recognises symbolic links, hard links and path aliases.
|
|
25
|
+
- Fixed: non-finite drift thresholds such as NaN and infinity are rejected before processing.
|
|
26
|
+
|
|
27
|
+
## 1.1.0 - 2026-10-04
|
|
28
|
+
|
|
29
|
+
- New: `--verify [JAR]` runs epubcheck on the result right after the build, prints its verdict,
|
|
30
|
+
records it under `verify` in the report, and exits 4 when it finds errors. epubcheck needs
|
|
31
|
+
Java, so the flag is opt-in: with a path it uses that jar, without one it searches `PATH`,
|
|
32
|
+
`EPUBCHECK_HOME` and `JAVA_HOME`.
|
|
33
|
+
- New: every unaligned region in the report carries an `excerpt` quoting the book's own words,
|
|
34
|
+
so a stretch that never matched the narration is recognisable without a token diff.
|
|
35
|
+
- Fixed: `--audio` accepted a glob in the documentation but not in the code, so a pattern like
|
|
36
|
+
`book-*.mp3` failed with "audio not found" on Windows (and anywhere the shell was not asked
|
|
37
|
+
to expand it). Patterns are now expanded by readalign itself, in natural order, with the same
|
|
38
|
+
DRM refusal as a directory.
|
|
39
|
+
- Fixed: `readalign check` raised a raw traceback on input that was a valid zip but broken XML:
|
|
40
|
+
an unparseable `META-INF/container.xml` or package document is now one line and exit 1, and
|
|
41
|
+
an unparseable overlay or content document is reported as a finding like any other fault
|
|
42
|
+
instead of crashing the walk.
|
|
43
|
+
- Fixed: rebuilding over an EPUB that already carried overlays kept the previous run's audio
|
|
44
|
+
manifest item, so a re-run whose prepared audio changed name or format shipped the old file
|
|
45
|
+
as dead weight (a full extra audiobook when the suffix drifted). A re-run now drops the
|
|
46
|
+
previous audio and reuses the same manifest ids, the way it already did for overlays and the
|
|
47
|
+
stylesheet.
|
|
48
|
+
- Fixed: manifest items added while writing an output were not registered in the package's
|
|
49
|
+
manifest index, so two builds over one package could hand out the same item id twice.
|
|
50
|
+
- Fixed: the transcript cache key ignored `--beam-size` (and the window plan), so changing the
|
|
51
|
+
beam silently served a transcript made with the old setting. Keys now cover every setting
|
|
52
|
+
that changes the transcription, which invalidates caches written by 1.0.0 once.
|
|
53
|
+
- Fixed: a sentence ending in the word "no" never split from the next one ("He said no. She
|
|
54
|
+
left."), because "no" sat in the abbreviation table. Numbered labels like "No. 5" still stay
|
|
55
|
+
together: the digit after the period is not an uppercase word.
|
|
56
|
+
- Fixed: the warning for a failed cache write bypassed the `--quiet` log and printed straight
|
|
57
|
+
to stderr; it now goes through the log like every other progress line.
|
|
58
|
+
- DRM refusals name the file they refused, which matters when a directory of audio contains
|
|
59
|
+
one protected straggler.
|
|
60
|
+
- `readalign check` says "and N more" after its first twenty warnings, as it already did for
|
|
61
|
+
errors, instead of truncating silently.
|
|
62
|
+
|
|
63
|
+
## 1.0.0 - 2026-09-18
|
|
64
|
+
|
|
65
|
+
First release.
|
|
66
|
+
|
|
67
|
+
- `readalign build` turns a DRM-free audiobook and the matching DRM-free EPUB into one EPUB 3
|
|
68
|
+
with conformant Media Overlays: SMIL per content document, `media-overlay` on the manifest
|
|
69
|
+
items, `media:duration` per overlay plus a total, active-class metadata and a highlight
|
|
70
|
+
stylesheet. The overlay's `seq` elements mirror the document's nesting, so both skippability
|
|
71
|
+
(footnotes, endnotes, page numbers, sidebars, annotations) and escapability (tables, lists,
|
|
72
|
+
figures) work. The archive is written mimetype-first and uncompressed as OCF requires.
|
|
73
|
+
- `readalign check` validates the overlays in any EPUB: fragment ids resolve, audio is
|
|
74
|
+
manifested, no `clipEnd` runs past the duration ffprobe reports, declared durations match the
|
|
75
|
+
clips. `--json` for scripting.
|
|
76
|
+
- Transcription with faster-whisper, CUDA when available and CPU otherwise, word timestamps,
|
|
77
|
+
and a per-file transcript cache keyed by content and settings. A GPU that reports itself
|
|
78
|
+
present but cannot load its CUDA libraries falls back to the CPU with one warning line
|
|
79
|
+
naming what to install, rather than a traceback.
|
|
80
|
+
- Anchored alignment: unique shared n-grams, longest increasing subsequence, diff only in the
|
|
81
|
+
gaps. No global dynamic-programming matrix, and audio is streamed rather than buffered on the
|
|
82
|
+
way both in and out, so a 7.5 hour book builds in about 130 MB of memory and opening the
|
|
83
|
+
205 MB result costs 33 MB. Two runs of the same build produce byte-identical archives.
|
|
84
|
+
- Sidecars: one WebVTT per chapter and audio file, plus `align.json`. `readalign-report.json`
|
|
85
|
+
records coverage, drift, per-document statistics and every region that did not match.
|
|
86
|
+
- Handles one file spanning several chapters, several files in one chapter, audio chapter counts
|
|
87
|
+
that disagree with the spine, narrator front and back matter with no text counterpart, notes
|
|
88
|
+
and page-break markers, existing ids, non-UTF8 XHTML, and clip ends past the real duration.
|
|
89
|
+
- DRM-protected input (`.aax`, `.aa`, `.acsm`, an encrypted EPUB) exits 2 and is never opened.
|
|
90
|
+
- Wrong input gets one line and an exit code, never a traceback: a missing file, an unzipped
|
|
91
|
+
book passed as a directory, something that is not a zip, an EPUB 2 package, an empty audio
|
|
92
|
+
directory, or a file ffprobe cannot read.
|
|
93
|
+
- Optional `readalign[refine]` extra adds a CTC forced-alignment pass behind `--refine`. The
|
|
94
|
+
MMS-300m aligner it uses is CC BY-NC 4.0, so it is never downloaded by the default path.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: readalign
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Turn an audiobook and the matching ebook you own into one EPUB 3 with Media Overlays.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Booyaka101/readalign
|
|
6
6
|
Project-URL: Repository, https://github.com/Booyaka101/readalign
|
|
@@ -50,7 +50,8 @@ readalign build --audio audiobook/ --epub book.epub --out read-along.epub
|
|
|
50
50
|
`media-overlay` links, `media:duration` metadata and a highlight stylesheet. It passes
|
|
51
51
|
epubcheck 5.2.1 with zero messages.
|
|
52
52
|
- **`readalign-report.json`** next to the output: coverage, drift, per-document statistics, which
|
|
53
|
-
documents got no overlay and which regions never matched
|
|
53
|
+
documents got no overlay and which regions never matched, each quoted with a short excerpt of
|
|
54
|
+
the book's own words so a bad match is obvious without opening the JSON.
|
|
54
55
|
- **sidecars** (`<name>-sidecars/`): one WebVTT file per chapter and audio file, plus `align.json`
|
|
55
56
|
with every sentence's id, clip times, confidence and text. A rerun clears the `.vtt` files and
|
|
56
57
|
`align.json` it wrote last time, so nothing stale is left behind. Turn them off with
|
|
@@ -93,7 +94,7 @@ Two minutes of audio is not worth spinning up a GPU for, hence `--device cpu`. R
|
|
|
93
94
|
from that command, on a cold cache:
|
|
94
95
|
|
|
95
96
|
```
|
|
96
|
-
readalign 1.
|
|
97
|
+
readalign 1.1.0
|
|
97
98
|
reading examples/aesop/aesop-two-fables.epub
|
|
98
99
|
2 content documents, 18 sentences
|
|
99
100
|
preparing 1 audio file(s)
|
|
@@ -150,6 +151,12 @@ It is worth running the official validator too:
|
|
|
150
151
|
java -jar epubcheck.jar aligned.epub
|
|
151
152
|
```
|
|
152
153
|
|
|
154
|
+
Or let readalign do it: `--verify` runs epubcheck on the result right after writing it, prints
|
|
155
|
+
its verdict, records it in the report, and exits 4 if it found errors. It needs Java and the
|
|
156
|
+
epubcheck jar, which is why it is opt-in: pass the jar's path (`--verify C:/tools/epubcheck.jar`)
|
|
157
|
+
or leave the value off and readalign will look for one on `PATH`, in `EPUBCHECK_HOME` and in
|
|
158
|
+
`JAVA_HOME`.
|
|
159
|
+
|
|
153
160
|
## A full-length run
|
|
154
161
|
|
|
155
162
|
The 25-file LibriVox recording of *Frankenstein* (7 h 26 m) against the Standard Ebooks edition,
|
|
@@ -203,13 +210,23 @@ Drift is how far a sentence's start could be wrong, in seconds.
|
|
|
203
210
|
`--drift-threshold` (default 2.5s) is a gate on the maximum, not the average. The EPUB is always
|
|
204
211
|
written; exceeding the threshold only changes the exit code to 3 so a script can catch it.
|
|
205
212
|
|
|
213
|
+
For batch builds, `--min-token-match 0.9` also requires at least 90% of the ebook's normalised
|
|
214
|
+
tokens to match the narration. This optional gate defaults to 0 (disabled). A failed gate exits 5
|
|
215
|
+
but keeps the EPUB, report and enabled sidecars for inspection. The report's `token_match_gate`
|
|
216
|
+
records the threshold and verdict using the unrounded match fraction. Unnarrated front matter
|
|
217
|
+
and abridgements lower this score, so choose a threshold appropriate to your books. This is a
|
|
218
|
+
screening aid, not proof that every sentence is correctly timed. Drift and epubcheck failures
|
|
219
|
+
take precedence over this gate when more than one check fails.
|
|
220
|
+
|
|
206
221
|
## How it works
|
|
207
222
|
|
|
208
223
|
1. **ffprobe** reads every audio file. FLAC, WAV, ALAC and WMA are transcoded to AAC because
|
|
209
224
|
EPUB 3 only takes MP3, MP4 audio and Opus; MP3, M4A/M4B and Opus are stream-copied, so no
|
|
210
225
|
audio is re-encoded unless it has to be.
|
|
211
226
|
2. **faster-whisper** transcribes with word timestamps. Transcripts are cached per file, keyed by
|
|
212
|
-
|
|
227
|
+
a streamed SHA-256 of the prepared audio, chapter boundaries and recogniser settings.
|
|
228
|
+
A second run avoids transcription, but still prepares and hashes the audio. Older cache keys
|
|
229
|
+
are invalidated once; malformed entries are ignored and rebuilt.
|
|
213
230
|
3. Both sides are normalised to comparable tokens: case folded, accents stripped, punctuation
|
|
214
231
|
dropped, digits and roman numerals spelled out ("Chapter XVIII" and "chapter eighteen" become
|
|
215
232
|
the same tokens).
|
|
@@ -233,9 +250,11 @@ written; exceeding the threshold only changes the exit code to 3 so a script can
|
|
|
233
250
|
--audio PATH a file, a directory of numbered files, or a glob
|
|
234
251
|
--epub PATH the matching DRM-free EPUB
|
|
235
252
|
--out PATH the EPUB to write
|
|
253
|
+
--report PATH JSON report path (default: readalign-report.json beside --out)
|
|
236
254
|
--model NAME faster-whisper model (default: large-v3-turbo)
|
|
237
255
|
--language CODE ISO code of the narration (default: detect)
|
|
238
256
|
--drift-threshold SECS exit 3 if any sentence drifts more than this (default: 2.5)
|
|
257
|
+
--min-token-match N exit 5 if matched book-token fraction is below N (0 to 1; default: 0)
|
|
239
258
|
--sidecars/--no-sidecars write WebVTT and align.json next to the output (default: on)
|
|
240
259
|
--dry-run probe the inputs and print the plan, transcribe nothing
|
|
241
260
|
--narrator NAME value for the media:narrator metadata
|
|
@@ -244,6 +263,9 @@ written; exceeding the threshold only changes the exit code to 3 so a script can
|
|
|
244
263
|
--beam-size N whisper beam size (default: 5)
|
|
245
264
|
--no-vad disable the voice-activity filter
|
|
246
265
|
--audio-bitrate RATE bitrate when audio has to be transcoded (default: 96k)
|
|
266
|
+
--verify [JAR] validate the result with epubcheck after writing it (needs Java);
|
|
267
|
+
pass its jar path, or leave the value off to search PATH,
|
|
268
|
+
EPUBCHECK_HOME and JAVA_HOME for one
|
|
247
269
|
--work-dir PATH scratch directory instead of a temporary one
|
|
248
270
|
--cache-dir PATH transcript cache location
|
|
249
271
|
--no-cache neither read nor write the transcript cache
|
|
@@ -300,6 +322,8 @@ against the reference Media Overlays title from the EPUB test suite at
|
|
|
300
322
|
| 1 | bad input, missing file, missing ffmpeg, nothing could be aligned |
|
|
301
323
|
| 2 | the input is DRM-protected |
|
|
302
324
|
| 3 | the EPUB was written but max drift exceeded `--drift-threshold` |
|
|
325
|
+
| 4 | the EPUB was written but epubcheck reported problems (only with `--verify`) |
|
|
326
|
+
| 5 | the EPUB was written but token match was below `--min-token-match` |
|
|
303
327
|
| 130 | interrupted |
|
|
304
328
|
|
|
305
329
|
Failures print one line, not a traceback.
|
|
@@ -17,7 +17,8 @@ readalign build --audio audiobook/ --epub book.epub --out read-along.epub
|
|
|
17
17
|
`media-overlay` links, `media:duration` metadata and a highlight stylesheet. It passes
|
|
18
18
|
epubcheck 5.2.1 with zero messages.
|
|
19
19
|
- **`readalign-report.json`** next to the output: coverage, drift, per-document statistics, which
|
|
20
|
-
documents got no overlay and which regions never matched
|
|
20
|
+
documents got no overlay and which regions never matched, each quoted with a short excerpt of
|
|
21
|
+
the book's own words so a bad match is obvious without opening the JSON.
|
|
21
22
|
- **sidecars** (`<name>-sidecars/`): one WebVTT file per chapter and audio file, plus `align.json`
|
|
22
23
|
with every sentence's id, clip times, confidence and text. A rerun clears the `.vtt` files and
|
|
23
24
|
`align.json` it wrote last time, so nothing stale is left behind. Turn them off with
|
|
@@ -60,7 +61,7 @@ Two minutes of audio is not worth spinning up a GPU for, hence `--device cpu`. R
|
|
|
60
61
|
from that command, on a cold cache:
|
|
61
62
|
|
|
62
63
|
```
|
|
63
|
-
readalign 1.
|
|
64
|
+
readalign 1.1.0
|
|
64
65
|
reading examples/aesop/aesop-two-fables.epub
|
|
65
66
|
2 content documents, 18 sentences
|
|
66
67
|
preparing 1 audio file(s)
|
|
@@ -117,6 +118,12 @@ It is worth running the official validator too:
|
|
|
117
118
|
java -jar epubcheck.jar aligned.epub
|
|
118
119
|
```
|
|
119
120
|
|
|
121
|
+
Or let readalign do it: `--verify` runs epubcheck on the result right after writing it, prints
|
|
122
|
+
its verdict, records it in the report, and exits 4 if it found errors. It needs Java and the
|
|
123
|
+
epubcheck jar, which is why it is opt-in: pass the jar's path (`--verify C:/tools/epubcheck.jar`)
|
|
124
|
+
or leave the value off and readalign will look for one on `PATH`, in `EPUBCHECK_HOME` and in
|
|
125
|
+
`JAVA_HOME`.
|
|
126
|
+
|
|
120
127
|
## A full-length run
|
|
121
128
|
|
|
122
129
|
The 25-file LibriVox recording of *Frankenstein* (7 h 26 m) against the Standard Ebooks edition,
|
|
@@ -170,13 +177,23 @@ Drift is how far a sentence's start could be wrong, in seconds.
|
|
|
170
177
|
`--drift-threshold` (default 2.5s) is a gate on the maximum, not the average. The EPUB is always
|
|
171
178
|
written; exceeding the threshold only changes the exit code to 3 so a script can catch it.
|
|
172
179
|
|
|
180
|
+
For batch builds, `--min-token-match 0.9` also requires at least 90% of the ebook's normalised
|
|
181
|
+
tokens to match the narration. This optional gate defaults to 0 (disabled). A failed gate exits 5
|
|
182
|
+
but keeps the EPUB, report and enabled sidecars for inspection. The report's `token_match_gate`
|
|
183
|
+
records the threshold and verdict using the unrounded match fraction. Unnarrated front matter
|
|
184
|
+
and abridgements lower this score, so choose a threshold appropriate to your books. This is a
|
|
185
|
+
screening aid, not proof that every sentence is correctly timed. Drift and epubcheck failures
|
|
186
|
+
take precedence over this gate when more than one check fails.
|
|
187
|
+
|
|
173
188
|
## How it works
|
|
174
189
|
|
|
175
190
|
1. **ffprobe** reads every audio file. FLAC, WAV, ALAC and WMA are transcoded to AAC because
|
|
176
191
|
EPUB 3 only takes MP3, MP4 audio and Opus; MP3, M4A/M4B and Opus are stream-copied, so no
|
|
177
192
|
audio is re-encoded unless it has to be.
|
|
178
193
|
2. **faster-whisper** transcribes with word timestamps. Transcripts are cached per file, keyed by
|
|
179
|
-
|
|
194
|
+
a streamed SHA-256 of the prepared audio, chapter boundaries and recogniser settings.
|
|
195
|
+
A second run avoids transcription, but still prepares and hashes the audio. Older cache keys
|
|
196
|
+
are invalidated once; malformed entries are ignored and rebuilt.
|
|
180
197
|
3. Both sides are normalised to comparable tokens: case folded, accents stripped, punctuation
|
|
181
198
|
dropped, digits and roman numerals spelled out ("Chapter XVIII" and "chapter eighteen" become
|
|
182
199
|
the same tokens).
|
|
@@ -200,9 +217,11 @@ written; exceeding the threshold only changes the exit code to 3 so a script can
|
|
|
200
217
|
--audio PATH a file, a directory of numbered files, or a glob
|
|
201
218
|
--epub PATH the matching DRM-free EPUB
|
|
202
219
|
--out PATH the EPUB to write
|
|
220
|
+
--report PATH JSON report path (default: readalign-report.json beside --out)
|
|
203
221
|
--model NAME faster-whisper model (default: large-v3-turbo)
|
|
204
222
|
--language CODE ISO code of the narration (default: detect)
|
|
205
223
|
--drift-threshold SECS exit 3 if any sentence drifts more than this (default: 2.5)
|
|
224
|
+
--min-token-match N exit 5 if matched book-token fraction is below N (0 to 1; default: 0)
|
|
206
225
|
--sidecars/--no-sidecars write WebVTT and align.json next to the output (default: on)
|
|
207
226
|
--dry-run probe the inputs and print the plan, transcribe nothing
|
|
208
227
|
--narrator NAME value for the media:narrator metadata
|
|
@@ -211,6 +230,9 @@ written; exceeding the threshold only changes the exit code to 3 so a script can
|
|
|
211
230
|
--beam-size N whisper beam size (default: 5)
|
|
212
231
|
--no-vad disable the voice-activity filter
|
|
213
232
|
--audio-bitrate RATE bitrate when audio has to be transcoded (default: 96k)
|
|
233
|
+
--verify [JAR] validate the result with epubcheck after writing it (needs Java);
|
|
234
|
+
pass its jar path, or leave the value off to search PATH,
|
|
235
|
+
EPUBCHECK_HOME and JAVA_HOME for one
|
|
214
236
|
--work-dir PATH scratch directory instead of a temporary one
|
|
215
237
|
--cache-dir PATH transcript cache location
|
|
216
238
|
--no-cache neither read nor write the transcript cache
|
|
@@ -267,6 +289,8 @@ against the reference Media Overlays title from the EPUB test suite at
|
|
|
267
289
|
| 1 | bad input, missing file, missing ffmpeg, nothing could be aligned |
|
|
268
290
|
| 2 | the input is DRM-protected |
|
|
269
291
|
| 3 | the EPUB was written but max drift exceeded `--drift-threshold` |
|
|
292
|
+
| 4 | the EPUB was written but epubcheck reported problems (only with `--verify`) |
|
|
293
|
+
| 5 | the EPUB was written but token match was below `--min-token-match` |
|
|
270
294
|
| 130 | interrupted |
|
|
271
295
|
|
|
272
296
|
Failures print one line, not a traceback.
|
|
@@ -175,6 +175,14 @@ def _track_for(tracks: list[AudioTrack], moment: float) -> AudioTrack:
|
|
|
175
175
|
return tracks[-1]
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
def _excerpt(raw: list[str], start: int, end: int, limit: int = 12) -> str:
|
|
179
|
+
"""A short human-readable preview of one unaligned stretch, for the report."""
|
|
180
|
+
words = raw[max(0, start): max(0, start, end)]
|
|
181
|
+
if len(words) <= limit:
|
|
182
|
+
return " ".join(words)
|
|
183
|
+
return " ".join(words[:limit]) + " ..."
|
|
184
|
+
|
|
185
|
+
|
|
178
186
|
def align(
|
|
179
187
|
sentences: list[Sentence],
|
|
180
188
|
words: list[Word],
|
|
@@ -192,11 +200,14 @@ def align(
|
|
|
192
200
|
)
|
|
193
201
|
|
|
194
202
|
book_tokens: list[str] = []
|
|
203
|
+
book_raw: list[str] = []
|
|
195
204
|
sentence_spans: list[tuple[int, int]] = []
|
|
196
205
|
for sentence in sentences:
|
|
197
|
-
tokens,
|
|
206
|
+
tokens, origins = tokenize_words(sentence.words)
|
|
198
207
|
sentence_spans.append((len(book_tokens), len(book_tokens) + len(tokens)))
|
|
199
208
|
book_tokens.extend(tokens)
|
|
209
|
+
# The raw word behind every token, so the report can quote the book's own spelling.
|
|
210
|
+
book_raw.extend(sentence.words[origin] for origin in origins)
|
|
200
211
|
|
|
201
212
|
asr_tokens, asr_origin = tokenize_words([word.text for word in words])
|
|
202
213
|
if not book_tokens:
|
|
@@ -224,6 +235,7 @@ def align(
|
|
|
224
235
|
unaligned_regions=[
|
|
225
236
|
{
|
|
226
237
|
"book_tokens": [region[0], region[1]],
|
|
238
|
+
"excerpt": _excerpt(book_raw, region[0], region[1]),
|
|
227
239
|
"audio_seconds": [
|
|
228
240
|
round(words[asr_origin[min(region[2], len(asr_origin) - 1)]].start, 2),
|
|
229
241
|
round(words[asr_origin[min(region[3] - 1, len(asr_origin) - 1)]].end, 2),
|