audioexport 0.1.2__tar.gz → 0.1.5__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.
Files changed (46) hide show
  1. {audioexport-0.1.2 → audioexport-0.1.5}/PKG-INFO +45 -25
  2. {audioexport-0.1.2 → audioexport-0.1.5}/README.md +44 -24
  3. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/__init__.py +22 -1
  4. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/_version.py +3 -3
  5. audioexport-0.1.5/audioexport/_workspace.py +215 -0
  6. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/api.py +21 -0
  7. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/book.py +47 -198
  8. audioexport-0.1.5/audioexport/bundle.py +322 -0
  9. audioexport-0.1.5/audioexport/bundle_contract.py +100 -0
  10. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/chapters.py +3 -3
  11. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/cli.py +205 -134
  12. audioexport-0.1.5/audioexport/document.py +476 -0
  13. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/formats.py +2 -2
  14. audioexport-0.1.5/audioexport/variants.py +339 -0
  15. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport.egg-info/SOURCES.txt +6 -2
  16. {audioexport-0.1.2 → audioexport-0.1.5}/docs/changelog.md +31 -2
  17. audioexport-0.1.5/docs/index.md +186 -0
  18. audioexport-0.1.2/examples/create_ssmdbook_demo.py → audioexport-0.1.5/examples/create_ssmd_bundle_demo.py +5 -3
  19. audioexport-0.1.2/docs/index.md +0 -187
  20. audioexport-0.1.2/docs/readio-migration.md +0 -87
  21. {audioexport-0.1.2 → audioexport-0.1.5}/LICENSE +0 -0
  22. {audioexport-0.1.2 → audioexport-0.1.5}/MANIFEST.in +0 -0
  23. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/__main__.py +0 -0
  24. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/audiobook.py +0 -0
  25. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/audiobook_profile.py +0 -0
  26. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/errors.py +0 -0
  27. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/fftools.py +0 -0
  28. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/inputs.py +0 -0
  29. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/metadata.py +0 -0
  30. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/pipeline.py +0 -0
  31. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/preflight.py +0 -0
  32. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/profile.py +0 -0
  33. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/py.typed +0 -0
  34. {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/validation.py +0 -0
  35. {audioexport-0.1.2 → audioexport-0.1.5}/docs/Makefile +0 -0
  36. {audioexport-0.1.2 → audioexport-0.1.5}/docs/conf.py +0 -0
  37. {audioexport-0.1.2 → audioexport-0.1.5}/docs/make.bat +0 -0
  38. {audioexport-0.1.2 → audioexport-0.1.5}/docs/make.py +0 -0
  39. {audioexport-0.1.2 → audioexport-0.1.5}/docs/requirements.txt +0 -0
  40. {audioexport-0.1.2 → audioexport-0.1.5}/examples/audiobook.toml +0 -0
  41. {audioexport-0.1.2 → audioexport-0.1.5}/examples/chapters.json +0 -0
  42. {audioexport-0.1.2 → audioexport-0.1.5}/examples/chapters.txt +0 -0
  43. {audioexport-0.1.2 → audioexport-0.1.5}/examples/create_demo_wav.py +0 -0
  44. {audioexport-0.1.2 → audioexport-0.1.5}/examples/export.toml +0 -0
  45. {audioexport-0.1.2 → audioexport-0.1.5}/pyproject.toml +0 -0
  46. {audioexport-0.1.2 → audioexport-0.1.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: audioexport
3
- Version: 0.1.2
3
+ Version: 0.1.5
4
4
  Summary: Standalone FFmpeg audio encoder with metadata, chapter, cover and reproducible export manifests
5
5
  Author: audioexport contributors
6
6
  License-Expression: Apache-2.0
@@ -33,7 +33,7 @@ Dynamic: license-file
33
33
 
34
34
  # audioexport
35
35
 
36
- An independent Python library and command-line interface for exporting audio through **FFmpeg**. This is the first extraction step from Readio's output/encoding subsystem, but **audioexport does not import or install Readio, AudioCompose, UtterPlan, or synthesis engines**.
36
+ AudioExport is a standalone Python library and CLI for verified FFmpeg audio export.
37
37
 
38
38
  Supported formats: WAV (PCM16), FLAC, MP3, M4A (AAC), M4B (AAC audiobook), OGG (Vorbis), and Opus. Handles tagged metadata, embedded JPEG/PNG cover art for MP3/M4A/M4B, JSON chapter timelines for M4A/M4B, standalone ordered multi-track audiobook assembly, FFprobe verification, artifact identities and safe reuse.
39
39
 
@@ -57,7 +57,7 @@ audioexport doctor --json
57
57
 
58
58
  `doctor` reports package/tool versions and encoder availability per format. Its existing `ready` key means FFmpeg and FFprobe are present; `all_formats_ready` and each `formats` entry report encoder capability and a reason when unavailable. The additive `features` entries report `m4b_encode`, `m4b_audiobook`, and `m4b_stream_copy` availability; missing optional capabilities do not make `ready` false. `run_profile()` checks the encoders selected by the profile before writing outputs.
59
59
 
60
- Base Python dependencies are `langcodes>=3.3` on all supported versions and `tomli>=2` on Python 3.10. FFmpeg/FFprobe are external executables; AudioExport does not install or import Readio, AudioCompose, UtterPlan, VoiceRender, soundfile, or NumPy.
60
+ Base Python dependencies are `langcodes>=3.3` on all supported versions and `tomli>=2` on Python 3.10. FFmpeg/FFprobe are external executables; AudioExport has no runtime dependency on synthesis, planning, or composition packages.
61
61
 
62
62
  ## CLI quickstart
63
63
 
@@ -96,7 +96,7 @@ audioexport audiobook book-tracks/ -o book.m4b \
96
96
  audioexport audiobook 01-intro.mp3 02-chapter-one.flac 03-chapter-two.wav \
97
97
  -o book.m4b --bitrate 96k --language eng
98
98
 
99
- # Explicit chapter starts from chapters.txt or the existing JSON/Readio timeline format.
99
+ # Explicit chapter starts from chapters.txt or a generic JSON timeline.
100
100
  audioexport audiobook book-tracks/ -o book.m4b --chapters-file chapters.txt
101
101
  ```
102
102
 
@@ -147,29 +147,53 @@ mode = "explicit" # tracks | explicit | none
147
147
  # title_source = "tag" # tag (falls back to filename) | filename
148
148
  ```
149
149
 
150
- Run it with `audioexport audiobook --profile audiobook.toml`. CLI `--output`, metadata, audio settings, cover, and `--chapters-file` override profile values; the TOML input order remains authoritative. `mode = "tracks"` (the default) creates one chapter per track; `mode = "explicit"` requires `chapters_file`; `mode = "none"` disables chapters. Existing `encode(..., format="m4b", timeline=...)` remains the right choice for Readio's prepared master and project timeline.
150
+ Run it with `audioexport audiobook --profile audiobook.toml`. CLI `--output`, metadata, audio settings, cover, and `--chapters-file` override profile values; the TOML input order remains authoritative. `mode = "tracks"` (the default) creates one chapter per track; `mode = "explicit"` requires `chapters_file`; `mode = "none"` disables chapters. Existing `encode(..., format="m4b", timeline=...)` remains the right choice for one prepared source with caller-supplied chapters.
151
151
 
152
- ## Native `.ssmdbook` pipeline
152
+ ## SSMD bundle export
153
153
 
154
- Use `export-book` when AudioCompose has already written a variant in a standard `.ssmdbook` workspace. It is the native book-workspace adapter; `audioexport audiobook` remains the generic standalone track/directory assembler, and `encode` remains for one prepared source with a timeline.
154
+ Book and single-document workspaces share one canonical editable directory root, `<name>.ssmd-bundle/`. The manifest selects the export path: `format = "ssmdconvert.book"` uses verified audiobook assembly, while `format = "ssmdconvert.document"` uses verified one-document encoding; both manifest schemas remain at version 1. AudioExport accepts only the bundle root, not an internal chapter directory, the document source file, or a ZIP archive.
155
+
156
+ Use the same command for either kind:
155
157
 
156
158
  ```bash
157
- audiocompose compose-book . --variant "$VARIANT"
158
- audioexport export-book . --variant "$VARIANT"
159
+ audiocompose bundle compose "My Book.ssmd-bundle" --variant "$VARIANT"
160
+ audioexport bundle export "My Book.ssmd-bundle" --variant "$VARIANT" \
161
+ --chapters 5,7,11-13 --audiocompose-root /srv/audio-stages/audiocompose \
162
+ --output-root /srv/audio-exports --title "My Book" --author "An Author" \
163
+ --language en-US
164
+
165
+ audioexport bundle export story.ssmd-bundle --variant "$VARIANT" \
166
+ --output story.m4a --title "Story" --artist "An Author"
167
+
168
+ # Inspect the AudioCompose export-stage inventory (BUNDLE defaults to .).
169
+ audioexport bundle variants
170
+
171
+ # Export every discovered variant in deterministic name order.
172
+ audioexport bundle export story.ssmd-bundle --format mp3
173
+ # A batch can relocate its variant-separated outputs.
174
+ audioexport bundle export story.ssmd-bundle --format mp3 --output-root /srv/audio-exports
159
175
  ```
160
176
 
161
- `export-book` reads `manifest.json` and `audiocompose/<variant>/compose-report.toml`, accepts the producer's singular `[[item]]` tables (and legacy `[[items]]` tables), validates selected entries and WAV checksums/geometry with bare or `sha256:`-prefixed digests, then delegates final M4B assembly and verification to AudioExport. It does not run AudioCompose, repair or synthesize chapters, or use Readio-specific state. Chapter selection accepts `all` (the default), a source number, an inclusive range, or comma-separated source numbers/ranges, for example `--chapters 5,7,11-13`. Selection follows manifest chapter order.
177
+ `audioexport bundle variants [BUNDLE]` is a read-only AudioExport-stage inventory: it inspects immediate variant directories and compose reports only, does not hash WAVs or create output directories, and reports malformed candidates without hiding other variants. Use `--json` for the `audioexport-bundle-variants` envelope or `--audiocompose-root PATH` to select another stage root. Full render/compose pipeline status remains AudioCompose's concern.
178
+
179
+ AudioExport verifies selected reports, report-relative output paths, WAV checksums, and audio geometry before encoding. Current AudioCompose document reports store geometry in `[output_geometry]`; AudioExport also accepts legacy flat reports, but rejects contradictory mixed layouts. Books use verified M4B assembly, support chapter selection and audiobook profiles, and accept only `--format m4b` (M4B is the default). Documents support only `--chapters all`; explicit single-variant export accepts `--format` or infers the format from `--output`, while all-variant document export requires `--format`. `--timeline` supplies an optional generic document chapter timeline.
162
180
 
163
- For M4A/M4B, language metadata is written on the audio track using ISO 639-2/T. BCP 47 tags such as `en-US` are normalized to their three-letter track code (`eng`).
181
+ Single-variant default names strip only `.ssmd-bundle`: books use `audioexport/<variant>/<name>.m4b`, and documents use `audioexport/<variant>/<name>.<format>`. When `--variant` is omitted, export processes every immediate AudioCompose variant in deterministic name order and keeps outputs separated as `audioexport/<variant>/<name>.<format>` (books use `.m4b`). `--output-root` relocates single-variant defaults or, in batch mode, the whole variant-separated tree. A single `--output FILE` requires an explicit `--variant`; it is rejected in batch mode before encoding. Batch export is fail-fast and does not remove outputs already completed before a later variant fails. `--author` and `--artist` are aliases for the appropriate metadata field; options unsupported for the detected bundle kind fail explicitly.
164
182
 
165
- By default, output is written to `audioexport/<variant>/<workspace-name-without-.ssmdbook>.m4b` in the workspace. `--output` may override this; when an audiobook profile is supplied, its output is used unless `--output` is specified.
183
+ The explicit single-variant JSON result remains `audioexport-bundle-result` with version 1 and a `result` object. Human single-variant output begins with `AudioExport bundle export` and reports kind, workspace, variant, output, and the selected chapter count or composed document item. All-variant export uses `audioexport-bundle-batch-result`; human output lists each variant and its output. Book metadata precedence is manifest, then audiobook profile, then explicit CLI/API values. BCP 47 language tags such as `en-US` normalize to ISO 639-2/T track codes such as `eng` for M4A/M4B.
166
184
 
167
- Metadata precedence is **manifest metadata < audiobook profile metadata < explicit CLI/API metadata**. Audio settings use **AudioExport defaults < audiobook profile < explicit CLI/API flags**. Thus explicit metadata fields replace matching profile/manifest values, profile values replace manifest defaults, and unset values continue to fall through. Explicit cover and audio flags override corresponding profile settings. The profile's inputs and chapter configuration do not replace the variant chapters selected from the compose report.
185
+ Python callers can inspect and export variants through the same public API:
168
186
 
169
- If `--output` is not set for `encode`, the output filename defaults to the input stem and chosen format. To avoid destroying the input, encoding input and output to the same real path is always rejected.
187
+ ```python
188
+ from audioexport import export_bundle, export_bundle_all, list_bundle_variants
170
189
 
171
- Without `--force`, an untracked, symlink, or manually modified output is never overwritten. A previously generated output is updated when its sidecar and checksum still match; re-running identical options returns `reused: true` without re-encoding. `--force` authorizes replacing an unknown or modified regular file, but symlink outputs and sidecars are always rejected. Writes are staged with their manifest; a failed manifest commit restores the last good pair when possible, and an incomplete recovery reports the preserved backup path. A per-target process lock rejects concurrent writers with `audioexport.output_busy`; remove a stale lock only after confirming no writer is active.
190
+ inventory = list_bundle_variants("story.ssmd-bundle")
191
+ single = export_bundle("story.ssmd-bundle", variant=variant, format="mp3")
192
+ batch = export_bundle_all("story.ssmd-bundle", format="mp3")
193
+ print(inventory.to_dict(), single.output, batch.outputs)
194
+ ```
172
195
 
196
+ `BundleExportResult.value` contains the specialized book or document result, and its `output` property gives the artifact path. It remains the result type for explicit single-variant export. `BundleExportBatchResult` contains the ordered per-variant results and outputs. These APIs and `BundleVariantInfo`/`BundleVariantsResult` are available from both `audioexport` and `audioexport.api`.
173
197
  OGG defaults to Vorbis quality-based encoding (`-qscale:a 4`) to avoid low-rate/mono bitrate incompatibilities. `--bitrate` accepts values such as `192k`, `192000`, or `1M`; whitespace is trimmed. A bare integer is interpreted as **bits per second**, so use `192k` (or `192000`), not `192`, for a nominal 192 kb/s rate. WAV/FLAC don't accept bitrate. WAV output is always PCM16 conversion, not a byte-for-byte passthrough; lossy codecs and even PCM output can vary with FFmpeg versions, which are included in the export identity.
174
198
 
175
199
  ## Python API
@@ -203,7 +227,7 @@ print(probe("exported/book.m4b")["chapters"])
203
227
 
204
228
  You may import the same public functions from `audioexport.api` as well as from `audioexport`.
205
229
 
206
- The public errors are `AudioExportError` and subclasses `InvalidExportError`, `ToolNotFoundError`, `EncodingError`, and `VerificationError`, with a stable machine-readable `.code` value. `encode()` returns an immutable `ExportResult`; `build_audiobook()` returns an `AudiobookResult`; `export_book()` and `resolve_book_export()` provide verified composed-book export through `BookExportContext`, `BookExportResult`, and `ComposedChapter`. These symbols, like the other public functions, are available from both `audioexport` and `audioexport.api`.
230
+ The public errors are `AudioExportError` and subclasses `InvalidExportError`, `ToolNotFoundError`, `EncodingError`, and `VerificationError`, with a stable machine-readable `.code` value. `encode()` returns an immutable `ExportResult`; `build_audiobook()` returns an `AudiobookResult`; `export_bundle()` provides one manifest-dispatched entry point for verified book and document bundles and returns `BundleExportResult`; `export_bundle_all()` batches all variants, and `list_bundle_variants()` provides read-only inventory. The batch and inventory result types are available from both `audioexport` and `audioexport.api`.
207
231
 
208
232
  ## Export profile
209
233
 
@@ -279,7 +303,7 @@ resolved_outputs = preflight_profile(profile, source)
279
303
 
280
304
  for resolved in resolved_outputs:
281
305
  target = Path("exports") / resolved.filename
282
- # A consumer such as Readio performs its target/ownership/overwrite checks here.
306
+ # Applications should perform their own target and overwrite checks here.
283
307
  result = encode(
284
308
  source,
285
309
  target,
@@ -291,7 +315,7 @@ for resolved in resolved_outputs:
291
315
  )
292
316
  ```
293
317
 
294
- A consumer may instead call `resolve_output()` for a single selected spec; use full-profile preflight when the operation requires every configured output validated. Preflight is not an ownership authorization: Readio remains responsible for project locks and freshness, destination selection and collision/ownership checks, overwrite authorization, state/index updates, result types, and partial-batch bookkeeping. Call `encode()` only after those consumer-owned checks pass. `ResolvedOutput`, `resolve_output`, and `preflight_profile` are available from both `audioexport` and `audioexport.api`.
318
+ A consumer may instead call `resolve_output()` for a single selected spec; use full-profile preflight when the operation requires every configured output validated. Preflight is not an ownership authorization: applications remain responsible for their own locks, target selection, overwrite authorization, and state updates. Call `encode()` only after those consumer-owned checks pass. `ResolvedOutput`, `resolve_output`, and `preflight_profile` are available from both `audioexport` and `audioexport.api`.
295
319
 
296
320
  ## Chapters/timeline
297
321
 
@@ -308,7 +332,7 @@ Use an independent JSON file such as [`examples/chapters.json`](examples/chapter
308
332
 
309
333
  `end_ms` is optional; without it, chapter ends are the next chapter start or the audio duration. Chapter offsets must be strictly increasing and within the audio file. FFmpeg metadata escaping is handled internally. Embedded chapter markers are currently supported for **M4A and M4B**.
310
334
 
311
- For an incremental Readio migration, the same `--timeline` API also reads **Readio's `readio.composition-timeline` JSON** with `sample_rate` and chapter `start_sample`. FFmetadata is constructed with its original sample timebase for precision, with generic millisecond chapter fields available for non-Readio workflows.
335
+ Exact-sample timelines may include a top-level `sample_rate` and chapter `start_sample` values. FFmetadata preserves their original sample timebase and coordinates using `TIMEBASE=1/<sample_rate>`.
312
336
 
313
337
  ## Integrity and versioning
314
338
 
@@ -320,10 +344,6 @@ Version is dynamic through `setuptools-scm`: `pyproject.toml` has no static `ver
320
344
 
321
345
  For a release, first commit the code and wait for the complete Linux/Windows/macOS, Python 3.10–3.14 CI matrix, including its Sphinx warning-as-error documentation build. Then create and push the intended version tag (for example `v0.1.2`). Manually dispatch the GitHub Actions workflow named **Release** with that existing tag. It reruns the full supported-Python test matrix, checks the tag against wheel/sdist metadata, inspects package contents, installs the wheel outside the checkout, and checks CLI/API version parity. Publishing uses the configured `PYPI_API_TOKEN` secret after approval in the `pypi` GitHub environment; repository administrators must ensure the token and environment approval rules are configured. Do not publish from an untagged/fallback build.
322
346
 
323
- ## Readio migration
324
-
325
- **Readio is deliberately not patched by this project.** Its existing export commands continue using their current code until an opt-in bridge is tested. A future integration must call AudioExport's Python library API; it must never launch the `audioexport` CLI as a subprocess. See [`docs/readio-migration.md`](docs/readio-migration.md) for the compatibility boundary, integration order, and rollback rules. Readio continues owning project locks, path defaults, staleness, indexed state, and orchestration.
326
-
327
347
  ## Tests
328
348
 
329
349
  ```bash
@@ -339,11 +359,11 @@ python scripts/check_package_artifacts.py
339
359
  audioexport doctor --json
340
360
  ```
341
361
 
342
- Tests cover seven real formats in one profile, mixed-format audiobook assembly, 100-track ordering/chapters, audiobook cache invalidation, M4A/M4B chapters and covers, Readio sample timelines, malformed probes, encoder availability, transactional failure recovery, no-clobber behavior, and concurrent writers. Normal CI runs Python 3.10–3.14 on Linux, Windows, and macOS, and checks tests, lint, formatting, typing, build metadata, Twine, package contents, and isolated wheel installation. CI also builds the Sphinx documentation on Linux/Python 3.13 with warnings treated as errors. The manually dispatched **Release** workflow retests the full supported Python range before packaging and API-token publishing. Cross-platform encoder availability depends on the FFmpeg build installed by each runner.
362
+ Tests cover seven real formats in one profile, mixed-format audiobook assembly, 100-track ordering/chapters, audiobook cache invalidation, M4A/M4B chapters and covers, exact-sample chapter timelines, malformed probes, encoder availability, transactional failure recovery, no-clobber behavior, and concurrent writers. Normal CI runs Python 3.10–3.14 on Linux, Windows, and macOS, and checks tests, lint, formatting, typing, build metadata, Twine, package contents, and isolated wheel installation. CI also builds the Sphinx documentation on Linux/Python 3.13 with warnings treated as errors. The manually dispatched **Release** workflow retests the full supported Python range before packaging and API-token publishing. Cross-platform encoder availability depends on the FFmpeg build installed by each runner.
343
363
 
344
364
  ## Limitations (MVP)
345
365
 
346
- No audio mastering, TTS, audio mixing, streaming PCM API, FFmpeg filter graphs, network service, or GUI. No automatic conversion of Readio project configs, legacy export cache, or Audiocompose timeline semantics other than chapter starts. Encoder output may vary across FFmpeg versions; the cache key includes tool versions. Chapter verification has a small tolerance for FFmpeg container timebase rounding. Standalone encoding does not modify Readio projects.
366
+ No audio mastering, TTS, audio mixing, streaming PCM API, FFmpeg filter graphs, network service, or GUI. Encoder output may vary across FFmpeg versions; the export identity includes tool versions. Chapter verification tolerates small FFmpeg container timebase rounding.
347
367
 
348
368
  ## License
349
369
 
@@ -1,6 +1,6 @@
1
1
  # audioexport
2
2
 
3
- An independent Python library and command-line interface for exporting audio through **FFmpeg**. This is the first extraction step from Readio's output/encoding subsystem, but **audioexport does not import or install Readio, AudioCompose, UtterPlan, or synthesis engines**.
3
+ AudioExport is a standalone Python library and CLI for verified FFmpeg audio export.
4
4
 
5
5
  Supported formats: WAV (PCM16), FLAC, MP3, M4A (AAC), M4B (AAC audiobook), OGG (Vorbis), and Opus. Handles tagged metadata, embedded JPEG/PNG cover art for MP3/M4A/M4B, JSON chapter timelines for M4A/M4B, standalone ordered multi-track audiobook assembly, FFprobe verification, artifact identities and safe reuse.
6
6
 
@@ -24,7 +24,7 @@ audioexport doctor --json
24
24
 
25
25
  `doctor` reports package/tool versions and encoder availability per format. Its existing `ready` key means FFmpeg and FFprobe are present; `all_formats_ready` and each `formats` entry report encoder capability and a reason when unavailable. The additive `features` entries report `m4b_encode`, `m4b_audiobook`, and `m4b_stream_copy` availability; missing optional capabilities do not make `ready` false. `run_profile()` checks the encoders selected by the profile before writing outputs.
26
26
 
27
- Base Python dependencies are `langcodes>=3.3` on all supported versions and `tomli>=2` on Python 3.10. FFmpeg/FFprobe are external executables; AudioExport does not install or import Readio, AudioCompose, UtterPlan, VoiceRender, soundfile, or NumPy.
27
+ Base Python dependencies are `langcodes>=3.3` on all supported versions and `tomli>=2` on Python 3.10. FFmpeg/FFprobe are external executables; AudioExport has no runtime dependency on synthesis, planning, or composition packages.
28
28
 
29
29
  ## CLI quickstart
30
30
 
@@ -63,7 +63,7 @@ audioexport audiobook book-tracks/ -o book.m4b \
63
63
  audioexport audiobook 01-intro.mp3 02-chapter-one.flac 03-chapter-two.wav \
64
64
  -o book.m4b --bitrate 96k --language eng
65
65
 
66
- # Explicit chapter starts from chapters.txt or the existing JSON/Readio timeline format.
66
+ # Explicit chapter starts from chapters.txt or a generic JSON timeline.
67
67
  audioexport audiobook book-tracks/ -o book.m4b --chapters-file chapters.txt
68
68
  ```
69
69
 
@@ -114,29 +114,53 @@ mode = "explicit" # tracks | explicit | none
114
114
  # title_source = "tag" # tag (falls back to filename) | filename
115
115
  ```
116
116
 
117
- Run it with `audioexport audiobook --profile audiobook.toml`. CLI `--output`, metadata, audio settings, cover, and `--chapters-file` override profile values; the TOML input order remains authoritative. `mode = "tracks"` (the default) creates one chapter per track; `mode = "explicit"` requires `chapters_file`; `mode = "none"` disables chapters. Existing `encode(..., format="m4b", timeline=...)` remains the right choice for Readio's prepared master and project timeline.
117
+ Run it with `audioexport audiobook --profile audiobook.toml`. CLI `--output`, metadata, audio settings, cover, and `--chapters-file` override profile values; the TOML input order remains authoritative. `mode = "tracks"` (the default) creates one chapter per track; `mode = "explicit"` requires `chapters_file`; `mode = "none"` disables chapters. Existing `encode(..., format="m4b", timeline=...)` remains the right choice for one prepared source with caller-supplied chapters.
118
118
 
119
- ## Native `.ssmdbook` pipeline
119
+ ## SSMD bundle export
120
120
 
121
- Use `export-book` when AudioCompose has already written a variant in a standard `.ssmdbook` workspace. It is the native book-workspace adapter; `audioexport audiobook` remains the generic standalone track/directory assembler, and `encode` remains for one prepared source with a timeline.
121
+ Book and single-document workspaces share one canonical editable directory root, `<name>.ssmd-bundle/`. The manifest selects the export path: `format = "ssmdconvert.book"` uses verified audiobook assembly, while `format = "ssmdconvert.document"` uses verified one-document encoding; both manifest schemas remain at version 1. AudioExport accepts only the bundle root, not an internal chapter directory, the document source file, or a ZIP archive.
122
+
123
+ Use the same command for either kind:
122
124
 
123
125
  ```bash
124
- audiocompose compose-book . --variant "$VARIANT"
125
- audioexport export-book . --variant "$VARIANT"
126
+ audiocompose bundle compose "My Book.ssmd-bundle" --variant "$VARIANT"
127
+ audioexport bundle export "My Book.ssmd-bundle" --variant "$VARIANT" \
128
+ --chapters 5,7,11-13 --audiocompose-root /srv/audio-stages/audiocompose \
129
+ --output-root /srv/audio-exports --title "My Book" --author "An Author" \
130
+ --language en-US
131
+
132
+ audioexport bundle export story.ssmd-bundle --variant "$VARIANT" \
133
+ --output story.m4a --title "Story" --artist "An Author"
134
+
135
+ # Inspect the AudioCompose export-stage inventory (BUNDLE defaults to .).
136
+ audioexport bundle variants
137
+
138
+ # Export every discovered variant in deterministic name order.
139
+ audioexport bundle export story.ssmd-bundle --format mp3
140
+ # A batch can relocate its variant-separated outputs.
141
+ audioexport bundle export story.ssmd-bundle --format mp3 --output-root /srv/audio-exports
126
142
  ```
127
143
 
128
- `export-book` reads `manifest.json` and `audiocompose/<variant>/compose-report.toml`, accepts the producer's singular `[[item]]` tables (and legacy `[[items]]` tables), validates selected entries and WAV checksums/geometry with bare or `sha256:`-prefixed digests, then delegates final M4B assembly and verification to AudioExport. It does not run AudioCompose, repair or synthesize chapters, or use Readio-specific state. Chapter selection accepts `all` (the default), a source number, an inclusive range, or comma-separated source numbers/ranges, for example `--chapters 5,7,11-13`. Selection follows manifest chapter order.
144
+ `audioexport bundle variants [BUNDLE]` is a read-only AudioExport-stage inventory: it inspects immediate variant directories and compose reports only, does not hash WAVs or create output directories, and reports malformed candidates without hiding other variants. Use `--json` for the `audioexport-bundle-variants` envelope or `--audiocompose-root PATH` to select another stage root. Full render/compose pipeline status remains AudioCompose's concern.
145
+
146
+ AudioExport verifies selected reports, report-relative output paths, WAV checksums, and audio geometry before encoding. Current AudioCompose document reports store geometry in `[output_geometry]`; AudioExport also accepts legacy flat reports, but rejects contradictory mixed layouts. Books use verified M4B assembly, support chapter selection and audiobook profiles, and accept only `--format m4b` (M4B is the default). Documents support only `--chapters all`; explicit single-variant export accepts `--format` or infers the format from `--output`, while all-variant document export requires `--format`. `--timeline` supplies an optional generic document chapter timeline.
129
147
 
130
- For M4A/M4B, language metadata is written on the audio track using ISO 639-2/T. BCP 47 tags such as `en-US` are normalized to their three-letter track code (`eng`).
148
+ Single-variant default names strip only `.ssmd-bundle`: books use `audioexport/<variant>/<name>.m4b`, and documents use `audioexport/<variant>/<name>.<format>`. When `--variant` is omitted, export processes every immediate AudioCompose variant in deterministic name order and keeps outputs separated as `audioexport/<variant>/<name>.<format>` (books use `.m4b`). `--output-root` relocates single-variant defaults or, in batch mode, the whole variant-separated tree. A single `--output FILE` requires an explicit `--variant`; it is rejected in batch mode before encoding. Batch export is fail-fast and does not remove outputs already completed before a later variant fails. `--author` and `--artist` are aliases for the appropriate metadata field; options unsupported for the detected bundle kind fail explicitly.
131
149
 
132
- By default, output is written to `audioexport/<variant>/<workspace-name-without-.ssmdbook>.m4b` in the workspace. `--output` may override this; when an audiobook profile is supplied, its output is used unless `--output` is specified.
150
+ The explicit single-variant JSON result remains `audioexport-bundle-result` with version 1 and a `result` object. Human single-variant output begins with `AudioExport bundle export` and reports kind, workspace, variant, output, and the selected chapter count or composed document item. All-variant export uses `audioexport-bundle-batch-result`; human output lists each variant and its output. Book metadata precedence is manifest, then audiobook profile, then explicit CLI/API values. BCP 47 language tags such as `en-US` normalize to ISO 639-2/T track codes such as `eng` for M4A/M4B.
133
151
 
134
- Metadata precedence is **manifest metadata < audiobook profile metadata < explicit CLI/API metadata**. Audio settings use **AudioExport defaults < audiobook profile < explicit CLI/API flags**. Thus explicit metadata fields replace matching profile/manifest values, profile values replace manifest defaults, and unset values continue to fall through. Explicit cover and audio flags override corresponding profile settings. The profile's inputs and chapter configuration do not replace the variant chapters selected from the compose report.
152
+ Python callers can inspect and export variants through the same public API:
135
153
 
136
- If `--output` is not set for `encode`, the output filename defaults to the input stem and chosen format. To avoid destroying the input, encoding input and output to the same real path is always rejected.
154
+ ```python
155
+ from audioexport import export_bundle, export_bundle_all, list_bundle_variants
137
156
 
138
- Without `--force`, an untracked, symlink, or manually modified output is never overwritten. A previously generated output is updated when its sidecar and checksum still match; re-running identical options returns `reused: true` without re-encoding. `--force` authorizes replacing an unknown or modified regular file, but symlink outputs and sidecars are always rejected. Writes are staged with their manifest; a failed manifest commit restores the last good pair when possible, and an incomplete recovery reports the preserved backup path. A per-target process lock rejects concurrent writers with `audioexport.output_busy`; remove a stale lock only after confirming no writer is active.
157
+ inventory = list_bundle_variants("story.ssmd-bundle")
158
+ single = export_bundle("story.ssmd-bundle", variant=variant, format="mp3")
159
+ batch = export_bundle_all("story.ssmd-bundle", format="mp3")
160
+ print(inventory.to_dict(), single.output, batch.outputs)
161
+ ```
139
162
 
163
+ `BundleExportResult.value` contains the specialized book or document result, and its `output` property gives the artifact path. It remains the result type for explicit single-variant export. `BundleExportBatchResult` contains the ordered per-variant results and outputs. These APIs and `BundleVariantInfo`/`BundleVariantsResult` are available from both `audioexport` and `audioexport.api`.
140
164
  OGG defaults to Vorbis quality-based encoding (`-qscale:a 4`) to avoid low-rate/mono bitrate incompatibilities. `--bitrate` accepts values such as `192k`, `192000`, or `1M`; whitespace is trimmed. A bare integer is interpreted as **bits per second**, so use `192k` (or `192000`), not `192`, for a nominal 192 kb/s rate. WAV/FLAC don't accept bitrate. WAV output is always PCM16 conversion, not a byte-for-byte passthrough; lossy codecs and even PCM output can vary with FFmpeg versions, which are included in the export identity.
141
165
 
142
166
  ## Python API
@@ -170,7 +194,7 @@ print(probe("exported/book.m4b")["chapters"])
170
194
 
171
195
  You may import the same public functions from `audioexport.api` as well as from `audioexport`.
172
196
 
173
- The public errors are `AudioExportError` and subclasses `InvalidExportError`, `ToolNotFoundError`, `EncodingError`, and `VerificationError`, with a stable machine-readable `.code` value. `encode()` returns an immutable `ExportResult`; `build_audiobook()` returns an `AudiobookResult`; `export_book()` and `resolve_book_export()` provide verified composed-book export through `BookExportContext`, `BookExportResult`, and `ComposedChapter`. These symbols, like the other public functions, are available from both `audioexport` and `audioexport.api`.
197
+ The public errors are `AudioExportError` and subclasses `InvalidExportError`, `ToolNotFoundError`, `EncodingError`, and `VerificationError`, with a stable machine-readable `.code` value. `encode()` returns an immutable `ExportResult`; `build_audiobook()` returns an `AudiobookResult`; `export_bundle()` provides one manifest-dispatched entry point for verified book and document bundles and returns `BundleExportResult`; `export_bundle_all()` batches all variants, and `list_bundle_variants()` provides read-only inventory. The batch and inventory result types are available from both `audioexport` and `audioexport.api`.
174
198
 
175
199
  ## Export profile
176
200
 
@@ -246,7 +270,7 @@ resolved_outputs = preflight_profile(profile, source)
246
270
 
247
271
  for resolved in resolved_outputs:
248
272
  target = Path("exports") / resolved.filename
249
- # A consumer such as Readio performs its target/ownership/overwrite checks here.
273
+ # Applications should perform their own target and overwrite checks here.
250
274
  result = encode(
251
275
  source,
252
276
  target,
@@ -258,7 +282,7 @@ for resolved in resolved_outputs:
258
282
  )
259
283
  ```
260
284
 
261
- A consumer may instead call `resolve_output()` for a single selected spec; use full-profile preflight when the operation requires every configured output validated. Preflight is not an ownership authorization: Readio remains responsible for project locks and freshness, destination selection and collision/ownership checks, overwrite authorization, state/index updates, result types, and partial-batch bookkeeping. Call `encode()` only after those consumer-owned checks pass. `ResolvedOutput`, `resolve_output`, and `preflight_profile` are available from both `audioexport` and `audioexport.api`.
285
+ A consumer may instead call `resolve_output()` for a single selected spec; use full-profile preflight when the operation requires every configured output validated. Preflight is not an ownership authorization: applications remain responsible for their own locks, target selection, overwrite authorization, and state updates. Call `encode()` only after those consumer-owned checks pass. `ResolvedOutput`, `resolve_output`, and `preflight_profile` are available from both `audioexport` and `audioexport.api`.
262
286
 
263
287
  ## Chapters/timeline
264
288
 
@@ -275,7 +299,7 @@ Use an independent JSON file such as [`examples/chapters.json`](examples/chapter
275
299
 
276
300
  `end_ms` is optional; without it, chapter ends are the next chapter start or the audio duration. Chapter offsets must be strictly increasing and within the audio file. FFmpeg metadata escaping is handled internally. Embedded chapter markers are currently supported for **M4A and M4B**.
277
301
 
278
- For an incremental Readio migration, the same `--timeline` API also reads **Readio's `readio.composition-timeline` JSON** with `sample_rate` and chapter `start_sample`. FFmetadata is constructed with its original sample timebase for precision, with generic millisecond chapter fields available for non-Readio workflows.
302
+ Exact-sample timelines may include a top-level `sample_rate` and chapter `start_sample` values. FFmetadata preserves their original sample timebase and coordinates using `TIMEBASE=1/<sample_rate>`.
279
303
 
280
304
  ## Integrity and versioning
281
305
 
@@ -287,10 +311,6 @@ Version is dynamic through `setuptools-scm`: `pyproject.toml` has no static `ver
287
311
 
288
312
  For a release, first commit the code and wait for the complete Linux/Windows/macOS, Python 3.10–3.14 CI matrix, including its Sphinx warning-as-error documentation build. Then create and push the intended version tag (for example `v0.1.2`). Manually dispatch the GitHub Actions workflow named **Release** with that existing tag. It reruns the full supported-Python test matrix, checks the tag against wheel/sdist metadata, inspects package contents, installs the wheel outside the checkout, and checks CLI/API version parity. Publishing uses the configured `PYPI_API_TOKEN` secret after approval in the `pypi` GitHub environment; repository administrators must ensure the token and environment approval rules are configured. Do not publish from an untagged/fallback build.
289
313
 
290
- ## Readio migration
291
-
292
- **Readio is deliberately not patched by this project.** Its existing export commands continue using their current code until an opt-in bridge is tested. A future integration must call AudioExport's Python library API; it must never launch the `audioexport` CLI as a subprocess. See [`docs/readio-migration.md`](docs/readio-migration.md) for the compatibility boundary, integration order, and rollback rules. Readio continues owning project locks, path defaults, staleness, indexed state, and orchestration.
293
-
294
314
  ## Tests
295
315
 
296
316
  ```bash
@@ -306,11 +326,11 @@ python scripts/check_package_artifacts.py
306
326
  audioexport doctor --json
307
327
  ```
308
328
 
309
- Tests cover seven real formats in one profile, mixed-format audiobook assembly, 100-track ordering/chapters, audiobook cache invalidation, M4A/M4B chapters and covers, Readio sample timelines, malformed probes, encoder availability, transactional failure recovery, no-clobber behavior, and concurrent writers. Normal CI runs Python 3.10–3.14 on Linux, Windows, and macOS, and checks tests, lint, formatting, typing, build metadata, Twine, package contents, and isolated wheel installation. CI also builds the Sphinx documentation on Linux/Python 3.13 with warnings treated as errors. The manually dispatched **Release** workflow retests the full supported Python range before packaging and API-token publishing. Cross-platform encoder availability depends on the FFmpeg build installed by each runner.
329
+ Tests cover seven real formats in one profile, mixed-format audiobook assembly, 100-track ordering/chapters, audiobook cache invalidation, M4A/M4B chapters and covers, exact-sample chapter timelines, malformed probes, encoder availability, transactional failure recovery, no-clobber behavior, and concurrent writers. Normal CI runs Python 3.10–3.14 on Linux, Windows, and macOS, and checks tests, lint, formatting, typing, build metadata, Twine, package contents, and isolated wheel installation. CI also builds the Sphinx documentation on Linux/Python 3.13 with warnings treated as errors. The manually dispatched **Release** workflow retests the full supported Python range before packaging and API-token publishing. Cross-platform encoder availability depends on the FFmpeg build installed by each runner.
310
330
 
311
331
  ## Limitations (MVP)
312
332
 
313
- No audio mastering, TTS, audio mixing, streaming PCM API, FFmpeg filter graphs, network service, or GUI. No automatic conversion of Readio project configs, legacy export cache, or Audiocompose timeline semantics other than chapter starts. Encoder output may vary across FFmpeg versions; the cache key includes tool versions. Chapter verification has a small tolerance for FFmpeg container timebase rounding. Standalone encoding does not modify Readio projects.
333
+ No audio mastering, TTS, audio mixing, streaming PCM API, FFmpeg filter graphs, network service, or GUI. Encoder output may vary across FFmpeg versions; the export identity includes tool versions. Chapter verification tolerates small FFmpeg container timebase rounding.
314
334
 
315
335
  ## License
316
336
 
@@ -1,4 +1,4 @@
1
- """Standalone audio export; no import of Readio or AudioCompose."""
1
+ """Standalone verified audio export and workspace adapters."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -13,7 +13,15 @@ from .book import (
13
13
  export_book,
14
14
  resolve_book_export,
15
15
  )
16
+ from .bundle import BundleExportBatchResult, BundleExportResult, export_bundle, export_bundle_all
16
17
  from .chapters import Chapter, load_chapters
18
+ from .document import (
19
+ ComposedDocument,
20
+ DocumentExportContext,
21
+ DocumentExportResult,
22
+ export_document,
23
+ resolve_document_export,
24
+ )
17
25
  from .errors import (
18
26
  AudioExportError,
19
27
  EncodingError,
@@ -27,6 +35,7 @@ from .metadata import AudiobookMetadata
27
35
  from .pipeline import ExportResult, encode, run_profile
28
36
  from .preflight import preflight_profile
29
37
  from .profile import ExportProfile, OutputSpec, ResolvedOutput, load_profile, resolve_output
38
+ from .variants import BundleVariantInfo, BundleVariantsResult, list_bundle_variants
30
39
 
31
40
  try:
32
41
  __version__ = version("audioexport")
@@ -43,8 +52,15 @@ __all__ = [
43
52
  "AudiobookTrack",
44
53
  "BookExportContext",
45
54
  "BookExportResult",
55
+ "BundleExportBatchResult",
56
+ "BundleExportResult",
57
+ "BundleVariantInfo",
58
+ "BundleVariantsResult",
46
59
  "Chapter",
47
60
  "ComposedChapter",
61
+ "ComposedDocument",
62
+ "DocumentExportContext",
63
+ "DocumentExportResult",
48
64
  "EncodingError",
49
65
  "ExportProfile",
50
66
  "ExportResult",
@@ -58,6 +74,10 @@ __all__ = [
58
74
  "doctor",
59
75
  "encode",
60
76
  "export_book",
77
+ "export_bundle",
78
+ "export_bundle_all",
79
+ "export_document",
80
+ "list_bundle_variants",
61
81
  "load_audiobook_profile",
62
82
  "load_chapters",
63
83
  "load_profile",
@@ -65,6 +85,7 @@ __all__ = [
65
85
  "preflight_profile",
66
86
  "probe",
67
87
  "resolve_book_export",
88
+ "resolve_document_export",
68
89
  "resolve_output",
69
90
  "run_profile",
70
91
  ]
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.2'
22
- __version_tuple__ = version_tuple = (0, 1, 2)
21
+ __version__ = version = '0.1.5'
22
+ __version_tuple__ = version_tuple = (0, 1, 5)
23
23
 
24
- __commit_id__ = commit_id = 'g356ce267b'
24
+ __commit_id__ = commit_id = 'g2148a3791'