mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Return an array of SSMLs instead of ssmlItems
This commit is contained in:
parent
71144d3c84
commit
9826262ef4
5 changed files with 44 additions and 41 deletions
8
packages/api/src/textToSpeech.d.ts
vendored
8
packages/api/src/textToSpeech.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
declare module '@omnivore/text-to-speech-handler' {
|
||||
function htmlToSsml(html: string, options: SSMLOptions): SSMLItem[]
|
||||
function htmlToSsml(html: string, options: SSMLOptions): string[]
|
||||
|
||||
interface SSMLOptions {
|
||||
primaryVoice: string
|
||||
|
|
@ -8,11 +8,5 @@ declare module '@omnivore/text-to-speech-handler' {
|
|||
language: string
|
||||
}
|
||||
|
||||
interface SSMLItem {
|
||||
open: string
|
||||
close: string
|
||||
textItems: string[]
|
||||
}
|
||||
|
||||
export { htmlToSsml }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,10 @@ export const ssmlItemText = (item: SSMLItem): string => {
|
|||
return [item.open, ...item.textItems, item.close].join('')
|
||||
}
|
||||
|
||||
export const htmlToSsml = (html: string, options: SSMLOptions): SSMLItem[] => {
|
||||
export const htmlToSsmlItems = (
|
||||
html: string,
|
||||
options: SSMLOptions
|
||||
): SSMLItem[] => {
|
||||
console.log('creating ssml with options', options)
|
||||
|
||||
const dom = parseHTML(html)
|
||||
|
|
@ -218,3 +221,7 @@ export const htmlToSsml = (html: string, options: SSMLOptions): SSMLItem[] => {
|
|||
|
||||
return items
|
||||
}
|
||||
|
||||
export const htmlToSsml = (html: string, options: SSMLOptions): string[] => {
|
||||
return htmlToSsmlItems(html, options).map(ssmlItemText)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-d
|
|||
import { synthesizeTextToSpeech, TextToSpeechInput } from './textToSpeech'
|
||||
import { File, Storage } from '@google-cloud/storage'
|
||||
import { PassThrough } from 'stream'
|
||||
import { htmlToSsml, SSMLItem } from './htmlToSsml'
|
||||
import { htmlToSsml } from './htmlToSsml'
|
||||
|
||||
dotenv.config()
|
||||
Sentry.GCPFunction.init({
|
||||
|
|
@ -141,7 +141,10 @@ export const textToSpeechStreamingHandler = Sentry.GCPFunction.wrapHttpFunction(
|
|||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
const ssmlItems = req.body.ssmlItems as SSMLItem[]
|
||||
const ssmlItems = req.body.ssmlItems as string[]
|
||||
if (!ssmlItems || ssmlItems.length === 0) {
|
||||
return res.status(200).send({ errorCode: 'INVALID_DATA' })
|
||||
}
|
||||
const audioStream = new PassThrough()
|
||||
const input: TextToSpeechInput = {
|
||||
text: '',
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
SpeechSynthesisResult,
|
||||
SpeechSynthesizer,
|
||||
} from 'microsoft-cognitiveservices-speech-sdk'
|
||||
import { htmlToSsml, SSMLItem, ssmlItemText } from './htmlToSsml'
|
||||
import { htmlToSsmlItems, ssmlItemText } from './htmlToSsml'
|
||||
|
||||
export interface TextToSpeechInput {
|
||||
id?: string
|
||||
|
|
@ -20,7 +20,7 @@ export interface TextToSpeechInput {
|
|||
complimentaryVoice?: string
|
||||
bucket?: string
|
||||
audioStream: NodeJS.ReadWriteStream
|
||||
ssmlItems?: SSMLItem[]
|
||||
ssmlItems?: string[]
|
||||
}
|
||||
|
||||
export interface TextToSpeechOutput {
|
||||
|
|
@ -41,6 +41,7 @@ export const synthesizeTextToSpeech = async (
|
|||
if (!process.env.AZURE_SPEECH_KEY || !process.env.AZURE_SPEECH_REGION) {
|
||||
throw new Error('Azure Speech Key or Region not set')
|
||||
}
|
||||
const textType = input.textType || 'html'
|
||||
const writeStream = input.audioStream
|
||||
const speechConfig = SpeechConfig.fromSubscription(
|
||||
process.env.AZURE_SPEECH_KEY,
|
||||
|
|
@ -131,24 +132,22 @@ export const synthesizeTextToSpeech = async (
|
|||
}
|
||||
|
||||
try {
|
||||
const ssmlItems =
|
||||
input.textType === 'ssml'
|
||||
? input.ssmlItems
|
||||
: htmlToSsml(input.text, {
|
||||
primaryVoice: input.voice || 'en-US-JennyNeural',
|
||||
secondaryVoice: input.complimentaryVoice || 'en-US-GuyNeural',
|
||||
language: input.languageCode || 'en-US',
|
||||
rate: '1',
|
||||
})
|
||||
if (!ssmlItems || ssmlItems.length === 0) {
|
||||
throw new Error('No SSML items found')
|
||||
}
|
||||
|
||||
for (const ssmlItem of Array.from(ssmlItems)) {
|
||||
const ssml = ssmlItemText(ssmlItem)
|
||||
console.debug('start synthesizing', ssml)
|
||||
const result = await speakSsmlAsyncPromise(ssml)
|
||||
timeOffset = timeOffset + result.audioDuration
|
||||
if (textType === 'html') {
|
||||
const ssmlItems = htmlToSsmlItems(input.text, {
|
||||
primaryVoice: input.voice || 'en-US-JennyNeural',
|
||||
secondaryVoice: input.complimentaryVoice || 'en-US-GuyNeural',
|
||||
language: input.languageCode || 'en-US',
|
||||
rate: '1',
|
||||
})
|
||||
for (const ssmlItem of ssmlItems) {
|
||||
const ssml = ssmlItemText(ssmlItem)
|
||||
const result = await speakSsmlAsyncPromise(ssml)
|
||||
timeOffset = timeOffset + result.audioDuration
|
||||
}
|
||||
} else {
|
||||
for (const ssmlItem of input.ssmlItems || []) {
|
||||
await speakSsmlAsyncPromise(ssmlItem)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('synthesis error', error)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import 'mocha'
|
||||
import { expect } from 'chai'
|
||||
import { htmlToSsml } from '../src/htmlToSsml'
|
||||
import { htmlToSsmlItems } from '../src/htmlToSsml'
|
||||
|
||||
describe('htmlToSsml', () => {
|
||||
describe('htmlToSsmlItems', () => {
|
||||
const TEST_OPTIONS = {
|
||||
primaryVoice: 'test-primary',
|
||||
secondaryVoice: 'test-secondary',
|
||||
|
|
@ -12,7 +12,7 @@ describe('htmlToSsml', () => {
|
|||
|
||||
describe('a simple html file', () => {
|
||||
xit('should convert Html to SSML', () => {
|
||||
const ssml = htmlToSsml(
|
||||
const ssml = htmlToSsmlItems(
|
||||
`
|
||||
<div id="readability-content">
|
||||
<div id="readability-page-1">
|
||||
|
|
@ -28,7 +28,7 @@ describe('htmlToSsml', () => {
|
|||
})
|
||||
describe('a file with nested elements', () => {
|
||||
xit('should collapse spans into the parent paragraph', () => {
|
||||
const ssml = htmlToSsml(
|
||||
const ssml = htmlToSsmlItems(
|
||||
`
|
||||
<div id="readability-content">
|
||||
<div class="page" id="readability-page-1">
|
||||
|
|
@ -57,7 +57,7 @@ describe('htmlToSsml', () => {
|
|||
)
|
||||
})
|
||||
xit('should extract child paragraphs to the top level', () => {
|
||||
const ssml = htmlToSsml(
|
||||
const ssml = htmlToSsmlItems(
|
||||
`
|
||||
<div id="readability-content">
|
||||
<div>
|
||||
|
|
@ -77,7 +77,7 @@ describe('htmlToSsml', () => {
|
|||
)
|
||||
})
|
||||
xit('should hoist paragraphs in spans to the top level', () => {
|
||||
const ssml = htmlToSsml(
|
||||
const ssml = htmlToSsmlItems(
|
||||
`
|
||||
<div id="readability-content">
|
||||
<div>
|
||||
|
|
@ -95,7 +95,7 @@ describe('htmlToSsml', () => {
|
|||
expect(text).to.equal(`TBD`.trim())
|
||||
})
|
||||
xit('should hoist lists to the top level', () => {
|
||||
const ssml = htmlToSsml(
|
||||
const ssml = htmlToSsmlItems(
|
||||
`
|
||||
<div id="readability-content">
|
||||
<div>
|
||||
|
|
@ -113,7 +113,7 @@ describe('htmlToSsml', () => {
|
|||
expect(text).to.equal(`TBD`.trim())
|
||||
})
|
||||
xit('should hoist headers to the top level', () => {
|
||||
const ssml = htmlToSsml(
|
||||
const ssml = htmlToSsmlItems(
|
||||
`
|
||||
<div id="readability-content">
|
||||
<div>
|
||||
|
|
@ -131,7 +131,7 @@ describe('htmlToSsml', () => {
|
|||
expect(text).to.equal(`TBD`.trim())
|
||||
})
|
||||
xit('should hoist blockquotes to the top level', () => {
|
||||
const ssml = htmlToSsml(
|
||||
const ssml = htmlToSsmlItems(
|
||||
`
|
||||
<div id="readability-content">
|
||||
<div>
|
||||
|
|
@ -151,7 +151,7 @@ describe('htmlToSsml', () => {
|
|||
})
|
||||
describe('a file with blockquotes', () => {
|
||||
xit('should convert Html to SSML with complimentary voices', () => {
|
||||
const ssml = htmlToSsml(
|
||||
const ssml = htmlToSsmlItems(
|
||||
`
|
||||
<div id="readability-content">
|
||||
<div class="page" id="readability-page-1">
|
||||
|
|
@ -193,7 +193,7 @@ describe('htmlToSsml', () => {
|
|||
// continue
|
||||
// }
|
||||
// const html = fs.readFileSync(readablePath, { encoding: 'utf-8' })
|
||||
// const ssmlItems = htmlToSsml(html, TEST_OPTIONS)
|
||||
// const ssmlItems = htmlToSsmlItems(html, TEST_OPTIONS)
|
||||
// console.log('SSML ITEMS', ssmlItems)
|
||||
// }
|
||||
// })
|
||||
|
|
|
|||
Loading…
Reference in a new issue