audioexport 0.1.4__tar.gz → 0.1.7__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.4 → audioexport-0.1.7}/PKG-INFO +22 -12
- {audioexport-0.1.4 → audioexport-0.1.7}/README.md +21 -11
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/__init__.py +10 -1
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/_version.py +3 -3
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/api.py +10 -1
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/bundle.py +167 -11
- audioexport-0.1.7/audioexport/bundle_inspection.py +700 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/cli.py +93 -21
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/document.py +30 -3
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/pipeline.py +34 -4
- audioexport-0.1.7/audioexport/variants.py +339 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport.egg-info/SOURCES.txt +2 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/docs/changelog.md +29 -1
- {audioexport-0.1.4 → audioexport-0.1.7}/docs/index.md +18 -10
- {audioexport-0.1.4 → audioexport-0.1.7}/pyproject.toml +5 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/LICENSE +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/MANIFEST.in +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/__main__.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/_workspace.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/audiobook.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/audiobook_profile.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/book.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/bundle_contract.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/chapters.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/errors.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/fftools.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/formats.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/inputs.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/metadata.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/preflight.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/profile.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/py.typed +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/audioexport/validation.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/docs/Makefile +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/docs/conf.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/docs/make.bat +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/docs/make.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/docs/requirements.txt +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/examples/audiobook.toml +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/examples/chapters.json +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/examples/chapters.txt +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/examples/create_demo_wav.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/examples/create_ssmd_bundle_demo.py +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/examples/export.toml +0 -0
- {audioexport-0.1.4 → audioexport-0.1.7}/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.7
|
|
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
|
|
@@ -164,26 +164,36 @@ audioexport bundle export "My Book.ssmd-bundle" --variant "$VARIANT" \
|
|
|
164
164
|
|
|
165
165
|
audioexport bundle export story.ssmd-bundle --variant "$VARIANT" \
|
|
166
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
|
|
167
175
|
```
|
|
168
176
|
|
|
169
|
-
|
|
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.
|
|
170
180
|
|
|
171
|
-
|
|
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.
|
|
172
182
|
|
|
173
|
-
The JSON result
|
|
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.
|
|
174
184
|
|
|
175
|
-
Python callers
|
|
185
|
+
Python callers can inspect and export variants through the same public API:
|
|
176
186
|
|
|
177
187
|
```python
|
|
178
|
-
from audioexport import export_bundle
|
|
188
|
+
from audioexport import export_bundle, export_bundle_all, list_bundle_variants
|
|
179
189
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
print(
|
|
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)
|
|
184
194
|
```
|
|
185
195
|
|
|
186
|
-
`BundleExportResult.value` contains the specialized book or document result
|
|
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`.
|
|
187
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.
|
|
188
198
|
|
|
189
199
|
## Python API
|
|
@@ -217,7 +227,7 @@ print(probe("exported/book.m4b")["chapters"])
|
|
|
217
227
|
|
|
218
228
|
You may import the same public functions from `audioexport.api` as well as from `audioexport`.
|
|
219
229
|
|
|
220
|
-
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
|
|
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`.
|
|
221
231
|
|
|
222
232
|
## Export profile
|
|
223
233
|
|
|
@@ -131,26 +131,36 @@ audioexport bundle export "My Book.ssmd-bundle" --variant "$VARIANT" \
|
|
|
131
131
|
|
|
132
132
|
audioexport bundle export story.ssmd-bundle --variant "$VARIANT" \
|
|
133
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
|
|
134
142
|
```
|
|
135
143
|
|
|
136
|
-
|
|
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.
|
|
137
147
|
|
|
138
|
-
|
|
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.
|
|
139
149
|
|
|
140
|
-
The JSON result
|
|
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.
|
|
141
151
|
|
|
142
|
-
Python callers
|
|
152
|
+
Python callers can inspect and export variants through the same public API:
|
|
143
153
|
|
|
144
154
|
```python
|
|
145
|
-
from audioexport import export_bundle
|
|
155
|
+
from audioexport import export_bundle, export_bundle_all, list_bundle_variants
|
|
146
156
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
print(
|
|
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)
|
|
151
161
|
```
|
|
152
162
|
|
|
153
|
-
`BundleExportResult.value` contains the specialized book or document result
|
|
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`.
|
|
154
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.
|
|
155
165
|
|
|
156
166
|
## Python API
|
|
@@ -184,7 +194,7 @@ print(probe("exported/book.m4b")["chapters"])
|
|
|
184
194
|
|
|
185
195
|
You may import the same public functions from `audioexport.api` as well as from `audioexport`.
|
|
186
196
|
|
|
187
|
-
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
|
|
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`.
|
|
188
198
|
|
|
189
199
|
## Export profile
|
|
190
200
|
|
|
@@ -13,7 +13,8 @@ from .book import (
|
|
|
13
13
|
export_book,
|
|
14
14
|
resolve_book_export,
|
|
15
15
|
)
|
|
16
|
-
from .bundle import BundleExportResult, export_bundle
|
|
16
|
+
from .bundle import BundleExportBatchResult, BundleExportResult, export_bundle, export_bundle_all
|
|
17
|
+
from .bundle_inspection import BundleExportInspection, inspect_bundle_export
|
|
17
18
|
from .chapters import Chapter, load_chapters
|
|
18
19
|
from .document import (
|
|
19
20
|
ComposedDocument,
|
|
@@ -35,6 +36,7 @@ from .metadata import AudiobookMetadata
|
|
|
35
36
|
from .pipeline import ExportResult, encode, run_profile
|
|
36
37
|
from .preflight import preflight_profile
|
|
37
38
|
from .profile import ExportProfile, OutputSpec, ResolvedOutput, load_profile, resolve_output
|
|
39
|
+
from .variants import BundleVariantInfo, BundleVariantsResult, list_bundle_variants
|
|
38
40
|
|
|
39
41
|
try:
|
|
40
42
|
__version__ = version("audioexport")
|
|
@@ -51,7 +53,11 @@ __all__ = [
|
|
|
51
53
|
"AudiobookTrack",
|
|
52
54
|
"BookExportContext",
|
|
53
55
|
"BookExportResult",
|
|
56
|
+
"BundleExportBatchResult",
|
|
57
|
+
"BundleExportInspection",
|
|
54
58
|
"BundleExportResult",
|
|
59
|
+
"BundleVariantInfo",
|
|
60
|
+
"BundleVariantsResult",
|
|
55
61
|
"Chapter",
|
|
56
62
|
"ComposedChapter",
|
|
57
63
|
"ComposedDocument",
|
|
@@ -71,7 +77,10 @@ __all__ = [
|
|
|
71
77
|
"encode",
|
|
72
78
|
"export_book",
|
|
73
79
|
"export_bundle",
|
|
80
|
+
"export_bundle_all",
|
|
74
81
|
"export_document",
|
|
82
|
+
"inspect_bundle_export",
|
|
83
|
+
"list_bundle_variants",
|
|
75
84
|
"load_audiobook_profile",
|
|
76
85
|
"load_chapters",
|
|
77
86
|
"load_profile",
|
|
@@ -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.7'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 7)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g0852cf5d3'
|
|
@@ -11,7 +11,8 @@ from .book import (
|
|
|
11
11
|
export_book,
|
|
12
12
|
resolve_book_export,
|
|
13
13
|
)
|
|
14
|
-
from .bundle import BundleExportResult, export_bundle
|
|
14
|
+
from .bundle import BundleExportBatchResult, BundleExportResult, export_bundle, export_bundle_all
|
|
15
|
+
from .bundle_inspection import BundleExportInspection, inspect_bundle_export
|
|
15
16
|
from .chapters import Chapter, load_chapters
|
|
16
17
|
from .document import (
|
|
17
18
|
ComposedDocument,
|
|
@@ -33,6 +34,7 @@ from .metadata import AudiobookMetadata
|
|
|
33
34
|
from .pipeline import ExportResult, encode, run_profile
|
|
34
35
|
from .preflight import preflight_profile
|
|
35
36
|
from .profile import ExportProfile, OutputSpec, ResolvedOutput, load_profile, resolve_output
|
|
37
|
+
from .variants import BundleVariantInfo, BundleVariantsResult, list_bundle_variants
|
|
36
38
|
|
|
37
39
|
__all__ = [
|
|
38
40
|
"FORMATS",
|
|
@@ -44,7 +46,11 @@ __all__ = [
|
|
|
44
46
|
"AudiobookTrack",
|
|
45
47
|
"BookExportContext",
|
|
46
48
|
"BookExportResult",
|
|
49
|
+
"BundleExportBatchResult",
|
|
50
|
+
"BundleExportInspection",
|
|
47
51
|
"BundleExportResult",
|
|
52
|
+
"BundleVariantInfo",
|
|
53
|
+
"BundleVariantsResult",
|
|
48
54
|
"Chapter",
|
|
49
55
|
"ComposedChapter",
|
|
50
56
|
"ComposedDocument",
|
|
@@ -63,7 +69,10 @@ __all__ = [
|
|
|
63
69
|
"encode",
|
|
64
70
|
"export_book",
|
|
65
71
|
"export_bundle",
|
|
72
|
+
"export_bundle_all",
|
|
66
73
|
"export_document",
|
|
74
|
+
"inspect_bundle_export",
|
|
75
|
+
"list_bundle_variants",
|
|
67
76
|
"load_audiobook_profile",
|
|
68
77
|
"load_chapters",
|
|
69
78
|
"load_profile",
|
|
@@ -7,12 +7,14 @@ from dataclasses import dataclass
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
from .audiobook_profile import load_audiobook_profile
|
|
10
11
|
from .book import BookExportResult, export_book
|
|
11
12
|
from .bundle_contract import BundleKind, load_bundle_identity
|
|
12
13
|
from .document import DocumentExportResult, export_document
|
|
13
14
|
from .errors import InvalidExportError
|
|
14
15
|
from .formats import normalize_format
|
|
15
16
|
from .metadata import AudiobookMetadata
|
|
17
|
+
from .variants import BundleVariantsResult, list_bundle_variants
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
@dataclass(frozen=True, slots=True)
|
|
@@ -60,6 +62,29 @@ class BundleExportResult:
|
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
|
|
65
|
+
@dataclass(frozen=True, slots=True)
|
|
66
|
+
class BundleExportBatchResult:
|
|
67
|
+
"""Results from exporting every discovered variant of one bundle."""
|
|
68
|
+
|
|
69
|
+
kind: BundleKind
|
|
70
|
+
workspace: Path
|
|
71
|
+
results: tuple[BundleExportResult, ...]
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def outputs(self) -> tuple[Path, ...]:
|
|
75
|
+
return tuple(result.output for result in self.results)
|
|
76
|
+
|
|
77
|
+
def to_dict(self) -> dict[str, Any]:
|
|
78
|
+
return {
|
|
79
|
+
"format": "audioexport-bundle-batch-result",
|
|
80
|
+
"version": 1,
|
|
81
|
+
"kind": self.kind,
|
|
82
|
+
"workspace": str(self.workspace),
|
|
83
|
+
"count": len(self.results),
|
|
84
|
+
"results": [result.to_dict()["result"] for result in self.results],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
63
88
|
def _option_error(message: str) -> InvalidExportError:
|
|
64
89
|
return InvalidExportError(message, code="audioexport.bundle_option_invalid")
|
|
65
90
|
|
|
@@ -91,6 +116,49 @@ def _metadata_for_kind(
|
|
|
91
116
|
return values
|
|
92
117
|
|
|
93
118
|
|
|
119
|
+
def _validate_options_for_kind(
|
|
120
|
+
*,
|
|
121
|
+
kind: BundleKind,
|
|
122
|
+
chapters: str,
|
|
123
|
+
output: str | Path | None,
|
|
124
|
+
format: str | None,
|
|
125
|
+
profile: str | Path | None,
|
|
126
|
+
sample_rate: int | None,
|
|
127
|
+
channels: int | None,
|
|
128
|
+
timeline: str | Path | None,
|
|
129
|
+
metadata: AudiobookMetadata | Mapping[str, str] | None,
|
|
130
|
+
batch: bool,
|
|
131
|
+
) -> AudiobookMetadata | dict[str, str] | None:
|
|
132
|
+
"""Validate shared kind restrictions before dispatch or batch iteration."""
|
|
133
|
+
if batch and output is not None:
|
|
134
|
+
raise _option_error(
|
|
135
|
+
"--output requires --variant; use --output-root when exporting all variants"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
normalized_metadata = _metadata_for_kind(metadata, kind=kind)
|
|
139
|
+
if kind == "book":
|
|
140
|
+
if format is not None and normalize_format(format) != "m4b":
|
|
141
|
+
raise _option_error("book bundles support only --format m4b")
|
|
142
|
+
if timeline is not None:
|
|
143
|
+
raise _option_error("--timeline is not supported for book bundles")
|
|
144
|
+
return normalized_metadata
|
|
145
|
+
|
|
146
|
+
if chapters != "all":
|
|
147
|
+
raise _option_error("document bundles support only --chapters all")
|
|
148
|
+
if profile is not None:
|
|
149
|
+
raise _option_error("--profile is supported only for book bundles")
|
|
150
|
+
if sample_rate is not None or channels is not None:
|
|
151
|
+
raise _option_error("--sample-rate and --channels are supported only for book bundles")
|
|
152
|
+
if batch:
|
|
153
|
+
if format is None:
|
|
154
|
+
raise InvalidExportError(
|
|
155
|
+
"document export requires a format when exporting all variants",
|
|
156
|
+
code="audioexport.document_format_required",
|
|
157
|
+
)
|
|
158
|
+
normalize_format(format)
|
|
159
|
+
return normalized_metadata
|
|
160
|
+
|
|
161
|
+
|
|
94
162
|
def export_bundle(
|
|
95
163
|
bundle: str | Path,
|
|
96
164
|
*,
|
|
@@ -113,14 +181,21 @@ def export_bundle(
|
|
|
113
181
|
) -> BundleExportResult:
|
|
114
182
|
"""Export one canonical bundle, selecting the specialized path from its manifest."""
|
|
115
183
|
identity = load_bundle_identity(bundle)
|
|
116
|
-
normalized_metadata =
|
|
184
|
+
normalized_metadata = _validate_options_for_kind(
|
|
185
|
+
kind=identity.kind,
|
|
186
|
+
chapters=chapters,
|
|
187
|
+
output=output,
|
|
188
|
+
format=format,
|
|
189
|
+
profile=profile,
|
|
190
|
+
sample_rate=sample_rate,
|
|
191
|
+
channels=channels,
|
|
192
|
+
timeline=timeline,
|
|
193
|
+
metadata=metadata,
|
|
194
|
+
batch=False,
|
|
195
|
+
)
|
|
117
196
|
|
|
118
197
|
value: BookExportResult | DocumentExportResult
|
|
119
198
|
if identity.kind == "book":
|
|
120
|
-
if format is not None and normalize_format(format) != "m4b":
|
|
121
|
-
raise _option_error("book bundles support only --format m4b")
|
|
122
|
-
if timeline is not None:
|
|
123
|
-
raise _option_error("--timeline is not supported for book bundles")
|
|
124
199
|
value = export_book(
|
|
125
200
|
identity.root,
|
|
126
201
|
variant=variant,
|
|
@@ -140,12 +215,6 @@ def export_bundle(
|
|
|
140
215
|
)
|
|
141
216
|
return BundleExportResult(kind="book", value=value)
|
|
142
217
|
|
|
143
|
-
if chapters != "all":
|
|
144
|
-
raise _option_error("document bundles support only --chapters all")
|
|
145
|
-
if profile is not None:
|
|
146
|
-
raise _option_error("--profile is supported only for book bundles")
|
|
147
|
-
if sample_rate is not None or channels is not None:
|
|
148
|
-
raise _option_error("--sample-rate and --channels are supported only for book bundles")
|
|
149
218
|
if isinstance(normalized_metadata, AudiobookMetadata):
|
|
150
219
|
raise _option_error("document bundle metadata must be a string mapping")
|
|
151
220
|
value = export_document(
|
|
@@ -164,3 +233,90 @@ def export_bundle(
|
|
|
164
233
|
ffprobe=ffprobe,
|
|
165
234
|
)
|
|
166
235
|
return BundleExportResult(kind="document", value=value)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def export_bundle_all(
|
|
239
|
+
bundle: str | Path,
|
|
240
|
+
*,
|
|
241
|
+
chapters: str = "all",
|
|
242
|
+
output: str | Path | None = None,
|
|
243
|
+
format: str | None = None,
|
|
244
|
+
profile: str | Path | None = None,
|
|
245
|
+
bitrate: str | int | None = None,
|
|
246
|
+
sample_rate: int | None = None,
|
|
247
|
+
channels: int | None = None,
|
|
248
|
+
metadata: AudiobookMetadata | Mapping[str, str] | None = None,
|
|
249
|
+
cover: str | Path | None = None,
|
|
250
|
+
timeline: str | Path | None = None,
|
|
251
|
+
audiocompose_root: str | Path | None = None,
|
|
252
|
+
output_root: str | Path | None = None,
|
|
253
|
+
force: bool = False,
|
|
254
|
+
ffmpeg: str | Path | None = None,
|
|
255
|
+
ffprobe: str | Path | None = None,
|
|
256
|
+
) -> BundleExportBatchResult:
|
|
257
|
+
"""Export every discovered AudioCompose variant in deterministic name order."""
|
|
258
|
+
identity = load_bundle_identity(bundle)
|
|
259
|
+
_validate_options_for_kind(
|
|
260
|
+
kind=identity.kind,
|
|
261
|
+
chapters=chapters,
|
|
262
|
+
output=output,
|
|
263
|
+
format=format,
|
|
264
|
+
profile=profile,
|
|
265
|
+
sample_rate=sample_rate,
|
|
266
|
+
channels=channels,
|
|
267
|
+
timeline=timeline,
|
|
268
|
+
metadata=metadata,
|
|
269
|
+
batch=True,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
profile_output_name: str | None = None
|
|
273
|
+
if identity.kind == "book" and profile is not None:
|
|
274
|
+
profile_output_name = load_audiobook_profile(profile).output.name
|
|
275
|
+
discovered: BundleVariantsResult = list_bundle_variants(
|
|
276
|
+
identity.root,
|
|
277
|
+
audiocompose_root=audiocompose_root,
|
|
278
|
+
)
|
|
279
|
+
if not discovered.variants:
|
|
280
|
+
raise InvalidExportError(
|
|
281
|
+
"no AudioCompose variants were found",
|
|
282
|
+
code="audioexport.bundle_no_variants",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
results: list[BundleExportResult] = []
|
|
286
|
+
for item in sorted(discovered.variants, key=lambda variant_info: variant_info.name):
|
|
287
|
+
batch_output = output
|
|
288
|
+
if profile_output_name is not None:
|
|
289
|
+
base_output = (
|
|
290
|
+
Path(output_root).expanduser()
|
|
291
|
+
if output_root is not None
|
|
292
|
+
else identity.root / "audioexport"
|
|
293
|
+
)
|
|
294
|
+
if not base_output.is_absolute():
|
|
295
|
+
base_output = Path.cwd() / base_output
|
|
296
|
+
batch_output = base_output.absolute() / item.name / profile_output_name
|
|
297
|
+
results.append(
|
|
298
|
+
export_bundle(
|
|
299
|
+
identity.root,
|
|
300
|
+
variant=item.name,
|
|
301
|
+
chapters=chapters,
|
|
302
|
+
output=batch_output,
|
|
303
|
+
format=format,
|
|
304
|
+
profile=profile,
|
|
305
|
+
bitrate=bitrate,
|
|
306
|
+
sample_rate=sample_rate,
|
|
307
|
+
channels=channels,
|
|
308
|
+
metadata=metadata,
|
|
309
|
+
cover=cover,
|
|
310
|
+
timeline=timeline,
|
|
311
|
+
audiocompose_root=audiocompose_root,
|
|
312
|
+
output_root=output_root,
|
|
313
|
+
force=force,
|
|
314
|
+
ffmpeg=ffmpeg,
|
|
315
|
+
ffprobe=ffprobe,
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
return BundleExportBatchResult(
|
|
319
|
+
kind=identity.kind,
|
|
320
|
+
workspace=identity.root,
|
|
321
|
+
results=tuple(results),
|
|
322
|
+
)
|