Skip to content

Commit

Permalink
Added TTS feature + some clean-up
Browse files Browse the repository at this point in the history
  • Loading branch information
svub committed Sep 29, 2023
1 parent 8d283f3 commit f0444ca
Show file tree
Hide file tree
Showing 11 changed files with 30,421 additions and 82 deletions.
30,077 changes: 30,077 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@vue/language-plugin-pug": "^1.8.11",
"core-js": "^3.6.5",
"hash-sum": "^2.0.0",
"jszip": "^3.10.1",
"lodash": "^4.17.21",
"microsoft-cognitiveservices-speech-sdk": "^1.32.0",
"showdown": "^1.9.1",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
"vue-property-decorator": "^8.4.2",
"vue-router": "^3.2.0",
"vuex": "^3.4.0",
"vuex-class": "^0.3.2"
"vuex-class": "^0.3.2",
"vuex-map-fields": "^1.4.1",
"vuex-persist": "^3.1.3"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.33.0",
Expand All @@ -27,6 +34,7 @@
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-typescript": "^5.0.2",
"@vue/runtime-dom": "^3.3.4",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"stylus": "^0.54.7",
Expand Down
17 changes: 8 additions & 9 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/sources">Sources</router-link>
</div>
<router-view/>
</div>
<template lang="pug">
#app
h1 Text adventure book creator!
//- #nav
//- router-link(to="/") Home
//- router-link(to="/sources") Sources
router-view
</template>

<style lang="stylus">
#app
font-family Avenir, Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
text-align left
color #2c3e50
margin-top 60px
</style>
7 changes: 5 additions & 2 deletions src/Parser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import showdown from 'showdown';
import hash from 'hash-sum';
import { Book, Chapter, Section, ElementType, Paragraph, If, Else, HasElements, Link, ChangeState, AddItem, RemoveItem,
SpecialLink, Style, Specials, Option, Config, Item, MediaType, Choice, FeedbackMode } from './shared/entities';
import { Token, TokenType } from './Lexer';
import { Functions } from './shared/entities';
import { parseBool } from './shared/util';
import showdown from 'showdown';

export enum CommandType {
book = 'book',
Expand Down Expand Up @@ -403,9 +404,11 @@ export default class Parser {
}
} else { // text
const container = topContainer(token);
const text = parseMarkdown(token.data);
const element: Paragraph = {
type: ElementType.paragraph,
text: parseMarkdown(token.data),
text,
id: hash(text),
};
container.elements.push(element);
}
Expand Down
22 changes: 11 additions & 11 deletions src/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import Home from '../views/Home.vue'
import Sources from '../views/Sources.vue'

Vue.use(VueRouter)

const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/sources',
name: 'Sources',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "sources" */ '../views/Sources.vue')
}
component: Sources
},
// {
// path: '/sources',
// name: 'Sources',
// // route level code-splitting
// // this generates a separate chunk (about.[hash].js) for this route
// // which is lazy-loaded when the route is visited.
// component: () => import(/* webpackChunkName: "sources" */ '../views/Sources.vue')
// }
]

