diff --git a/tts_frontend/package.json b/tts_frontend/package.json index 85f08a2937..f0b3e6ccc0 100644 --- a/tts_frontend/package.json +++ b/tts_frontend/package.json @@ -17,6 +17,9 @@ }, "scripts": { "start": "PORT=12345 react-scripts start", + "start7000": "PORT=7000 react-scripts start", + "start8000": "PORT=8000 react-scripts start", + "start8080": "PORT=8080 react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/tts_frontend/src/TtsService.ts b/tts_frontend/src/TtsService.ts index e2b0ec6679..4c07ab8603 100644 --- a/tts_frontend/src/TtsService.ts +++ b/tts_frontend/src/TtsService.ts @@ -28,7 +28,7 @@ class TtsResponse { let makeTtsRequest = (sentence: string): Promise => { const request = new TtsRequest(sentence); - const url = `/tts`; + const url = `http://localhost:12345/advanced_tts`; return fetch(url, { method: 'POST', diff --git a/tts_frontend/src/components/MainComponent.tsx b/tts_frontend/src/components/MainComponent.tsx index 3f486bfc6d..e293d8577a 100644 --- a/tts_frontend/src/components/MainComponent.tsx +++ b/tts_frontend/src/components/MainComponent.tsx @@ -1,14 +1,19 @@ import React from 'react'; -import Howl from 'howler'; import TextInput from './TextInput'; import TrackList from './TrackList'; -import { ModelPickerDropdownComponent } from './model_picker/ModelPickerDropdownComponent'; +import { SpeakerModeComponent } from './modes/speaker_mode/SpeakerModeComponent'; + +enum Mode { + SPEAKER, + ADVANCED, +} interface Props { } interface State { utterances: Array + mode: Mode, } class TextAudioPair { @@ -26,7 +31,10 @@ class MainComponent extends React.Component { constructor(props: Props) { super(props); - this.state = { utterances: new Array() }; + this.state = { + utterances: new Array(), + mode: Mode.ADVANCED, + }; } /** Add a new audio track. */ @@ -38,7 +46,40 @@ class MainComponent extends React.Component { this.setState({utterances: utterances}); } + changeMode = () => { + let nextMode; + switch (this.state.mode) { + case Mode.ADVANCED: + nextMode = Mode.SPEAKER; + break; + + case Mode.SPEAKER: + nextMode = Mode.ADVANCED; + break; + } + this.setState({ + mode: nextMode + }); + } + public render() { + let component; + if (this.state.mode == Mode.ADVANCED) { + component = this.renderAdvancedMode(); + } else { + component = this.renderSpeakerMode(); + } + + return ( +
+ +
+ {component} +
+ ) + } + + public renderAdvancedMode() { return (
@@ -46,6 +87,14 @@ class MainComponent extends React.Component {
); } + + public renderSpeakerMode() { + return ( +
+ +
+ ); + } } export {TextAudioPair, MainComponent}; diff --git a/tts_frontend/src/components/TextInput.tsx b/tts_frontend/src/components/TextInput.tsx index 05458024e9..0534218de5 100644 --- a/tts_frontend/src/components/TextInput.tsx +++ b/tts_frontend/src/components/TextInput.tsx @@ -70,7 +70,7 @@ class TextInput extends React.Component { request.arpabet_tacotron_model = this.state.arpabet_tacotron_model || ""; request.melgan_model = this.state.melgan_model || ""; - const url = '/tts'; + const url = 'http://localhost:12345/advanced_tts'; fetch(url, { method: 'POST', headers: { diff --git a/tts_frontend/src/components/model_picker/ModelPickerDropdownComponent.tsx b/tts_frontend/src/components/model_picker/ModelPickerDropdownComponent.tsx index 2d7c08ab31..97f9670102 100644 --- a/tts_frontend/src/components/model_picker/ModelPickerDropdownComponent.tsx +++ b/tts_frontend/src/components/model_picker/ModelPickerDropdownComponent.tsx @@ -26,7 +26,7 @@ class ModelPickerDropdownComponent extends React.Component { } public loadModels() { - const url = '/models'; + const url = 'http://localhost:12345/models'; fetch(url) .then(res => res.json()) .then( diff --git a/tts_frontend/src/components/modes/speaker_mode/SpeakerAudioForm.tsx b/tts_frontend/src/components/modes/speaker_mode/SpeakerAudioForm.tsx new file mode 100644 index 0000000000..dd43fba1a2 --- /dev/null +++ b/tts_frontend/src/components/modes/speaker_mode/SpeakerAudioForm.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import Howl from 'howler'; + +interface Props { + speaker?: String, +} + +interface State { + text?: String, + howl?: Howl, +} + +/// Requests to the "speak" endpoint. +class SpeakRequest { + text: String + speaker: String + + constructor(text: String, speaker: String) { + this.text = text; + this.speaker = speaker; + } +} + +class SpeakerAudioForm extends React.Component { + + constructor(props: Props) { + super(props); + this.state = { + text: undefined, + howl: undefined, + }; + } + + componentDidMount() { + // OnMount events. An async CTOR, essentially. + } + + handleTextChange = (ev: React.FormEvent) => { + const text = (ev.target as HTMLInputElement).value; + this.setState({text: text}); + } + + makeRequest = (ev: React.FormEvent) => { + console.log("Form Submit"); + + if (!this.state.text) { + return; + } + + let request = new SpeakRequest(this.state.text, this.props.speaker!); + + const url = 'http://localhost:12345/speak'; + fetch(url, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request), + }) + .then(res => res.blob()) + .then(blob => { + console.log(blob); + + const url = window.URL.createObjectURL(blob); + console.log(url); + + const sound = new Howl.Howl({ + src: [url], + format: 'wav', + }); + + this.setState({howl: sound}); + sound.play(); + + (window as any).sound = sound; + }); + + ev.preventDefault(); + return false; + } + + public render() { + return ( +
+ + +
+ ); + } +} + +export default SpeakerAudioForm; diff --git a/tts_frontend/src/components/modes/speaker_mode/SpeakerDropdownComponent.tsx b/tts_frontend/src/components/modes/speaker_mode/SpeakerDropdownComponent.tsx new file mode 100644 index 0000000000..77ac1e85ae --- /dev/null +++ b/tts_frontend/src/components/modes/speaker_mode/SpeakerDropdownComponent.tsx @@ -0,0 +1,36 @@ + +import React from 'react'; +import { SpeakerResponse, SpeakerDetails } from './SpeakerResponse'; + +interface Props { + changeCallback: (ev: React.FormEvent) => void, + speakers: SpeakerDetails[] +} + +interface State { +} + +class SpeakerDropdownComponent extends React.Component { + + constructor(props: Props) { + super(props); + } + + public render() { + return ( +
+ Select Speaker +
+ +
+ ); + } +} + +export {SpeakerDropdownComponent}; diff --git a/tts_frontend/src/components/modes/speaker_mode/SpeakerModeComponent.tsx b/tts_frontend/src/components/modes/speaker_mode/SpeakerModeComponent.tsx new file mode 100644 index 0000000000..f041c72757 --- /dev/null +++ b/tts_frontend/src/components/modes/speaker_mode/SpeakerModeComponent.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { SpeakerDetails, SpeakerResponse } from './SpeakerResponse'; +import { SpeakerDropdownComponent } from './SpeakerDropdownComponent'; +import SpeakerAudioForm from './SpeakerAudioForm'; + +interface Props { +} + +interface State { + speaker_response?: SpeakerResponse, + speaker_slug?: string, +} + +class SpeakerModeComponent extends React.Component { + + constructor(props: Props) { + super(props); + this.state = { + speaker_response: undefined, + speaker_slug: undefined, + }; + } + + componentDidMount() { + this.loadSpeakers(); + } + + public loadSpeakers() { + const url = 'http://localhost:12345/speakers'; + fetch(url) + .then(res => res.json()) + .then( + (result) => { + console.log('loaded speakers', result); + let first_speaker = result.speakers[0].slug; + this.setState({ + speaker_response: result, + speaker_slug: first_speaker, + }) + } + ); + } + + changeSpeaker = (ev: React.FormEvent) => { + const speakerSlug = (ev.target as HTMLInputElement).value; + this.setState({ + speaker_slug: speakerSlug, + }) + } + + public render() { + let speakers = this.state.speaker_response?.speakers; + let dropdown; + + if (speakers?.length || 0 > 0) { + dropdown = + } + + return ( +
+ SpeakerMode + {dropdown} + +
+ ); + } +} + +export {SpeakerModeComponent}; diff --git a/tts_frontend/src/components/modes/speaker_mode/SpeakerResponse.ts b/tts_frontend/src/components/modes/speaker_mode/SpeakerResponse.ts new file mode 100644 index 0000000000..e6532c35de --- /dev/null +++ b/tts_frontend/src/components/modes/speaker_mode/SpeakerResponse.ts @@ -0,0 +1,15 @@ + + +class SpeakerResponse { + speakers?: SpeakerDetails[] +} + +class SpeakerDetails { + name?: string + slug?: string + model_pipeline?: string + tacotron?: string + melgan?: string +} + +export {SpeakerResponse, SpeakerDetails}; diff --git a/tts_service/src/main.rs b/tts_service/src/main.rs index 53e772c8cb..2f78ade8d0 100644 --- a/tts_service/src/main.rs +++ b/tts_service/src/main.rs @@ -101,7 +101,7 @@ async fn main() -> std::io::Result<()> { .allowed_origin("http://trumped.com") .allowed_origin("http://jungle.horse") .allowed_methods(vec!["GET", "POST"]) - .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT]) + .allowed_headers(vec![http::header::ACCEPT]) .allowed_header(http::header::CONTENT_TYPE) .max_age(3600) .finish())