multi-voice: per-character dialogue rendering
Schema: characters.voice_id + characters.slug (migration 0007). voice_id is FK to voices(id); slug is the stable lowercase token the narrate_prep pass uses inside [voice:slug]...[/voice]. Forge::narrate_prep takes &[CharacterSpeaker]. System prompt expanded to instruct the author to wrap dialogue lines in voice tags based on a roster supplied in the user prompt (slug + name + short hint from key_facts). Unattributed dialogue stays unwrapped and inherits the narrator voice. skald narrate substitutes [voice:<character-slug>] → [voice:<kokoro-voice-name>] right before sending to Kokoro, using characters.voice_id JOIN voices.reference_path as the map. Slugs with no voice or no character row fall back to the narrator voice defensively (logged as warn). kokoro_server.py v0.4: splitter recognises [voice:X]...[/voice] blocks at the paragraph level. Each text node carries an optional voice attribution; renderer feeds it to Kokoro per-segment. Outside voice blocks the request's default voice is used. voices_used is reported back so callers can verify multi-voice actually ran. Only kokoro-routed renders pre-process voice tags; F5 paths leave the tags in place (F5 multi-voice not implemented). Defensive fallback: orphan/unclosed [/voice] markers are silently absorbed rather than failing the render.
This commit is contained in:
parent
330bc8bde2
commit
c9bd38034c
6 changed files with 186 additions and 10 deletions
16
migrations/0007_characters_voice.sql
Normal file
16
migrations/0007_characters_voice.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-- Per-character voice assignment for multi-voice audiobook rendering.
|
||||
-- A NULL voice_id means "use the story's default narrator voice for
|
||||
-- this character's dialogue" (effectively no voice change vs the
|
||||
-- narrator). A non-NULL voice_id pins the character's dialogue to
|
||||
-- that specific voice.
|
||||
--
|
||||
-- slug is the stable lowercase token the narrate_prep pass uses
|
||||
-- inside [voice:<slug>]...[/voice] markers. Distinct from name so
|
||||
-- "Anatoly Dyatlov" can carry slug "dyatlov" without re-deriving on
|
||||
-- every render.
|
||||
ALTER TABLE characters
|
||||
ADD COLUMN voice_id uuid REFERENCES voices(id) ON DELETE SET NULL,
|
||||
ADD COLUMN slug text;
|
||||
|
||||
CREATE INDEX idx_characters_voice ON characters(voice_id) WHERE voice_id IS NOT NULL;
|
||||
CREATE UNIQUE INDEX idx_characters_story_slug ON characters(story_id, slug) WHERE slug IS NOT NULL;
|
||||
Loading…
Add table
Add a link
Reference in a new issue