0
0
Fork 0
tts-stuff/index.html

274 lines
7.1 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="robots" content="noindex, nofollow" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<style>
:root {
--acolor: black;
}
@supports (color-scheme: dark only) {
:root[data-darkmode='f'] {
color-scheme: dark only;
--acolor: white;
}
}
* {
touch-action: manipulation;
}
a,
a:link,
a:visited,
a:hover,
a:focus,
a:active {
text-decoration: none;
color: var(--acolor);
}
hr {
visibility: hidden;
}
#div1 {
height: 100vh;
}
@supports (max-width: 100dvw) {
#div1 {
height: 100dvh;
}
}
</style>
<script>
if (
CSS.supports('color-scheme', 'dark only') &&
location.hash !== '#forceDarkMode=0'
) {
document.documentElement.dataset.darkmode = 'f'
}
</script>
</head>
<body style="padding: 0; margin: 0">
<div
id="div1"
style="
display: grid;
margin-left: 8px;
margin-right: 8px;
overflow-x: hidden;
overflow-y: auto;
grid-template-rows: auto auto auto 1fr;
"
>
<hr />
<div
style="
display: grid;
overflow-x: hidden;
overflow-y: auto;
grid-template-columns: repeat(2, minmax(0, 1fr));
"
>
<div style="overflow-x: hidden; overflow-y: auto">
Load file
<br />
<input type="file" id="file" autocomplete="off" />
</div>
<div style="display: grid; place-items: center end">
<span>
<input
type="checkbox"
id="forceDarkMode"
autocomplete="off"
disabled
/>
<label for="forceDarkMode">Force dark mode</label>
</span>
</div>
</div>
<hr />
<textarea style="resize: none" autocomplete="off"></textarea>
<hr />
<div
style="
display: grid;
overflow-x: auto;
overflow-y: auto;
place-items: center;
"
>
Voice:
<input type="text" id="voice" autocomplete="off" />
</div>
<hr />
<div
style="
display: grid;
overflow-x: auto;
overflow-y: auto;
place-items: center;
"
>
<button id="convert">Convert</button>
<br />
<a href="https://git.projectsegfau.lt/a/tts-stuff" target="_blank"
>Source code</a
>
<a href="license.txt" target="_blank">License (MIT)</a>
</div>
<hr />
</div>
<div
id="div2"
style="
display: none;
overflow-x: hidden;
overflow-y: auto;
place-items: start center;
"
>
<hr />
<span id="audioSpan"></span>
<hr />
<span id="durationSpan"></span>
<hr />
<span id="saveSpan"></span>
<hr />
<button id="back">Back</button>
<hr />
</div>
<script>
let blobUrl = null
document.querySelector('#file').onchange = evt => {
if (evt.target.files.length > 0) {
const reader = new FileReader()
reader.onload = readerEvent => {
document.querySelector('textarea').value = readerEvent.target.result
document.querySelector('#file').value = ''
}
reader.readAsText(evt.target.files[0])
}
}
document.querySelector('#convert').onclick = () => {
const text = document.querySelector('textarea').value
if (text.length === 0) {
return
}
const voice = document.querySelector('#voice').value.trim()
if (voice.length === 0) {
return
}
document.querySelector('#div1').style.setProperty('display', 'none')
fetch('/', {
method: 'POST',
body: JSON.stringify([voice, text])
})
.then(r => {
if (r.ok) {
r.blob()
.then(blob => {
document.querySelector(
'#durationSpan'
).textContent = `Duration: ${r.headers.get('Duration')}`
blobUrl = URL.createObjectURL(blob)
const a = document.createElement('a')
a.innerText = 'Save'
a.href = blobUrl
switch (r.headers.get('Content-Type')) {
case 'audio/wav':
a.download = 'file.wav'
break
case 'audio/flac':
a.download = 'file.flac'
break
default:
a.download = 'file'
}
const audio = document.createElement('audio')
audio.src = blobUrl
audio.controls = 'controls'
document.querySelector('#saveSpan').appendChild(a)
document.querySelector('#audioSpan').appendChild(audio)
document
.querySelector('#div2')
.style.setProperty('display', 'grid')
})
.catch(() => {
document
.querySelector('#div1')
.style.setProperty('display', 'grid')
})
} else {
document
.querySelector('#div1')
.style.setProperty('display', 'grid')
}
})
.catch(() => {
document.querySelector('#div1').style.setProperty('display', 'grid')
})
}
document.querySelector('#back').onclick = () => {
document.querySelector('#div2').style.setProperty('display', 'none')
document.querySelector('#durationSpan').textContent = ''
document
.querySelector('#saveSpan')
.removeChild(document.querySelector('#saveSpan a'))
document
.querySelector('#audioSpan')
.removeChild(document.querySelector('#audioSpan audio'))
URL.revokeObjectURL(blobUrl)
blobUrl = null
document.querySelector('#div1').style.setProperty('display', 'grid')
}
if (CSS.supports('color-scheme', 'dark only')) {
document.querySelector('#forceDarkMode').disabled = false
document.querySelector('#forceDarkMode').checked =
'darkmode' in document.documentElement.dataset &&
document.documentElement.dataset.darkmode === 'f'
document.querySelector('#forceDarkMode').onclick = () => {
if (location.hash === '#forceDarkMode=0') {
location.href = location.href.substring(
0,
location.href.indexOf('#')
)
} else {
location.hash = '#forceDarkMode=0'
location.reload()
}
}
}
</script>
</body>
</html>