renderplan 0.1.3__tar.gz → 0.1.4__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.
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/events/events.jsonl +2 -0
- renderplan-0.1.4/.ledger/releaseledger/data/ledgers/main/releases/v0.1.4/entries/entry-0001.md +29 -0
- renderplan-0.1.4/.ledger/releaseledger/data/ledgers/main/releases/v0.1.4/release.md +27 -0
- {renderplan-0.1.3/renderplan.egg-info → renderplan-0.1.4}/PKG-INFO +21 -1
- {renderplan-0.1.3 → renderplan-0.1.4}/README.md +20 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/docs/changelog.md +8 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/__init__.py +14 -1
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/_version.py +3 -3
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/contracts.py +17 -1
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/model.py +23 -0
- {renderplan-0.1.3 → renderplan-0.1.4/renderplan.egg-info}/PKG-INFO +21 -1
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan.egg-info/SOURCES.txt +2 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan.egg-info/scm_file_list.json +2 -0
- renderplan-0.1.4/renderplan.egg-info/scm_version.json +8 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/tests/test_contracts.py +82 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/tests/test_identity.py +18 -0
- renderplan-0.1.3/renderplan.egg-info/scm_version.json +0 -8
- {renderplan-0.1.3 → renderplan-0.1.4}/.codecrate.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.github/workflows/codecov.yml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.github/workflows/pre-commit.yml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.github/workflows/python-publish.yml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.github/workflows/tests.yml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.gitignore +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/ledger.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/.ledger-project.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/config.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/.ledger-project.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.0/audit/commit-audit.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.0/entries/entry-0001.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.0/entries/entry-0002.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.0/entries/entry-0003.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.0/entries/entry-0004.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.0/release.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.1/audit/commit-audit.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.1/entries/entry-0001.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.1/entries/entry-0002.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.1/release.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.2/audit/commit-audit.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.2/entries/entry-0001.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.2/release.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.3/audit/commit-audit.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.3/entries/entry-0001.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/releases/v0.1.3/release.md +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/audit-decisions.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/audit.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/entries.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/evidence/0e39bcd.patch +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/evidence/f27def9.patch +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/evidence/manifest.json +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/range.json +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.2/audit-decisions.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.2/audit.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.2/entries.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.2/evidence/f88173e.patch +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.2/evidence/manifest.json +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.2/range.json +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.3/audit-decisions.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.3/audit.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.3/entries.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.3/evidence/8c9c937.patch +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.3/evidence/manifest.json +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.3/range.json +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/write.lock +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/taskledger/.ledger-project.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.ledger/taskledger/config.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/.pre-commit-config.yaml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/LICENSE +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/docs/Makefile +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/docs/conf.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/docs/make.bat +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/docs/make.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/docs/requirements.txt +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/examples/af_heart.renderprofile.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/examples/chapter.preflight.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/examples/chapter.render-manifest.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/examples/chapter.renderplan.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/examples/chapter.replan.preflight.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/pyproject.toml +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/__main__.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/_toml.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/cli.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/codec.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/collection.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/errors.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/identity.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/paths.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/py.typed +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/set.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/validation.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan/version.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan.egg-info/dependency_links.txt +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan.egg-info/entry_points.txt +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan.egg-info/requires.txt +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/renderplan.egg-info/top_level.txt +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/setup.cfg +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/tests/test_collection.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/tests/test_profile_reidentification.py +0 -0
- {renderplan-0.1.3 → renderplan-0.1.4}/tests/test_renderplan_set.py +0 -0
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/data/ledgers/main/events/events.jsonl
RENAMED
|
@@ -22,3 +22,5 @@
|
|
|
22
22
|
{"data":{"updated_rows":1},"event":"audit.updated","event_id":"event-0022","record_revisions":{"commit_audit_sheet":2},"release_version":"v0.1.3","schema_version":2}
|
|
23
23
|
{"data":{"entry_ids":["entry-0001"],"synced_audit_rows":1},"event":"entry.batch_added","event_id":"event-0023","record_revisions":{"commit_audit_sheet":3,"entry:v0.1.3/entry-0001":1,"release:v0.1.3":3},"release_version":"v0.1.3","schema_version":2}
|
|
24
24
|
{"data":{"released_at":"2026-10-25"},"event":"release.finalized","event_id":"event-0024","record_revisions":{"release:v0.1.3":4},"release_version":"v0.1.3","schema_version":2}
|
|
25
|
+
{"data":{"status":"planned"},"event":"release.created","event_id":"event-0025","record_revisions":{"release:v0.1.4":1},"release_version":"v0.1.4","schema_version":2}
|
|
26
|
+
{"data":{"kind":"added","status":"accepted"},"entry_id":"entry-0001","event":"entry.added","event_id":"event-0026","record_revisions":{"entry:v0.1.4/entry-0001":1,"release:v0.1.4":2},"release_version":"v0.1.4","schema_version":2}
|
renderplan-0.1.4/.ledger/releaseledger/data/ledgers/main/releases/v0.1.4/entries/entry-0001.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 2
|
|
3
|
+
object_type: release_entry
|
|
4
|
+
versioning:
|
|
5
|
+
schema_version: 1
|
|
6
|
+
revision: 1
|
|
7
|
+
entry_id: entry-0001
|
|
8
|
+
release_version: v0.1.4
|
|
9
|
+
kind: added
|
|
10
|
+
summary: Added portable character interpretation semantics to speech controls
|
|
11
|
+
status: accepted
|
|
12
|
+
audience: null
|
|
13
|
+
scopes: []
|
|
14
|
+
source_refs:
|
|
15
|
+
- tl:task-0004
|
|
16
|
+
paths:
|
|
17
|
+
- renderplan/model.py
|
|
18
|
+
- renderplan/contracts.py
|
|
19
|
+
- README.md
|
|
20
|
+
issues: []
|
|
21
|
+
prs: []
|
|
22
|
+
sources: []
|
|
23
|
+
contributors: []
|
|
24
|
+
breaking: false
|
|
25
|
+
internal: false
|
|
26
|
+
order: 1
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
RenderPlan formalizes say_as parameter validation while preserving exact request text and leaving engine-specific realization to renderer preflight.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 2
|
|
3
|
+
object_type: release
|
|
4
|
+
versioning:
|
|
5
|
+
schema_version: 1
|
|
6
|
+
revision: 2
|
|
7
|
+
version: v0.1.4
|
|
8
|
+
status: planned
|
|
9
|
+
history_state: curated
|
|
10
|
+
title: null
|
|
11
|
+
released_at: null
|
|
12
|
+
previous_version: v0.1.3
|
|
13
|
+
cancel_reason: null
|
|
14
|
+
superseded_by: null
|
|
15
|
+
changelog_file: docs/changelog.md
|
|
16
|
+
boundary_ref: null
|
|
17
|
+
source_refs: []
|
|
18
|
+
source_count: null
|
|
19
|
+
entry_count: 1
|
|
20
|
+
artifact_count: 0
|
|
21
|
+
git_base_ref: null
|
|
22
|
+
git_base_sha: null
|
|
23
|
+
git_head_ref: null
|
|
24
|
+
git_head_sha: null
|
|
25
|
+
git_range: null
|
|
26
|
+
git_commit_count: null
|
|
27
|
+
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: renderplan
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Engine-neutral render profile, plan, preflight, manifest, and collection contracts for speech pipelines.
|
|
5
5
|
Classifier: Development Status :: 3 - Alpha
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -90,6 +90,26 @@ The destination start offset of source span `N` inside the request is the sum of
|
|
|
90
90
|
|
|
91
91
|
`SpeechControl` is request-local and renderer-neutral: it carries `start`, `end`, `name`, and TOML-safe `params`. Initial semantic names include `pronunciation`, `prosody`, `emphasis`, `say_as`, and `extension:<name>`. Controls participate in `segment_id`; renderers decide support, and unsupported controls must fail preflight rather than be ignored. Control ranges may overlap only when the source semantics permit it. Engine-specific voice selection is resolved into the opaque logical role/binding; audio directives become `AudioItem`s.
|
|
92
92
|
|
|
93
|
+
### Standard `say_as` semantics
|
|
94
|
+
|
|
95
|
+
`SpeechControl(name="say_as")` requires a non-empty `interpret_as` string and accepts optional non-empty `format` and `detail` strings. The standard value `interpret_as = "characters"` means: speak the covered text as a sequence of separately pronounced characters, preserving the surrounding request as one continuous speech request when the engine can do so. It is portable intent; RenderPlan does not dictate an engine's realization or restrict future `interpret_as` values.
|
|
96
|
+
|
|
97
|
+
For example, the following control covers exactly `A R T`:
|
|
98
|
+
|
|
99
|
+
```toml
|
|
100
|
+
[[flow.control]]
|
|
101
|
+
start = 0
|
|
102
|
+
end = 5
|
|
103
|
+
name = "say_as"
|
|
104
|
+
|
|
105
|
+
[flow.control.params]
|
|
106
|
+
interpret_as = "characters"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The exact-range invariant is `speech.text[0:5] == "A R T"`. The control does not rewrite the canonical request text, prescribe spaces, hyphens, commas, SSML, phonemes, or pauses, and does not split a sentence or add a `PauseItem`. A renderer that does not support the requested semantics must reject the request during preflight rather than silently ignore the control.
|
|
110
|
+
|
|
111
|
+
Do not store `A- R- T` in RenderPlan merely because one Kokoro frontend prefers that spelling; an engine may choose such a frontend-specific realization internally.
|
|
112
|
+
|
|
93
113
|
Bindings copied into plans must be self-contained enough to execute without reopening the RenderProfile. This contract does not define renderer-specific path keys or local-asset resolution.
|
|
94
114
|
This schema has no generic asset-reference type yet. A VoiceRender profile producer must refuse bindings that depend on profile-local or otherwise uncopied asset state; do not introduce renderer-specific asset-path keys into this shared contract.
|
|
95
115
|
|
|
@@ -67,6 +67,26 @@ The destination start offset of source span `N` inside the request is the sum of
|
|
|
67
67
|
|
|
68
68
|
`SpeechControl` is request-local and renderer-neutral: it carries `start`, `end`, `name`, and TOML-safe `params`. Initial semantic names include `pronunciation`, `prosody`, `emphasis`, `say_as`, and `extension:<name>`. Controls participate in `segment_id`; renderers decide support, and unsupported controls must fail preflight rather than be ignored. Control ranges may overlap only when the source semantics permit it. Engine-specific voice selection is resolved into the opaque logical role/binding; audio directives become `AudioItem`s.
|
|
69
69
|
|
|
70
|
+
### Standard `say_as` semantics
|
|
71
|
+
|
|
72
|
+
`SpeechControl(name="say_as")` requires a non-empty `interpret_as` string and accepts optional non-empty `format` and `detail` strings. The standard value `interpret_as = "characters"` means: speak the covered text as a sequence of separately pronounced characters, preserving the surrounding request as one continuous speech request when the engine can do so. It is portable intent; RenderPlan does not dictate an engine's realization or restrict future `interpret_as` values.
|
|
73
|
+
|
|
74
|
+
For example, the following control covers exactly `A R T`:
|
|
75
|
+
|
|
76
|
+
```toml
|
|
77
|
+
[[flow.control]]
|
|
78
|
+
start = 0
|
|
79
|
+
end = 5
|
|
80
|
+
name = "say_as"
|
|
81
|
+
|
|
82
|
+
[flow.control.params]
|
|
83
|
+
interpret_as = "characters"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The exact-range invariant is `speech.text[0:5] == "A R T"`. The control does not rewrite the canonical request text, prescribe spaces, hyphens, commas, SSML, phonemes, or pauses, and does not split a sentence or add a `PauseItem`. A renderer that does not support the requested semantics must reject the request during preflight rather than silently ignore the control.
|
|
87
|
+
|
|
88
|
+
Do not store `A- R- T` in RenderPlan merely because one Kokoro frontend prefers that spelling; an engine may choose such a frontend-specific realization internally.
|
|
89
|
+
|
|
70
90
|
Bindings copied into plans must be self-contained enough to execute without reopening the RenderProfile. This contract does not define renderer-specific path keys or local-asset resolution.
|
|
71
91
|
This schema has no generic asset-reference type yet. A VoiceRender profile producer must refuse bindings that depend on profile-local or otherwise uncopied asset state; do not introduce renderer-specific asset-path keys into this shared contract.
|
|
72
92
|
|
|
@@ -24,7 +24,15 @@ from .collection import (
|
|
|
24
24
|
validate_renderplan_set,
|
|
25
25
|
validate_renderplan_set_profile,
|
|
26
26
|
)
|
|
27
|
-
from .contracts import
|
|
27
|
+
from .contracts import (
|
|
28
|
+
CONTRACTS,
|
|
29
|
+
CONTROL_EMPHASIS,
|
|
30
|
+
CONTROL_PRONUNCIATION,
|
|
31
|
+
CONTROL_PROSODY,
|
|
32
|
+
CONTROL_SAY_AS,
|
|
33
|
+
SAY_AS_CHARACTERS,
|
|
34
|
+
supported_contracts,
|
|
35
|
+
)
|
|
28
36
|
from .errors import CodecError, RenderPlanError, ValidationError
|
|
29
37
|
from .model import (
|
|
30
38
|
MANIFEST_FORMAT,
|
|
@@ -66,12 +74,17 @@ from .version import __version__
|
|
|
66
74
|
|
|
67
75
|
__all__ = [
|
|
68
76
|
"CONTRACTS",
|
|
77
|
+
"CONTROL_EMPHASIS",
|
|
78
|
+
"CONTROL_PRONUNCIATION",
|
|
79
|
+
"CONTROL_PROSODY",
|
|
80
|
+
"CONTROL_SAY_AS",
|
|
69
81
|
"MANIFEST_FORMAT",
|
|
70
82
|
"PLAN_FORMAT",
|
|
71
83
|
"PREFLIGHT_FORMAT",
|
|
72
84
|
"PROFILE_FORMAT",
|
|
73
85
|
"RENDERPLAN_SET_FORMAT",
|
|
74
86
|
"RENDERPLAN_SET_VERSION",
|
|
87
|
+
"SAY_AS_CHARACTERS",
|
|
75
88
|
"AudioItem",
|
|
76
89
|
"CapacityConstraint",
|
|
77
90
|
"CodecError",
|
|
@@ -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.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 4)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'ga434ece68'
|
|
@@ -25,9 +25,25 @@ CONTRACTS: Mapping[str, int] = MappingProxyType(
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
CONTROL_PRONUNCIATION = "pronunciation"
|
|
29
|
+
CONTROL_PROSODY = "prosody"
|
|
30
|
+
CONTROL_EMPHASIS = "emphasis"
|
|
31
|
+
CONTROL_SAY_AS = "say_as"
|
|
32
|
+
|
|
33
|
+
SAY_AS_CHARACTERS = "characters"
|
|
34
|
+
|
|
35
|
+
|
|
28
36
|
def supported_contracts() -> Mapping[str, int]:
|
|
29
37
|
"""Return the immutable mapping of supported document formats to versions."""
|
|
30
38
|
return CONTRACTS
|
|
31
39
|
|
|
32
40
|
|
|
33
|
-
__all__ = [
|
|
41
|
+
__all__ = [
|
|
42
|
+
"CONTRACTS",
|
|
43
|
+
"CONTROL_EMPHASIS",
|
|
44
|
+
"CONTROL_PRONUNCIATION",
|
|
45
|
+
"CONTROL_PROSODY",
|
|
46
|
+
"CONTROL_SAY_AS",
|
|
47
|
+
"SAY_AS_CHARACTERS",
|
|
48
|
+
"supported_contracts",
|
|
49
|
+
]
|
|
@@ -247,6 +247,7 @@ class SpeechControl:
|
|
|
247
247
|
raise ValidationError("control must satisfy 0 <= start < end")
|
|
248
248
|
require_nonempty(self.name, "control.name")
|
|
249
249
|
require_toml_value(self.params, "control.params")
|
|
250
|
+
validate_standard_control(self)
|
|
250
251
|
|
|
251
252
|
def ordering_key(self) -> tuple[int, int, str, bytes]:
|
|
252
253
|
return (self.start, self.end, self.name, canonical_json_bytes(self.params))
|
|
@@ -260,6 +261,28 @@ class SpeechControl:
|
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
|
|
264
|
+
def validate_standard_control(control: SpeechControl) -> None:
|
|
265
|
+
"""Validate parameters for known standard speech controls."""
|
|
266
|
+
if control.name != "say_as":
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
allowed = {"interpret_as", "format", "detail"}
|
|
270
|
+
unknown = set(control.params).difference(allowed)
|
|
271
|
+
if unknown:
|
|
272
|
+
raise ValidationError(
|
|
273
|
+
"say_as control has unsupported parameter(s): " + ", ".join(sorted(unknown))
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
interpret_as = control.params.get("interpret_as")
|
|
277
|
+
if not isinstance(interpret_as, str) or not interpret_as.strip():
|
|
278
|
+
raise ValidationError("say_as.interpret_as must be a non-empty string")
|
|
279
|
+
|
|
280
|
+
for name in ("format", "detail"):
|
|
281
|
+
value = control.params.get(name)
|
|
282
|
+
if value is not None and (not isinstance(value, str) or not value.strip()):
|
|
283
|
+
raise ValidationError(f"say_as.{name} must be a non-empty string when present")
|
|
284
|
+
|
|
285
|
+
|
|
263
286
|
@dataclass(frozen=True, slots=True)
|
|
264
287
|
class TokenBundle:
|
|
265
288
|
"""Dense token spans/pos/tag and sparse, token-indexed lemma/morph values."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: renderplan
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Engine-neutral render profile, plan, preflight, manifest, and collection contracts for speech pipelines.
|
|
5
5
|
Classifier: Development Status :: 3 - Alpha
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -90,6 +90,26 @@ The destination start offset of source span `N` inside the request is the sum of
|
|
|
90
90
|
|
|
91
91
|
`SpeechControl` is request-local and renderer-neutral: it carries `start`, `end`, `name`, and TOML-safe `params`. Initial semantic names include `pronunciation`, `prosody`, `emphasis`, `say_as`, and `extension:<name>`. Controls participate in `segment_id`; renderers decide support, and unsupported controls must fail preflight rather than be ignored. Control ranges may overlap only when the source semantics permit it. Engine-specific voice selection is resolved into the opaque logical role/binding; audio directives become `AudioItem`s.
|
|
92
92
|
|
|
93
|
+
### Standard `say_as` semantics
|
|
94
|
+
|
|
95
|
+
`SpeechControl(name="say_as")` requires a non-empty `interpret_as` string and accepts optional non-empty `format` and `detail` strings. The standard value `interpret_as = "characters"` means: speak the covered text as a sequence of separately pronounced characters, preserving the surrounding request as one continuous speech request when the engine can do so. It is portable intent; RenderPlan does not dictate an engine's realization or restrict future `interpret_as` values.
|
|
96
|
+
|
|
97
|
+
For example, the following control covers exactly `A R T`:
|
|
98
|
+
|
|
99
|
+
```toml
|
|
100
|
+
[[flow.control]]
|
|
101
|
+
start = 0
|
|
102
|
+
end = 5
|
|
103
|
+
name = "say_as"
|
|
104
|
+
|
|
105
|
+
[flow.control.params]
|
|
106
|
+
interpret_as = "characters"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The exact-range invariant is `speech.text[0:5] == "A R T"`. The control does not rewrite the canonical request text, prescribe spaces, hyphens, commas, SSML, phonemes, or pauses, and does not split a sentence or add a `PauseItem`. A renderer that does not support the requested semantics must reject the request during preflight rather than silently ignore the control.
|
|
110
|
+
|
|
111
|
+
Do not store `A- R- T` in RenderPlan merely because one Kokoro frontend prefers that spelling; an engine may choose such a frontend-specific realization internally.
|
|
112
|
+
|
|
93
113
|
Bindings copied into plans must be self-contained enough to execute without reopening the RenderProfile. This contract does not define renderer-specific path keys or local-asset resolution.
|
|
94
114
|
This schema has no generic asset-reference type yet. A VoiceRender profile producer must refuse bindings that depend on profile-local or otherwise uncopied asset state; do not introduce renderer-specific asset-path keys into this shared contract.
|
|
95
115
|
|
|
@@ -30,6 +30,8 @@ pyproject.toml
|
|
|
30
30
|
.ledger/releaseledger/data/ledgers/main/releases/v0.1.3/release.md
|
|
31
31
|
.ledger/releaseledger/data/ledgers/main/releases/v0.1.3/audit/commit-audit.yaml
|
|
32
32
|
.ledger/releaseledger/data/ledgers/main/releases/v0.1.3/entries/entry-0001.md
|
|
33
|
+
.ledger/releaseledger/data/ledgers/main/releases/v0.1.4/release.md
|
|
34
|
+
.ledger/releaseledger/data/ledgers/main/releases/v0.1.4/entries/entry-0001.md
|
|
33
35
|
.ledger/releaseledger/work/v0.1.1/audit-decisions.yaml
|
|
34
36
|
.ledger/releaseledger/work/v0.1.1/audit.yaml
|
|
35
37
|
.ledger/releaseledger/work/v0.1.1/entries.yaml
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
".ledger/releaseledger/data/ledgers/main/releases/v0.1.3/audit/commit-audit.yaml",
|
|
28
28
|
".ledger/releaseledger/data/ledgers/main/releases/v0.1.3/entries/entry-0001.md",
|
|
29
29
|
".ledger/releaseledger/data/ledgers/main/releases/v0.1.3/release.md",
|
|
30
|
+
".ledger/releaseledger/data/ledgers/main/releases/v0.1.4/entries/entry-0001.md",
|
|
31
|
+
".ledger/releaseledger/data/ledgers/main/releases/v0.1.4/release.md",
|
|
30
32
|
".ledger/releaseledger/work/v0.1.1/audit-decisions.yaml",
|
|
31
33
|
".ledger/releaseledger/work/v0.1.1/audit.yaml",
|
|
32
34
|
".ledger/releaseledger/work/v0.1.1/entries.yaml",
|
|
@@ -9,6 +9,11 @@ import pytest
|
|
|
9
9
|
|
|
10
10
|
from renderplan import (
|
|
11
11
|
CONTRACTS,
|
|
12
|
+
CONTROL_EMPHASIS,
|
|
13
|
+
CONTROL_PRONUNCIATION,
|
|
14
|
+
CONTROL_PROSODY,
|
|
15
|
+
CONTROL_SAY_AS,
|
|
16
|
+
SAY_AS_CHARACTERS,
|
|
12
17
|
AudioItem,
|
|
13
18
|
CapacityConstraint,
|
|
14
19
|
CodecError,
|
|
@@ -218,6 +223,83 @@ def test_renderplan_roundtrip_with_controls_and_generic_binding(tmp_path: Path)
|
|
|
218
223
|
assert loaded.speech[0].controls[0].params == {"level": "moderate"}
|
|
219
224
|
|
|
220
225
|
|
|
226
|
+
@pytest.mark.parametrize(
|
|
227
|
+
"params",
|
|
228
|
+
[
|
|
229
|
+
{},
|
|
230
|
+
{"format": "letters"},
|
|
231
|
+
{"interpret_as": ""},
|
|
232
|
+
{"interpret_as": 1},
|
|
233
|
+
{"interpret_as": "characters", "format": ""},
|
|
234
|
+
{"interpret_as": "characters", "detail": False},
|
|
235
|
+
{"interpret_as": "characters", "detail": ""},
|
|
236
|
+
{"interpret_as": "characters", "unexpected": "value"},
|
|
237
|
+
],
|
|
238
|
+
)
|
|
239
|
+
def test_say_as_control_rejects_invalid_standard_parameters(params: dict[str, Any]) -> None:
|
|
240
|
+
with pytest.raises(ValidationError, match="say_as"):
|
|
241
|
+
SpeechControl(0, 5, "say_as", params)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def test_say_as_standard_constants_and_future_interpretations() -> None:
|
|
245
|
+
assert CONTROL_SAY_AS == "say_as"
|
|
246
|
+
assert CONTROL_EMPHASIS == "emphasis"
|
|
247
|
+
assert CONTROL_PRONUNCIATION == "pronunciation"
|
|
248
|
+
assert CONTROL_PROSODY == "prosody"
|
|
249
|
+
assert SAY_AS_CHARACTERS == "characters"
|
|
250
|
+
control = SpeechControl(
|
|
251
|
+
0,
|
|
252
|
+
5,
|
|
253
|
+
CONTROL_SAY_AS,
|
|
254
|
+
{"interpret_as": "date", "format": "dd.mm.yyyy", "detail": "1"},
|
|
255
|
+
)
|
|
256
|
+
assert control.params["interpret_as"] == "date"
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_say_as_characters_roundtrip_preserves_sentence_text_and_flow(tmp_path: Path) -> None:
|
|
260
|
+
text = "Because someone called A R T finally."
|
|
261
|
+
start = text.index("A R T")
|
|
262
|
+
control = SpeechControl(
|
|
263
|
+
start,
|
|
264
|
+
start + len("A R T"),
|
|
265
|
+
CONTROL_SAY_AS,
|
|
266
|
+
{"interpret_as": SAY_AS_CHARACTERS},
|
|
267
|
+
)
|
|
268
|
+
value = plan(speech(text=text, controls=(control,)))
|
|
269
|
+
path = tmp_path / "say-as.renderplan.toml"
|
|
270
|
+
save_plan(value, path)
|
|
271
|
+
loaded = load_plan(path)
|
|
272
|
+
|
|
273
|
+
assert len(loaded.flow) == 1
|
|
274
|
+
assert len(loaded.speech) == 1
|
|
275
|
+
restored = loaded.speech[0]
|
|
276
|
+
restored_control = restored.controls[0]
|
|
277
|
+
assert restored.text == text
|
|
278
|
+
assert restored.text[restored_control.start : restored_control.end] == "A R T"
|
|
279
|
+
assert restored_control.name == CONTROL_SAY_AS
|
|
280
|
+
assert restored_control.params["interpret_as"] == SAY_AS_CHARACTERS
|
|
281
|
+
assert restored.planning_level == "sentence"
|
|
282
|
+
assert not any(isinstance(item, PauseItem) for item in loaded.flow)
|
|
283
|
+
assert "A- R- T" not in path.read_text(encoding="utf-8")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@pytest.mark.parametrize(
|
|
287
|
+
("text", "possessive"),
|
|
288
|
+
[
|
|
289
|
+
("A R T's house is nice.", "'s"),
|
|
290
|
+
("A R T’s house is nice.", "’s"),
|
|
291
|
+
],
|
|
292
|
+
)
|
|
293
|
+
def test_say_as_control_excludes_possessive_suffix(text: str, possessive: str) -> None:
|
|
294
|
+
start = text.index("A R T")
|
|
295
|
+
end = start + len("A R T")
|
|
296
|
+
control = SpeechControl(start, end, CONTROL_SAY_AS, {"interpret_as": SAY_AS_CHARACTERS})
|
|
297
|
+
item = speech(text=text, controls=(control,))
|
|
298
|
+
|
|
299
|
+
assert item.text[control.start : control.end] == "A R T"
|
|
300
|
+
assert item.text[control.end :].startswith(possessive)
|
|
301
|
+
|
|
302
|
+
|
|
221
303
|
def test_leading_pause_is_legal() -> None:
|
|
222
304
|
item = speech()
|
|
223
305
|
value = plan(flow=(PauseItem(120, "authored"), item))
|
|
@@ -7,6 +7,7 @@ from renderplan import (
|
|
|
7
7
|
RenderProfile,
|
|
8
8
|
RoleProfile,
|
|
9
9
|
SourceSpan,
|
|
10
|
+
SpeechControl,
|
|
10
11
|
SpeechItem,
|
|
11
12
|
)
|
|
12
13
|
|
|
@@ -43,6 +44,23 @@ def test_speech_request_identity_excludes_number_and_provenance() -> None:
|
|
|
43
44
|
assert first.segment_id == renumbered.segment_id == different_source.segment_id
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
def test_say_as_control_changes_speech_request_identity() -> None:
|
|
48
|
+
plain = replace(
|
|
49
|
+
speech(),
|
|
50
|
+
text="A R T",
|
|
51
|
+
source=(SourceSpan("f000000.s000", 0, 5),),
|
|
52
|
+
segment_id=None,
|
|
53
|
+
).with_identity()
|
|
54
|
+
controlled = replace(
|
|
55
|
+
plain,
|
|
56
|
+
controls=(SpeechControl(0, 5, "say_as", {"interpret_as": "characters"}),),
|
|
57
|
+
segment_id=None,
|
|
58
|
+
).with_identity()
|
|
59
|
+
|
|
60
|
+
assert plain.text == controlled.text == "A R T"
|
|
61
|
+
assert plain.segment_id != controlled.segment_id
|
|
62
|
+
|
|
63
|
+
|
|
46
64
|
def test_pause_is_part_of_plan_identity() -> None:
|
|
47
65
|
item = speech()
|
|
48
66
|
p1 = RenderPlan(SOURCE, PROFILE_ID, (item, PauseItem(100, "sentence"))).with_identity()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/audit-decisions.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/evidence/0e39bcd.patch
RENAMED
|
File without changes
|
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/evidence/f27def9.patch
RENAMED
|
File without changes
|
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.1/evidence/manifest.json
RENAMED
|
File without changes
|
|
File without changes
|
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.2/audit-decisions.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.2/evidence/f88173e.patch
RENAMED
|
File without changes
|
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.2/evidence/manifest.json
RENAMED
|
File without changes
|
|
File without changes
|
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.3/audit-decisions.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.3/evidence/8c9c937.patch
RENAMED
|
File without changes
|
{renderplan-0.1.3 → renderplan-0.1.4}/.ledger/releaseledger/work/v0.1.3/evidence/manifest.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|