[+] refactor book1
1. move book1 into a private repo;
This commit is contained in:
parent
584b4b652f
commit
136b5709b0
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -21,4 +21,4 @@
|
||||
url = https://gitea.fxreader.online/nartes/com.github.aiortc.aiortc
|
||||
[submodule "deps/online.fxreader.nartes.books"]
|
||||
path = deps/online.fxreader.nartes.books
|
||||
url = https://gitea.fxreader.online/nartes/books
|
||||
url = ssh://gitea@127.0.0.1:19000/nartes/books.git
|
||||
|
2
d2/book1/NoSleep.min.js
vendored
2
d2/book1/NoSleep.min.js
vendored
File diff suppressed because one or more lines are too long
4756
d2/book1/book.js
4756
d2/book1/book.js
File diff suppressed because it is too large
Load Diff
@ -1,78 +0,0 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.6.0.slim.min.js"
|
||||
integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI="
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
|
||||
<title>Speech synthesiser</title>
|
||||
|
||||
<script>
|
||||
window.context = {};
|
||||
window.context.loading = {};
|
||||
window.context.books = [];
|
||||
</script>
|
||||
<script src="NoSleep.min.js"></script>
|
||||
<script src="script.js"></script>
|
||||
<script src="book.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class=voice-settings>
|
||||
<h1>Speech synthesiser</h1>
|
||||
|
||||
<p>Enter some text in the input below and press return or the "play" button to hear it. change voices using the dropdown menu.</p>
|
||||
|
||||
<form>
|
||||
<input type="text" class="txt">
|
||||
<div>
|
||||
<label for="rate">Rate</label><input type="range" min="0.5" max="2" value="1" step="0.1" id="rate">
|
||||
<div class="rate-value">1</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="pitch">Pitch</label><input type="range" min="0" max="2" value="1" step="0.1" id="pitch">
|
||||
<div class="pitch-value">1</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<select class=voice-select>
|
||||
</select>
|
||||
<div class="controls">
|
||||
<button id="play" type="submit">Play</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class=screen>
|
||||
<div class=widget>
|
||||
<select name=book>
|
||||
<!--<option value=0>Death of a Hear</option>-->
|
||||
</select>
|
||||
<br/>
|
||||
<span>Current Sentence: </span>
|
||||
<input type=input name=current-sentence></input>
|
||||
<span>Total Sentences: </span>
|
||||
<input type=input name=total-sentences disabled>
|
||||
</input>
|
||||
<br/>
|
||||
<input type=button name=add-book value="Add Book">
|
||||
<input type=button name=read-aloud value="Read Aloud">
|
||||
<input type=button name=debug value="Debug">
|
||||
</input>
|
||||
<br/>
|
||||
</div>
|
||||
<pre class=status>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,550 +0,0 @@
|
||||
class Future {
|
||||
constructor() {
|
||||
let self = this;
|
||||
|
||||
self.promise = new Promise(
|
||||
(resolve, reject) => {
|
||||
self.resolve = resolve;
|
||||
self.reject = reject;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
context.loading.script = new Future();
|
||||
|
||||
$(window).on('load', async () => {
|
||||
|
||||
var synth = window.speechSynthesis;
|
||||
|
||||
var inputForm = document.querySelector('form');
|
||||
var inputTxt = document.querySelector('.txt');
|
||||
var voiceSelect = document.querySelector('select');
|
||||
|
||||
var pitch = document.querySelector('#pitch');
|
||||
var pitchValue = document.querySelector('.pitch-value');
|
||||
var rate = document.querySelector('#rate');
|
||||
var rateValue = document.querySelector('.rate-value');
|
||||
|
||||
var voices = [];
|
||||
|
||||
|
||||
context.nosleep_timer = null;
|
||||
|
||||
context.ui = {
|
||||
voice_settings_div: $('.voice-settings'),
|
||||
voice_select: $('.voice-select'),
|
||||
status_pre: $('.status'),
|
||||
books_select: $('.screen .widget select[name=book]'),
|
||||
current_sentence_input:
|
||||
$('.screen .widget input[name=current-sentence]'),
|
||||
total_sentences_input:
|
||||
$('.screen .widget input[name=total-sentences]'),
|
||||
read_aloud:
|
||||
$('.screen .widget input[name=read-aloud]'),
|
||||
add_book:
|
||||
$('.screen .widget input[name=add-book]'),
|
||||
debug:
|
||||
$('.screen .widget input[name=debug]'),
|
||||
};
|
||||
context.update_books = () => {
|
||||
context.ui.books_select.empty();
|
||||
window.context.books.map(
|
||||
(o, i) =>
|
||||
$('<option>')
|
||||
.attr('value', '' + i)
|
||||
.attr('url', o.url || '')
|
||||
.text(o.text.slice(0, 10))
|
||||
).forEach((o) => context.ui.books_select.append(o))
|
||||
}
|
||||
|
||||
context.update_books();
|
||||
|
||||
context.sentences = null;
|
||||
context.pending_stop = false;
|
||||
context.current_book = null;
|
||||
context.nosleep = new NoSleep();
|
||||
context.is_debug = false;
|
||||
context.log = {
|
||||
error: [],
|
||||
info: [],
|
||||
};
|
||||
context.callbacks = {
|
||||
log_error: (msg) => {
|
||||
if (context.is_debug)
|
||||
{
|
||||
console.error(msg);
|
||||
context.log.error.push(msg);
|
||||
}
|
||||
},
|
||||
enable_no_sleep: () => {
|
||||
if (context.nosleep_timer != null)
|
||||
{
|
||||
context.callbacks.log_error('running already');
|
||||
}
|
||||
|
||||
context.nosleep_timer = setInterval(
|
||||
() => {
|
||||
location.hash = 'nosleep' + Math.random();
|
||||
context.callbacks.update_status();
|
||||
/*
|
||||
if ('vibrate' in window.navigator)
|
||||
{
|
||||
window.navigator.vibrate(200);
|
||||
}
|
||||
*/
|
||||
}, 1000
|
||||
);
|
||||
},
|
||||
get_state: () => {
|
||||
let t1 = localStorage['state'];
|
||||
if (t1)
|
||||
{
|
||||
return JSON.parse(t1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return {};
|
||||
}
|
||||
},
|
||||
get_cookie: (key) => {
|
||||
/*
|
||||
return document.cookie.split('; ').map(
|
||||
(o) => o.split('=')
|
||||
).reduce(
|
||||
(b, a) => {
|
||||
if (a.length == 2) {b[a[0]] = a[1]};
|
||||
return b
|
||||
},
|
||||
{}
|
||||
)[key];
|
||||
*/
|
||||
let t1 = localStorage['state'];
|
||||
if (t1 != undefined)
|
||||
{
|
||||
let t2 = JSON.parse(t1);
|
||||
return t2[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
set_cookie: (key, value) => {
|
||||
let state = context.callbacks.get_state('state');
|
||||
|
||||
state[key] = value;
|
||||
|
||||
//document.cookie = `${key}=${value};`;
|
||||
localStorage['state'] = JSON.stringify(state);
|
||||
|
||||
context.callbacks.update_status();
|
||||
},
|
||||
disable_no_sleep: () => {
|
||||
if (context.nosleep_timer == null)
|
||||
{
|
||||
context.callbacks.log_error('nothing is running');
|
||||
}
|
||||
clearInterval(context.nosleep_timer);
|
||||
location.hash = '';
|
||||
context.nosleep_timer = null;
|
||||
synth.cancel();
|
||||
},
|
||||
continuous_reading: async() => {
|
||||
if (context.is_reading)
|
||||
{
|
||||
context.pending_stop = true;
|
||||
return;
|
||||
}
|
||||
context.is_reading = true;
|
||||
context.nosleep.enable();
|
||||
context.callbacks.enable_no_sleep();
|
||||
context.ui.voice_settings_div.addClass('hidden');
|
||||
context.ui.current_sentence_input.attr(
|
||||
'disabled',
|
||||
'disabled'
|
||||
);
|
||||
|
||||
while (
|
||||
context.callbacks.get_cookie('sentence_id') < context.sentences.length &&
|
||||
!context.pending_stop
|
||||
)
|
||||
{
|
||||
let sentence =
|
||||
context.sentences[context.callbacks.get_cookie('sentence_id')];
|
||||
//context.callbacks.log_error('start');
|
||||
try {
|
||||
await context.read_aloud(
|
||||
context.sentences[
|
||||
context.callbacks.get_cookie('sentence_id')
|
||||
]
|
||||
);
|
||||
} catch (e) {
|
||||
context.callbacks.log_error(e);
|
||||
}
|
||||
//context.callbacks.log_error('finished');
|
||||
if (!context.pending_stop)
|
||||
{
|
||||
context.callbacks.set_cookie(
|
||||
'sentence_id',
|
||||
context.callbacks.get_cookie('sentence_id') + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
context.pending_stop = false;
|
||||
context.ui.current_sentence_input.removeAttr('disabled');
|
||||
context.nosleep.disable();
|
||||
context.ui.voice_settings_div.removeClass('hidden');
|
||||
context.callbacks.disable_no_sleep();
|
||||
context.is_reading = false;
|
||||
},
|
||||
update_status: () => {
|
||||
let data = {};
|
||||
data.state = context.callbacks.get_state();
|
||||
if (
|
||||
context.callbacks.get_cookie('sentence_id') != null &&
|
||||
context.sentences != null &&
|
||||
context.callbacks.get_cookie('sentence_id') < context.sentences.length
|
||||
)
|
||||
{
|
||||
data.sentence = context.sentences[context.callbacks.get_cookie('sentence_id')];
|
||||
}
|
||||
data.pending_stop = context.pending_stop;
|
||||
data.is_reading = context.is_reading;
|
||||
data.log = context.log;
|
||||
context.ui.current_sentence_input.val(
|
||||
context.callbacks.get_cookie('sentence_id')
|
||||
);
|
||||
data.timestamp = (new Date());
|
||||
data.version = 'v0.1.7';
|
||||
data.speech_synthesis = {
|
||||
paused: synth.paused,
|
||||
pending: synth.pending,
|
||||
speaking: synth.speaking,
|
||||
};
|
||||
/*
|
||||
if (!synth.speaking && context.is_reading)
|
||||
{
|
||||
synth.cancel();
|
||||
}
|
||||
*/
|
||||
context.ui.status_pre.text(
|
||||
JSON.stringify(
|
||||
data,
|
||||
null,
|
||||
4,
|
||||
)
|
||||
);
|
||||
},
|
||||
ui_read_aloud_on_click: async() => {
|
||||
let book_id = parseInt(context.ui.books_select.val());
|
||||
if (context.current_book != book_id)
|
||||
{
|
||||
if (context.books[book_id].url)
|
||||
{
|
||||
context.callbacks.set_cookie(
|
||||
'book_url',
|
||||
context.books[book_id].url,
|
||||
);
|
||||
}
|
||||
|
||||
context.current_book = book_id;
|
||||
context.sentences =
|
||||
context.books[
|
||||
context.current_book
|
||||
].text.replaceAll(/([\.\?\!])\s+/g,'$1\n')
|
||||
.split('\n');
|
||||
context.ui.total_sentences_input.val(
|
||||
context.sentences.length,
|
||||
);
|
||||
{
|
||||
let state = context.callbacks.get_state();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
context.ui.current_sentence_input.val() != ''
|
||||
)
|
||||
{
|
||||
try{
|
||||
let sentence_id = parseInt(
|
||||
context.ui.current_sentence_input.val()
|
||||
);
|
||||
|
||||
if (
|
||||
sentence_id >= 0 &&
|
||||
sentence_id < context.sentences.length
|
||||
)
|
||||
{
|
||||
context.callbacks.set_cookie(
|
||||
'sentence_id',
|
||||
sentence_id
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
context.callbacks.log_error(e);
|
||||
}
|
||||
}
|
||||
if (context.is_reading && !context.pending_stop)
|
||||
{
|
||||
context.pending_stop = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.callbacks.continuous_reading();
|
||||
}
|
||||
},
|
||||
book_add: async (url) => {
|
||||
let book = await (
|
||||
(await fetch(url)).text()
|
||||
);
|
||||
//let book = prompt('enter text', '');
|
||||
//let title = prompt('enter title', '');
|
||||
//window.context.books.push(title + '\n' + book);
|
||||
window.context.books.push({
|
||||
text: book, url
|
||||
});
|
||||
window.context.update_books();
|
||||
},
|
||||
populateVoiceList: () => {
|
||||
voices = synth.getVoices().sort(function (a, b) {
|
||||
const aname = a.name.toUpperCase(), bname = b.name.toUpperCase();
|
||||
if ( aname < bname ) return -1;
|
||||
else if ( aname == bname ) return 0;
|
||||
else return +1;
|
||||
});
|
||||
//var selectedIndex = voiceSelect.selectedIndex < 0 ? 0 : voiceSelect.selectedIndex;
|
||||
voiceSelect.innerHTML = '';
|
||||
for(i = 0; i < voices.length ; i++) {
|
||||
var option = document.createElement('option');
|
||||
option.textContent = voices[i].name + ' (' + voices[i].lang + ')';
|
||||
|
||||
if(voices[i].default) {
|
||||
option.textContent += ' -- DEFAULT';
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
let voice = context.callbacks.get_cookie('voice');
|
||||
if (voice && option.textContent == voice)
|
||||
{
|
||||
$(option).attr('selected', 'selected');
|
||||
}
|
||||
}
|
||||
|
||||
option.setAttribute('data-lang', voices[i].lang);
|
||||
option.setAttribute('data-name', voices[i].name);
|
||||
voiceSelect.appendChild(option);
|
||||
}
|
||||
|
||||
//voiceSelect.selectedIndex = selectedIndex;
|
||||
},
|
||||
init: async () => {
|
||||
let state = context.callbacks.get_state();
|
||||
context.ui.voice_select.val(state.voice);
|
||||
if (!state.book_id)
|
||||
{
|
||||
context.callbacks.set_cookie(
|
||||
'book_id',
|
||||
0,
|
||||
);
|
||||
}
|
||||
if (!state.sentence_id)
|
||||
{
|
||||
context.callbacks.set_cookie(
|
||||
'sentence_id',
|
||||
0,
|
||||
);
|
||||
}
|
||||
if (state.book_url)
|
||||
{
|
||||
await context.callbacks.book_add(state.book_url);
|
||||
state.book_id = context.books.length - 1;
|
||||
}
|
||||
|
||||
if (state.book_id)
|
||||
{
|
||||
context.ui.books_select.find(
|
||||
'>option',
|
||||
).eq(state.book_id).attr('selected', 'selected');
|
||||
}
|
||||
|
||||
if (state.sentence_id)
|
||||
{
|
||||
context.ui.current_sentence_input.val(
|
||||
state.sentence_id,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
context.callbacks.populateVoiceList();
|
||||
if (speechSynthesis.onvoiceschanged !== undefined) {
|
||||
speechSynthesis.onvoiceschanged = context.callbacks.populateVoiceList;
|
||||
}
|
||||
|
||||
await context.callbacks.init();
|
||||
|
||||
context.ui.add_book.on(
|
||||
'click',
|
||||
async () => {
|
||||
// alert('fuck');
|
||||
let url = 'books/' + prompt('enter book file', '1.txt');
|
||||
|
||||
await context.callbacks.book_add(url);
|
||||
},
|
||||
);
|
||||
context.ui.read_aloud.on(
|
||||
'click',
|
||||
context.callbacks.ui_read_aloud_on_click,
|
||||
);
|
||||
context.ui.voice_select.on(
|
||||
'change',
|
||||
() => {
|
||||
context.callbacks.set_cookie(
|
||||
'voice',
|
||||
context.ui.voice_select.val()
|
||||
);
|
||||
}
|
||||
);
|
||||
context.ui.debug.on(
|
||||
'click',
|
||||
() => {
|
||||
if (context.is_debug)
|
||||
{
|
||||
context.is_debug = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.is_debug = true;
|
||||
}
|
||||
context.callbacks.update_status();
|
||||
}
|
||||
);
|
||||
context.read_aloud = async (raw_line) => {
|
||||
line = raw_line.trim();
|
||||
if (line.length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let sleep_detect = null;
|
||||
let exit = () => {
|
||||
if (sleep_detect != null)
|
||||
{
|
||||
clearInterval(sleep_detect);
|
||||
}
|
||||
}
|
||||
return new Promise((response, reject) => {
|
||||
if (synth.speaking) {
|
||||
context.callbacks.log_error('speechSynthesis.speaking');
|
||||
if (reject != undefined)
|
||||
{
|
||||
reject('error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
let utterThis = new SpeechSynthesisUtterance(line);
|
||||
utterThis.onend = function (event) {
|
||||
exit();
|
||||
context.callbacks.log_error(
|
||||
'SpeechSynthesisUtterance.onend ' + event.error
|
||||
);
|
||||
if (response != undefined)
|
||||
{
|
||||
response('done ' + event.error);
|
||||
}
|
||||
}
|
||||
utterThis.onpause = function (event) {
|
||||
exit();
|
||||
context.callbacks.log_error('SpeechSynthesisUtterance.onpause');
|
||||
if (reject != undefined)
|
||||
{
|
||||
reject('paused ' + event.error);
|
||||
}
|
||||
}
|
||||
utterThis.onerror = function (event) {
|
||||
exit();
|
||||
context.callbacks.log_error(
|
||||
'SpeechSynthesisUtterance.onerror ' + event.error
|
||||
);
|
||||
if (reject != undefined)
|
||||
{
|
||||
reject('error ' + event.error);
|
||||
}
|
||||
}
|
||||
let selectedOption = voiceSelect.selectedOptions[0].getAttribute('data-name');
|
||||
for(i = 0; i < voices.length ; i++) {
|
||||
if(voices[i].name === selectedOption) {
|
||||
utterThis.voice = voices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
//window.alert('fuck3');
|
||||
utterThis.pitch = pitch.value;
|
||||
utterThis.rate = rate.value;
|
||||
synth.speak(utterThis);
|
||||
let silence_count = 0;
|
||||
sleep_detect = setInterval(
|
||||
() => {
|
||||
if (!synth.speaking)
|
||||
{
|
||||
context.callbacks.log_error(
|
||||
'silence count is ' + silence_count
|
||||
)
|
||||
|
||||
++silence_count;
|
||||
}
|
||||
|
||||
if (silence_count == 3 || context.pending_stop)
|
||||
{
|
||||
exit();
|
||||
if (context.pending_stop)
|
||||
{
|
||||
synth.cancel();
|
||||
reject('pending stop');
|
||||
}
|
||||
else
|
||||
{
|
||||
context.callbacks.log_error('phone is sleeping, retry');
|
||||
response('utterance is not present');
|
||||
}
|
||||
/*
|
||||
context.read_aloud(
|
||||
line
|
||||
).then(response).catch(reject);
|
||||
*/
|
||||
}
|
||||
},
|
||||
100,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function speak(){
|
||||
let line = inputTxt.value;
|
||||
if (line !== '') {
|
||||
context.read_aloud(line);
|
||||
}
|
||||
}
|
||||
|
||||
inputForm.onsubmit = function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
speak();
|
||||
|
||||
inputTxt.blur();
|
||||
}
|
||||
|
||||
pitch.onchange = function() {
|
||||
pitchValue.textContent = pitch.value;
|
||||
}
|
||||
|
||||
rate.onchange = function() {
|
||||
rateValue.textContent = rate.value;
|
||||
}
|
||||
|
||||
voiceSelect.onchange = function(){
|
||||
speak();
|
||||
}
|
||||
|
||||
context.loading.script.resolve(true);
|
||||
});
|
@ -1,84 +0,0 @@
|
||||
body, html {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 90%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1, p {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.txt, select, form > div {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.txt {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 83%;
|
||||
}
|
||||
|
||||
form > div {
|
||||
width: 81%;
|
||||
}
|
||||
|
||||
.txt, form > div {
|
||||
margin-bottom: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
label {
|
||||
float: left;
|
||||
width: 10%;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.rate-value, .pitch-value {
|
||||
float: right;
|
||||
width: 5%;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#rate, #pitch {
|
||||
float: right;
|
||||
width: 81%;
|
||||
}
|
||||
|
||||
.controls {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.controls button {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.hidden
|
||||
{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
pre {
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
2
deps/com.github.aiortc.aiortc
vendored
2
deps/com.github.aiortc.aiortc
vendored
@ -1 +1 @@
|
||||
Subproject commit f94ded8fbfe62bea6e96827312eb25853990ecd3
|
||||
Subproject commit e27d400c07ddbe66e1ef73da1a5754c28cfec157
|
1
deps/online.fxreader.nartes.books
vendored
Submodule
1
deps/online.fxreader.nartes.books
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7cc59533c0eddbe04e3b58109d1d861a6f92f40d
|
Loading…
Reference in New Issue
Block a user