Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update TTS configuration #5681

Merged
merged 2 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions d.ts/src/helpers/socket.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { HighlightInterface } from '@entity/highlight';
import type { HowLongToBeatGameInterface, HowLongToBeatGameItemInterface } from '@entity/howLongToBeatGame';
import type { KeywordGroupInterface, KeywordInterface } from '@entity/keyword';
import type { OBSWebsocketInterface } from '@entity/obswebsocket';
import type { OverlayMapperMarathon, Overlay } from '@entity/overlay';
import type { OverlayMapperMarathon, Overlay, TTSService } from '@entity/overlay';
import type { Permissions } from '@entity/permissions';
import type { QueueInterface } from '@entity/queue';
import type { QuotesInterface } from '@entity/quotes';
Expand Down Expand Up @@ -194,7 +194,7 @@ export type ClientToServerEventsWithNamespace = {
'message': (data: { id: string, show: boolean; message: string; username: string, timestamp: number, badges: any }) => void,
},
'/overlays/texttospeech': GenericEvents & {
'speak': (data: { text: string; highlight: boolean, service: 0 | 1, key: string }) => void,
'speak': (data: { text: string; highlight: boolean, key: string }) => void,
},
'/overlays/wordcloud': GenericEvents & {
'wordcloud:word': (words: string[]) => void,
Expand Down Expand Up @@ -251,7 +251,6 @@ export type ClientToServerEventsWithNamespace = {
'/registries/alerts': GenericEvents & {
'alerts::settings': (data: null | { areAlertsMuted: boolean; isSoundMuted: boolean; isTTSMuted: boolean; }, cb: (item: { areAlertsMuted: boolean; isSoundMuted: boolean; isTTSMuted: boolean; }) => void) => void,
'test': (emit: EmitData) => void,
'speak': (opts: { text: string, key: string, voice: string; volume: number; rate: number; pitch: number }, cb: (error: Error | string | null | unknown, b64mp3: string) => void) => void,
'alert': (data: (EmitData & {
id: string;
isTTSMuted: boolean;
Expand Down Expand Up @@ -459,8 +458,7 @@ export type ClientToServerEventsWithNamespace = {
'events::remove': (eventId: Required<Event['id']>, cb: (error: Error | string | null | unknown) => void) => void,
},
'/core/tts': GenericEvents & {
'google::speak': (opts: { volume: number; pitch: number; rate: number; text: string; voice: string; }, cb: (error: Error | string | null | unknown, audioContent?: string | null) => void) => void,
'speak': (opts: { text: string, key: string, voice: string; volume: number; rate: number; pitch: number; triggerTTSByHighlightedMessage?: boolean; }, cb: (error: Error | string | null | unknown, b64mp3: string) => void) => void,
'speak': (opts: { service: TTSService, text: string, key: string, voice: string; volume: number; rate: number; pitch: number; triggerTTSByHighlightedMessage?: boolean; }, cb: (error: Error | string | null | unknown, b64mp3?: string) => void) => void,
},
'/core/ui': GenericEvents & {
'configuration': (cb: (error: Error | string | null | unknown, data?: Configuration) => void) => void,
Expand Down
37 changes: 33 additions & 4 deletions src/database/entity/overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,13 @@ export interface Media {
typeId: 'media';
}

export enum TTSService {
NONE = '-1',
RESPONSIVEVOICE = '0',
GOOGLE = '1',
SPEECHSYNTHESIS = '2',
}

export interface Alerts {
typeId: 'alerts';
alertDelayInMs: number;
Expand All @@ -349,6 +356,29 @@ export interface Alerts {
}>;
globalFont2: ExpandRecursively<Alerts['globalFont1']>;
tts: {
selectedService: TTSService;
// we are using this to store tts settings for each service, so if we are changing service, then we can previously set settings
services: {
[TTSService.NONE]?: null,
[TTSService.SPEECHSYNTHESIS]?: {
voice: string;
pitch: number;
volume: number;
rate: number;
},
[TTSService.GOOGLE]?: {
voice: string;
pitch: number;
volume: number;
rate: number;
},
[TTSService.RESPONSIVEVOICE]?: {
voice: string;
pitch: number;
volume: number;
rate: number;
}
}
voice: string;
pitch: number;
volume: number;
Expand Down Expand Up @@ -533,10 +563,9 @@ export interface ClipsCarousel {

export interface TTS {
typeId: 'tts';
voice: string,
volume: number,
rate: number,
pitch: number,
selectedService: TTSService;
// we are using this to store tts settings for each service, so if we are changing service, then we can previously set settings
services: Alerts['tts']['services'];
triggerTTSByHighlightedMessage: boolean,
}

Expand Down
4 changes: 2 additions & 2 deletions src/database/entity/randomizer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BeforeInsert, Column, Entity, Index, PrimaryColumn } from 'typeorm';
import { z } from 'zod';

import { AlertTTS } from './overlay.js';
import { Alerts } from './overlay.js';
import { BotEntity } from '../BotEntity.js';
import { command } from '../validators/IsCommand.js';

Expand Down Expand Up @@ -89,5 +89,5 @@ export class Randomizer extends BotEntity {
};

@Column({ type: (process.env.TYPEORM_CONNECTION ?? 'better-sqlite3') !== 'better-sqlite3' ? 'json' : 'simple-json' })
tts: AlertTTS['tts'] & { enabled: boolean };
tts: Alerts['tts'] & { enabled: boolean };
}
141 changes: 141 additions & 0 deletions src/database/migration/mysql/21.x/1678892044038-updateTTSSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

import { insertItemIntoTable } from '../../../insertItemIntoTable.js';

export class updateTTSSettings1678892044038 implements MigrationInterface {
name = 'updateTTSSettings1678892044038';

public async up(queryRunner: QueryRunner): Promise<void> {
const items = await queryRunner.query(`SELECT * FROM \`settings\` WHERE \`namespace\` = '/core/tts' AND \`name\` = 'service'`);
if (items.length === 0) {
return;
}
const service = JSON.parse(items[0].value) as number;

const randomizers = await queryRunner.query(`SELECT * FROM \`randomizer\``);
for (const randomizer of randomizers) {
randomizer.tts = JSON.parse(randomizer.tts);
console.log(`Updating randomizer with id ${randomizer.id} for new TTS settings.`);
randomizer.tts.selectedService = String(service);
randomizer.tts.services = {
[String(service)]: {
voice: randomizer.tts.voice,
volume: randomizer.tts.volume,
rate: randomizer.tts.rate,
pitch: randomizer.tts.pitch,
},
};
delete randomizer.tts.voice;
delete randomizer.tts.volume;
delete randomizer.tts.rate;
delete randomizer.tts.pitch;
randomizer.tts = JSON.stringify(randomizer.tts);

// save back to db
await queryRunner.query(`DELETE FROM \`randomizer\` WHERE \`id\` = '${randomizer.id}'`);
await insertItemIntoTable('randomizer', randomizer, queryRunner);
}

const overlays = await queryRunner.query(`SELECT * FROM \`overlay\``);
for (const overlay of overlays) {
overlay.items = JSON.parse(overlay.items);
for (const item of overlay.items) {
// update TTS overlay
if (item.opts.typeId === 'tts') {
console.log(`Updating TTS overlay with id ${item.id} for new TTS settings.`);
item.opts.selectedService = String(service);
item.opts.services = {
[String(service)]: {
voice: item.opts.voice,
volume: item.opts.volume,
rate: item.opts.rate,
pitch: item.opts.pitch,
},
};
delete item.opts.voice;
delete item.opts.volume;
delete item.opts.rate;
delete item.opts.pitch;
}

// update Alert overlay
if (item.opts.typeId === 'alerts') {
console.log(`Updating Alert overlay with id ${item.id} for new TTS settings.`);
item.opts.tts.selectedService = String(service);
item.opts.tts.services = {
[String(service)]: {
voice: item.opts.tts.voice,
volume: item.opts.tts.volume,
rate: item.opts.tts.rate,
pitch: item.opts.tts.pitch,
},
};
delete item.opts.tts.voice;
delete item.opts.tts.volume;
delete item.opts.tts.rate;
delete item.opts.tts.pitch;

for (const group of item.opts.items) {
for (const variant of group.variants) {
for (const component of variant.items) {
if (component.type === 'tts') {
if (component.tts == null) {
console.log(`\t== Skipping Alert overlay variant component with id ${component.id} as it is inheriting global value.`);
continue;
}
console.log(`\t== Updating Alert overlay variant component with id ${component.id} for new TTS settings.`);
component.tts.selectedService = String(service);
component.tts.services = {
[String(service)]: {
voice: component.tts.voice,
volume: component.tts.volume,
rate: component.tts.rate,
pitch: component.tts.pitch,
},
};
delete component.tts.voice;
delete component.tts.volume;
delete component.tts.rate;
delete component.tts.pitch;
}
}
}
for (const component of group.items) {
if (component.type === 'tts') {
if (component.tts == null) {
console.log(`\t== Skipping Alert overlay component with id ${component.id} as it is inheriting global value.`);
continue;
}
console.log(`\t== Updating Alert overlay component with id ${component.id} for new TTS settings.`);
component.tts.selectedService = String(service);
component.tts.services = {
[String(service)]: {
voice: component.tts.voice,
volume: component.tts.volume,
rate: component.tts.rate,
pitch: component.tts.pitch,
},
};
delete component.tts.voice;
delete component.tts.volume;
delete component.tts.rate;
delete component.tts.pitch;
}
}
}
}
}
overlay.items = JSON.stringify(overlay.items);

// save back to db
await queryRunner.query(`DELETE FROM \`overlay\` WHERE \`id\` = '${overlay.id}'`);
await insertItemIntoTable('overlay', overlay, queryRunner);
}
return;
}

public async down(queryRunner: QueryRunner): Promise<void> {
return;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

import { insertItemIntoTable } from '../../../insertItemIntoTable.js';

export class updateTTSSettings1678892044038 implements MigrationInterface {
name = 'updateTTSSettings1678892044038';

public async up(queryRunner: QueryRunner): Promise<void> {
const items = await queryRunner.query(`SELECT * FROM "settings" WHERE "namespace" = '/core/tts' AND "name" = 'service'`);
if (items.length === 0) {
return;
}
const service = JSON.parse(items[0].value) as number;

const randomizers = await queryRunner.query(`SELECT * FROM "randomizer"`);
for (const randomizer of randomizers) {
randomizer.tts = JSON.parse(randomizer.tts);
console.log(`Updating randomizer with id ${randomizer.id} for new TTS settings.`);
randomizer.tts.selectedService = String(service);
randomizer.tts.services = {
[String(service)]: {
voice: randomizer.tts.voice,
volume: randomizer.tts.volume,
rate: randomizer.tts.rate,
pitch: randomizer.tts.pitch,
},
};
delete randomizer.tts.voice;
delete randomizer.tts.volume;
delete randomizer.tts.rate;
delete randomizer.tts.pitch;
randomizer.tts = JSON.stringify(randomizer.tts);

// save back to db
await queryRunner.query(`DELETE FROM "randomizer" WHERE "id" = '${randomizer.id}'`);
await insertItemIntoTable('randomizer', randomizer, queryRunner);
}

const overlays = await queryRunner.query(`SELECT * FROM "overlay"`);
for (const overlay of overlays) {
overlay.items = JSON.parse(overlay.items);
for (const item of overlay.items) {
// update TTS overlay
if (item.opts.typeId === 'tts') {
console.log(`Updating TTS overlay with id ${item.id} for new TTS settings.`);
item.opts.selectedService = String(service);
item.opts.services = {
[String(service)]: {
voice: item.opts.voice,
volume: item.opts.volume,
rate: item.opts.rate,
pitch: item.opts.pitch,
},
};
delete item.opts.voice;
delete item.opts.volume;
delete item.opts.rate;
delete item.opts.pitch;
}

// update Alert overlay
if (item.opts.typeId === 'alerts') {
console.log(`Updating Alert overlay with id ${item.id} for new TTS settings.`);
item.opts.tts.selectedService = String(service);
item.opts.tts.services = {
[String(service)]: {
voice: item.opts.tts.voice,
volume: item.opts.tts.volume,
rate: item.opts.tts.rate,
pitch: item.opts.tts.pitch,
},
};
delete item.opts.tts.voice;
delete item.opts.tts.volume;
delete item.opts.tts.rate;
delete item.opts.tts.pitch;

for (const group of item.opts.items) {
for (const variant of group.variants) {
for (const component of variant.items) {
if (component.type === 'tts') {
if (component.tts == null) {
console.log(`\t== Skipping Alert overlay variant component with id ${component.id} as it is inheriting global value.`);
continue;
}
console.log(`\t== Updating Alert overlay variant component with id ${component.id} for new TTS settings.`);
component.tts.selectedService = String(service);
component.tts.services = {
[String(service)]: {
voice: component.tts.voice,
volume: component.tts.volume,
rate: component.tts.rate,
pitch: component.tts.pitch,
},
};
delete component.tts.voice;
delete component.tts.volume;
delete component.tts.rate;
delete component.tts.pitch;
}
}
}
for (const component of group.items) {
if (component.type === 'tts') {
if (component.tts == null) {
console.log(`\t== Skipping Alert overlay component with id ${component.id} as it is inheriting global value.`);
continue;
}
console.log(`\t== Updating Alert overlay component with id ${component.id} for new TTS settings.`);
component.tts.selectedService = String(service);
component.tts.services = {
[String(service)]: {
voice: component.tts.voice,
volume: component.tts.volume,
rate: component.tts.rate,
pitch: component.tts.pitch,
},
};
delete component.tts.voice;
delete component.tts.volume;
delete component.tts.rate;
delete component.tts.pitch;
}
}
}
}
}
overlay.items = JSON.stringify(overlay.items);

// save back to db
await queryRunner.query(`DELETE FROM "overlay" WHERE "id" = '${overlay.id}'`);
await insertItemIntoTable('overlay', overlay, queryRunner);
}
return;
}

public async down(queryRunner: QueryRunner): Promise<void> {
return;
}

}
Loading