Skip to content

Public API reference

Import these names from kenkui. This page covers kenkui.__all__; signatures and fields below are rendered from the source at documentation build time. Underscore modules and operation implementation classes are private, even when a type annotation mentions them. Prefer the constructors and fluent methods to creating Pipeline.operations directly. __version__ is the installed package's version string.

See the usage guide for complete workflows, operation ordering, checkpoint reuse, publication semantics, and examples.

Constructors and convenience rendering

book() and epub() record a path without opening it. magic_run() performs resolution and rendering, returning the same result as Pipeline.write().

kenkui.book

book(path: str | PathLike[str]) -> Pipeline

Dispatch a supported source by its format marker without reading it.

kenkui.epub

epub(path: str | PathLike[str]) -> Pipeline

Record EPUB source intent without opening or parsing the path.

kenkui.magic_run

magic_run(
    book_path: str | PathLike[str],
    *,
    narrator: str,
    multi: bool = False,
    model: str = "openrouter/deepseek/deepseek-v4-flash",
) -> Result

Render an EPUB with one narrator or an automatically assigned cast.

Pipeline and source

Intent methods return new immutable branches. pipe() immediately calls an ordinary function; validate() reads inexpensive local state; inspect() parses the selected source or returns a saved checkpoint. resolve() may load local models, make provider calls, and save casting/series records. write() and write_m4b() validate and publish an M4B using spawned synthesis processes. Call effectful workflows from a guarded script entry point.

attribute(), silence(), and the scoped form of pronounce() declare per-book tuning rules addressed with where=; annotations() and write_annotations() load and save them as a JSON sidecar. select() and preview() narrow a render to part of a book for a quick probe. identity, tuning, and style summarize a pipeline's declared intent by tier. See tuning a book and the dial-in loop for the full workflow.

kenkui.Source dataclass

A lazily recorded readable source.

path instance-attribute

path: Path

format instance-attribute

format: Literal['epub']

kenkui.Pipeline dataclass

A source plus ordered immutable audiobook intent.

source instance-attribute

source: Source

operations class-attribute instance-attribute

operations: tuple[Operation, ...] = ()

metadata_intent property

metadata_intent: MetadataIntent | None

Return recorded metadata intent, if present.

identity property

identity: TierSummary

Return this book's identity-tier intent: what makes it this book.

A tuple scan over operations with no parse and no I/O. repr is the summary; iterate it for the declared operations themselves.

tuning property

tuning: TuningSummary

Return this book's tuning rules, grouped by kind and marked unsaved.

A tuple scan over operations with no parse and no I/O. repr is a truncated summary; iterate it for every declared rule.

style property

style: TierSummary

Return this book's style-tier intent: reusable taste, not this book.

A tuple scan over operations with no parse and no I/O. Style settings replace rather than accumulate, so each entry already shows its effective value. repr is the summary; iterate it for the declared operations themselves.

pipe

pipe(
    function: Callable[
        Concatenate[Pipeline, _PipeArgs], _PipeResult
    ],
    /,
    *args: args,
    **kwargs: kwargs,
) -> _PipeResult

Pass this pipeline to an ordinary function and return its result.

The function runs immediately with this pipeline as its first argument. Return a pipeline to continue chaining, or a report to leave the chain. Any effects and exceptions belong to the supplied function.

select_chapters

select_chapters(*chapter_ids: str) -> Pipeline

Return a branch selecting explicit stable chapter IDs.

select_chapter_range

select_chapter_range(
    start_id: str, end_id: str
) -> Pipeline

Return a branch selecting an inclusive stable-ID spine range.

select

select(
    *patterns: Pattern | Mapping[str, object],
) -> Pipeline

Select a union of grid subtrees while retaining source paths and casting.

Patterns use the original book's coordinates and sibling counts. They combine in source order without duplication. Full-render chunks wholly inside each selected interval retain their cache identities; chunks at either edge are clipped to the requested text.

pronounce

pronounce(
    lexicon: Mapping[str, str] | None = None,
    *,
    where: WhereArg = None,
    numbers: str | None = None,
    builtin: bool | None = None,
    **features: bool,
) -> Pipeline

Return a branch shaping what the engine says, not what it counts.

This is the only stage that rewrites text for speech. It runs last, per segment, and its output is thrown away after synthesis: canonical text, the billable character count, attribution offsets, and chapter identity are all unaffected. Normalization -- NFC, line endings, Unicode spaces, whitespace runs -- is not part of this and is not optional; it happens when the source is parsed.

Off unless called. What it does when called:

  • replaces entries from lexicon, then a small built-in pronunciation table unless builtin=False;
  • reads numbers aloud, at the depth numbers selects -- "off", "conservative" (the default), "standard" or "aggressive";
  • collapses hyphenated vocal gestures the engine would otherwise read out as letter names -- an elongation ("Ah-h-h" becomes "Ahhh") and a stammer ("S-s-sorry" becomes "Sssorry"). Both are on, and each is declined by name: elongation, stammer. Ordinary hyphenation is untouched, including reduplication such as "drip-drip-drip".

numbers is a preset over individually switchable features, each of which may be overridden by keyword: currency, percent, ordinals, units, decimals, integers, years, clock, roman, fractions, numbered. Passing False declines a feature the tier supplies; passing True asks for one it does not, without accepting the rest of the tier that carries it.

pipeline.pronounce(numbers="standard", roman=False)

Features compose rather than nest, so declining a specific form leaves a general one free to match inside it: currency=False alone reads "£5" as "£five", because the integer rule still applies. Decline integers too to leave the digits alone.

Lexicons accumulate as scoped tuning rules selected by where. A tuple of patterns or mappings adds one rule per selector in order. A whole-book rule applies everywhere; a scoped one overrides it inside its own region and shares the keys it does not mention.

Number presets, built-in pronunciation, and feature switches are global settings rather than rules, and each keeps its last stated value. Adding a pronunciation therefore leaves the style a house function already chose alone -- only naming a setting changes it.

attribute

attribute(
    character_id: str, *, where: WhereArg = None
) -> Pipeline

Return a branch attributing matched units to one character.

Calls accumulate ordered rules. Narrower match sets take precedence; later declarations break ties between equal or incomparable match sets. A tuple of patterns or mappings adds one rule per selector in order.

annotations

annotations(
    path: str | PathLike[str] | None = None,
) -> Pipeline

Load one immutable tuning baseline beneath inline declarations.

Reads only the sidecar. The digest identifies exactly the bytes parsed; later edits to that file cannot change this pipeline branch. Exact duplicates across the file/code boundary retain their loaded copy and anchor metadata. Other inline rules follow the loaded prefix.

A book that has never been saved has no sidecar yet: that is a no-op baseline, not an error. A sidecar that exists but cannot be read or parsed is a different problem -- a damaged correction file -- and still raises INVALID_SIDECAR.

write_annotations

write_annotations(
    path: str | PathLike[str] | None = None,
) -> Path

Save tuning with current text anchors, without changing this branch.

Annotation scope is the source book, independent of render selection. Writing explicitly inspects that book and builds only touched grids.

silence

silence(
    duration_ms: int, *, where: WhereArg = None
) -> Pipeline

Return a branch replacing the silence after matched positions.

Zero removes a derived pause. A subtree anchors to its final leaf; declaration records that intent without reading or resolving the book. A tuple of patterns or mappings adds one rule per selector in order.

pauses

pauses(
    *,
    chapter_ms: int = 0,
    heading_before_ms: int = 0,
    heading_after_ms: int = 0,
    paragraph_ms: int = 0,
    line_ms: int = 0,
) -> Pipeline

Return a branch requesting silence at structural boundaries.

Off unless called. Durations are retunable without re-synthesis: only turning a tier on or off changes segment identity, because only that changes where a segment ends.

infer_characters

infer_characters(
    model: str,
    *,
    identity: str | None = DEFAULT_IDENTITY_MODEL,
) -> Pipeline

Return a branch that will derive a character roster.

identity selects the reasoning model that merges aliases and removes non-people from a spaCy roster. None stays fully offline.

attribute_quotes

attribute_quotes(model: str) -> Pipeline

Return a branch that will assign a speaker to each quoted run.

assign_voice

assign_voice(voice: str | Voice) -> Pipeline

Return a branch assigning one stable voice ID to all speech.

The degenerate cast: narrator only. One VoicePlan and one renderer serve this and a full character cast alike.

assign_voices

assign_voices(
    *,
    narrator: str | Voice,
    unknown: str | Voice | None = None,
    cast: Mapping[str, str | Voice] | None = None,
    method: str = "gendered",
) -> Pipeline

Return a branch casting narrator, unknown, and named characters.

unknown defaults to the narrator's voice, so a line nobody could place sounds like narration rather than vanishing.

tts

tts() -> Pipeline

Return a branch with explicit synthesis intent.

metadata

metadata(
    *,
    title: str | None = None,
    author: str | None = None,
    cover: Literal["source"]
    | PathLike[str]
    | None = "source",
) -> Pipeline

Return a branch with output metadata inheritance and overrides.

series

series(
    series_id: str,
    *,
    book: int | None = None,
    allow_recast: bool = False,
    allow_narrator_change: bool = False,
) -> Pipeline

Return a branch tying this book to a series.

A character the series already cast keeps their voice, and voices continue spreading across volumes rather than restarting. book is stored in pipeline intent as an ordering hint; it does not schedule or sort volumes. Continuity is decided by identity, not by volume number.

Two ways a series can be contradicted fail validation before any model call: a pinned voice missing from the pool, and a narrator differing from the one the series recorded. allow_recast and allow_narrator_change each waive their matching failure so the render proceeds instead of refusing.

resolve

resolve(
    *,
    until: Literal["characters", "casting"] = "casting",
    cancel: CancellationToken | None = None,
    on_event: Callable[[ExecutionEvent], None]
    | None = None,
) -> Pipeline

Resolve voices, attribution, and casting, returning a new Pipeline.

Optional. write() resolves internally, so this exists only to pay the model cost early and inspect the outcome. It is an effect -- it reaches the network and writes the store -- but it is immutable, idempotent for unchanged source bytes, and leaves intent untouched. Use until="characters" to stop after discovery without requiring voices or attributing quotes. inspect().roster exposes that result; with_characters() records corrections before continuing.

After source changes, a roster checkpoint must be refreshed with resolve(until="characters") and reviewed again. Pipelines without roster checkpoints can refresh their cast with ordinary resolve(). Rendering an old checkpoint raises source_changed.

Pass a cancellation token to stop between model calls and before committing series changes. A running provider call must return before cooperative cancellation can take effect.

on_event receives discovery and attribution stage progress on the calling thread, between Started and Completed events. An unchanged checkpoint emits only those run events because no stages repeat.

with_characters

with_characters(roster: CharacterRoster) -> Pipeline

Return a branch using a reviewed character-discovery checkpoint.

First call resolve(until="characters"). Changes replace the roster, preserve its source snapshot, and invalidate attribution and casting. This method performs no I/O.

validate

validate() -> ValidationResult

Perform inexpensive source and operation validation without parsing.

script

script() -> Script

Inspect canonical review rows lazily, without resolution or model calls.

inspect

inspect() -> BookInspection

Inspect this checkpoint without synthesis or model calls.

Before resolution, parse and select current source text. Afterwards, return the frozen source, roster, attribution, and cast snapshot.

write

write(
    output: str | PathLike[str],
    *,
    on_event: Callable[[ExecutionEvent], None]
    | None = None,
    cancel: CancellationToken | None = None,
    workers: int | Literal["auto"] = "auto",
    overwrite: bool = False,
    keep_audio_cache: bool = False,
) -> Result

Write an M4B through the private execution orchestration boundary.

write_m4b

write_m4b(
    output: str | PathLike[str],
    *,
    on_event: Callable[[ExecutionEvent], None]
    | None = None,
    cancel: CancellationToken | None = None,
    workers: int | Literal["auto"] = "auto",
    overwrite: bool = False,
    keep_audio_cache: bool = False,
) -> Result

Validate controls and execute using privately bound rendering resources.

preview

preview(
    output: str | PathLike[str],
    *,
    on_event: Callable[[ExecutionEvent], None]
    | None = None,
    workers: int | Literal["auto"] = "auto",
    overwrite: bool = False,
) -> Result

Write a PCM WAV probe and retain synthesized segments for later reuse.

Requires assigned voices and adds TTS intent on a temporary branch when needed. The original pipeline is unchanged. The destination must end in .wav. Bibliographic metadata, cover art, and chapter markers are omitted from the artifact; normal execution events still report work.

kenkui.MetadataIntent dataclass

Semantic output metadata overrides and cover choice.

title class-attribute instance-attribute

title: str | None = None

author class-attribute instance-attribute

author: str | None = None

cover class-attribute instance-attribute

cover: Literal['source'] | Path | None = 'source'

The tuning read model

Pipeline.script() returns a Script: one ScriptRow per grid unit, carrying the effective speaker, its provenance, and the silence that follows it, without resolving voices or calling a model.

kenkui.Script dataclass

A chapter-lazy read model whose iteration yields rows in source order.

script[path] returns a single row (a partial path must be unambiguous). at(pattern) yields matching rows. This is mapping-like, rather than a Mapping subclass, because iteration yields rows instead of keys.

Construction inspects source text without resolution or model calls. Grids are built only on access, once per chapter. materialized and warnings are cheap immutable snapshots; neither triggers more work.

materialized property

materialized: tuple[str, ...]

Return cached chapter IDs in their first-access order.

warnings property

warnings: tuple[ValidationIssue, ...]

Return drift findings discovered so far, in declaration order.

at

at(
    pattern: Pattern | Mapping[str, object],
) -> Iterator[ScriptRow]

Yield matches while materializing only chapters the pattern selects.

kenkui.ScriptRow dataclass

One canonical unit and the effective speaker and following silence.

Whitespace-only units retain their text and path but carry zero silence; their gaps settle onto the preceding speech-bearing row, as in planning.

path instance-attribute

path: Path

text instance-attribute

text: str

character instance-attribute

character: str | None

provenance instance-attribute

provenance: Provenance

rule_index instance-attribute

rule_index: int | None

silence_after_ms instance-attribute

silence_after_ms: int

is_dialogue instance-attribute

is_dialogue: bool

is_emphasised instance-attribute

is_emphasised: bool

Inspection and character review

BookInspection.roster is populated at a character-review checkpoint; BookInspection.casting is populated after full resolution. They are absent before the corresponding work is done. Span offsets refer to the normalized chapter text in the same inspection. Edit frozen records using dataclasses.replace() and pass a reviewed roster to with_characters().

