From 02ba753375ac559745f34585ce3aed3c560da708 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 17 Aug 2022 11:24:53 +0800 Subject: [PATCH] Upload speech marks file to gcs too --- packages/api/src/entity/speech.ts | 2 +- packages/api/src/routers/article_router.ts | 41 +++++++++++++++++----- packages/api/src/utils/textToSpeech.ts | 16 +++++++-- packages/db/migrations/0093.do.speech.sql | 2 +- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/packages/api/src/entity/speech.ts b/packages/api/src/entity/speech.ts index 0f61d7dd7..ac64f7e71 100644 --- a/packages/api/src/entity/speech.ts +++ b/packages/api/src/entity/speech.ts @@ -25,7 +25,7 @@ export class Speech { audioUrl!: string @Column('text') - speechMarks!: string + speechMarksUrl!: string @Column('text') voice!: string diff --git a/packages/api/src/routers/article_router.ts b/packages/api/src/routers/article_router.ts index d9baf737c..85ccf9b0a 100644 --- a/packages/api/src/routers/article_router.ts +++ b/packages/api/src/routers/article_router.ts @@ -72,10 +72,11 @@ export function articleRouter() { }) router.get( - '/:id/mp3', + '/:id/:outputFormat', cors(corsConfig), async (req, res) => { const id = req.params.id + const outputFormat = req.params.outputFormat const token = req.cookies?.auth || req.headers?.authorization if (!token || !jwt.verify(token, env.server.jwtSecret)) { return res.status(401).send({ errorCode: 'UNAUTHORIZED' }) @@ -83,15 +84,27 @@ export function articleRouter() { const { uid } = jwt.decode(token) as Claims const startTime = Date.now() - logger.info('Get article speech in mp3 format', { + logger.info(`Get article speech in ${outputFormat} format`, { params: req.params, labels: { userId: uid, source: 'GetArticleSpeechMp3', articleId: id, + outputFormat, }, }) + const existingSpeech = await getRepository(Speech).findOneBy({ + elasticPageId: id, + }) + if (existingSpeech) { + logger.info('Found existing speech', { + audioUrl: existingSpeech.audioUrl, + speechMarksUrl: existingSpeech.speechMarksUrl, + }) + return res.redirect(redirectUrl(existingSpeech, outputFormat)) + } + logger.debug('Text to speech request', { articleId: id }) const userPersonalization = await getRepository( UserPersonalization @@ -112,27 +125,39 @@ export function articleRouter() { return res.status(200).send('Page has no text') } - const speech = await synthesizeTextToSpeech({ + const speechOutput = await synthesizeTextToSpeech({ id, text, languageCode: page.language, voice: userPersonalization.speechVoice, }) - await getRepository(Speech).save({ + const speech = await getRepository(Speech).save({ elasticPageId: id, - audioUrl: speech.audioUrl, - speechMarks: JSON.stringify(speech.speechMarks), + audioUrl: speechOutput.audioUrl, + speechMarksUrl: speechOutput.speechMarksUrl, user: { id: uid }, }) - logger.info('Found speech mp3', { + logger.info('Created speech', { audioUrl: speech.audioUrl, + speechMarksUrl: speech.speechMarksUrl, duration: Date.now() - startTime, }) - res.redirect(speech.audioUrl) + res.redirect(redirectUrl(speech, outputFormat)) } ) return router } + +const redirectUrl = (speech: Speech, outputFormat: string) => { + switch (outputFormat) { + case 'mp3': + return speech.audioUrl + case 'json': + return speech.speechMarksUrl + default: + return speech.audioUrl + } +} diff --git a/packages/api/src/utils/textToSpeech.ts b/packages/api/src/utils/textToSpeech.ts index 53aefe881..7f1a1d45c 100644 --- a/packages/api/src/utils/textToSpeech.ts +++ b/packages/api/src/utils/textToSpeech.ts @@ -1,5 +1,5 @@ import { buildLogger } from './logger' -import { createGCSFile, getFilePublicUrl } from './uploads' +import { createGCSFile, getFilePublicUrl, uploadToBucket } from './uploads' import { CancellationDetails, CancellationReason, @@ -21,7 +21,7 @@ export interface TextToSpeechInput { export interface TextToSpeechOutput { audioUrl: string - speechMarks: SpeechMark[] + speechMarksUrl: string } export interface SpeechMark { @@ -142,9 +142,19 @@ export const synthesizeTextToSpeech = async ( logger.debug(`audio file: ${audioFile}`) + // upload Speech Marks file to GCS + const speechMarksFile = `speech/${input.id}.json` + await uploadToBucket( + speechMarksFile, + Buffer.from(JSON.stringify(speechMarks)), + { + public: true, + } + ) + return { audioUrl: getFilePublicUrl(audioFile), - speechMarks, + speechMarksUrl: getFilePublicUrl(speechMarksFile), } } diff --git a/packages/db/migrations/0093.do.speech.sql b/packages/db/migrations/0093.do.speech.sql index 39a50aadf..fcda74145 100755 --- a/packages/db/migrations/0093.do.speech.sql +++ b/packages/db/migrations/0093.do.speech.sql @@ -10,7 +10,7 @@ CREATE TABLE omnivore.speech ( elastic_page_id TEXT NOT NULL, voice text, audio_url text NOT NULL, - speech_marks text NOT NULL, + speech_marks_url text NOT NULL, created_at timestamptz NOT NULL DEFAULT current_timestamp, updated_at timestamptz NOT NULL DEFAULT current_timestamp );