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.
- {audioexport-0.1.2 → audioexport-0.1.5}/PKG-INFO +45 -25
- {audioexport-0.1.2 → audioexport-0.1.5}/README.md +44 -24
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/__init__.py +22 -1
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/_version.py +3 -3
- audioexport-0.1.5/audioexport/_workspace.py +215 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/api.py +21 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/book.py +47 -198
- audioexport-0.1.5/audioexport/bundle.py +322 -0
- audioexport-0.1.5/audioexport/bundle_contract.py +100 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/chapters.py +3 -3
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/cli.py +205 -134
- audioexport-0.1.5/audioexport/document.py +476 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/formats.py +2 -2
- audioexport-0.1.5/audioexport/variants.py +339 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport.egg-info/SOURCES.txt +6 -2
- {audioexport-0.1.2 → audioexport-0.1.5}/docs/changelog.md +31 -2
- audioexport-0.1.5/docs/index.md +186 -0
- audioexport-0.1.2/examples/create_ssmdbook_demo.py → audioexport-0.1.5/examples/create_ssmd_bundle_demo.py +5 -3
- audioexport-0.1.2/docs/index.md +0 -187
- audioexport-0.1.2/docs/readio-migration.md +0 -87
- {audioexport-0.1.2 → audioexport-0.1.5}/LICENSE +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/MANIFEST.in +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/__main__.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/audiobook.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/audiobook_profile.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/errors.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/fftools.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/inputs.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/metadata.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/pipeline.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/preflight.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/profile.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/py.typed +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/audioexport/validation.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/docs/Makefile +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/docs/conf.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/docs/make.bat +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/docs/make.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/docs/requirements.txt +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/examples/audiobook.toml +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/examples/chapters.json +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/examples/chapters.txt +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/examples/create_demo_wav.py +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/examples/export.toml +0 -0
- {audioexport-0.1.2 → audioexport-0.1.5}/pyproject.toml +0 -0
- {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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
##
|
|
152
|
+
## SSMD bundle export
|
|
153
153
|
|
|
154
|
-
|
|
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
|
|
158
|
-
audioexport export
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
185
|
+
Python callers can inspect and export variants through the same public API:
|
|
168
186
|
|
|
169
|
-
|
|
187
|
+
```python
|
|
188
|
+
from audioexport import export_bundle, export_bundle_all, list_bundle_variants
|
|
170
189
|
|
|
171
|
-
|
|
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`; `
|
|
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
|
-
#
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
##
|
|
119
|
+
## SSMD bundle export
|
|
120
120
|
|
|
121
|
-
|
|
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
|
|
125
|
-
audioexport export
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
+
Python callers can inspect and export variants through the same public API:
|
|
135
153
|
|
|
136
|
-
|
|
154
|
+
```python
|
|
155
|
+
from audioexport import export_bundle, export_bundle_all, list_bundle_variants
|
|
137
156
|
|
|
138
|
-
|
|
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`; `
|
|
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
|
-
#
|
|
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:
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
21
|
+
__version__ = version = '0.1.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 5)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g2148a3791'
|