kenkui.BookInspection dataclass

Source information plus casting when this pipeline has been resolved.

metadata instance-attribute

metadata: BookMetadata

chapters instance-attribute

chapters: tuple[ChapterInspection, ...]

casting class-attribute instance-attribute

casting: CastingInspection | None = None

roster class-attribute instance-attribute

roster: CharacterRoster | None = None

kenkui.BookMetadata dataclass

Bibliographic metadata materialized from a readable source.

title class-attribute instance-attribute

title: str | None = None

author class-attribute instance-attribute

author: str | None = None

cover_available class-attribute instance-attribute

cover_available: bool = False

kenkui.ChapterInspection dataclass

Stable chapter information available without rendering.

id instance-attribute

id: str

index instance-attribute

index: int

title instance-attribute

title: str

speech_characters instance-attribute

speech_characters: int | None

text class-attribute instance-attribute

text: str = ''

headings class-attribute instance-attribute

headings: tuple[str, ...] = ()

emphasis class-attribute instance-attribute

emphasis: tuple[tuple[int, int], ...] = ()

heading_ranges class-attribute instance-attribute

heading_ranges: tuple[tuple[int, int], ...] = ()

kenkui.CharacterRoster dataclass

Discovered or reviewed characters before quote attribution.

Chapter IDs describe where a name was found, when known. Speech counts are measured later, after attribution. narrator_id identifies a first-person narrating character, independently of the narrator voice.

characters instance-attribute

characters: tuple[CharacterProfile, ...]

narrator_id class-attribute instance-attribute

narrator_id: str | None = None

kenkui.CharacterProfile dataclass

One speaking character, measured in normalized speech characters.

spoken_characters is the prominence weight throughout, matching the unit chunking, progress, and billing already use.

id instance-attribute

id: str

display_name instance-attribute

display_name: str

gender instance-attribute

gender: str | None

spoken_characters instance-attribute

spoken_characters: int

chapter_ids instance-attribute

chapter_ids: tuple[str, ...]

aliases class-attribute instance-attribute

aliases: tuple[str, ...] = ()

kenkui.CastingInspection dataclass

A resolved roster, cast, and attribution without rendering resources.

Assignments are immutable (character_id, voice_id) pairs. Collisions describe expected voice sharing within chapters when voices are limited. Span offsets refer to the normalized chapter text in the same inspection.

narrator_voice_id instance-attribute

narrator_voice_id: str

unknown_voice_id instance-attribute

unknown_voice_id: str

characters class-attribute instance-attribute

characters: tuple[CharacterProfile, ...] = ()

assignments class-attribute instance-attribute

assignments: tuple[tuple[str, str], ...] = ()

spans class-attribute instance-attribute

spans: tuple[SpeakerSpan, ...] = ()

collisions class-attribute instance-attribute

collisions: tuple[Collision, ...] = ()

kenkui.SpeakerSpan dataclass

One contiguous run of a chapter's normalized text with a single speaker.

chapter_id instance-attribute

chapter_id: str

start instance-attribute

start: int

end instance-attribute

end: int

character_id instance-attribute

character_id: str | None

kenkui.Collision dataclass

Two characters sharing one voice inside one chapter.

chapter_id instance-attribute

chapter_id: str

first instance-attribute

first: str

second instance-attribute

second: str

voice_id instance-attribute

voice_id: str

Voices and provisioning

list_voices() reads catalog/local state. Registration, loading, unloading, and removal modify local voice state; loading may download assets or compile a supplied WAV. Rendering never provisions voices implicitly. See provisioning for state transitions and models and voices for asset requirements.

kenkui.Voice dataclass

Reusable voice identity, content, compatibility, and rights metadata.

id instance-attribute

id: str

name instance-attribute

name: str

enabled instance-attribute

enabled: bool

provenance instance-attribute

provenance: str | None

license_id instance-attribute

license_id: str | None

commercial_use_allowed instance-attribute

commercial_use_allowed: bool | None

language class-attribute instance-attribute

language: str | None = None

content_fingerprint class-attribute instance-attribute

content_fingerprint: str | None = None

compatible_model_revisions class-attribute instance-attribute

compatible_model_revisions: tuple[str, ...] = ()

variety class-attribute instance-attribute

variety: VoiceVariety = 'built-in'

state class-attribute instance-attribute

state: VoiceState = 'registered'

asset_bytes class-attribute instance-attribute

asset_bytes: int | None = None

engine class-attribute instance-attribute

engine: Engine | None = None

perceived_gender class-attribute instance-attribute

perceived_gender: PerceivedGender = None

voice_rights class-attribute instance-attribute

voice_rights: str | None = None

kenkui.Engine dataclass

A provisioned per-language synthesis engine.

id instance-attribute

id: str

language instance-attribute

language: str

model_revision instance-attribute

model_revision: str

cloning_capable instance-attribute

cloning_capable: bool

size_bytes instance-attribute

size_bytes: int

kenkui.list_voices

