Skip to content

fix(tts): don't let a self-closing prosody marker swallow the next expr tag#6449

Open
chuenchen309 wants to merge 1 commit into
livekit:mainfrom
chuenchen309:fix/cartesia-self-closing-prosody-swallows-spell
Open

fix(tts): don't let a self-closing prosody marker swallow the next expr tag#6449
chuenchen309 wants to merge 1 commit into
livekit:mainfrom
chuenchen309:fix/cartesia-self-closing-prosody-swallows-spell

Conversation

@chuenchen309

Copy link
Copy Markdown

What

_EXPR_WRAP_RE (the regex that matches a wrapping <expr type="prosody"|"spell">...</expr> marker) matched any <expr ...type="prosody"|"spell"...> up to its first > — which also matches the closing /> of a self-closing point control like <expr type="prosody" label="slow"/>. When a self-closing prosody marker appears before a later wrapping marker (e.g. a spell tag), the lazy inner group swallows everything up to the next </expr>, merging the two markers and dropping the second marker's own conversion.

Repro (before fix):

text = 'Say it slow: <expr type="prosody" label="slow"/> and now spell it: <expr type="spell">A7X9</expr> done.'
convert_markup("cartesia", text)
# 'Say it slow: <speed ratio="0.85"/> and now spell it: A7X9 done.'
#                                                        ^^^^ spell wrapping lost — should be <spell>A7X9</spell>

Fix

Added a negative lookbehind ((?<!/)) before the opening tag's closing > so _EXPR_WRAP_RE never matches a self-closing tag as a wrap opener — those are left for _EXPR_SELF_RE to handle on its own pass, as intended.

Testing

  • New regression test test_convert_expr_cartesia_self_closing_prosody_before_spell — red before the fix (asserted, see commit), green after.
  • Full tests/test_expr_markup.py (42 tests) and tests/test_tokenizer_xml_markup.py (38 tests) pass.
  • ruff check / ruff format --check clean on both changed files.

AI disclosure

Investigated and implemented with Claude Code (Claude Opus 4.8); I reviewed the diff, ran the repro and full test suite myself before opening this PR.

…pr tag

_EXPR_WRAP_RE matched any <expr ...type="prosody"|"spell"...> up to its next
'>', which also matches a self-closing point control's own "/>". The lazy
inner group then consumed everything up to the *next* </expr>, merging a
following wrapping marker (e.g. spell) into its span and dropping that
marker's own conversion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chuenchen309
chuenchen309 requested a review from a team as a code owner July 15, 2026 23:54

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants