From 3633e3cbddd6b9909caf9f99c1ca72c9f7af6199 Mon Sep 17 00:00:00 2001 From: Google Team Member Date: Tue, 6 Oct 2026 16:06:33 -0700 Subject: [PATCH] fix(cli): keep punctuation-only speaker turns in `tts --multi-speaker` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bounding `lineMarkerStart` at the previous label also made "only whitespace since the previous label" count as the start of a line, so a punctuation-only turn after a label on the same line was read as the next label's list marker and dropped with it (`Alice: ... Bob: Yes?`, `Alice: β€” Bob: Sorry, go on.`, and `Alice: ?! Bob: What?` each sent only Bob's turn). Judge the line start on the whole text (`text[:floor+len(rest)]`) for every marker. The scan still stops at the previous label (`floor`), so emoji names (`πŸ˜€: πŸ€–:`) do not overlap or panic. PiperOrigin-RevId: 994686637 --- internal/cli/custom/custom_test.go | 8 +++++--- internal/cli/custom/tts.go | 16 +++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/cli/custom/custom_test.go b/internal/cli/custom/custom_test.go index 1c30853..992cb6d 100644 --- a/internal/cli/custom/custom_test.go +++ b/internal/cli/custom/custom_test.go @@ -516,11 +516,13 @@ func TestSpeakerTurns(t *testing.T) { {"Alice: 42. Bob: That is correct.", []string{"Alice", "Bob"}, []speakerTurn{turn("Alice", "42."), turn("Bob", "That is correct.")}}, // A combining mark is part of the word: neither "\u0936\u094d\u0930\u0940\u0930\u093e\u092e:" nor "\u0930\u093e\u092e\u0940:" holds a label for "\u0930\u093e\u092e". {"Alice: \u0936\u094d\u0930\u0940\u0930\u093e\u092e: \u0930\u093e\u092e\u0940: hi. \u0930\u093e\u092e: yo.", []string{"Alice", "\u0930\u093e\u092e"}, []speakerTurn{turn("Alice", "\u0936\u094d\u0930\u0940\u0930\u093e\u092e: \u0930\u093e\u092e\u0940: hi."), turn("\u0930\u093e\u092e", "yo.")}}, - // Marker scanning stops at the previous label: names made of marker runes - // stay labels, and a marker right after an empty label goes with the next. + // Marker scanning stops at the previous label, so names made of marker + // runes stay labels; a punctuation-only turn after a label is kept. {"\U0001F600: \U0001F916: hi.", []string{"\U0001F600", "\U0001F916"}, []speakerTurn{turn("\U0001F916", "hi.")}}, {"\U0001F600: hi.\n\U0001F916: yo.", []string{"\U0001F600", "\U0001F916"}, []speakerTurn{turn("\U0001F600", "hi."), turn("\U0001F916", "yo.")}}, - {"Alice: - Bob: yo.", []string{"Alice", "Bob"}, []speakerTurn{bob}}, + {"Alice: - Bob: yo.", []string{"Alice", "Bob"}, []speakerTurn{turn("Alice", "-"), bob}}, + {"Alice: ... Bob: yo.", []string{"Alice", "Bob"}, []speakerTurn{turn("Alice", "..."), bob}}, + {"Alice: \u2014 Bob: yo.", []string{"Alice", "Bob"}, []speakerTurn{turn("Alice", "\u2014"), bob}}, // "Annual:" is not a label for "Ann"; an undeclared "Carol:" stays in the turn. {"Ann: Annual: report. Carol: yes. Bo: ok.", []string{"Ann", "Bo"}, []speakerTurn{turn("Ann", "Annual: report. Carol: yes."), turn("Bo", "ok.")}}, // The longer name wins over a declared prefix of it. diff --git a/internal/cli/custom/tts.go b/internal/cli/custom/tts.go index fff13ea..08c51bb 100644 --- a/internal/cli/custom/tts.go +++ b/internal/cli/custom/tts.go @@ -264,23 +264,21 @@ func isMarkerRune(r rune) bool { // the label at start begins ("- Alice:", "> Alice:", "β€œAlice:", "1. Alice:"), // so the marker goes with the label instead of the previous turn or the text // before the first label. Without one, it returns start: punctuation or a -// number after words on the same line ("hi. - Bob:", "Alice: 42. Bob:") stays -// in the previous turn. The scan stops at floor, the end of the previous -// label, so a name made of marker runes ("πŸ˜€: πŸ€–:") is never claimed as the -// next label's marker. +// number after words or a label on the same line ("hi. - Bob:", +// "Alice: 42. Bob:", "Alice: ... Bob:") stays in the previous turn. The scan +// stops at floor, the end of the previous label, so a name made of marker +// runes ("πŸ˜€: πŸ€–:") is never claimed as the next label's marker; the line +// start is still judged on the whole text. func lineMarkerStart(text string, floor, start int) int { before := strings.TrimRightFunc(text[floor:start], isHorizontalSpace) rest := strings.TrimRightFunc(before, isMarkerRune) if rest == before { return start } - check := rest if marker := before[len(rest):]; marker == "." || marker == ")" { - if digits := strings.TrimRightFunc(rest, unicode.IsDigit); len(digits) < len(rest) { - rest, check = digits, text[:floor+len(digits)] - } + rest = strings.TrimRightFunc(rest, unicode.IsDigit) } - if line := strings.TrimRightFunc(check, isHorizontalSpace); line != "" { + if line := strings.TrimRightFunc(text[:floor+len(rest)], isHorizontalSpace); line != "" { if last, _ := utf8.DecodeLastRuneInString(line); !isLineBreak(last) { return start }