list_voices(
    *, manifest: Path | None = None
) -> tuple[Voice, ...]

Return every known voice, unioning the catalog with the manifest.

No network and no hashing: asset presence is a stat. This is the only enumeration primitive, so multi-voice work composes over it.

kenkui.add_voice

add_voice(
    path: str | PathLike[str],
    *,
    voice_id: str,
    name: str,
    language: str,
    provenance: str,
    license_id: str,
    commercial_use_allowed: bool,
    voice_rights: str,
    perceived_gender: PerceivedGender = None,
    manifest: Path | None = None,
) -> Voice

Register a local WAV or safetensors voice with explicit rights metadata.

Cheap and offline: hashes the source, records the rights fields verbatim, and writes a registered entry. Call load_voice to make it renderable.

kenkui.load_voice

load_voice(
    voice_id: str, *, manifest: Path | None = None
) -> Voice

Make one voice renderable, downloading or compiling only if required.

Idempotent: a voice whose asset is present and hash-verified performs no network access at all.

kenkui.unload_voice

unload_voice(
    voice_id: str, *, manifest: Path | None = None
) -> Voice

Delete a voice's asset, keep its rights metadata, and prune its engine.

kenkui.remove_voice

remove_voice(
    voice_id: str, *, manifest: Path | None = None
) -> None

Delete a voice entry entirely, including hand-entered rights metadata.

Equivalent to unload_voice for a built-in, whose catalog registration cannot be deleted.

Pronunciation lexicons

builtin_lexicon() returns a fresh dictionary. read_lexicon() reads and validates a local JSON file. Pass either result to Pipeline.pronounce(); reading a lexicon does not change a pipeline by itself.

kenkui.builtin_lexicon

builtin_lexicon() -> dict[str, str]

Return a copy of the pronunciation table Kenkui ships.

A copy rather than the cached table itself, so a caller extending it cannot corrupt what every later pipeline in the process reads.

kenkui.read_lexicon

read_lexicon(path: str | PathLike[str]) -> dict[str, str]

Read a pronunciation table from a JSON file, validated as a literal is.

Accepts a plain object of word to replacement, or the shape the shipped table uses. The result is an ordinary dict: merge it, edit it, or pass it straight to Pipeline.pronounce.

Stored casts and series

These functions use the managed casting database by default. Their optional path argument selects a database file, not a voice manifest. list_castings() returns immutable records with cast_id, attribution_id, method, narrator_voice_id, unknown_voice_id, and assignments fields. Each assignment is a (character_id, voice_id, pinned) tuple. The record's implementation class is private; use its fields and the public removal verbs. Removing attribution cascades to its stored casts and requires new attribution work later. Removing a series forgets its continuity pins.

kenkui.list_castings

list_castings(
    path: Path | None = None,
) -> tuple[CastRecord, ...]

Return every stored cast. Filtering composes over this.

kenkui.remove_casting

remove_casting(
    cast_id: str, path: Path | None = None
) -> None

Discard one cast. The pure solver rebuilds it for free.

kenkui.remove_attribution

remove_attribution(
    attribution_id: str, path: Path | None = None
) -> None

Discard one attribution and every cast beneath it.

Distinct from remove_casting because the costs differ by orders of magnitude: rebuilding a cast is free, re-deriving attribution is a fresh model pass. One verb doing both would hide that.

kenkui.list_series

list_series(
    path: Path | None = None,
) -> tuple[SeriesRecord, ...]

Return every stored series. Filtering composes over this.

kenkui.remove_series

remove_series(
    series_id: str, path: Path | None = None
) -> None

Drop a series and its pins. The next volume is cast fresh.

kenkui.SeriesRecord dataclass

A series' cast, ordered by accumulated speech.

Keyed on the series and the character, never on an attribution: a volume re-read by a different model must not re-cast the series.

series_id instance-attribute

series_id: str

narrator_voice_id instance-attribute

narrator_voice_id: str

characters instance-attribute

characters: tuple[SeriesCharacter, ...]

kenkui.SeriesCharacter dataclass

One person across a series, and the voice they keep.

canonical_id instance-attribute

canonical_id: str

display_name instance-attribute

display_name: str

gender instance-attribute

gender: str | None

voice_id instance-attribute

voice_id: str

spoken_characters instance-attribute

spoken_characters: int

aliases instance-attribute

aliases: tuple[str, ...]

contributions class-attribute instance-attribute

contributions: tuple[tuple[str, int], ...] = ()

Validation and execution results

validate() reports issues as values; execution methods raise the relevant exception if validation fails. A successful Result.output is the published path. Its statistics describe semantic work even when synthesis uses cached audio; they do not measure provider charges.

kenkui.ValidationIssue dataclass

One inexpensive pipeline validation finding.

code instance-attribute

code: ErrorCode

message instance-attribute

message: str

severity class-attribute instance-attribute

severity: Literal['error', 'warning'] = 'error'

kenkui.ValidationResult dataclass

All inexpensive validation findings for a pipeline.

issues class-attribute instance-attribute

issues: tuple[ValidationIssue, ...] = ()

errors property

errors: tuple[ValidationIssue, ...]

Issues severe enough to block rendering.

warnings property

warnings: tuple[ValidationIssue, ...]

Issues worth surfacing but not severe enough to block rendering.

is_valid property

is_valid: bool

Whether validation found no errors.

kenkui.Result dataclass

A published artifact and its execution statistics.

output instance-attribute

output: Path

stats instance-attribute

stats: ExecutionStats

kenkui.ExecutionStats dataclass

Stable technical statistics for one successful execution.

normalized_speech_characters instance-attribute

normalized_speech_characters: int

synthesized_characters instance-attribute

synthesized_characters: int

synthesized_segments instance-attribute

synthesized_segments: int

rendered_chapters instance-attribute

rendered_chapters: int

duration_ms instance-attribute

duration_ms: int

Events and cancellation

Callbacks receive immutable ExecutionEvent values. Sequence numbers describe delivery order. See events for the pre-publication callback failure boundary and best-effort terminal events. Cancellation is cooperative; a running model call cannot be forcibly recalled.

kenkui.ExecutionEvent module-attribute

ExecutionEvent: TypeAlias = (
    CastResolved
    | Started
    | StageStarted
    | StageProgress
    | StageCompleted
    | Warning
    | Completed
)

kenkui.Started dataclass

An execution run started.

sequence instance-attribute

sequence: int

kenkui.StageStarted dataclass

A named execution stage started.

sequence instance-attribute

sequence: int

stage instance-attribute

stage: str

kenkui.StageProgress dataclass

A named stage made measurable progress.

sequence instance-attribute

sequence: int

stage instance-attribute

stage: str

completed instance-attribute

completed: int

total instance-attribute

total: int

chapter_id class-attribute instance-attribute

chapter_id: str | None = None

kenkui.StageCompleted dataclass

A named stage completed.

sequence instance-attribute

sequence: int

stage instance-attribute

stage: str

elapsed_ms class-attribute instance-attribute

elapsed_ms: int | None = None

kenkui.CastResolved dataclass

The character-to-voice assignment resolved for this run.

Emitted before any worker exists, so a caller may inspect the cast and cancel without paying for a render. Single-voice runs emit it with no assignments, keeping one event shape for both castings.

sequence instance-attribute

sequence: int

stage instance-attribute

stage: str

narrator_voice_id instance-attribute

narrator_voice_id: str

unknown_voice_id instance-attribute

unknown_voice_id: str

assignments class-attribute instance-attribute

assignments: tuple[tuple[str, str], ...] = ()

kenkui.Warning dataclass

A recoverable condition was observed.

sequence instance-attribute

sequence: int

stage instance-attribute

stage: str

code instance-attribute

code: str

message instance-attribute

message: str

chapter_id class-attribute instance-attribute

chapter_id: str | None = None

kenkui.Completed dataclass

An execution run completed successfully.

sequence instance-attribute

sequence: int

elapsed_ms class-attribute instance-attribute

elapsed_ms: int | None = None

kenkui.CancellationToken

Thread-safe cooperative cancellation state shared with one execution.

cancelled property

cancelled: bool

Whether cancellation has been requested.

cancel

cancel() -> None

Request cancellation idempotently.

raise_if_cancelled

raise_if_cancelled() -> None

Raise the stable public cancellation failure when requested.

Errors

Catch KenkuiError or a narrower subclass and branch on error.code, an ErrorCode member. Human-readable messages are not a stable matching interface. See troubleshooting for recovery actions.

kenkui.ErrorCode

Bases: StrEnum

Machine-readable public failure codes.

UNSUPPORTED_FORMAT class-attribute instance-attribute

UNSUPPORTED_FORMAT = 'unsupported_format'

SOURCE_NOT_FOUND class-attribute instance-attribute

SOURCE_NOT_FOUND = 'source_not_found'

SOURCE_NOT_READABLE class-attribute instance-attribute

SOURCE_NOT_READABLE = 'source_not_readable'

SOURCE_CHANGED class-attribute instance-attribute

SOURCE_CHANGED = 'source_changed'

DUPLICATE_OPERATION class-attribute instance-attribute

DUPLICATE_OPERATION = 'duplicate_operation'

INVALID_OPERATION_ORDER class-attribute instance-attribute

INVALID_OPERATION_ORDER = 'invalid_operation_order'

EMPTY_SELECTION class-attribute instance-attribute

EMPTY_SELECTION = 'empty_selection'

DUPLICATE_CHAPTER_ID class-attribute instance-attribute

DUPLICATE_CHAPTER_ID = 'duplicate_chapter_id'

INVALID_VOICE class-attribute instance-attribute

INVALID_VOICE = 'invalid_voice'

VOICE_REQUIRED class-attribute instance-attribute

VOICE_REQUIRED = 'voice_required'

