diff --git a/packages/api/src/routers/article_router.ts b/packages/api/src/routers/article_router.ts index 89f164838..00c5d94ab 100644 --- a/packages/api/src/routers/article_router.ts +++ b/packages/api/src/routers/article_router.ts @@ -29,7 +29,7 @@ interface SpeechInput { secondaryVoice?: string priority?: 'low' | 'high' } -const outputFormats = ['mp3', 'speech-marks', 'speech-file'] +const outputFormats = ['mp3', 'speech-marks', 'speech'] const logger = buildLogger('app.dispatch') export function articleRouter() { @@ -102,7 +102,7 @@ export function articleRouter() { }, }) - if (outputFormat === 'speech-file') { + if (outputFormat === 'speech') { const page = await getPageById(articleId) if (!page) { return res.status(404).send('Page not found') @@ -112,7 +112,7 @@ export function articleRouter() { secondaryVoice: secondaryVoice, language: page.language, }) - return res.send(speechFile) + return res.send({ ...speechFile, pageId: articleId }) } const existingSpeech = await getRepository(Speech).findOne({ diff --git a/packages/api/src/textToSpeech.d.ts b/packages/api/src/textToSpeech.d.ts index 051b9be8f..45e43969d 100644 --- a/packages/api/src/textToSpeech.d.ts +++ b/packages/api/src/textToSpeech.d.ts @@ -7,7 +7,7 @@ declare module '@omnivore/text-to-speech-handler' { export interface SSMLOptions { primaryVoice?: string secondaryVoice?: string - rate?: number + rate?: string language?: string } @@ -21,7 +21,6 @@ declare module '@omnivore/text-to-speech-handler' { export interface SpeechFile { wordCount: number - averageWPM: number language: string defaultVoice: string utterances: Utterance[] diff --git a/packages/text-to-speech/src/htmlToSsml.ts b/packages/text-to-speech/src/htmlToSsml.ts index e1008001a..5988453e6 100644 --- a/packages/text-to-speech/src/htmlToSsml.ts +++ b/packages/text-to-speech/src/htmlToSsml.ts @@ -16,7 +16,6 @@ export interface Utterance { export interface SpeechFile { wordCount: number - averageWPM: number language: string defaultVoice: string utterances: Utterance[] @@ -33,14 +32,13 @@ export type SSMLItem = { export type SSMLOptions = { primaryVoice?: string secondaryVoice?: string - rate?: number + rate?: string language?: string } -const WORDS_PER_MINUTE = 200 const DEFAULT_LANGUAGE = 'en-US' const DEFAULT_VOICE = 'en-US-JennyNeural' -const DEFAULT_RATE = 1.25 +const DEFAULT_RATE = '1.0' const ANCHOR_ELEMENTS_BLOCKED_ATTRIBUTES = [ 'omnivore-highlight-id', @@ -300,14 +298,13 @@ export const htmlToSpeechFile = ( wordOffset, node.nodeName === 'BLOCKQUOTE' ? options.secondaryVoice : undefined ) - utterances.push(utterance) + utterance.wordCount > 0 && utterances.push(utterance) wordOffset += utterance.wordCount } } return { wordCount: wordOffset, - averageWPM: WORDS_PER_MINUTE, language: options.language || DEFAULT_LANGUAGE, defaultVoice: options.primaryVoice || DEFAULT_VOICE, utterances, diff --git a/packages/text-to-speech/src/index.ts b/packages/text-to-speech/src/index.ts index 5ca4d54f7..63f9a6631 100644 --- a/packages/text-to-speech/src/index.ts +++ b/packages/text-to-speech/src/index.ts @@ -13,7 +13,7 @@ import { htmlToSpeechFile } from './htmlToSsml' interface UtteranceInput { voice?: string - rate?: number + rate?: string language?: string text: string idx: string @@ -24,7 +24,7 @@ interface HTMLInput { text: string voice?: string language?: string - rate?: number + rate?: string complimentaryVoice?: string bucket: string } @@ -160,9 +160,6 @@ export const textToSpeechStreamingHandler = Sentry.GCPFunction.wrapHttpFunction( try { const utteranceInput = req.body as UtteranceInput - if (!utteranceInput.text) { - return res.status(400).send({ errorCode: 'INVALID_DATA' }) - } const input: TextToSpeechInput = { ...utteranceInput, textType: 'utterance', diff --git a/packages/text-to-speech/src/textToSpeech.ts b/packages/text-to-speech/src/textToSpeech.ts index f9679c6c4..449668548 100644 --- a/packages/text-to-speech/src/textToSpeech.ts +++ b/packages/text-to-speech/src/textToSpeech.ts @@ -8,13 +8,14 @@ import { SpeechSynthesizer, } from 'microsoft-cognitiveservices-speech-sdk' import { endSsml, htmlToSsmlItems, ssmlItemText, startSsml } from './htmlToSsml' +import * as _ from 'underscore' export interface TextToSpeechInput { text: string voice?: string language?: string textType?: 'html' | 'utterance' - rate?: number + rate?: string secondaryVoice?: string audioStream?: NodeJS.ReadWriteStream } @@ -150,7 +151,7 @@ export const synthesizeTextToSpeech = async ( // for utterance const start = startSsml(ssmlOptions) wordOffset = -start.length - const ssml = `${start}${input.text}${endSsml()}` + const ssml = `${start}${_.escape(input.text)}${endSsml()}` const result = await speakSsmlAsyncPromise(ssml) return { audioData: Buffer.from(result.audioData), diff --git a/packages/text-to-speech/test/htmlToSsml.test.ts b/packages/text-to-speech/test/htmlToSsml.test.ts index 5f3b6bc90..0ab3acdf1 100644 --- a/packages/text-to-speech/test/htmlToSsml.test.ts +++ b/packages/text-to-speech/test/htmlToSsml.test.ts @@ -7,7 +7,7 @@ describe('htmlToSsmlItems', () => { primaryVoice: 'test-primary', secondaryVoice: 'test-secondary', language: 'en-US', - rate: 1, + rate: '1.0', } describe('a simple html file', () => {