const router = new VueRouter({
Expand Down
4 changes: 4 additions & 0 deletions src/shared/entities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export interface HasElements {
elements: Element[];
}
export function hasElements(element: any): element is HasElements {
return (element as HasElements).elements !== undefined;
}

// generics
export interface Entity {
Expand Down Expand Up @@ -131,6 +134,7 @@ export type Element = {
export interface Paragraph extends Element {
type: ElementType.paragraph;
text: string;
id: string;
}

export interface If extends Element, HasElements {
Expand Down
46 changes: 44 additions & 2 deletions src/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,73 @@
import Vue from 'vue'
import Vuex from 'vuex'
import { Book, Config } from '../shared/entities';
import VuexPersistence from 'vuex-persist';
import { getField, updateField } from 'vuex-map-fields';

Vue.use(Vuex)

interface AppState {
book: Book | null;
config: Config | null;
sources: string;
azureKey: string | null;
azureRegion: string | null;
azureVoice: string | null;
decisionIntroSingle: string;
decisionIntro: string;
decisionConjunction: string;
maxMBytesPerZip: number;
replacements: string;
synthesisProgress: number;
synthesisLimit: number;
}

const state: AppState = {
book: null,
config: null,
sources: "",
azureKey: null,
azureRegion: null,
azureVoice: "de-DE-KillianNeural",
decisionIntroSingle: "",
decisionIntro: "",
decisionConjunction: "",
maxMBytesPerZip: 100,
replacements: "",
synthesisProgress: 0,
synthesisLimit: 0,
}

export default new Vuex.Store({
const vuexLocal = new VuexPersistence<AppState>({
storage: window.localStorage
})

export default new Vuex.Store<AppState>({
state,
getters: {
getField,
},
mutations: {
setBook(state, { book }: { book: Book }) {
state.book = book;
},
setConfig(state, { config }: { config: Config }) {
state.config = config;
},
// setAzureKey(state, azureKey: string) {
// state.azureKey = azureKey;
// },
// setAzureRegion(state, azureRegion: string) {
// state.azureRegion = azureRegion;
// },
// setAzureVoice(state, azureVoice: string) {
// state.azureVoice = azureVoice;
// },
updateField,
},
actions: {
},
modules: {
}
},
plugins: [vuexLocal.plugin],
})
33 changes: 33 additions & 0 deletions src/util/audio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ResultReason, SpeechConfig, SpeechSynthesisOutputFormat, SpeechSynthesizer }
from "microsoft-cognitiveservices-speech-sdk";

export type TextAndFilename = {
text: string;
filename: string;
}

export function createSynthesizer(key, region, voice) {
const speechConfig = SpeechConfig.fromSubscription(key, region);
speechConfig.speechSynthesisOutputFormat = SpeechSynthesisOutputFormat.Audio48Khz96KBitRateMonoMp3;
speechConfig.speechSynthesisVoiceName = voice;
return new SpeechSynthesizer(speechConfig);
}

export async function synthesizeAudio(text: string, synthesizer: SpeechSynthesizer) {
const result = new Promise<{ data: ArrayBuffer; message: string }>((resolve, reject) => {
synthesizer.speakTextAsync(text,
result => {
if (result.reason === ResultReason.SynthesizingAudioCompleted) {
resolve({
data: result.audioData,
message: `Synthesis successful. Audio data: ${result.audioData.byteLength} bytes.`
});
} else if (result.reason === ResultReason.Canceled) {
reject("Synthesis failed. Error detail: " + result.errorDetails);
}
reject("Unknown error " + result);
},
err => reject("Error: " + err));
});
return result;
}
42 changes: 42 additions & 0 deletions src/util/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Book, ElementType, HasElements, Reference, hasElements, Element } from "@/shared/entities";

export function downloadFile(data: Blob, fileName: string) {
const url = window.URL.createObjectURL(data);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(url);
link.remove();
}

export type ElementReference<Type extends Element> = Reference & {
element: Type;
}

export function getAllElements<Type extends Element>(book: Book, filterType?: ElementType): ElementReference<Type>[] {
function findElements(chapterId: string, sectionId: string, has: HasElements) {
const results: ElementReference<Type>[] = [];
has.elements.forEach((element) => {
if (hasElements(element)) {
results.push(...findElements(chapterId, sectionId, element as HasElements));
}
if (filterType && element.type != filterType) return;
results.push({
chapterId,
sectionId,
element: element as Type,
});
});
return results;
}

const elements: ElementReference<Type>[] = [];
book.chapters.forEach((chapter) => {
chapter.sections.forEach((section) => {
elements.push(...findElements(chapter.id, section.id, section));
});
});
return elements;
}
Loading

0 comments on commit f0444ca

Please sign in to comment.