VOICE_UNRESOLVED class-attribute instance-attribute

VOICE_UNRESOLVED = 'voice_unresolved'

VOICE_DISABLED class-attribute instance-attribute

VOICE_DISABLED = 'voice_disabled'

VOICE_INCOMPATIBLE class-attribute instance-attribute

VOICE_INCOMPATIBLE = 'voice_incompatible'

VOICE_PROVENANCE_REQUIRED class-attribute instance-attribute

VOICE_PROVENANCE_REQUIRED = 'voice_provenance_required'

VOICE_NOT_PROVISIONED class-attribute instance-attribute

VOICE_NOT_PROVISIONED = 'voice_not_provisioned'

VOICE_UNKNOWN class-attribute instance-attribute

VOICE_UNKNOWN = 'voice_unknown'

ENGINE_NOT_CLONING_CAPABLE class-attribute instance-attribute

ENGINE_NOT_CLONING_CAPABLE = 'engine_not_cloning_capable'

VOICE_VARIETY_INVALID class-attribute instance-attribute

VOICE_VARIETY_INVALID = 'voice_variety_invalid'

CASTING_METHOD_UNKNOWN class-attribute instance-attribute

CASTING_METHOD_UNKNOWN = 'casting_method_unknown'

CAST_POOL_EMPTY class-attribute instance-attribute

CAST_POOL_EMPTY = 'cast_pool_empty'

CAST_LANGUAGE_MIXED class-attribute instance-attribute

CAST_LANGUAGE_MIXED = 'cast_language_mixed'

CHARACTER_UNKNOWN class-attribute instance-attribute

CHARACTER_UNKNOWN = 'character_unknown'

CAST_UNATTRIBUTED class-attribute instance-attribute

CAST_UNATTRIBUTED = 'cast_unattributed'

INVALID_SERIES class-attribute instance-attribute

INVALID_SERIES = 'invalid_series'

INVALID_MODEL class-attribute instance-attribute

INVALID_MODEL = 'invalid_model'

INVALID_ROSTER class-attribute instance-attribute

INVALID_ROSTER = 'invalid_roster'

ROSTER_UNAVAILABLE class-attribute instance-attribute

ROSTER_UNAVAILABLE = 'roster_unavailable'

INVALID_RESOLUTION_STAGE class-attribute instance-attribute

INVALID_RESOLUTION_STAGE = 'invalid_resolution_stage'

MODEL_CALL_FAILED class-attribute instance-attribute

MODEL_CALL_FAILED = 'model_call_failed'

MODEL_RESPONSE_INVALID class-attribute instance-attribute

MODEL_RESPONSE_INVALID = 'model_response_invalid'

SPACY_PACKAGE_MISSING class-attribute instance-attribute

SPACY_PACKAGE_MISSING = 'spacy_package_missing'

SPACY_PIPELINE_MISSING class-attribute instance-attribute

SPACY_PIPELINE_MISSING = 'spacy_pipeline_missing'

ATTRIBUTION_UNAVAILABLE class-attribute instance-attribute

ATTRIBUTION_UNAVAILABLE = 'attribution_unavailable'

TTS_REQUIRED class-attribute instance-attribute

TTS_REQUIRED = 'tts_required'

EMPTY_SPEECH class-attribute instance-attribute

EMPTY_SPEECH = 'empty_speech'

INVALID_SOURCE_HASH class-attribute instance-attribute

INVALID_SOURCE_HASH = 'invalid_source_hash'

INVALID_MODEL_REVISION class-attribute instance-attribute

INVALID_MODEL_REVISION = 'invalid_model_revision'

INVALID_METADATA class-attribute instance-attribute

INVALID_METADATA = 'invalid_metadata'

INVALID_PRONUNCIATION class-attribute instance-attribute

INVALID_PRONUNCIATION = 'invalid_pronunciation'

INVALID_PAUSE class-attribute instance-attribute

INVALID_PAUSE = 'invalid_pause'

INVALID_WORKERS class-attribute instance-attribute

INVALID_WORKERS = 'invalid_workers'

INVALID_OUTPUT class-attribute instance-attribute

INVALID_OUTPUT = 'invalid_output'

OUTPUT_EXISTS class-attribute instance-attribute

OUTPUT_EXISTS = 'output_exists'

MALFORMED_EPUB class-attribute instance-attribute

MALFORMED_EPUB = 'malformed_epub'

UNSAFE_ARCHIVE_PATH class-attribute instance-attribute

UNSAFE_ARCHIVE_PATH = 'unsafe_archive_path'

ARCHIVE_LIMIT class-attribute instance-attribute

ARCHIVE_LIMIT = 'archive_limit'

CHAPTER_NOT_FOUND class-attribute instance-attribute

CHAPTER_NOT_FOUND = 'chapter_not_found'

EMPTY_CHAPTER class-attribute instance-attribute

EMPTY_CHAPTER = 'empty_chapter'

REVERSED_CHAPTER_RANGE class-attribute instance-attribute

