mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Stop throwing error and return empty speech if there is no content in the article
This commit is contained in:
parent
d893978567
commit
b995dc0500
2 changed files with 97 additions and 75 deletions
|
|
@ -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')
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue