diff --git a/packages/text-to-speech/src/htmlToSsml.ts b/packages/text-to-speech/src/htmlToSsml.ts index a64a370bf..2cfad13c1 100644 --- a/packages/text-to-speech/src/htmlToSsml.ts +++ b/packages/text-to-speech/src/htmlToSsml.ts @@ -273,13 +273,12 @@ const textToUtterances = ({ let text = textItems.join('') if (!isHtml) { // for title - const wordCount = tokenizer.tokenize(text).length return [ { idx, text, wordOffset, - wordCount, + wordCount: tokenizer.tokenize(text).length, voice, }, ] @@ -298,21 +297,39 @@ const textToUtterances = ({ text = parseHTML(text).document.documentElement.textContent ?? text console.info('Converted HTML to text:', text) } - // split text into chunks of 256 characters to stream faster without breaking on words - const textChunks = text.match(/.{1,256}(?= |$)/g) - if (textChunks) { - for (const chunk of textChunks) { - const wordCount = tokenizer.tokenize(chunk).length - utterances.push({ - idx, - text: chunk, - wordOffset, - wordCount, - voice, - }) - wordOffset += wordCount + // if we hit 256, look back for first ending sentence within 80 chars + const MAX_CHARS = 256 + const MAX_LOOKBACK = 80 + while (text.length > MAX_CHARS) { + let end = MAX_CHARS - MAX_LOOKBACK - 1 + while (end < text.length && !text[end].match(/[.!?]/)) { + end++ } + + const utterance = text.substring(0, end + 1) + const wordCount = tokenizer.tokenize(utterance).length + utterances.push({ + idx, + text: utterance, + wordOffset, + wordCount, + voice, + }) + text = text.substring(end + 1) + wordOffset += wordCount } + + if (text.length > 0) { + const wordCount = tokenizer.tokenize(text).length + utterances.push({ + idx, + text, + wordOffset, + wordCount, + voice, + }) + } + return utterances } diff --git a/packages/text-to-speech/src/textToSpeech.ts b/packages/text-to-speech/src/textToSpeech.ts index c7c35fed3..2c72a4852 100644 --- a/packages/text-to-speech/src/textToSpeech.ts +++ b/packages/text-to-speech/src/textToSpeech.ts @@ -8,6 +8,7 @@ import { SpeechSynthesizer, } from 'microsoft-cognitiveservices-speech-sdk' import { endSsml, htmlToSsmlItems, ssmlItemText, startSsml } from './htmlToSsml' +import * as _ from 'underscore' export interface TextToSpeechInput { text: string @@ -139,7 +140,8 @@ export const synthesizeTextToSpeech = async ( } // for ssml const startSsmlTag = startSsml(ssmlOptions) - const ssml = `${startSsmlTag}${input.text}${endSsml()}` + const text = _.escape(input.text) + const ssml = `${startSsmlTag}${text}${endSsml()}` // set the text offset to be the end of SSML start tag wordOffset -= startSsmlTag.length const result = await speakSsmlAsyncPromise(ssml) diff --git a/packages/text-to-speech/test/fixtures/large.html b/packages/text-to-speech/test/fixtures/li.html similarity index 100% rename from packages/text-to-speech/test/fixtures/large.html rename to packages/text-to-speech/test/fixtures/li.html diff --git a/packages/text-to-speech/test/htmlToSsml.test.ts b/packages/text-to-speech/test/htmlToSsml.test.ts index 5e3270ced..3d2350378 100644 --- a/packages/text-to-speech/test/htmlToSsml.test.ts +++ b/packages/text-to-speech/test/htmlToSsml.test.ts @@ -227,7 +227,7 @@ describe('htmlToSpeechFile', () => { describe('convert HTML to Speech file', () => { it('converts each