diff --git a/packages/api/src/entity/user_personalization.ts b/packages/api/src/entity/user_personalization.ts index 8044ef1a7..5e7cc51f8 100644 --- a/packages/api/src/entity/user_personalization.ts +++ b/packages/api/src/entity/user_personalization.ts @@ -39,11 +39,14 @@ export class UserPersonalization { @Column('text', { nullable: true }) speechVoice?: string - @Column('integer', { nullable: true }) - speechRate?: number + @Column('text', { nullable: true }) + speechSecondaryVoice?: string - @Column('integer', { nullable: true }) - speechVolume?: number + @Column('text', { nullable: true }) + speechRate?: string + + @Column('text', { nullable: true }) + speechVolume?: string @CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' }) createdAt!: Date diff --git a/packages/api/src/routers/text_to_speech.ts b/packages/api/src/routers/text_to_speech.ts index f7dd7bdcc..7a959838a 100644 --- a/packages/api/src/routers/text_to_speech.ts +++ b/packages/api/src/routers/text_to_speech.ts @@ -68,7 +68,8 @@ export function textToSpeechRouter() { content: page.content, options: { primaryVoice: userPersonalization?.speechVoice || 'Axel', - secondaryVoice: 'Evelyn', + secondaryVoice: + userPersonalization?.speechSecondaryVoice || 'Evelyn', language: page.language, }, }) @@ -83,7 +84,7 @@ export function textToSpeechRouter() { priority: 'high', isUltraRealisticVoice: true, language: speechFile.language, - rate: userPersonalization?.speechRate?.toString() || '1.1', + rate: userPersonalization?.speechRate || '1.1', }) logger.info('Start Text to speech task', { taskName }) } diff --git a/packages/db/migrations/0098.do.create_features_table.sql b/packages/db/migrations/0098.do.create_features_table.sql new file mode 100755 index 000000000..618315732 --- /dev/null +++ b/packages/db/migrations/0098.do.create_features_table.sql @@ -0,0 +1,28 @@ +-- Type: DO +-- Name: create_features_table +-- Description: Create features table to store opt-in features by users + +BEGIN; + +CREATE TABLE IF NOT EXISTS omnivore.features ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(), + user_id uuid NOT NULL REFERENCES omnivore.user ON DELETE CASCADE, + name text NOT NULL, + token text NOT NULL, + granted_at timestamptz, + created_at timestamptz NOT NULL DEFAULT current_timestamp, + updated_at timestamptz NOT NULL DEFAULT current_timestamp, + UNIQUE (user_id, name) +); + +CREATE TRIGGER features_modtime BEFORE UPDATE ON omnivore.features + FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column(); + +GRANT SELECT, INSERT, UPDATE, DELETE ON omnivore.features TO omnivore_user; + +ALTER TABLE omnivore.user_personalization + ADD COLUMN IF NOT EXISTS speech_secondary_voice text, + ALTER COLUMN speech_rate TYPE text, + ALTER COLUMN speech_volume TYPE text; + +COMMIT; diff --git a/packages/db/migrations/0098.undo.create_features_table.sql b/packages/db/migrations/0098.undo.create_features_table.sql new file mode 100755 index 000000000..c399a16c9 --- /dev/null +++ b/packages/db/migrations/0098.undo.create_features_table.sql @@ -0,0 +1,14 @@ +-- Type: UNDO +-- Name: create_features_table +-- Description: Create features table to store opt-in features by users + +BEGIN; + +DROP TABLE IF EXISTS omnivore.features; + +ALTER TABLE omnivore.user_personalization + DROP COLUMN IF EXISTS speech_secondary_voice, + ALTER COLUMN speech_rate TYPE integer, + ALTER COLUMN speech_volume TYPE integer; + +COMMIT; diff --git a/packages/text-to-speech/src/htmlToSsml.ts b/packages/text-to-speech/src/htmlToSsml.ts index 8fb0b7e35..be3f1c2e8 100644 --- a/packages/text-to-speech/src/htmlToSsml.ts +++ b/packages/text-to-speech/src/htmlToSsml.ts @@ -16,7 +16,7 @@ export interface Utterance { text: string wordOffset: number wordCount: number - voice?: string + voice: string } export interface SpeechFile { @@ -269,7 +269,7 @@ const textToUtterances = ({ idx: string textItems: string[] wordOffset: number - voice?: string + voice: string isHtml?: boolean }): Utterance[] => { let text = textItems.join('') @@ -393,6 +393,7 @@ export const htmlToSpeechFile = (htmlInput: HtmlInput): SpeechFile => { textItems: [stripEmojis(title)], // title could have emoji wordOffset, isHtml: false, + voice: defaultVoice, })[0] utterances.push(titleUtterance) wordOffset += titleUtterance.wordCount @@ -413,7 +414,9 @@ export const htmlToSpeechFile = (htmlInput: HtmlInput): SpeechFile => { textItems, wordOffset, voice: - node.nodeName === 'BLOCKQUOTE' ? options.secondaryVoice : undefined, + node.nodeName === 'BLOCKQUOTE' + ? options.secondaryVoice || defaultVoice + : defaultVoice, }) const wordCount = newUtterances.reduce((acc, u) => acc + u.wordCount, 0) wordCount > 0 && utterances.push(...newUtterances)