diff --git a/packages/api/src/routers/article_router.ts b/packages/api/src/routers/article_router.ts index fe9dd4a99..5891c025f 100644 --- a/packages/api/src/routers/article_router.ts +++ b/packages/api/src/routers/article_router.ts @@ -94,6 +94,9 @@ export function articleRouter() { return res.status(401).send({ errorCode: 'UNAUTHORIZED' }) } const { uid } = jwt.decode(token) as Claims + if (!uid) { + return res.status(401).send({ errorCode: 'UNAUTHORIZED' }) + } logger.info(`Get article speech in ${outputFormat} format`, { params: req.params, labels: { @@ -102,84 +105,89 @@ export function articleRouter() { }, }) - if (outputFormat === 'speech') { + try { + if (outputFormat === 'speech') { + const page = await getPageById(articleId) + if (!page) { + return res.status(404).send('Page not found') + } + const speechFile = htmlToSpeechFile({ + title: page.title, + content: page.content, + options: { + primaryVoice: voice, + secondaryVoice: secondaryVoice, + language: page.language, + }, + }) + return res.send({ ...speechFile, pageId: articleId }) + } + + const existingSpeech = await getRepository(Speech).findOne({ + where: { + elasticPageId: articleId, + voice, + }, + order: { + createdAt: 'DESC', + }, + relations: ['user'], + }) + if (existingSpeech) { + if (existingSpeech.user.id !== uid) { + logger.info('User is not allowed to access speech of the article', { + userId: uid, + articleId, + }) + return res.status(401).send({ errorCode: 'UNAUTHORIZED' }) + } + if (existingSpeech.state === SpeechState.COMPLETED) { + logger.info('Found existing completed speech', { + audioUrl: existingSpeech.audioFileName, + speechMarksUrl: existingSpeech.speechMarksFileName, + }) + await updatePage( + existingSpeech.elasticPageId, + { + listenedAt: new Date(), + }, + { uid, pubsub: createPubSubClient() } + ) + return res.redirect(await redirectUrl(existingSpeech, outputFormat)) + } + if (existingSpeech.state === SpeechState.INITIALIZED) { + logger.info('Found existing in progress speech') + // retry later + return res.status(202).send('Speech is in progress') + } + } + + logger.info('Create Text to speech task', { articleId }) const page = await getPageById(articleId) if (!page) { return res.status(404).send('Page not found') } - const speechFile = htmlToSpeechFile({ - title: page.title, - content: page.content, - options: { - primaryVoice: voice, - secondaryVoice: secondaryVoice, - language: page.language, - }, - }) - return res.send({ ...speechFile, pageId: articleId }) - } - - const existingSpeech = await getRepository(Speech).findOne({ - where: { + // initialize state + const speech = await getRepository(Speech).save({ + user: { id: uid }, elasticPageId: articleId, + state: SpeechState.INITIALIZED, voice, - }, - order: { - createdAt: 'DESC', - }, - relations: ['user'], - }) - if (existingSpeech) { - if (existingSpeech.user.id !== uid) { - logger.info('User is not allowed to access speech of the article', { - userId: uid, - articleId, - }) - return res.status(401).send({ errorCode: 'UNAUTHORIZED' }) - } - if (existingSpeech.state === SpeechState.COMPLETED) { - logger.info('Found existing completed speech', { - audioUrl: existingSpeech.audioFileName, - speechMarksUrl: existingSpeech.speechMarksFileName, - }) - await updatePage( - existingSpeech.elasticPageId, - { - listenedAt: new Date(), - }, - { uid, pubsub: createPubSubClient() } - ) - return res.redirect(await redirectUrl(existingSpeech, outputFormat)) - } - if (existingSpeech.state === SpeechState.INITIALIZED) { - logger.info('Found existing in progress speech') - // retry later - return res.status(202).send('Speech is in progress') - } + }) + // enqueue a task to convert text to speech + const taskName = await enqueueTextToSpeech({ + userId: uid, + speechId: speech.id, + text: page.content, + voice: speech.voice, + priority: priority || 'high', + }) + logger.info('Start Text to speech task', { taskName }) + res.status(202).send('Text to speech task started') + } catch (error) { + logger.error('Error getting article speech:', error) + res.status(500).send({ errorCode: 'INTERNAL_ERROR' }) } - - logger.info('Create Text to speech task', { articleId }) - const page = await getPageById(articleId) - if (!page) { - return res.status(404).send('Page not found') - } - // initialize state - const speech = await getRepository(Speech).save({ - user: { id: uid }, - elasticPageId: articleId, - state: SpeechState.INITIALIZED, - voice, - }) - // enqueue a task to convert text to speech - const taskName = await enqueueTextToSpeech({ - userId: uid, - speechId: speech.id, - text: page.content, - voice: speech.voice, - priority: priority || 'high', - }) - logger.info('Start Text to speech task', { taskName }) - res.status(202).send('Text to speech task started') } ) diff --git a/packages/text-to-speech/src/htmlToSsml.ts b/packages/text-to-speech/src/htmlToSsml.ts index 817049114..53e228f96 100644 --- a/packages/text-to-speech/src/htmlToSsml.ts +++ b/packages/text-to-speech/src/htmlToSsml.ts @@ -303,16 +303,30 @@ const textToUtterance = ({ export const htmlToSpeechFile = (htmlInput: HtmlInput): SpeechFile => { const { title, content, options } = htmlInput console.log('creating speech file with options:', options) + const language = options.language || DEFAULT_LANGUAGE + const defaultVoice = options.primaryVoice || DEFAULT_VOICE const dom = parseHTML(content) const body = dom.document.querySelector('#readability-page-1') if (!body) { - throw new Error('Unable to parse HTML document') + console.log('No HTML body found:', content) + return { + wordCount: 0, + language, + defaultVoice, + utterances: [], + } } const parsedNodes = parseDomTree(body) if (parsedNodes.length < 1) { - throw new Error('No HTML nodes found') + console.log('No HTML nodes found:', body) + return { + wordCount: 0, + language, + defaultVoice, + utterances: [], + } } const tokenizer = new WordPunctTokenizer() @@ -354,8 +368,8 @@ export const htmlToSpeechFile = (htmlInput: HtmlInput): SpeechFile => { return { wordCount: wordOffset, - language: options.language || DEFAULT_LANGUAGE, - defaultVoice: options.primaryVoice || DEFAULT_VOICE, + language, + defaultVoice, utterances, } }