REVERSED_CHAPTER_RANGE = 'reversed_chapter_range'

RENDERER_UNAVAILABLE class-attribute instance-attribute

RENDERER_UNAVAILABLE = 'renderer_unavailable'

POCKET_PACKAGE_MISSING class-attribute instance-attribute

POCKET_PACKAGE_MISSING = 'pocket_package_missing'

POCKET_VERSION_UNSUPPORTED class-attribute instance-attribute

POCKET_VERSION_UNSUPPORTED = 'pocket_version_unsupported'

POCKET_MODEL_INVALID class-attribute instance-attribute

POCKET_MODEL_INVALID = 'pocket_model_invalid'

POCKET_VOICE_INVALID class-attribute instance-attribute

POCKET_VOICE_INVALID = 'pocket_voice_invalid'

POCKET_MODEL_LOAD_FAILED class-attribute instance-attribute

POCKET_MODEL_LOAD_FAILED = 'pocket_model_load_failed'

POCKET_VOICE_LOAD_FAILED class-attribute instance-attribute

POCKET_VOICE_LOAD_FAILED = 'pocket_voice_load_failed'

POCKET_INFERENCE_FAILED class-attribute instance-attribute

POCKET_INFERENCE_FAILED = 'pocket_inference_failed'

FFMPEG_NOT_FOUND class-attribute instance-attribute

FFMPEG_NOT_FOUND = 'ffmpeg_not_found'

FFPROBE_NOT_FOUND class-attribute instance-attribute

FFPROBE_NOT_FOUND = 'ffprobe_not_found'

FFMPEG_UNSUPPORTED class-attribute instance-attribute

FFMPEG_UNSUPPORTED = 'ffmpeg_unsupported'

FFPROBE_UNSUPPORTED class-attribute instance-attribute

FFPROBE_UNSUPPORTED = 'ffprobe_unsupported'

SYNTHESIS_FAILED class-attribute instance-attribute

SYNTHESIS_FAILED = 'synthesis_failed'

INVALID_AUDIO class-attribute instance-attribute

INVALID_AUDIO = 'invalid_audio'

ASSEMBLY_FAILED class-attribute instance-attribute

ASSEMBLY_FAILED = 'assembly_failed'

ENCODING_FAILED class-attribute instance-attribute

ENCODING_FAILED = 'encoding_failed'

COVER_FAILED class-attribute instance-attribute

COVER_FAILED = 'cover_failed'

COVER_INVALID class-attribute instance-attribute

COVER_INVALID = 'cover_invalid'

PROBE_FAILED class-attribute instance-attribute

PROBE_FAILED = 'probe_failed'

DECODE_FAILED class-attribute instance-attribute

DECODE_FAILED = 'decode_failed'

INVALID_ARTIFACT class-attribute instance-attribute

INVALID_ARTIFACT = 'invalid_artifact'

PUBLICATION_FAILED class-attribute instance-attribute

PUBLICATION_FAILED = 'publication_failed'

CALLBACK_FAILED class-attribute instance-attribute

CALLBACK_FAILED = 'callback_failed'

CANCELLED class-attribute instance-attribute

CANCELLED = 'cancelled'

SERIES_VOICE_MISSING class-attribute instance-attribute

SERIES_VOICE_MISSING = 'series_voice_missing'

SERIES_NARRATOR_CHANGED class-attribute instance-attribute

SERIES_NARRATOR_CHANGED = 'series_narrator_changed'

INVALID_PATH class-attribute instance-attribute

INVALID_PATH = 'invalid_path'

INVALID_PATTERN class-attribute instance-attribute

INVALID_PATTERN = 'invalid_pattern'

INVALID_SIDECAR class-attribute instance-attribute

INVALID_SIDECAR = 'invalid_sidecar'

RULE_OVERLAP class-attribute instance-attribute

RULE_OVERLAP = 'rule_overlap'

ANCHOR_DIGEST_MISMATCH class-attribute instance-attribute

ANCHOR_DIGEST_MISMATCH = 'anchor_digest_mismatch'

PATTERN_MATCH_COUNT_DRIFT class-attribute instance-attribute

PATTERN_MATCH_COUNT_DRIFT = 'pattern_match_count_drift'

kenkui.KenkuiError

Bases: Exception

Base class for sanitized public failures.

code instance-attribute

code: ErrorCode

message instance-attribute

message: str | None

kenkui.SourceError

Bases: KenkuiError

The source could not be accepted or inspected.

kenkui.ValidationError

Bases: KenkuiError

Pipeline intent or argument validation failed.

kenkui.VoiceError

Bases: KenkuiError

Voice resolution failed.

kenkui.ModelError

Bases: KenkuiError

Synthesis model setup failed.

kenkui.RenderError

Bases: KenkuiError

Speech rendering failed.

kenkui.EncodingError

Bases: KenkuiError

Artifact encoding or publication failed.

kenkui.CancelledError

Bases: KenkuiError

Execution stopped after cooperative cancellation.