Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
xVASynth v3 code for English
Browse filesThis view is limited to 50 files because it contains too many changes. Β
See raw diff
- .gitignore +43 -0
- LICENSE.md +674 -0
- README.md +1 -1
- arpabet/custom.json +17 -0
- arpabet/xvasynth.json +61 -0
- index.html +0 -0
- javascript/appLogger.js +39 -0
- javascript/arpabet.js +498 -0
- javascript/batch.js +1573 -0
- javascript/dragdrop_model_install.js +180 -0
- javascript/editor.js +2089 -0
- javascript/embeddings.js +795 -0
- javascript/i18n.js +1070 -0
- javascript/nexus.js +983 -0
- javascript/outputFiles.js +412 -0
- javascript/plugins_manager.js +594 -0
- javascript/script.js +1730 -0
- javascript/settingsMenu.js +960 -0
- javascript/speech2speech.js +379 -0
- javascript/style_embeddings.js +335 -0
- javascript/textarea.js +580 -0
- javascript/totd.js +145 -0
- javascript/util.js +740 -0
- javascript/workbench.js +497 -0
- lib/AbortControllerPolyfill.js +4 -0
- lib/OrbitControls.js +1102 -0
- lib/Three.min.js +0 -0
- lib/Three.sprite.js +1 -0
- lib/Three.texture.js +213 -0
- lib/TrackballControls.js +778 -0
- lib/ffmpeg_normalize/__init__.py +5 -0
- lib/ffmpeg_normalize/__main__.py +548 -0
- lib/ffmpeg_normalize/_cmd_utils.py +177 -0
- lib/ffmpeg_normalize/_errors.py +15 -0
- lib/ffmpeg_normalize/_ffmpeg_normalize.py +202 -0
- lib/ffmpeg_normalize/_logger.py +83 -0
- lib/ffmpeg_normalize/_media_file.py +371 -0
- lib/ffmpeg_normalize/_streams.py +344 -0
- lib/ffmpeg_normalize/_version.py +1 -0
- lib/osutils.js +213 -0
- lib/wavesurfer.js +0 -0
- lib/xp_error.mp3 +0 -0
- main.js +140 -0
- package.json +27 -0
- patreon.txt +1 -0
- plugins.txt +0 -0
- plugins/eg_custom_event/frontendPlugin.js +33 -0
- plugins/eg_custom_event/main.py +10 -0
- plugins/eg_custom_event/plugin.json +28 -0
- plugins/test_plugin/custom_event.py +5 -0
.gitignore
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
xVAEnv
|
2 |
+
xVAEnvCPU
|
3 |
+
node_modules
|
4 |
+
*.wav
|
5 |
+
downloads/*
|
6 |
+
output/*
|
7 |
+
output/*.wav
|
8 |
+
output/**/*.wav
|
9 |
+
output/*.mp3
|
10 |
+
output/**/*.mp3
|
11 |
+
output/*.ogg
|
12 |
+
output/**/*.ogg
|
13 |
+
output/**/*.json
|
14 |
+
*.ckpt
|
15 |
+
*.todo
|
16 |
+
__pycache__/
|
17 |
+
.cache/
|
18 |
+
*.pyc
|
19 |
+
server.log
|
20 |
+
server.log.*
|
21 |
+
cpython
|
22 |
+
xVASynth-win*
|
23 |
+
model.rar
|
24 |
+
build
|
25 |
+
dist
|
26 |
+
FASTPITCH_LOADING
|
27 |
+
WAVEGLOW_LOADING
|
28 |
+
SERVER_STARTING
|
29 |
+
DEBUG*
|
30 |
+
models
|
31 |
+
release-builds
|
32 |
+
package-lock.json
|
33 |
+
app.log
|
34 |
+
batch
|
35 |
+
python/wav2vec2/pytorch_model.bin
|
36 |
+
python/ffmpeg.exe
|
37 |
+
python/xvapitch/base_v1.0.pt
|
38 |
+
env*
|
39 |
+
embeddings.txt
|
40 |
+
*.exe
|
41 |
+
*.pt
|
42 |
+
*.dll
|
43 |
+
*.bin
|
LICENSE.md
ADDED
@@ -0,0 +1,674 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GNU GENERAL PUBLIC LICENSE
|
2 |
+
Version 3, 29 June 2007
|
3 |
+
|
4 |
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
5 |
+
Everyone is permitted to copy and distribute verbatim copies
|
6 |
+
of this license document, but changing it is not allowed.
|
7 |
+
|
8 |
+
Preamble
|
9 |
+
|
10 |
+
The GNU General Public License is a free, copyleft license for
|
11 |
+
software and other kinds of works.
|
12 |
+
|
13 |
+
The licenses for most software and other practical works are designed
|
14 |
+
to take away your freedom to share and change the works. By contrast,
|
15 |
+
the GNU General Public License is intended to guarantee your freedom to
|
16 |
+
share and change all versions of a program--to make sure it remains free
|
17 |
+
software for all its users. We, the Free Software Foundation, use the
|
18 |
+
GNU General Public License for most of our software; it applies also to
|
19 |
+
any other work released this way by its authors. You can apply it to
|
20 |
+
your programs, too.
|
21 |
+
|
22 |
+
When we speak of free software, we are referring to freedom, not
|
23 |
+
price. Our General Public Licenses are designed to make sure that you
|
24 |
+
have the freedom to distribute copies of free software (and charge for
|
25 |
+
them if you wish), that you receive source code or can get it if you
|
26 |
+
want it, that you can change the software or use pieces of it in new
|
27 |
+
free programs, and that you know you can do these things.
|
28 |
+
|
29 |
+
To protect your rights, we need to prevent others from denying you
|
30 |
+
these rights or asking you to surrender the rights. Therefore, you have
|
31 |
+
certain responsibilities if you distribute copies of the software, or if
|
32 |
+
you modify it: responsibilities to respect the freedom of others.
|
33 |
+
|
34 |
+
For example, if you distribute copies of such a program, whether
|
35 |
+
gratis or for a fee, you must pass on to the recipients the same
|
36 |
+
freedoms that you received. You must make sure that they, too, receive
|
37 |
+
or can get the source code. And you must show them these terms so they
|
38 |
+
know their rights.
|
39 |
+
|
40 |
+
Developers that use the GNU GPL protect your rights with two steps:
|
41 |
+
(1) assert copyright on the software, and (2) offer you this License
|
42 |
+
giving you legal permission to copy, distribute and/or modify it.
|
43 |
+
|
44 |
+
For the developers' and authors' protection, the GPL clearly explains
|
45 |
+
that there is no warranty for this free software. For both users' and
|
46 |
+
authors' sake, the GPL requires that modified versions be marked as
|
47 |
+
changed, so that their problems will not be attributed erroneously to
|
48 |
+
authors of previous versions.
|
49 |
+
|
50 |
+
Some devices are designed to deny users access to install or run
|
51 |
+
modified versions of the software inside them, although the manufacturer
|
52 |
+
can do so. This is fundamentally incompatible with the aim of
|
53 |
+
protecting users' freedom to change the software. The systematic
|
54 |
+
pattern of such abuse occurs in the area of products for individuals to
|
55 |
+
use, which is precisely where it is most unacceptable. Therefore, we
|
56 |
+
have designed this version of the GPL to prohibit the practice for those
|
57 |
+
products. If such problems arise substantially in other domains, we
|
58 |
+
stand ready to extend this provision to those domains in future versions
|
59 |
+
of the GPL, as needed to protect the freedom of users.
|
60 |
+
|
61 |
+
Finally, every program is threatened constantly by software patents.
|
62 |
+
States should not allow patents to restrict development and use of
|
63 |
+
software on general-purpose computers, but in those that do, we wish to
|
64 |
+
avoid the special danger that patents applied to a free program could
|
65 |
+
make it effectively proprietary. To prevent this, the GPL assures that
|
66 |
+
patents cannot be used to render the program non-free.
|
67 |
+
|
68 |
+
The precise terms and conditions for copying, distribution and
|
69 |
+
modification follow.
|
70 |
+
|
71 |
+
TERMS AND CONDITIONS
|
72 |
+
|
73 |
+
0. Definitions.
|
74 |
+
|
75 |
+
"This License" refers to version 3 of the GNU General Public License.
|
76 |
+
|
77 |
+
"Copyright" also means copyright-like laws that apply to other kinds of
|
78 |
+
works, such as semiconductor masks.
|
79 |
+
|
80 |
+
"The Program" refers to any copyrightable work licensed under this
|
81 |
+
License. Each licensee is addressed as "you". "Licensees" and
|
82 |
+
"recipients" may be individuals or organizations.
|
83 |
+
|
84 |
+
To "modify" a work means to copy from or adapt all or part of the work
|
85 |
+
in a fashion requiring copyright permission, other than the making of an
|
86 |
+
exact copy. The resulting work is called a "modified version" of the
|
87 |
+
earlier work or a work "based on" the earlier work.
|
88 |
+
|
89 |
+
A "covered work" means either the unmodified Program or a work based
|
90 |
+
on the Program.
|
91 |
+
|
92 |
+
To "propagate" a work means to do anything with it that, without
|
93 |
+
permission, would make you directly or secondarily liable for
|
94 |
+
infringement under applicable copyright law, except executing it on a
|
95 |
+
computer or modifying a private copy. Propagation includes copying,
|
96 |
+
distribution (with or without modification), making available to the
|
97 |
+
public, and in some countries other activities as well.
|
98 |
+
|
99 |
+
To "convey" a work means any kind of propagation that enables other
|
100 |
+
parties to make or receive copies. Mere interaction with a user through
|
101 |
+
a computer network, with no transfer of a copy, is not conveying.
|
102 |
+
|
103 |
+
An interactive user interface displays "Appropriate Legal Notices"
|
104 |
+
to the extent that it includes a convenient and prominently visible
|
105 |
+
feature that (1) displays an appropriate copyright notice, and (2)
|
106 |
+
tells the user that there is no warranty for the work (except to the
|
107 |
+
extent that warranties are provided), that licensees may convey the
|
108 |
+
work under this License, and how to view a copy of this License. If
|
109 |
+
the interface presents a list of user commands or options, such as a
|
110 |
+
menu, a prominent item in the list meets this criterion.
|
111 |
+
|
112 |
+
1. Source Code.
|
113 |
+
|
114 |
+
The "source code" for a work means the preferred form of the work
|
115 |
+
for making modifications to it. "Object code" means any non-source
|
116 |
+
form of a work.
|
117 |
+
|
118 |
+
A "Standard Interface" means an interface that either is an official
|
119 |
+
standard defined by a recognized standards body, or, in the case of
|
120 |
+
interfaces specified for a particular programming language, one that
|
121 |
+
is widely used among developers working in that language.
|
122 |
+
|
123 |
+
The "System Libraries" of an executable work include anything, other
|
124 |
+
than the work as a whole, that (a) is included in the normal form of
|
125 |
+
packaging a Major Component, but which is not part of that Major
|
126 |
+
Component, and (b) serves only to enable use of the work with that
|
127 |
+
Major Component, or to implement a Standard Interface for which an
|
128 |
+
implementation is available to the public in source code form. A
|
129 |
+
"Major Component", in this context, means a major essential component
|
130 |
+
(kernel, window system, and so on) of the specific operating system
|
131 |
+
(if any) on which the executable work runs, or a compiler used to
|
132 |
+
produce the work, or an object code interpreter used to run it.
|
133 |
+
|
134 |
+
The "Corresponding Source" for a work in object code form means all
|
135 |
+
the source code needed to generate, install, and (for an executable
|
136 |
+
work) run the object code and to modify the work, including scripts to
|
137 |
+
control those activities. However, it does not include the work's
|
138 |
+
System Libraries, or general-purpose tools or generally available free
|
139 |
+
programs which are used unmodified in performing those activities but
|
140 |
+
which are not part of the work. For example, Corresponding Source
|
141 |
+
includes interface definition files associated with source files for
|
142 |
+
the work, and the source code for shared libraries and dynamically
|
143 |
+
linked subprograms that the work is specifically designed to require,
|
144 |
+
such as by intimate data communication or control flow between those
|
145 |
+
subprograms and other parts of the work.
|
146 |
+
|
147 |
+
The Corresponding Source need not include anything that users
|
148 |
+
can regenerate automatically from other parts of the Corresponding
|
149 |
+
Source.
|
150 |
+
|
151 |
+
The Corresponding Source for a work in source code form is that
|
152 |
+
same work.
|
153 |
+
|
154 |
+
2. Basic Permissions.
|
155 |
+
|
156 |
+
All rights granted under this License are granted for the term of
|
157 |
+
copyright on the Program, and are irrevocable provided the stated
|
158 |
+
conditions are met. This License explicitly affirms your unlimited
|
159 |
+
permission to run the unmodified Program. The output from running a
|
160 |
+
covered work is covered by this License only if the output, given its
|
161 |
+
content, constitutes a covered work. This License acknowledges your
|
162 |
+
rights of fair use or other equivalent, as provided by copyright law.
|
163 |
+
|
164 |
+
You may make, run and propagate covered works that you do not
|
165 |
+
convey, without conditions so long as your license otherwise remains
|
166 |
+
in force. You may convey covered works to others for the sole purpose
|
167 |
+
of having them make modifications exclusively for you, or provide you
|
168 |
+
with facilities for running those works, provided that you comply with
|
169 |
+
the terms of this License in conveying all material for which you do
|
170 |
+
not control copyright. Those thus making or running the covered works
|
171 |
+
for you must do so exclusively on your behalf, under your direction
|
172 |
+
and control, on terms that prohibit them from making any copies of
|
173 |
+
your copyrighted material outside their relationship with you.
|
174 |
+
|
175 |
+
Conveying under any other circumstances is permitted solely under
|
176 |
+
the conditions stated below. Sublicensing is not allowed; section 10
|
177 |
+
makes it unnecessary.
|
178 |
+
|
179 |
+
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
180 |
+
|
181 |
+
No covered work shall be deemed part of an effective technological
|
182 |
+
measure under any applicable law fulfilling obligations under article
|
183 |
+
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
184 |
+
similar laws prohibiting or restricting circumvention of such
|
185 |
+
measures.
|
186 |
+
|
187 |
+
When you convey a covered work, you waive any legal power to forbid
|
188 |
+
circumvention of technological measures to the extent such circumvention
|
189 |
+
is effected by exercising rights under this License with respect to
|
190 |
+
the covered work, and you disclaim any intention to limit operation or
|
191 |
+
modification of the work as a means of enforcing, against the work's
|
192 |
+
users, your or third parties' legal rights to forbid circumvention of
|
193 |
+
technological measures.
|
194 |
+
|
195 |
+
4. Conveying Verbatim Copies.
|
196 |
+
|
197 |
+
You may convey verbatim copies of the Program's source code as you
|
198 |
+
receive it, in any medium, provided that you conspicuously and
|
199 |
+
appropriately publish on each copy an appropriate copyright notice;
|
200 |
+
keep intact all notices stating that this License and any
|
201 |
+
non-permissive terms added in accord with section 7 apply to the code;
|
202 |
+
keep intact all notices of the absence of any warranty; and give all
|
203 |
+
recipients a copy of this License along with the Program.
|
204 |
+
|
205 |
+
You may charge any price or no price for each copy that you convey,
|
206 |
+
and you may offer support or warranty protection for a fee.
|
207 |
+
|
208 |
+
5. Conveying Modified Source Versions.
|
209 |
+
|
210 |
+
You may convey a work based on the Program, or the modifications to
|
211 |
+
produce it from the Program, in the form of source code under the
|
212 |
+
terms of section 4, provided that you also meet all of these conditions:
|
213 |
+
|
214 |
+
a) The work must carry prominent notices stating that you modified
|
215 |
+
it, and giving a relevant date.
|
216 |
+
|
217 |
+
b) The work must carry prominent notices stating that it is
|
218 |
+
released under this License and any conditions added under section
|
219 |
+
7. This requirement modifies the requirement in section 4 to
|
220 |
+
"keep intact all notices".
|
221 |
+
|
222 |
+
c) You must license the entire work, as a whole, under this
|
223 |
+
License to anyone who comes into possession of a copy. This
|
224 |
+
License will therefore apply, along with any applicable section 7
|
225 |
+
additional terms, to the whole of the work, and all its parts,
|
226 |
+
regardless of how they are packaged. This License gives no
|
227 |
+
permission to license the work in any other way, but it does not
|
228 |
+
invalidate such permission if you have separately received it.
|
229 |
+
|
230 |
+
d) If the work has interactive user interfaces, each must display
|
231 |
+
Appropriate Legal Notices; however, if the Program has interactive
|
232 |
+
interfaces that do not display Appropriate Legal Notices, your
|
233 |
+
work need not make them do so.
|
234 |
+
|
235 |
+
A compilation of a covered work with other separate and independent
|
236 |
+
works, which are not by their nature extensions of the covered work,
|
237 |
+
and which are not combined with it such as to form a larger program,
|
238 |
+
in or on a volume of a storage or distribution medium, is called an
|
239 |
+
"aggregate" if the compilation and its resulting copyright are not
|
240 |
+
used to limit the access or legal rights of the compilation's users
|
241 |
+
beyond what the individual works permit. Inclusion of a covered work
|
242 |
+
in an aggregate does not cause this License to apply to the other
|
243 |
+
parts of the aggregate.
|
244 |
+
|
245 |
+
6. Conveying Non-Source Forms.
|
246 |
+
|
247 |
+
You may convey a covered work in object code form under the terms
|
248 |
+
of sections 4 and 5, provided that you also convey the
|
249 |
+
machine-readable Corresponding Source under the terms of this License,
|
250 |
+
in one of these ways:
|
251 |
+
|
252 |
+
a) Convey the object code in, or embodied in, a physical product
|
253 |
+
(including a physical distribution medium), accompanied by the
|
254 |
+
Corresponding Source fixed on a durable physical medium
|
255 |
+
customarily used for software interchange.
|
256 |
+
|
257 |
+
b) Convey the object code in, or embodied in, a physical product
|
258 |
+
(including a physical distribution medium), accompanied by a
|
259 |
+
written offer, valid for at least three years and valid for as
|
260 |
+
long as you offer spare parts or customer support for that product
|
261 |
+
model, to give anyone who possesses the object code either (1) a
|
262 |
+
copy of the Corresponding Source for all the software in the
|
263 |
+
product that is covered by this License, on a durable physical
|
264 |
+
medium customarily used for software interchange, for a price no
|
265 |
+
more than your reasonable cost of physically performing this
|
266 |
+
conveying of source, or (2) access to copy the
|
267 |
+
Corresponding Source from a network server at no charge.
|
268 |
+
|
269 |
+
c) Convey individual copies of the object code with a copy of the
|
270 |
+
written offer to provide the Corresponding Source. This
|
271 |
+
alternative is allowed only occasionally and noncommercially, and
|
272 |
+
only if you received the object code with such an offer, in accord
|
273 |
+
with subsection 6b.
|
274 |
+
|
275 |
+
d) Convey the object code by offering access from a designated
|
276 |
+
place (gratis or for a charge), and offer equivalent access to the
|
277 |
+
Corresponding Source in the same way through the same place at no
|
278 |
+
further charge. You need not require recipients to copy the
|
279 |
+
Corresponding Source along with the object code. If the place to
|
280 |
+
copy the object code is a network server, the Corresponding Source
|
281 |
+
may be on a different server (operated by you or a third party)
|
282 |
+
that supports equivalent copying facilities, provided you maintain
|
283 |
+
clear directions next to the object code saying where to find the
|
284 |
+
Corresponding Source. Regardless of what server hosts the
|
285 |
+
Corresponding Source, you remain obligated to ensure that it is
|
286 |
+
available for as long as needed to satisfy these requirements.
|
287 |
+
|
288 |
+
e) Convey the object code using peer-to-peer transmission, provided
|
289 |
+
you inform other peers where the object code and Corresponding
|
290 |
+
Source of the work are being offered to the general public at no
|
291 |
+
charge under subsection 6d.
|
292 |
+
|
293 |
+
A separable portion of the object code, whose source code is excluded
|
294 |
+
from the Corresponding Source as a System Library, need not be
|
295 |
+
included in conveying the object code work.
|
296 |
+
|
297 |
+
A "User Product" is either (1) a "consumer product", which means any
|
298 |
+
tangible personal property which is normally used for personal, family,
|
299 |
+
or household purposes, or (2) anything designed or sold for incorporation
|
300 |
+
into a dwelling. In determining whether a product is a consumer product,
|
301 |
+
doubtful cases shall be resolved in favor of coverage. For a particular
|
302 |
+
product received by a particular user, "normally used" refers to a
|
303 |
+
typical or common use of that class of product, regardless of the status
|
304 |
+
of the particular user or of the way in which the particular user
|
305 |
+
actually uses, or expects or is expected to use, the product. A product
|
306 |
+
is a consumer product regardless of whether the product has substantial
|
307 |
+
commercial, industrial or non-consumer uses, unless such uses represent
|
308 |
+
the only significant mode of use of the product.
|
309 |
+
|
310 |
+
"Installation Information" for a User Product means any methods,
|
311 |
+
procedures, authorization keys, or other information required to install
|
312 |
+
and execute modified versions of a covered work in that User Product from
|
313 |
+
a modified version of its Corresponding Source. The information must
|
314 |
+
suffice to ensure that the continued functioning of the modified object
|
315 |
+
code is in no case prevented or interfered with solely because
|
316 |
+
modification has been made.
|
317 |
+
|
318 |
+
If you convey an object code work under this section in, or with, or
|
319 |
+
specifically for use in, a User Product, and the conveying occurs as
|
320 |
+
part of a transaction in which the right of possession and use of the
|
321 |
+
User Product is transferred to the recipient in perpetuity or for a
|
322 |
+
fixed term (regardless of how the transaction is characterized), the
|
323 |
+
Corresponding Source conveyed under this section must be accompanied
|
324 |
+
by the Installation Information. But this requirement does not apply
|
325 |
+
if neither you nor any third party retains the ability to install
|
326 |
+
modified object code on the User Product (for example, the work has
|
327 |
+
been installed in ROM).
|
328 |
+
|
329 |
+
The requirement to provide Installation Information does not include a
|
330 |
+
requirement to continue to provide support service, warranty, or updates
|
331 |
+
for a work that has been modified or installed by the recipient, or for
|
332 |
+
the User Product in which it has been modified or installed. Access to a
|
333 |
+
network may be denied when the modification itself materially and
|
334 |
+
adversely affects the operation of the network or violates the rules and
|
335 |
+
protocols for communication across the network.
|
336 |
+
|
337 |
+
Corresponding Source conveyed, and Installation Information provided,
|
338 |
+
in accord with this section must be in a format that is publicly
|
339 |
+
documented (and with an implementation available to the public in
|
340 |
+
source code form), and must require no special password or key for
|
341 |
+
unpacking, reading or copying.
|
342 |
+
|
343 |
+
7. Additional Terms.
|
344 |
+
|
345 |
+
"Additional permissions" are terms that supplement the terms of this
|
346 |
+
License by making exceptions from one or more of its conditions.
|
347 |
+
Additional permissions that are applicable to the entire Program shall
|
348 |
+
be treated as though they were included in this License, to the extent
|
349 |
+
that they are valid under applicable law. If additional permissions
|
350 |
+
apply only to part of the Program, that part may be used separately
|
351 |
+
under those permissions, but the entire Program remains governed by
|
352 |
+
this License without regard to the additional permissions.
|
353 |
+
|
354 |
+
When you convey a copy of a covered work, you may at your option
|
355 |
+
remove any additional permissions from that copy, or from any part of
|
356 |
+
it. (Additional permissions may be written to require their own
|
357 |
+
removal in certain cases when you modify the work.) You may place
|
358 |
+
additional permissions on material, added by you to a covered work,
|
359 |
+
for which you have or can give appropriate copyright permission.
|
360 |
+
|
361 |
+
Notwithstanding any other provision of this License, for material you
|
362 |
+
add to a covered work, you may (if authorized by the copyright holders of
|
363 |
+
that material) supplement the terms of this License with terms:
|
364 |
+
|
365 |
+
a) Disclaiming warranty or limiting liability differently from the
|
366 |
+
terms of sections 15 and 16 of this License; or
|
367 |
+
|
368 |
+
b) Requiring preservation of specified reasonable legal notices or
|
369 |
+
author attributions in that material or in the Appropriate Legal
|
370 |
+
Notices displayed by works containing it; or
|
371 |
+
|
372 |
+
c) Prohibiting misrepresentation of the origin of that material, or
|
373 |
+
requiring that modified versions of such material be marked in
|
374 |
+
reasonable ways as different from the original version; or
|
375 |
+
|
376 |
+
d) Limiting the use for publicity purposes of names of licensors or
|
377 |
+
authors of the material; or
|
378 |
+
|
379 |
+
e) Declining to grant rights under trademark law for use of some
|
380 |
+
trade names, trademarks, or service marks; or
|
381 |
+
|
382 |
+
f) Requiring indemnification of licensors and authors of that
|
383 |
+
material by anyone who conveys the material (or modified versions of
|
384 |
+
it) with contractual assumptions of liability to the recipient, for
|
385 |
+
any liability that these contractual assumptions directly impose on
|
386 |
+
those licensors and authors.
|
387 |
+
|
388 |
+
All other non-permissive additional terms are considered "further
|
389 |
+
restrictions" within the meaning of section 10. If the Program as you
|
390 |
+
received it, or any part of it, contains a notice stating that it is
|
391 |
+
governed by this License along with a term that is a further
|
392 |
+
restriction, you may remove that term. If a license document contains
|
393 |
+
a further restriction but permits relicensing or conveying under this
|
394 |
+
License, you may add to a covered work material governed by the terms
|
395 |
+
of that license document, provided that the further restriction does
|
396 |
+
not survive such relicensing or conveying.
|
397 |
+
|
398 |
+
If you add terms to a covered work in accord with this section, you
|
399 |
+
must place, in the relevant source files, a statement of the
|
400 |
+
additional terms that apply to those files, or a notice indicating
|
401 |
+
where to find the applicable terms.
|
402 |
+
|
403 |
+
Additional terms, permissive or non-permissive, may be stated in the
|
404 |
+
form of a separately written license, or stated as exceptions;
|
405 |
+
the above requirements apply either way.
|
406 |
+
|
407 |
+
8. Termination.
|
408 |
+
|
409 |
+
You may not propagate or modify a covered work except as expressly
|
410 |
+
provided under this License. Any attempt otherwise to propagate or
|
411 |
+
modify it is void, and will automatically terminate your rights under
|
412 |
+
this License (including any patent licenses granted under the third
|
413 |
+
paragraph of section 11).
|
414 |
+
|
415 |
+
However, if you cease all violation of this License, then your
|
416 |
+
license from a particular copyright holder is reinstated (a)
|
417 |
+
provisionally, unless and until the copyright holder explicitly and
|
418 |
+
finally terminates your license, and (b) permanently, if the copyright
|
419 |
+
holder fails to notify you of the violation by some reasonable means
|
420 |
+
prior to 60 days after the cessation.
|
421 |
+
|
422 |
+
Moreover, your license from a particular copyright holder is
|
423 |
+
reinstated permanently if the copyright holder notifies you of the
|
424 |
+
violation by some reasonable means, this is the first time you have
|
425 |
+
received notice of violation of this License (for any work) from that
|
426 |
+
copyright holder, and you cure the violation prior to 30 days after
|
427 |
+
your receipt of the notice.
|
428 |
+
|
429 |
+
Termination of your rights under this section does not terminate the
|
430 |
+
licenses of parties who have received copies or rights from you under
|
431 |
+
this License. If your rights have been terminated and not permanently
|
432 |
+
reinstated, you do not qualify to receive new licenses for the same
|
433 |
+
material under section 10.
|
434 |
+
|
435 |
+
9. Acceptance Not Required for Having Copies.
|
436 |
+
|
437 |
+
You are not required to accept this License in order to receive or
|
438 |
+
run a copy of the Program. Ancillary propagation of a covered work
|
439 |
+
occurring solely as a consequence of using peer-to-peer transmission
|
440 |
+
to receive a copy likewise does not require acceptance. However,
|
441 |
+
nothing other than this License grants you permission to propagate or
|
442 |
+
modify any covered work. These actions infringe copyright if you do
|
443 |
+
not accept this License. Therefore, by modifying or propagating a
|
444 |
+
covered work, you indicate your acceptance of this License to do so.
|
445 |
+
|
446 |
+
10. Automatic Licensing of Downstream Recipients.
|
447 |
+
|
448 |
+
Each time you convey a covered work, the recipient automatically
|
449 |
+
receives a license from the original licensors, to run, modify and
|
450 |
+
propagate that work, subject to this License. You are not responsible
|
451 |
+
for enforcing compliance by third parties with this License.
|
452 |
+
|
453 |
+
An "entity transaction" is a transaction transferring control of an
|
454 |
+
organization, or substantially all assets of one, or subdividing an
|
455 |
+
organization, or merging organizations. If propagation of a covered
|
456 |
+
work results from an entity transaction, each party to that
|
457 |
+
transaction who receives a copy of the work also receives whatever
|
458 |
+
licenses to the work the party's predecessor in interest had or could
|
459 |
+
give under the previous paragraph, plus a right to possession of the
|
460 |
+
Corresponding Source of the work from the predecessor in interest, if
|
461 |
+
the predecessor has it or can get it with reasonable efforts.
|
462 |
+
|
463 |
+
You may not impose any further restrictions on the exercise of the
|
464 |
+
rights granted or affirmed under this License. For example, you may
|
465 |
+
not impose a license fee, royalty, or other charge for exercise of
|
466 |
+
rights granted under this License, and you may not initiate litigation
|
467 |
+
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
468 |
+
any patent claim is infringed by making, using, selling, offering for
|
469 |
+
sale, or importing the Program or any portion of it.
|
470 |
+
|
471 |
+
11. Patents.
|
472 |
+
|
473 |
+
A "contributor" is a copyright holder who authorizes use under this
|
474 |
+
License of the Program or a work on which the Program is based. The
|
475 |
+
work thus licensed is called the contributor's "contributor version".
|
476 |
+
|
477 |
+
A contributor's "essential patent claims" are all patent claims
|
478 |
+
owned or controlled by the contributor, whether already acquired or
|
479 |
+
hereafter acquired, that would be infringed by some manner, permitted
|
480 |
+
by this License, of making, using, or selling its contributor version,
|
481 |
+
but do not include claims that would be infringed only as a
|
482 |
+
consequence of further modification of the contributor version. For
|
483 |
+
purposes of this definition, "control" includes the right to grant
|
484 |
+
patent sublicenses in a manner consistent with the requirements of
|
485 |
+
this License.
|
486 |
+
|
487 |
+
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
488 |
+
patent license under the contributor's essential patent claims, to
|
489 |
+
make, use, sell, offer for sale, import and otherwise run, modify and
|
490 |
+
propagate the contents of its contributor version.
|
491 |
+
|
492 |
+
In the following three paragraphs, a "patent license" is any express
|
493 |
+
agreement or commitment, however denominated, not to enforce a patent
|
494 |
+
(such as an express permission to practice a patent or covenant not to
|
495 |
+
sue for patent infringement). To "grant" such a patent license to a
|
496 |
+
party means to make such an agreement or commitment not to enforce a
|
497 |
+
patent against the party.
|
498 |
+
|
499 |
+
If you convey a covered work, knowingly relying on a patent license,
|
500 |
+
and the Corresponding Source of the work is not available for anyone
|
501 |
+
to copy, free of charge and under the terms of this License, through a
|
502 |
+
publicly available network server or other readily accessible means,
|
503 |
+
then you must either (1) cause the Corresponding Source to be so
|
504 |
+
available, or (2) arrange to deprive yourself of the benefit of the
|
505 |
+
patent license for this particular work, or (3) arrange, in a manner
|
506 |
+
consistent with the requirements of this License, to extend the patent
|
507 |
+
license to downstream recipients. "Knowingly relying" means you have
|
508 |
+
actual knowledge that, but for the patent license, your conveying the
|
509 |
+
covered work in a country, or your recipient's use of the covered work
|
510 |
+
in a country, would infringe one or more identifiable patents in that
|
511 |
+
country that you have reason to believe are valid.
|
512 |
+
|
513 |
+
If, pursuant to or in connection with a single transaction or
|
514 |
+
arrangement, you convey, or propagate by procuring conveyance of, a
|
515 |
+
covered work, and grant a patent license to some of the parties
|
516 |
+
receiving the covered work authorizing them to use, propagate, modify
|
517 |
+
or convey a specific copy of the covered work, then the patent license
|
518 |
+
you grant is automatically extended to all recipients of the covered
|
519 |
+
work and works based on it.
|
520 |
+
|
521 |
+
A patent license is "discriminatory" if it does not include within
|
522 |
+
the scope of its coverage, prohibits the exercise of, or is
|
523 |
+
conditioned on the non-exercise of one or more of the rights that are
|
524 |
+
specifically granted under this License. You may not convey a covered
|
525 |
+
work if you are a party to an arrangement with a third party that is
|
526 |
+
in the business of distributing software, under which you make payment
|
527 |
+
to the third party based on the extent of your activity of conveying
|
528 |
+
the work, and under which the third party grants, to any of the
|
529 |
+
parties who would receive the covered work from you, a discriminatory
|
530 |
+
patent license (a) in connection with copies of the covered work
|
531 |
+
conveyed by you (or copies made from those copies), or (b) primarily
|
532 |
+
for and in connection with specific products or compilations that
|
533 |
+
contain the covered work, unless you entered into that arrangement,
|
534 |
+
or that patent license was granted, prior to 28 March 2007.
|
535 |
+
|
536 |
+
Nothing in this License shall be construed as excluding or limiting
|
537 |
+
any implied license or other defenses to infringement that may
|
538 |
+
otherwise be available to you under applicable patent law.
|
539 |
+
|
540 |
+
12. No Surrender of Others' Freedom.
|
541 |
+
|
542 |
+
If conditions are imposed on you (whether by court order, agreement or
|
543 |
+
otherwise) that contradict the conditions of this License, they do not
|
544 |
+
excuse you from the conditions of this License. If you cannot convey a
|
545 |
+
covered work so as to satisfy simultaneously your obligations under this
|
546 |
+
License and any other pertinent obligations, then as a consequence you may
|
547 |
+
not convey it at all. For example, if you agree to terms that obligate you
|
548 |
+
to collect a royalty for further conveying from those to whom you convey
|
549 |
+
the Program, the only way you could satisfy both those terms and this
|
550 |
+
License would be to refrain entirely from conveying the Program.
|
551 |
+
|
552 |
+
13. Use with the GNU Affero General Public License.
|
553 |
+
|
554 |
+
Notwithstanding any other provision of this License, you have
|
555 |
+
permission to link or combine any covered work with a work licensed
|
556 |
+
under version 3 of the GNU Affero General Public License into a single
|
557 |
+
combined work, and to convey the resulting work. The terms of this
|
558 |
+
License will continue to apply to the part which is the covered work,
|
559 |
+
but the special requirements of the GNU Affero General Public License,
|
560 |
+
section 13, concerning interaction through a network will apply to the
|
561 |
+
combination as such.
|
562 |
+
|
563 |
+
14. Revised Versions of this License.
|
564 |
+
|
565 |
+
The Free Software Foundation may publish revised and/or new versions of
|
566 |
+
the GNU General Public License from time to time. Such new versions will
|
567 |
+
be similar in spirit to the present version, but may differ in detail to
|
568 |
+
address new problems or concerns.
|
569 |
+
|
570 |
+
Each version is given a distinguishing version number. If the
|
571 |
+
Program specifies that a certain numbered version of the GNU General
|
572 |
+
Public License "or any later version" applies to it, you have the
|
573 |
+
option of following the terms and conditions either of that numbered
|
574 |
+
version or of any later version published by the Free Software
|
575 |
+
Foundation. If the Program does not specify a version number of the
|
576 |
+
GNU General Public License, you may choose any version ever published
|
577 |
+
by the Free Software Foundation.
|
578 |
+
|
579 |
+
If the Program specifies that a proxy can decide which future
|
580 |
+
versions of the GNU General Public License can be used, that proxy's
|
581 |
+
public statement of acceptance of a version permanently authorizes you
|
582 |
+
to choose that version for the Program.
|
583 |
+
|
584 |
+
Later license versions may give you additional or different
|
585 |
+
permissions. However, no additional obligations are imposed on any
|
586 |
+
author or copyright holder as a result of your choosing to follow a
|
587 |
+
later version.
|
588 |
+
|
589 |
+
15. Disclaimer of Warranty.
|
590 |
+
|
591 |
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
592 |
+
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
593 |
+
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
594 |
+
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
595 |
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
596 |
+
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
597 |
+
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
598 |
+
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
599 |
+
|
600 |
+
16. Limitation of Liability.
|
601 |
+
|
602 |
+
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
603 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
604 |
+
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
605 |
+
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
606 |
+
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
607 |
+
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
608 |
+
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
609 |
+
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
610 |
+
SUCH DAMAGES.
|
611 |
+
|
612 |
+
17. Interpretation of Sections 15 and 16.
|
613 |
+
|
614 |
+
If the disclaimer of warranty and limitation of liability provided
|
615 |
+
above cannot be given local legal effect according to their terms,
|
616 |
+
reviewing courts shall apply local law that most closely approximates
|
617 |
+
an absolute waiver of all civil liability in connection with the
|
618 |
+
Program, unless a warranty or assumption of liability accompanies a
|
619 |
+
copy of the Program in return for a fee.
|
620 |
+
|
621 |
+
END OF TERMS AND CONDITIONS
|
622 |
+
|
623 |
+
How to Apply These Terms to Your New Programs
|
624 |
+
|
625 |
+
If you develop a new program, and you want it to be of the greatest
|
626 |
+
possible use to the public, the best way to achieve this is to make it
|
627 |
+
free software which everyone can redistribute and change under these terms.
|
628 |
+
|
629 |
+
To do so, attach the following notices to the program. It is safest
|
630 |
+
to attach them to the start of each source file to most effectively
|
631 |
+
state the exclusion of warranty; and each file should have at least
|
632 |
+
the "copyright" line and a pointer to where the full notice is found.
|
633 |
+
|
634 |
+
<one line to give the program's name and a brief idea of what it does.>
|
635 |
+
Copyright (C) <year> <name of author>
|
636 |
+
|
637 |
+
This program is free software: you can redistribute it and/or modify
|
638 |
+
it under the terms of the GNU General Public License as published by
|
639 |
+
the Free Software Foundation, either version 3 of the License, or
|
640 |
+
(at your option) any later version.
|
641 |
+
|
642 |
+
This program is distributed in the hope that it will be useful,
|
643 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
644 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
645 |
+
GNU General Public License for more details.
|
646 |
+
|
647 |
+
You should have received a copy of the GNU General Public License
|
648 |
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
649 |
+
|
650 |
+
Also add information on how to contact you by electronic and paper mail.
|
651 |
+
|
652 |
+
If the program does terminal interaction, make it output a short
|
653 |
+
notice like this when it starts in an interactive mode:
|
654 |
+
|
655 |
+
<program> Copyright (C) <year> <name of author>
|
656 |
+
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
657 |
+
This is free software, and you are welcome to redistribute it
|
658 |
+
under certain conditions; type `show c' for details.
|
659 |
+
|
660 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
661 |
+
parts of the General Public License. Of course, your program's commands
|
662 |
+
might be different; for a GUI interface, you would use an "about box".
|
663 |
+
|
664 |
+
You should also get your employer (if you work as a programmer) or school,
|
665 |
+
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
666 |
+
For more information on this, and how to apply and follow the GNU GPL, see
|
667 |
+
<https://www.gnu.org/licenses/>.
|
668 |
+
|
669 |
+
The GNU General Public License does not permit incorporating your program
|
670 |
+
into proprietary programs. If your program is a subroutine library, you
|
671 |
+
may consider it more useful to permit linking proprietary applications with
|
672 |
+
the library. If this is what you want to do, use the GNU Lesser General
|
673 |
+
Public License instead of this License. But first, please read
|
674 |
+
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
README.md
CHANGED
@@ -5,7 +5,7 @@ colorFrom: gray
|
|
5 |
colorTo: gray
|
6 |
sdk: gradio
|
7 |
python_version: 3.9
|
8 |
-
app_file:
|
9 |
tags:
|
10 |
- tts
|
11 |
- t2s
|
|
|
5 |
colorTo: gray
|
6 |
sdk: gradio
|
7 |
python_version: 3.9
|
8 |
+
app_file: server.py
|
9 |
tags:
|
10 |
- tts
|
11 |
- t2s
|
arpabet/custom.json
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"title": "Custom Dictionary",
|
3 |
+
"description": "Your own custom dictionary",
|
4 |
+
"version": "1.0.0",
|
5 |
+
"author": "Your name",
|
6 |
+
"nexusLink": null,
|
7 |
+
"data": {
|
8 |
+
"stuff": {
|
9 |
+
"enabled": false,
|
10 |
+
"arpabet": "S T AA0 F"
|
11 |
+
},
|
12 |
+
"things": {
|
13 |
+
"enabled": false,
|
14 |
+
"arpabet": "T HH IH N G S"
|
15 |
+
}
|
16 |
+
}
|
17 |
+
}
|
arpabet/xvasynth.json
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"title": "xVASynth Terminology",
|
3 |
+
"description": "Some technical words specific to xVASynth related tech",
|
4 |
+
"version": "1.0.0",
|
5 |
+
"author": "Dan Ruta",
|
6 |
+
"nexusLink": null,
|
7 |
+
"data": {
|
8 |
+
"xvasynth": {
|
9 |
+
"enabled": true,
|
10 |
+
"arpabet": "EH1 G S V EY0 EY0 IH0 S IH0 N TH"
|
11 |
+
},
|
12 |
+
"model": {
|
13 |
+
"enabled": true,
|
14 |
+
"arpabet": "M AA1 D AH0 L"
|
15 |
+
},
|
16 |
+
"hifi": {
|
17 |
+
"enabled": true,
|
18 |
+
"arpabet": "HH AA0 IH0 F AA0 IH0"
|
19 |
+
},
|
20 |
+
"fastpitch": {
|
21 |
+
"enabled": true,
|
22 |
+
"arpabet": "F AE1 S T P IH0 T CH"
|
23 |
+
},
|
24 |
+
"tacotron": {
|
25 |
+
"enabled": true,
|
26 |
+
"arpabet": "T AA0 K OW0 T R OW0 N"
|
27 |
+
},
|
28 |
+
"ai": {
|
29 |
+
"enabled": true,
|
30 |
+
"arpabet": "EY1 IY0 AA0 IY0"
|
31 |
+
},
|
32 |
+
"cmudict": {
|
33 |
+
"enabled": true,
|
34 |
+
"arpabet": "S IY1 IY1 EH0 M Y UW1 D IH1 K T"
|
35 |
+
},
|
36 |
+
"ffmpeg": {
|
37 |
+
"enabled": true,
|
38 |
+
"arpabet": "EH1 F } { EH1 F } { EH0 M P EH0 G"
|
39 |
+
},
|
40 |
+
"vram": {
|
41 |
+
"enabled": true,
|
42 |
+
"arpabet": "V IY0 R AE1 M"
|
43 |
+
},
|
44 |
+
"nvidia": {
|
45 |
+
"enabled": true,
|
46 |
+
"arpabet": "EH0 N V IH1 D IY0 AA1"
|
47 |
+
},
|
48 |
+
"xvatrainer": {
|
49 |
+
"enabled": true,
|
50 |
+
"arpabet": "EH1 G S V EY0 IH0 } { T R EY1 N ER0"
|
51 |
+
},
|
52 |
+
"gpu": {
|
53 |
+
"enabled": true,
|
54 |
+
"arpabet": "JH IY1 P IY1 Y UW1"
|
55 |
+
},
|
56 |
+
"video": {
|
57 |
+
"enabled": true,
|
58 |
+
"arpabet": "V IY0 D IY0 OW0"
|
59 |
+
}
|
60 |
+
}
|
61 |
+
}
|
index.html
ADDED
The diff for this file is too large to render.
See raw diff
|
|
javascript/appLogger.js
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
const fs = require("fs")
|
4 |
+
|
5 |
+
class xVAAppLogger {
|
6 |
+
|
7 |
+
constructor (fileLocation, appVersion) {
|
8 |
+
this.lines = []
|
9 |
+
this.fileLocation = fileLocation
|
10 |
+
|
11 |
+
if (fs.existsSync(fileLocation)) {
|
12 |
+
const data = fs.readFileSync(fileLocation, "utf8")
|
13 |
+
this.lines = data.split("\n")
|
14 |
+
}
|
15 |
+
|
16 |
+
this.prefix = ""
|
17 |
+
this.log(`New session - ${appVersion}`)
|
18 |
+
}
|
19 |
+
|
20 |
+
log (message) {
|
21 |
+
this.lines.push(`${(new Date()).toJSON().replace("T", "_")} |${this.prefix.length?` [${this.prefix}]:`:""} ${message}`)
|
22 |
+
|
23 |
+
if (this.lines.length>1000) {
|
24 |
+
this.lines = this.lines.slice(this.lines.length-1000, this.lines.length)
|
25 |
+
}
|
26 |
+
|
27 |
+
fs.writeFileSync(this.fileLocation, this.lines.join("\n"), "utf8")
|
28 |
+
}
|
29 |
+
|
30 |
+
print (message) {
|
31 |
+
console.log(`${this.prefix.length?` [${this.prefix}]:`:""} ${message}`)
|
32 |
+
}
|
33 |
+
|
34 |
+
setPrefix (prefix) {
|
35 |
+
this.prefix = prefix
|
36 |
+
}
|
37 |
+
}
|
38 |
+
|
39 |
+
exports.xVAAppLogger = xVAAppLogger
|
javascript/arpabet.js
ADDED
@@ -0,0 +1,498 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
window.arpabetMenuState = {
|
4 |
+
currentDict: undefined,
|
5 |
+
dictionaries: {},
|
6 |
+
paginationIndex: 0,
|
7 |
+
totalPages: 0,
|
8 |
+
clickedRecord: undefined,
|
9 |
+
skipRefresh: false,
|
10 |
+
hasInitialised: false,
|
11 |
+
isRefreshing: false,
|
12 |
+
hasChangedARPAbet: false
|
13 |
+
}
|
14 |
+
|
15 |
+
window.ARPAbetSymbols = ['AA0', 'AA1', 'AA2', 'AA', 'AE0', 'AE1', 'AE2', 'AE', 'AH0', 'AH1', 'AH2', 'AH',
|
16 |
+
'AO0', 'AO1', 'AO2', 'AO', 'AW0', 'AW1', 'AW2', 'AW', 'AY0', 'AY1', 'AY2', 'AY',
|
17 |
+
'B', 'CH', 'D', 'DH', 'EH0', 'EH1', 'EH2', 'EH', 'ER0', 'ER1', 'ER2', 'ER',
|
18 |
+
'EY0', 'EY1', 'EY2', 'EY', 'F', 'G', 'HH', 'IH0', 'IH1', 'IH2', 'IH', 'IY0', 'IY1',
|
19 |
+
'IY2', 'IY', 'JH', 'K', 'L', 'M', 'N', 'NG', 'OW0', 'OW1', 'OW2', 'OW', 'OY0',
|
20 |
+
'OY1', 'OY2', 'OY', 'P', 'R', 'S', 'SH', 'T', 'TH', 'UH0', 'UH1', 'UH2', 'UH',
|
21 |
+
'UW0', 'UW1', 'UW2', 'UW', 'V', 'W', 'Y', 'Z', 'ZH', ,"}","{", "_",
|
22 |
+
"AX", "AXR", "IX", "UX", "DX", "EL", "EM", "EN0", "EN1", "EN2", "EN", "NX", "Q", "WH",
|
23 |
+
"RRR", "HR", "OE", "RH", "TS", "RR", "UU", "OO", "KH", "SJ", "HJ", "BR",
|
24 |
+
|
25 |
+
"A1", "A2", "A3", "A4", "A5", "AI1", "AI2", "AI3", "AI4", "AI5", "AIR2", "AIR3", "AIR4", "AN1", "AN2", "AN3", "AN4", "AN5", "ANG1", "ANG2", "ANG3", "ANG4", "ANG5", "ANGR2", "ANGR3", "ANGR4", "ANR1", "ANR3", "ANR4", "AO1", "AO2", "AO3", "AO4", "AO5", "AOR1", "AOR2", "AOR3", "AOR4", "AOR5", "AR2", "AR3", "AR4", "AR5", "E1", "E2", "E3", "E4", "E5", "EI1", "EI2", "EI3", "EI4", "EI5", "EIR4", "EN1", "EN2", "EN3", "EN4", "EN5", "ENG1", "ENG2", "ENG3", "ENG4", "ENG5", "ENGR1", "ENGR4", "ENR1", "ENR2", "ENR3", "ENR4", "ENR5", "ER1", "ER2", "ER3", "ER4", "ER5", "I1", "I2", "I3", "I4", "I5", "IA1", "IA2", "IA3", "IA4", "IA5", "IAN1", "IAN2", "IAN3", "IAN4", "IAN5", "IANG1", "IANG2", "IANG3", "IANG4", "IANG5", "IANGR2", "IANR1", "IANR2", "IANR3", "IANR4", "IANR5", "IAO1", "IAO2", "IAO3", "IAO4", "IAO5", "IAOR1", "IAOR2", "IAOR3", "IAOR4", "IAR1", "IAR4", "IE1", "IE2", "IE3", "IE4", "IE5", "IN1", "IN2", "IN3", "IN4", "IN5", "ING1", "ING2", "ING3", "ING4", "ING5", "INGR2", "INGR4", "INR1", "INR4", "IONG1", "IONG2", "IONG3", "IONG4", "IONG5", "IR1", "IR3", "IR4", "IU1", "IU2", "IU3", "IU4", "IU5", "IUR1", "IUR2", "O1", "O2", "O3", "O4", "O5", "ONG1", "ONG2", "ONG3", "ONG4", "ONG5", "OR1", "OR2", "OU1", "OU2", "OU3", "OU4", "OU5", "OUR2", "OUR3", "OUR4", "OUR5", "U1", "U2", "U3", "U4", "U5", "UA1", "UA2", "UA3", "UA4", "UA5", "UAI1", "UAI2", "UAI3", "UAI4", "UAIR4", "UAIR5", "UAN1", "UAN2", "UAN3", "UAN4", "UAN5", "UANG1", "UANG2", "UANG3", "UANG4", "UANG5", "UANR1", "UANR2", "UANR3", "UANR4", "UAR1", "UAR2", "UAR4", "UE1", "UE2", "UE3", "UE4", "UE5", "UER2", "UER3", "UI1", "UI2", "UI3", "UI4", "UI5", "UIR1", "UIR2", "UIR3", "UIR4", "UN1", "UN2", "UN3", "UN4", "UN5", "UNR1", "UNR2", "UNR3", "UNR4", "UO1", "UO2", "UO3", "UO4", "UO5", "UOR1", "UOR2", "UOR3", "UOR5", "UR1", "UR2", "UR4", "UR5", "V2", "V3", "V4", "V5", "VE4", "VR3", "WA1", "WA2", "WA3", "WA4", "WA5", "WAI1", "WAI2", "WAI3", "WAI4", "WAN1", "WAN2", "WAN3", "WAN4", "WAN5", "WANG1", "WANG2", "WANG3", "WANG4", "WANG5", "WANGR2", "WANGR4", "WANR2", "WANR4", "WANR5", "WEI1", "WEI2", "WEI3", "WEI4", "WEI5", "WEIR1", "WEIR2", "WEIR3", "WEIR4", "WEIR5", "WEN1", "WEN2", "WEN3", "WEN4", "WEN5", "WENG1", "WENG2", "WENG3", "WENG4", "WENR2", "WO1", "WO2", "WO3", "WO4", "WO5", "WU1", "WU2", "WU3", "WU4", "WU5", "WUR3", "YA1", "YA2", "YA3", "YA4", "YA5", "YAN1", "YAN2", "YAN3", "YAN4", "YANG1", "YANG2", "YANG3", "YANG4", "YANG5", "YANGR4", "YANR3", "YAO1", "YAO2", "YAO3", "YAO4", "YAO5", "YE1", "YE2", "YE3", "YE4", "YE5", "YER4", "YI1", "YI2", "YI3", "YI4", "YI5", "YIN1", "YIN2", "YIN3", "YIN4", "YIN5", "YING1", "YING2", "YING3", "YING4", "YING5", "YINGR1", "YINGR2", "YINGR3", "YIR4", "YO1", "YO3", "YONG1", "YONG2", "YONG3", "YONG4", "YONG5", "YONGR3", "YOU1", "YOU2", "YOU3", "YOU4", "YOU5", "YOUR2", "YOUR3", "YOUR4", "YU1", "YU2", "YU3", "YU4", "YU5", "YUAN1", "YUAN2", "YUAN3", "YUAN4", "YUAN5", "YUANR2", "YUANR4", "YUE1", "YUE2", "YUE4", "YUE5", "YUER4", "YUN1", "YUN2", "YUN3", "YUN4",
|
26 |
+
|
27 |
+
"@BREATHE_IN", "@BREATHE_OUT", "@LAUGH", "@GIGGLE", "@SIGH", "@COUGH", "@AHEM", "@SNEEZE", "@WHISTLE", "@UGH", "@HMM", "@GASP", "@AAH", "@GRUNT", "@YAWN", "@SNIFF"
|
28 |
+
|
29 |
+
]
|
30 |
+
// window.ARPAbetSymbols = [
|
31 |
+
// 'AA', 'AA0', 'AA1', 'AA2', 'AE', 'AE0', 'AE1', 'AE2', 'AH', 'AH0', 'AH1', 'AH2',
|
32 |
+
// 'AO', 'AO0', 'AO1', 'AO2', 'AW', 'AW0', 'AW1', 'AW2', 'AY', 'AY0', 'AY1', 'AY2',
|
33 |
+
// 'B', 'CH', 'D', 'DH', 'EH', 'EH0', 'EH1', 'EH2', 'ER', 'ER0', 'ER1', 'ER2', 'EY',
|
34 |
+
// 'EY0', 'EY1', 'EY2', 'F', 'G', 'HH', 'IH', 'IH0', 'IH1', 'IH2', 'IY', 'IY0', 'IY1',
|
35 |
+
// 'IY2', 'JH', 'K', 'L', 'M', 'N', 'NG', 'OW', 'OW0', 'OW1', 'OW2', 'OY', 'OY0',
|
36 |
+
// 'OY1', 'OY2', 'P', 'R', 'S', 'SH', 'T', 'TH', 'UH', 'UH0', 'UH1', 'UH2', 'UW',
|
37 |
+
// 'UW0', 'UW1', 'UW2', 'V', 'W', 'Y', 'Z', 'ZH'
|
38 |
+
// ,"}","{", "_"
|
39 |
+
// ]
|
40 |
+
|
41 |
+
window.refreshDictionariesList = () => {
|
42 |
+
|
43 |
+
return new Promise(resolve => {
|
44 |
+
|
45 |
+
// Don't spam with changes when the menu isn't open
|
46 |
+
// if (arpabetModal.parentElement.style.display!="flex" && window.arpabetMenuState.hasInitialised) {
|
47 |
+
if (arpabetModal.parentElement.style.display!="flex") {
|
48 |
+
return
|
49 |
+
}
|
50 |
+
|
51 |
+
window.arpabetMenuState.hasInitialised = true
|
52 |
+
if (window.arpabetMenuState.isRefreshing) {
|
53 |
+
resolve()
|
54 |
+
return
|
55 |
+
}
|
56 |
+
window.arpabetMenuState.isRefreshing = true
|
57 |
+
|
58 |
+
if (window.arpabetMenuState.skipRefresh) {
|
59 |
+
resolve()
|
60 |
+
return
|
61 |
+
}
|
62 |
+
|
63 |
+
spinnerModal(window.i18n.LOADING_DICTIONARIES)
|
64 |
+
window.arpabetMenuState.dictionaries = {}
|
65 |
+
arpabet_dicts_list.innerHTML = ""
|
66 |
+
|
67 |
+
const jsonFiles = fs.readdirSync(`${window.path}/arpabet`).filter(fname => fname.includes(".json"))
|
68 |
+
|
69 |
+
const readFile = (fileCounter) => {
|
70 |
+
|
71 |
+
const fname = jsonFiles[fileCounter]
|
72 |
+
if (!fname.includes(".json")) {
|
73 |
+
if ((fileCounter+1)<jsonFiles.length) {
|
74 |
+
readFile(fileCounter+1)
|
75 |
+
} else {
|
76 |
+
window.arpabetRunSearch()
|
77 |
+
window.arpabetMenuState.isRefreshing = false
|
78 |
+
closeModal(undefined, arpabetContainer)
|
79 |
+
resolve()
|
80 |
+
}
|
81 |
+
return
|
82 |
+
}
|
83 |
+
const dictId = fname.replace(".json", "")
|
84 |
+
|
85 |
+
fs.readFile(`${window.path}/arpabet/${fname}`, "utf8", (err, data) => {
|
86 |
+
const jsonData = JSON.parse(data)
|
87 |
+
|
88 |
+
const dictButton = createElem("button", jsonData.title)
|
89 |
+
dictButton.title = jsonData.description
|
90 |
+
dictButton.style.background = window.currentGame ? `#${window.currentGame.themeColourPrimary}` : "#aaa"
|
91 |
+
arpabet_dicts_list.appendChild(dictButton)
|
92 |
+
|
93 |
+
window.arpabetMenuState.dictionaries[dictId] = jsonData
|
94 |
+
|
95 |
+
dictButton.addEventListener("click", ()=>handleDictClick(dictId))
|
96 |
+
|
97 |
+
if ((fileCounter+1)<jsonFiles.length) {
|
98 |
+
readFile(fileCounter+1)
|
99 |
+
} else {
|
100 |
+
window.arpabetRunSearch()
|
101 |
+
window.arpabetMenuState.isRefreshing = false
|
102 |
+
closeModal(undefined, arpabetContainer)
|
103 |
+
resolve()
|
104 |
+
}
|
105 |
+
})
|
106 |
+
}
|
107 |
+
if (jsonFiles.length) {
|
108 |
+
readFile(0)
|
109 |
+
} else {
|
110 |
+
window.arpabetMenuState.isRefreshing = false
|
111 |
+
closeModal(undefined, arpabetContainer)
|
112 |
+
resolve()
|
113 |
+
}
|
114 |
+
})
|
115 |
+
}
|
116 |
+
|
117 |
+
window.handleDictClick = (dictId) => {
|
118 |
+
|
119 |
+
if (window.arpabetMenuState.currentDict==dictId) {
|
120 |
+
return
|
121 |
+
}
|
122 |
+
arpabet_enableall_button.disabled = false
|
123 |
+
arpabet_disableall_button.disabled = false
|
124 |
+
window.arpabetMenuState.currentDict = dictId
|
125 |
+
window.arpabetMenuState.paginationIndex = 0
|
126 |
+
window.arpabetMenuState.totalPages = 0
|
127 |
+
|
128 |
+
arpabet_word_search_input.value = ""
|
129 |
+
window.arpabetRunSearch()
|
130 |
+
window.refreshDictWordList()
|
131 |
+
}
|
132 |
+
|
133 |
+
window.refreshDictWordList = () => {
|
134 |
+
|
135 |
+
const dictId = window.arpabetMenuState.currentDict
|
136 |
+
arpabetWordsListContainer.innerHTML = ""
|
137 |
+
|
138 |
+
const wordKeys = Object.keys(window.arpabetMenuState.dictionaries[dictId].filteredData)
|
139 |
+
let startIndex = window.arpabetMenuState.paginationIndex*window.userSettings.arpabet_paginationSize
|
140 |
+
const endIndex = Math.min(startIndex+window.userSettings.arpabet_paginationSize, wordKeys.length)
|
141 |
+
|
142 |
+
window.arpabetMenuState.totalPages = Math.ceil(wordKeys.length/window.userSettings.arpabet_paginationSize)
|
143 |
+
arpabet_pagination_numbers.innerHTML = window.i18n.PAGINATION_X_OF_Y.replace("_1", window.arpabetMenuState.paginationIndex+1).replace("_2", window.arpabetMenuState.totalPages)
|
144 |
+
|
145 |
+
for (let i=startIndex; i<endIndex; i++) {
|
146 |
+
const data = window.arpabetMenuState.dictionaries[dictId].filteredData[wordKeys[i]]
|
147 |
+
const word = wordKeys[i]
|
148 |
+
|
149 |
+
const rowElem = createElem("div.arpabetRow")
|
150 |
+
const ckbx = createElem("input.arpabetRowItem", {type: "checkbox"})
|
151 |
+
ckbx.checked = data.enabled
|
152 |
+
ckbx.style.marginTop = 0
|
153 |
+
ckbx.addEventListener("click", () => {
|
154 |
+
window.arpabetMenuState.dictionaries[dictId].data[wordKeys[i]].enabled = ckbx.checked
|
155 |
+
window.arpabetMenuState.skipRefresh = true
|
156 |
+
window.saveARPAbetDict(dictId)
|
157 |
+
window.arpabetMenuState.hasChangedARPAbet = true
|
158 |
+
setTimeout(() => window.arpabetMenuState.skipRefresh = false, 1000)
|
159 |
+
})
|
160 |
+
|
161 |
+
const deleteButton = createElem("button.smallButton.arpabetRowItem", window.i18n.DELETE)
|
162 |
+
deleteButton.style.background = window.currentGame ? `#${window.currentGame.themeColourPrimary}` : "#aaa"
|
163 |
+
deleteButton.addEventListener("click", () => {
|
164 |
+
window.confirmModal(window.i18n.ARPABET_CONFIRM_DELETE_WORD.replace("_1", word)).then(response => {
|
165 |
+
if (response) {
|
166 |
+
setTimeout(() => {
|
167 |
+
delete window.arpabetMenuState.dictionaries[dictId].data[word]
|
168 |
+
delete window.arpabetMenuState.dictionaries[dictId].filteredData[word]
|
169 |
+
window.saveARPAbetDict(dictId)
|
170 |
+
window.refreshDictWordList()
|
171 |
+
}, 210)
|
172 |
+
}
|
173 |
+
})
|
174 |
+
})
|
175 |
+
|
176 |
+
const wordElem = createElem("div.arpabetRowItem", word)
|
177 |
+
wordElem.title = word
|
178 |
+
|
179 |
+
const arpabetElem = createElem("div.arpabetRowItem", data.arpabet)
|
180 |
+
arpabetElem.title = data.arpabet
|
181 |
+
|
182 |
+
|
183 |
+
rowElem.appendChild(createElem("div.arpabetRowItem", ckbx))
|
184 |
+
rowElem.appendChild(createElem("div.arpabetRowItem", deleteButton))
|
185 |
+
rowElem.appendChild(wordElem)
|
186 |
+
rowElem.appendChild(arpabetElem)
|
187 |
+
|
188 |
+
rowElem.addEventListener("click", () => {
|
189 |
+
window.arpabetMenuState.clickedRecord = {elem: rowElem, word}
|
190 |
+
arpabet_word_input.value = word
|
191 |
+
arpabet_arpabet_input.value = data.arpabet
|
192 |
+
})
|
193 |
+
|
194 |
+
arpabetWordsListContainer.appendChild(rowElem)
|
195 |
+
}
|
196 |
+
}
|
197 |
+
|
198 |
+
window.saveARPAbetDict = (dictId) => {
|
199 |
+
|
200 |
+
const dataOut = {
|
201 |
+
title: window.arpabetMenuState.dictionaries[dictId].title,
|
202 |
+
description: window.arpabetMenuState.dictionaries[dictId].description,
|
203 |
+
version: window.arpabetMenuState.dictionaries[dictId].version,
|
204 |
+
author: window.arpabetMenuState.dictionaries[dictId].author,
|
205 |
+
nexusLink: window.arpabetMenuState.dictionaries[dictId].nexusLink,
|
206 |
+
data: window.arpabetMenuState.dictionaries[dictId].data
|
207 |
+
}
|
208 |
+
|
209 |
+
doFetch(`http://localhost:8008/updateARPABet`, {
|
210 |
+
method: "Post",
|
211 |
+
body: JSON.stringify({})
|
212 |
+
})//.then(r => r.text()).then(r => {console.log(r)})
|
213 |
+
|
214 |
+
|
215 |
+
fs.writeFileSync(`${window.path}/arpabet/${dictId}.json`, JSON.stringify(dataOut, null, 4))
|
216 |
+
}
|
217 |
+
|
218 |
+
|
219 |
+
|
220 |
+
|
221 |
+
window.refreshARPABETReferenceDisplay = () => {
|
222 |
+
|
223 |
+
const V2 = [2]
|
224 |
+
const V3 = [3]
|
225 |
+
const V2_3 = [2,3]
|
226 |
+
|
227 |
+
const data = [
|
228 |
+
// Min model version, symbols, examples
|
229 |
+
[V2, "AA0, AA1, AA2", "b<b>al</b>m, b<b>o</b>t, c<b>o</b>t"],
|
230 |
+
[V3, "AA, AA0, AA1, AA2", "b<b>al</b>m, b<b>o</b>t, c<b>o</b>t"],
|
231 |
+
|
232 |
+
[V2, "AE0, AE1, AE2", "b<b>a</b>t, f<b>a</b>st"],
|
233 |
+
[V3, "AE, AE0, AE1, AE2", "b<b>a</b>t, f<b>a</b>st"],
|
234 |
+
|
235 |
+
[V2, "AH0, AH1, AH2", "b<b>u</b>tt"],
|
236 |
+
[V3, "AH, AH0, AH1, AH2", "b<b>u</b>tt"],
|
237 |
+
|
238 |
+
[V2, "AO0, AO1, AO2", "st<b>o</b>ry"],
|
239 |
+
[V3, "AO, AO0, AO1, AO2", "st<b>o</b>ry"],
|
240 |
+
|
241 |
+
[V2, "AW0, AW1, AW2", "b<b>ou</b>t"],
|
242 |
+
[V3, "AW, AW0, AW1, AW2", "b<b>ou</b>t"],
|
243 |
+
|
244 |
+
[V3, "AX", "comm<b>a</b>"],
|
245 |
+
[V3, "AXR", "lett<b>er</b>"],
|
246 |
+
|
247 |
+
[V2, "AY0, AY1, AY2", "b<b>i</b>te"],
|
248 |
+
[V3, "AY, AY0, AY1, AY2", "b<b>i</b>te"],
|
249 |
+
|
250 |
+
[V2_3, "B", "<b>b</b>uy"],
|
251 |
+
|
252 |
+
[V3, "BR", "B and RRR sounds, together"],
|
253 |
+
|
254 |
+
[V2_3, "CH", "<b>ch</b>ina"],
|
255 |
+
[V2_3, "D", "<b>d</b>ie"],
|
256 |
+
[V3, "DX", "bu<b>tt</b>er"],
|
257 |
+
[V2_3, "DH", "<b>th</b>y"],
|
258 |
+
[V2, "EH0, EH1, EH2", "b<b>e</b>t"],
|
259 |
+
[V3, "EH,EH0, EH1, EH2", "b<b>e</b>t"],
|
260 |
+
|
261 |
+
[V3, "EL", "bott<b>le</b>"],
|
262 |
+
[V3, "EM", "rhyth<b>m</b>"],
|
263 |
+
[V3, "EN, EN0, EN1, EN2", "butt<b>on</b>"],
|
264 |
+
|
265 |
+
[V2, "ER0, ER1, ER2", "b<b>i</b>rd"],
|
266 |
+
[V3, "ER, ER0, ER1, ER2", "b<b>i</b>rd"],
|
267 |
+
|
268 |
+
[V2, "EY0, EY1, EY2", "b<b>ai</b>t"],
|
269 |
+
[V3, "EY, EY0, EY1, EY2", "b<b>ai</b>t"],
|
270 |
+
|
271 |
+
[V2_3, "F", "<b>f</b>ight"],
|
272 |
+
[V2_3, "G", "<b>g</b>uy"],
|
273 |
+
[V2_3, "HH", "<b>h</b>igh"],
|
274 |
+
[V3, "HJ", "J sound if mouth was open"],
|
275 |
+
[V3, "HR", "hrr sound typical in Arabic"],
|
276 |
+
[V2, "IH0, IH1, IH2", "b<b>i</b>t"],
|
277 |
+
[V3, "IH, IH0, IH1, IH2", "b<b>i</b>t"],
|
278 |
+
[V3, "IX", "ros<b>e</b>s, rabb<b>i</b>t"],
|
279 |
+
|
280 |
+
[V2, "IY0, IY1, IY2", "b<b>ea</b>t"],
|
281 |
+
[V3, "IY, IY0, IY1, IY2", "b<b>ea</b>t"],
|
282 |
+
|
283 |
+
[V2_3, "JH", "<b>j</b>ive"],
|
284 |
+
[V2_3, "K", "<b>k</b>ite"],
|
285 |
+
[V3, "KH", "K and H sounds, but together"],
|
286 |
+
[V2_3, "L", "<b>l</b>ie"],
|
287 |
+
[V2_3, "M", "<b>m</b>y"],
|
288 |
+
[V2_3, "N", "<b>n</b>igh"],
|
289 |
+
[V2_3, "NG", "si<b>ng</b>"],
|
290 |
+
[V3, "NX", "wi<b>nn</b>er"],
|
291 |
+
|
292 |
+
[V3, "OE", "german m<b>ΓΆ</b>ve, french eu (bl<b>eu</b>)"],
|
293 |
+
[V3, "OO", "hard o sound"],
|
294 |
+
[V2, "OW0, OW1, OW2", "b<b>oa</b>t"],
|
295 |
+
[V3, "OW, OW0, OW1, OW2", "b<b>oa</b>t"],
|
296 |
+
|
297 |
+
[V2, "OY0, OY1, OY2", "b<b>oy</b>"],
|
298 |
+
[V3, "OY, OY0, OY1, OY2", "b<b>oy</b>"],
|
299 |
+
|
300 |
+
[V2_3, "P", "<b>p</b>ie"],
|
301 |
+
[V3, "Q", "(glottal stop) uh<b>-</b>oh)"],
|
302 |
+
[V2_3, "R", "<b>r</b>ye"],
|
303 |
+
[V3, "RH, RR", "<b>r</b>un"],
|
304 |
+
[V3, "RRR", "<strong r>"],
|
305 |
+
[V2_3, "S", "<b>s</b>igh"],
|
306 |
+
[V3, "SJ", "swedish sj"],
|
307 |
+
[V2_3, "SH", "<b>sh</b>y"],
|
308 |
+
[V2_3, "T", "<b>t</b>ie"],
|
309 |
+
[V2_3, "TH", "<b>th</b>igh"],
|
310 |
+
[V3, "TS", "T and S sounds together (eg romanian Θ)"],
|
311 |
+
[V2, "UH0, UH1, UH2", "b<b>oo</b>k"],
|
312 |
+
[V3, "UH, UH0, UH1, UH2", "b<b>oo</b>k"],
|
313 |
+
[V3, "UU", "german <b>ΓΌ</b>ber"],
|
314 |
+
|
315 |
+
[V2, "UW0, UW1, UW2", "b<b>oo</b>t"],
|
316 |
+
[V3, "UW, UW0, UW1, UW2", "b<b>oo</b>t"],
|
317 |
+
[V3, "UX", "d<b>u</b>de"],
|
318 |
+
[V3, "WH", "<b>wh</b>at, <b>wh</b>y (w with 'h' sound)"],
|
319 |
+
|
320 |
+
[V2_3, "V", "<b>v</b>ie"],
|
321 |
+
[V2_3, "W", "<b>w</b>ise"],
|
322 |
+
[V2_3, "Y", "<b>y</b>acht"],
|
323 |
+
[V2_3, "Z", "<b>z</b>oo"],
|
324 |
+
[V2_3, "ZH", "plea<b>s</b>ure"],
|
325 |
+
]
|
326 |
+
arpabetReferenceList.innerHTML = ""
|
327 |
+
data.forEach(item => {
|
328 |
+
if (item[0].includes(parseInt(arpabetMenuModelDropdown.value))) {
|
329 |
+
const div = createElem("div")
|
330 |
+
div.appendChild(createElem("div", item[1]))
|
331 |
+
|
332 |
+
const exampleDiv = createElem("div")
|
333 |
+
exampleDiv.appendChild(createElem("div",item[2]))
|
334 |
+
div.appendChild(exampleDiv)
|
335 |
+
|
336 |
+
arpabetReferenceList.appendChild(div)
|
337 |
+
}
|
338 |
+
})
|
339 |
+
}
|
340 |
+
arpabetMenuModelDropdown.value = "3"
|
341 |
+
window.refreshARPABETReferenceDisplay()
|
342 |
+
arpabetMenuModelDropdown.addEventListener("click", window.refreshARPABETReferenceDisplay)
|
343 |
+
|
344 |
+
|
345 |
+
arpabet_save.addEventListener("click", () => {
|
346 |
+
const word = arpabet_word_input.value.trim().toLowerCase()
|
347 |
+
const arpabet = arpabet_arpabet_input.value.trim().toUpperCase().replace(/\s{2,}/g, " ")
|
348 |
+
|
349 |
+
if (!word.length || !arpabet.length) {
|
350 |
+
return window.errorModal(window.i18n.ARPABET_ERROR_EMPTY_INPUT)
|
351 |
+
}
|
352 |
+
|
353 |
+
const badSymbols = arpabet.split(" ").filter(symb => !window.ARPAbetSymbols.includes(symb))
|
354 |
+
if (badSymbols.length) {
|
355 |
+
return window.errorModal(window.i18n.ARPABET_ERROR_BAD_SYMBOLS.replace("_1", badSymbols.join(", ")))
|
356 |
+
}
|
357 |
+
|
358 |
+
const wordKeys = Object.keys(window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data)
|
359 |
+
|
360 |
+
const doTheRest_updateDict = () => {
|
361 |
+
window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data[word] = {enabled: true, arpabet: arpabet}
|
362 |
+
window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].filteredData = window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data
|
363 |
+
|
364 |
+
window.refreshDictWordList()
|
365 |
+
window.saveARPAbetDict(window.arpabetMenuState.currentDict)
|
366 |
+
}
|
367 |
+
|
368 |
+
|
369 |
+
// Delete the old record
|
370 |
+
if (window.arpabetMenuState.clickedRecord && window.arpabetMenuState.clickedRecord.word != word) {
|
371 |
+
delete window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data[window.arpabetMenuState.clickedRecord.word]
|
372 |
+
}
|
373 |
+
|
374 |
+
let wordExists = []
|
375 |
+
Object.keys(window.arpabetMenuState.dictionaries).forEach(dictName => {
|
376 |
+
if (dictName==window.arpabetMenuState.currentDict) {
|
377 |
+
return
|
378 |
+
}
|
379 |
+
|
380 |
+
if (Object.keys(window.arpabetMenuState.dictionaries[dictName].data).includes(word)) {
|
381 |
+
wordExists.push(dictName)
|
382 |
+
}
|
383 |
+
})
|
384 |
+
|
385 |
+
if (wordExists.length) {
|
386 |
+
window.confirmModal(window.i18n.ARPABET_CONFIRM_SAME_WORD.replace("_1", word).replace("_2", wordExists.join("<br>"))).then(response => {
|
387 |
+
if (response) {
|
388 |
+
doTheRest_updateDict()
|
389 |
+
}
|
390 |
+
})
|
391 |
+
} else {
|
392 |
+
doTheRest_updateDict()
|
393 |
+
}
|
394 |
+
})
|
395 |
+
|
396 |
+
arpabetModal.addEventListener("click", (event) => {
|
397 |
+
if (window.arpabetMenuState.clickedRecord && event.target.className!="arpabetRow"&& event.target.className!="arpabetRowItem" && ![arpabet_word_input, arpabet_arpabet_input, arpabet_save, arpabet_prev_btn, arpabet_next_btn].includes(event.target)) {
|
398 |
+
window.arpabetMenuState.clickedRecord = undefined
|
399 |
+
arpabet_word_input.value = ""
|
400 |
+
arpabet_arpabet_input.value = ""
|
401 |
+
}
|
402 |
+
})
|
403 |
+
arpabet_prev_btn.addEventListener("click", () => {
|
404 |
+
window.arpabetMenuState.paginationIndex = Math.max(0, window.arpabetMenuState.paginationIndex-1)
|
405 |
+
window.refreshDictWordList()
|
406 |
+
})
|
407 |
+
arpabet_next_btn.addEventListener("click", () => {
|
408 |
+
window.arpabetMenuState.paginationIndex = Math.min(window.arpabetMenuState.totalPages-1, window.arpabetMenuState.paginationIndex+1)
|
409 |
+
window.refreshDictWordList()
|
410 |
+
})
|
411 |
+
|
412 |
+
window.arpabetRunSearch = () => {
|
413 |
+
if (!window.arpabetMenuState.currentDict) {
|
414 |
+
return
|
415 |
+
}
|
416 |
+
window.arpabetMenuState.paginationIndex = 0
|
417 |
+
window.arpabetMenuState.totalPages = 0
|
418 |
+
|
419 |
+
let query = arpabet_word_search_input.value.trim().toLowerCase()
|
420 |
+
|
421 |
+
if (!query.length) {
|
422 |
+
if (arpabet_search_only_enabled.checked) {
|
423 |
+
const filteredKeys = Object.keys(window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data).filter(key => window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data[key].enabled)
|
424 |
+
window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].filteredData = {}
|
425 |
+
filteredKeys.forEach(key => {
|
426 |
+
window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].filteredData[key] = window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data[key]
|
427 |
+
})
|
428 |
+
} else {
|
429 |
+
window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].filteredData = window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data
|
430 |
+
}
|
431 |
+
} else {
|
432 |
+
const strictQuery = query.startsWith("\"")
|
433 |
+
const filteredKeys = Object.keys(window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data)
|
434 |
+
.filter(key => {
|
435 |
+
if (strictQuery) {
|
436 |
+
query = query.replaceAll("\"", "")
|
437 |
+
return key==query && (arpabet_search_only_enabled.checked ? (window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data[key].enabled) : true)
|
438 |
+
} else {
|
439 |
+
return key.includes(query) && (arpabet_search_only_enabled.checked ? (window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data[key].enabled) : true)
|
440 |
+
}
|
441 |
+
})
|
442 |
+
|
443 |
+
window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].filteredData = {}
|
444 |
+
filteredKeys.forEach(key => {
|
445 |
+
window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].filteredData[key] = window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data[key]
|
446 |
+
})
|
447 |
+
}
|
448 |
+
|
449 |
+
window.refreshDictWordList()
|
450 |
+
}
|
451 |
+
let arpabetSearchInterval
|
452 |
+
arpabet_word_search_input.addEventListener("keyup", () => {
|
453 |
+
if (arpabetSearchInterval!=null) {
|
454 |
+
clearTimeout(arpabetSearchInterval)
|
455 |
+
}
|
456 |
+
arpabetSearchInterval = setTimeout(window.arpabetRunSearch, 500)
|
457 |
+
})
|
458 |
+
|
459 |
+
arpabet_enableall_button.addEventListener("click", () => {
|
460 |
+
|
461 |
+
const dictName = window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].title
|
462 |
+
window.confirmModal(window.i18n.ARPABET_CONFIRM_ENABLE_ALL.replace("_1", dictName)).then(response => {
|
463 |
+
if (response) {
|
464 |
+
setTimeout(() => {
|
465 |
+
const wordKeys = Object.keys(window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data)
|
466 |
+
wordKeys.forEach(word => {
|
467 |
+
window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data[word].enabled = true
|
468 |
+
})
|
469 |
+
|
470 |
+
window.saveARPAbetDict(window.arpabetMenuState.currentDict)
|
471 |
+
window.arpabetRunSearch()
|
472 |
+
}, 210)
|
473 |
+
}
|
474 |
+
})
|
475 |
+
})
|
476 |
+
|
477 |
+
arpabet_disableall_button.addEventListener("click", () => {
|
478 |
+
|
479 |
+
const dictName = window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].title
|
480 |
+
window.confirmModal(window.i18n.ARPABET_CONFIRM_DISABLE_ALL.replace("_1", dictName)).then(response => {
|
481 |
+
if (response) {
|
482 |
+
setTimeout(() => {
|
483 |
+
const wordKeys = Object.keys(window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data)
|
484 |
+
wordKeys.forEach(word => {
|
485 |
+
window.arpabetMenuState.dictionaries[window.arpabetMenuState.currentDict].data[word].enabled = false
|
486 |
+
})
|
487 |
+
|
488 |
+
window.saveARPAbetDict(window.arpabetMenuState.currentDict)
|
489 |
+
window.arpabetRunSearch()
|
490 |
+
}, 210)
|
491 |
+
}
|
492 |
+
})
|
493 |
+
})
|
494 |
+
arpabet_search_only_enabled.addEventListener("click", () => window.arpabetRunSearch())
|
495 |
+
|
496 |
+
|
497 |
+
|
498 |
+
fs.watch(`${window.path}/arpabet`, {recursive: false, persistent: true}, (eventType, filename) => {console.log(eventType, filename);refreshDictionariesList()})
|
javascript/batch.js
ADDED
@@ -0,0 +1,1573 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
const path = require('path')
|
4 |
+
const smi = require('node-nvidia-smi')
|
5 |
+
|
6 |
+
window.batch_state = {
|
7 |
+
lines: [],
|
8 |
+
fastModeActuallyFinishedTasks: 0,
|
9 |
+
fastModeOutputPromises: [],
|
10 |
+
lastModel: undefined,
|
11 |
+
lastVocoder: undefined,
|
12 |
+
lineIndex: 0,
|
13 |
+
state: false,
|
14 |
+
outPathsChecked: [],
|
15 |
+
skippedExisting: 0,
|
16 |
+
paginationIndex: 0,
|
17 |
+
taskBarPercent: 0,
|
18 |
+
startTime: undefined,
|
19 |
+
linesDoneSinceStart: 0
|
20 |
+
}
|
21 |
+
|
22 |
+
|
23 |
+
// https://stackoverflow.com/questions/1293147/example-javascript-code-to-parse-csv-data
|
24 |
+
function CSVToArray( strData, strDelimiter ){
|
25 |
+
// Check to see if the delimiter is defined. If not,
|
26 |
+
// then default to comma.
|
27 |
+
strDelimiter = (strDelimiter || ",");
|
28 |
+
|
29 |
+
// Create a regular expression to parse the CSV values.
|
30 |
+
var objPattern = new RegExp(
|
31 |
+
(
|
32 |
+
// Delimiters.
|
33 |
+
"(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
|
34 |
+
|
35 |
+
// Quoted fields.
|
36 |
+
"(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
|
37 |
+
|
38 |
+
// Standard fields.
|
39 |
+
"([^\"\\" + strDelimiter + "\\r\\n]*))"
|
40 |
+
),
|
41 |
+
"gi"
|
42 |
+
);
|
43 |
+
|
44 |
+
// Create an array to hold our data. Give the array
|
45 |
+
// a default empty first row.
|
46 |
+
var arrData = [[]];
|
47 |
+
|
48 |
+
// Create an array to hold our individual pattern
|
49 |
+
// matching groups.
|
50 |
+
var arrMatches = null;
|
51 |
+
|
52 |
+
// Keep looping over the regular expression matches
|
53 |
+
// until we can no longer find a match.
|
54 |
+
while (arrMatches = objPattern.exec( strData )){
|
55 |
+
|
56 |
+
// Get the delimiter that was found.
|
57 |
+
var strMatchedDelimiter = arrMatches[ 1 ];
|
58 |
+
|
59 |
+
// Check to see if the given delimiter has a length
|
60 |
+
// (is not the start of string) and if it matches
|
61 |
+
// field delimiter. If id does not, then we know
|
62 |
+
// that this delimiter is a row delimiter.
|
63 |
+
if (
|
64 |
+
strMatchedDelimiter.length &&
|
65 |
+
strMatchedDelimiter !== strDelimiter
|
66 |
+
){
|
67 |
+
// Since we have reached a new row of data,
|
68 |
+
// add an empty row to our data array.
|
69 |
+
arrData.push( [] );
|
70 |
+
}
|
71 |
+
|
72 |
+
var strMatchedValue;
|
73 |
+
|
74 |
+
// Now that we have our delimiter out of the way,
|
75 |
+
// let's check to see which kind of value we
|
76 |
+
// captured (quoted or unquoted).
|
77 |
+
if (arrMatches[ 2 ]){
|
78 |
+
// We found a quoted value. When we capture
|
79 |
+
// this value, unescape any double quotes.
|
80 |
+
strMatchedValue = arrMatches[ 2 ].replace(
|
81 |
+
new RegExp( "\"\"", "g" ),
|
82 |
+
"\""
|
83 |
+
);
|
84 |
+
} else {
|
85 |
+
// We found a non-quoted value.
|
86 |
+
strMatchedValue = arrMatches[ 3 ];
|
87 |
+
|
88 |
+
}
|
89 |
+
|
90 |
+
// Now that we have our value string, let's add
|
91 |
+
// it to the data array.
|
92 |
+
arrData[ arrData.length - 1 ].push( strMatchedValue );
|
93 |
+
}
|
94 |
+
|
95 |
+
// Return the parsed data.
|
96 |
+
return( arrData );
|
97 |
+
}
|
98 |
+
window.CSVToArray = CSVToArray
|
99 |
+
|
100 |
+
let smiInterval = setInterval(() => {
|
101 |
+
try {
|
102 |
+
if (window.userSettings.useGPU) {
|
103 |
+
smi((err, data) => {
|
104 |
+
if (err) {
|
105 |
+
console.log("smi error: ", err)
|
106 |
+
}
|
107 |
+
if (data && data.nvidia_smi_log.cuda_version) {
|
108 |
+
let total
|
109 |
+
let used
|
110 |
+
|
111 |
+
if (data.nvidia_smi_log.gpu.length) {
|
112 |
+
total = parseInt(data.nvidia_smi_log.gpu[0].fb_memory_usage.total.split(" ")[0])
|
113 |
+
used = parseInt(data.nvidia_smi_log.gpu[0].fb_memory_usage.used.split(" ")[0])
|
114 |
+
} else {
|
115 |
+
total = parseInt(data.nvidia_smi_log.gpu.fb_memory_usage.total.split(" ")[0])
|
116 |
+
used = parseInt(data.nvidia_smi_log.gpu.fb_memory_usage.used.split(" ")[0])
|
117 |
+
}
|
118 |
+
const percent = used/total*100
|
119 |
+
|
120 |
+
vramUsage.innerHTML = `${(used/1000).toFixed(1)}/${(total/1000).toFixed(1)} GB (${percent.toFixed(2)}%)`
|
121 |
+
}
|
122 |
+
})
|
123 |
+
} else {
|
124 |
+
vramUsage.innerHTML = window.i18n.NOT_USING_GPU
|
125 |
+
}
|
126 |
+
} catch (e) {
|
127 |
+
console.log(e)
|
128 |
+
window.appLogger.log(e.stack)
|
129 |
+
clearInterval(smiInterval)
|
130 |
+
}
|
131 |
+
}, 1000)
|
132 |
+
|
133 |
+
batch_instructions_btn.addEventListener("click", () => {
|
134 |
+
window.createModal("error", `${window.i18n.BATCH_INSTR1} <a href="https://www.youtube.com/watch?v=PK-m54f84q4" target="_blank">${window.i18n.BATCH_INSTR2}</a> <span>${window.i18n.BATCH_INSTR3}</span>`)
|
135 |
+
})
|
136 |
+
|
137 |
+
batch_generateSample.addEventListener("click", () => {
|
138 |
+
// const lines = []
|
139 |
+
const csv = ["game_id,voice_id,text,vocoder,out_path,pacing"] // TODO: ffmpeg options
|
140 |
+
const games = Object.keys(window.games)
|
141 |
+
|
142 |
+
if (games.length==0) {
|
143 |
+
window.errorModal(window.i18n.BATCH_ERR_NO_VOICES)
|
144 |
+
return
|
145 |
+
}
|
146 |
+
|
147 |
+
const sampleText = [
|
148 |
+
"Include as many lines of text you wish with one line of data per line of voice to be read out.",
|
149 |
+
"Make sure that the required columns (game_id, voice_id, and text) are filled out",
|
150 |
+
"The others can be left blank, and the app will figure out some sensible defaults",
|
151 |
+
"The valid options for vocoder are one of: quickanddirty, waveglow, waveglowBIG, hifi",
|
152 |
+
"If your specified model does not have a bespoke hifi model, it will use the waveglow model, also the default if you leave this blank.",
|
153 |
+
"For all the other options, you can leave them blank.",
|
154 |
+
"If no output path is specified for a specific voice, the default batch output directory will be used",
|
155 |
+
]
|
156 |
+
|
157 |
+
sampleText.forEach(line => {
|
158 |
+
const game = games[parseInt(Math.random()*games.length)]
|
159 |
+
const gameModels = window.games[game].models
|
160 |
+
const model = gameModels[parseInt(Math.random()*gameModels.length)].variants[0]
|
161 |
+
|
162 |
+
console.log("game", game, "model", model)
|
163 |
+
|
164 |
+
const record = {
|
165 |
+
game_id: game,
|
166 |
+
voice_id: model.voiceId,
|
167 |
+
text: line,
|
168 |
+
vocoder: ["quickanddirty","waveglow","waveglowBIG","hifi"][parseInt(Math.random()*4)],
|
169 |
+
out_path: "",
|
170 |
+
pacing: 1
|
171 |
+
}
|
172 |
+
// lines.push(record)
|
173 |
+
csv.push(Object.values(record).map(v => typeof v =="string" ? `"${v}"` : v).join(","))
|
174 |
+
})
|
175 |
+
|
176 |
+
const out_directory = `${__dirname.replace("\\javascript", "").replace(/\\/g,"/")}/batch`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app")
|
177 |
+
if (!fs.existsSync(out_directory)){
|
178 |
+
fs.mkdirSync(out_directory)
|
179 |
+
}
|
180 |
+
fs.writeFileSync(`${out_directory}/sample.csv`, csv.join("\n"))
|
181 |
+
// shell.showItemInFolder(`${out_directory}/sample.csv`)
|
182 |
+
er.shell.showItemInFolder(`${out_directory}/sample.csv`)
|
183 |
+
spawn(`explorer`, [out_directory], {stdio: "ignore"})
|
184 |
+
})
|
185 |
+
|
186 |
+
window.readFileTxt = (file) => {
|
187 |
+
return new Promise((resolve, reject) => {
|
188 |
+
const dataLines = []
|
189 |
+
const reader = new FileReader()
|
190 |
+
reader.readAsText(file)
|
191 |
+
reader.onloadend = () => {
|
192 |
+
const lines = reader.result.replace(/\r\n/g, "\n").split("\n")
|
193 |
+
lines.forEach(line => {
|
194 |
+
if (line.trim().length) {
|
195 |
+
const record = {}
|
196 |
+
record.game_id = window.currentModel.games[0].gameId
|
197 |
+
record.voice_id = window.currentModel.games[0].voiceId
|
198 |
+
record.text = line
|
199 |
+
if (window.currentModel.hifi) {
|
200 |
+
record.vocoder = "hifi"
|
201 |
+
}
|
202 |
+
dataLines.push(record)
|
203 |
+
}
|
204 |
+
})
|
205 |
+
resolve(dataLines)
|
206 |
+
}
|
207 |
+
})
|
208 |
+
}
|
209 |
+
|
210 |
+
window.readFile = (file) => {
|
211 |
+
return new Promise((resolve, reject) => {
|
212 |
+
const dataLines = []
|
213 |
+
const reader = new FileReader()
|
214 |
+
reader.readAsText(file)
|
215 |
+
reader.onloadend = () => {
|
216 |
+
const lines = reader.result.replace(/\r\n/g, "\n").split("\n")
|
217 |
+
const headerLine = lines.shift()
|
218 |
+
|
219 |
+
const doRest = () => {
|
220 |
+
|
221 |
+
const header = headerLine.split(window.userSettings.batch_delimiter).map(head => head.replace(/\r/, ""))
|
222 |
+
|
223 |
+
lines.forEach(line => {
|
224 |
+
const record = {}
|
225 |
+
if (line.trim().length) {
|
226 |
+
const parts = CSVToArray(line, window.userSettings.batch_delimiter)[0]
|
227 |
+
parts.forEach((val, vi) => {
|
228 |
+
try {
|
229 |
+
let header_val = header[vi].replace(/^"/, "").replace(/"$/, "")
|
230 |
+
record[header_val.replace(/\s/g,"")] = (val||"").replace(/\\/g, "/")
|
231 |
+
} catch (e) {
|
232 |
+
window.errorModal(`Error parsing line: ${val}`)
|
233 |
+
console.log(e)
|
234 |
+
window.appLogger.log(e)
|
235 |
+
}
|
236 |
+
})
|
237 |
+
dataLines.push(record)
|
238 |
+
}
|
239 |
+
})
|
240 |
+
resolve(dataLines)
|
241 |
+
}
|
242 |
+
|
243 |
+
const headerLine_clean = headerLine.replaceAll("\"", "")
|
244 |
+
if (headerLine_clean.includes(window.userSettings.batch_delimiter)) {
|
245 |
+
doRest()
|
246 |
+
} else {
|
247 |
+
const potentialDelimiter = headerLine_clean.split("voice_id")[0].split("game_id")[1]
|
248 |
+
|
249 |
+
window.confirmModal(window.i18n.BATCH_CHANGE_DELIMITER.replace("_1", window.userSettings.batch_delimiter).replace("_2", potentialDelimiter)).then(response => {
|
250 |
+
if (response) {
|
251 |
+
window.userSettings.batch_delimiter = potentialDelimiter
|
252 |
+
setting_batch_delimiter.value = potentialDelimiter
|
253 |
+
window.saveUserSettings()
|
254 |
+
doRest()
|
255 |
+
}
|
256 |
+
})
|
257 |
+
}
|
258 |
+
}
|
259 |
+
})
|
260 |
+
}
|
261 |
+
|
262 |
+
let handleMetadataCSVdrop_response = undefined
|
263 |
+
const sleep = ms => {
|
264 |
+
return new Promise(resolve => {
|
265 |
+
setTimeout(() => {
|
266 |
+
resolve()
|
267 |
+
}, ms)
|
268 |
+
})
|
269 |
+
}
|
270 |
+
const handleMetadataCSVdrop = (file) => {
|
271 |
+
return new Promise(async resolve => {
|
272 |
+
i18n_batch_metadata_open_btn.click()
|
273 |
+
|
274 |
+
handleMetadataCSVdrop_response = undefined
|
275 |
+
batch_metadata_input_gameID.value = window.currentGame.gameId
|
276 |
+
if (window.currentModel) {
|
277 |
+
batch_metadata_input_voiceID.value = window.currentModel.voiceId
|
278 |
+
|
279 |
+
}
|
280 |
+
|
281 |
+
await sleep(1000)
|
282 |
+
while (handleMetadataCSVdrop_response === undefined) {
|
283 |
+
if (batchMetadataCSVContainer.style.opacity.length==0 || parseFloat(batchMetadataCSVContainer.style.opacity)==1) {
|
284 |
+
await sleep(1000)
|
285 |
+
} else {
|
286 |
+
handleMetadataCSVdrop_response = false
|
287 |
+
}
|
288 |
+
}
|
289 |
+
|
290 |
+
if (handleMetadataCSVdrop_response) {
|
291 |
+
|
292 |
+
const dataLines = []
|
293 |
+
const reader = new FileReader()
|
294 |
+
reader.readAsText(file)
|
295 |
+
reader.onloadend = () => {
|
296 |
+
const lines = reader.result.replace(/\r\n/g, "\n").split("\n")
|
297 |
+
lines.forEach(line => {
|
298 |
+
if (line.trim().length) {
|
299 |
+
|
300 |
+
const text = line.split("|")[1]
|
301 |
+
const record = {}
|
302 |
+
record.game_id = batch_metadata_input_gameID.value
|
303 |
+
record.voice_id = batch_metadata_input_voiceID.value
|
304 |
+
record.text = text
|
305 |
+
if (!window.currentModel || window.currentModel.hifi) {
|
306 |
+
record.vocoder = "hifi"
|
307 |
+
}
|
308 |
+
dataLines.push(record)
|
309 |
+
}
|
310 |
+
})
|
311 |
+
resolve(dataLines)
|
312 |
+
}
|
313 |
+
|
314 |
+
} else {
|
315 |
+
resolve(false)
|
316 |
+
}
|
317 |
+
|
318 |
+
|
319 |
+
})
|
320 |
+
}
|
321 |
+
i18n_batch_metadata_confirm_btn.addEventListener("click", () => {
|
322 |
+
handleMetadataCSVdrop_response = true
|
323 |
+
batchMetadataCSVContainer.click()
|
324 |
+
batchIcon.click()
|
325 |
+
})
|
326 |
+
|
327 |
+
window.uploadBatchCSVs = async (eType, event) => {
|
328 |
+
|
329 |
+
if (["dragenter", "dragover"].includes(eType)) {
|
330 |
+
batch_main.style.background = "#5b5b5b"
|
331 |
+
batch_main.style.color = "white"
|
332 |
+
}
|
333 |
+
if (["dragleave", "drop"].includes(eType)) {
|
334 |
+
batch_main.style.background = "#4b4b4b"
|
335 |
+
batch_main.style.color = "gray"
|
336 |
+
}
|
337 |
+
|
338 |
+
event.preventDefault()
|
339 |
+
event.stopPropagation()
|
340 |
+
|
341 |
+
const dataLines = []
|
342 |
+
|
343 |
+
if (eType=="drop") {
|
344 |
+
|
345 |
+
batchDropZoneNote.innerHTML = window.i18n.PROCESSING_DATA
|
346 |
+
window.batch_state.skippedExisting = 0
|
347 |
+
|
348 |
+
const dataTransfer = event.dataTransfer
|
349 |
+
const files = Array.from(dataTransfer.files)
|
350 |
+
for (let fi=0; fi<files.length; fi++) {
|
351 |
+
const file = files[fi]
|
352 |
+
|
353 |
+
if (!file.name.toLowerCase().endsWith(".csv") || file.name.toLowerCase()=="metadata.csv") {
|
354 |
+
if ( file.name.toLowerCase().endsWith(".txt") || file.name.toLowerCase()=="metadata.csv" ) {
|
355 |
+
if (window.currentModel || file.name.toLowerCase()=="metadata.csv") {
|
356 |
+
window.appLogger.log(`Reading file: ${file.name}`)
|
357 |
+
|
358 |
+
let records
|
359 |
+
|
360 |
+
if (file.name.toLowerCase()=="metadata.csv") {
|
361 |
+
|
362 |
+
records = await handleMetadataCSVdrop(file)
|
363 |
+
if (records===false) {
|
364 |
+
continue
|
365 |
+
}
|
366 |
+
|
367 |
+
} else {
|
368 |
+
if (window.currentModel) {
|
369 |
+
records = await window.readFileTxt(file)
|
370 |
+
}
|
371 |
+
}
|
372 |
+
|
373 |
+
if (window.currentModel || file.name.toLowerCase()=="metadata.csv") {
|
374 |
+
if (window.userSettings.batch_skipExisting) {
|
375 |
+
window.appLogger.log("Checking existing files before adding to queue")
|
376 |
+
} else {
|
377 |
+
window.appLogger.log("Adding files to queue")
|
378 |
+
}
|
379 |
+
records.forEach(item => {
|
380 |
+
let outPath
|
381 |
+
|
382 |
+
if (item.out_path && item.out_path.split("/").reverse()[0].includes(".")) {
|
383 |
+
outPath = item.out_path
|
384 |
+
} else {
|
385 |
+
if (item.out_path) {
|
386 |
+
outPath = item.out_path
|
387 |
+
} else {
|
388 |
+
outPath = window.userSettings.batchOutFolder
|
389 |
+
}
|
390 |
+
if (item.vc_content) {
|
391 |
+
let vc_content_fname = item.vc_content.split("/").reverse()[0]
|
392 |
+
outPath = `${outPath}/${vc_content_fname.slice(0,vc_content_fname.length-4).slice(0, window.userSettings.max_filename_chars-10).replace(/\.$/, "")}.${window.userSettings.audio.format}`
|
393 |
+
} else {
|
394 |
+
outPath = `${outPath}/${item.voice_id}_${item.vocoder}_${item.text.replace(/[\/\\:\*?<>"|]*/g, "").slice(0, window.userSettings.max_filename_chars-10).replace(/\.$/, "")}.${window.userSettings.audio.format}`
|
395 |
+
}
|
396 |
+
}
|
397 |
+
|
398 |
+
outPath = outPath.startsWith("./") ? window.userSettings.batchOutFolder + outPath.slice(1,100000) : outPath
|
399 |
+
item.out_path = outPath
|
400 |
+
|
401 |
+
if (window.userSettings.batch_skipExisting && fs.existsSync(outPath)) {
|
402 |
+
window.batch_state.skippedExisting++
|
403 |
+
} else {
|
404 |
+
dataLines.push(item)
|
405 |
+
}
|
406 |
+
})
|
407 |
+
}
|
408 |
+
|
409 |
+
}
|
410 |
+
continue
|
411 |
+
} else {
|
412 |
+
continue
|
413 |
+
}
|
414 |
+
}
|
415 |
+
|
416 |
+
window.appLogger.log(`Reading file: ${file.name}`)
|
417 |
+
const records = await window.readFile(file)
|
418 |
+
if (window.userSettings.batch_skipExisting) {
|
419 |
+
window.appLogger.log("Checking existing files before adding to queue")
|
420 |
+
} else {
|
421 |
+
window.appLogger.log("Adding files to queue")
|
422 |
+
}
|
423 |
+
records.forEach((item, ii) => {
|
424 |
+
let outPath
|
425 |
+
|
426 |
+
if (item.out_path && item.out_path.split("/").reverse()[0].includes(".")) {
|
427 |
+
outPath = item.out_path
|
428 |
+
} else {
|
429 |
+
if (item.out_path) {
|
430 |
+
outPath = item.out_path
|
431 |
+
} else {
|
432 |
+
outPath = window.userSettings.batchOutFolder
|
433 |
+
}
|
434 |
+
if (item.vc_content) {
|
435 |
+
let vc_content_fname = item.vc_content.split("/").reverse()[0]
|
436 |
+
outPath = `${outPath}/${vc_content_fname.slice(0,vc_content_fname.length-4).slice(0,item.vc_content.length-4).slice(0, window.userSettings.max_filename_chars-10).replace(/\.$/, "")}.${window.userSettings.audio.format}`
|
437 |
+
} else {
|
438 |
+
outPath = `${outPath}/${item.voice_id}_${item.vocoder}_${item.text.replace(/[\/\\:\*?<>"|]*/g, "").slice(0, window.userSettings.max_filename_chars-10).replace(/\.$/, "")}.${window.userSettings.audio.format}`
|
439 |
+
}
|
440 |
+
}
|
441 |
+
|
442 |
+
outPath = outPath.startsWith("./") ? window.userSettings.batchOutFolder + outPath.slice(1,100000) : outPath
|
443 |
+
item.out_path = outPath
|
444 |
+
|
445 |
+
if (window.userSettings.batch_skipExisting && fs.existsSync(outPath)) {
|
446 |
+
window.batch_state.skippedExisting++
|
447 |
+
} else {
|
448 |
+
dataLines.push(item)
|
449 |
+
}
|
450 |
+
})
|
451 |
+
}
|
452 |
+
|
453 |
+
if (dataLines.length==0 && window.batch_state.skippedExisting) {
|
454 |
+
batchDropZoneNote.innerHTML = window.i18n.BATCH_DROPZONE
|
455 |
+
return window.errorModal(window.i18n.BATCH_ERR_SKIPPEDALL.replace("_1", window.batch_state.skippedExisting))
|
456 |
+
}
|
457 |
+
|
458 |
+
window.batch_state.paginationIndex = 0
|
459 |
+
batch_pageNum.value = 1
|
460 |
+
|
461 |
+
|
462 |
+
window.appLogger.log("Preprocessing data...")
|
463 |
+
const cleanedData = window.preProcessCSVData(dataLines)
|
464 |
+
if (cleanedData.length) {
|
465 |
+
window.populateBatchRecordsList(cleanedData)
|
466 |
+
window.appLogger.log("Grouping up lines...")
|
467 |
+
const finalOrder = window.groupBatchLines()
|
468 |
+
window.refreshBatchRecordsList(finalOrder)
|
469 |
+
window.batch_state.lines = finalOrder
|
470 |
+
} else {
|
471 |
+
// batch_clearBtn.click()
|
472 |
+
}
|
473 |
+
window.appLogger.log("batch import done")
|
474 |
+
|
475 |
+
const numPages = Math.ceil(window.batch_state.lines.length/window.userSettings.batch_paginationSize)
|
476 |
+
batch_total_pages.innerHTML = `of ${numPages}`
|
477 |
+
batchDropZoneNote.innerHTML = window.i18n.BATCH_DROPZONE
|
478 |
+
}
|
479 |
+
}
|
480 |
+
|
481 |
+
window.preProcessCSVData = data => {
|
482 |
+
|
483 |
+
batch_main.style.display = "block"
|
484 |
+
batchDropZoneNote.style.display = "none"
|
485 |
+
batchRecordsHeader.style.display = "flex"
|
486 |
+
batch_clearBtn.style.display = "inline-block"
|
487 |
+
Array.from(batchRecordsHeader.children).forEach(item => item.style.backgroundColor = `#${window.currentGame.themeColourPrimary}`)
|
488 |
+
|
489 |
+
const availableGames = Object.keys(window.games)
|
490 |
+
for (let di=0; di<data.length; di++) {
|
491 |
+
try {
|
492 |
+
const record = data[di]
|
493 |
+
|
494 |
+
// Validate the records first
|
495 |
+
// ==================
|
496 |
+
if (!record.game_id) {
|
497 |
+
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: ${window.i18n.MISSING} game_id`)
|
498 |
+
return []
|
499 |
+
}
|
500 |
+
if (!record.voice_id) {
|
501 |
+
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: ${window.i18n.MISSING} voice_id`)
|
502 |
+
return []
|
503 |
+
}
|
504 |
+
if ((!record.text || record.text.length==0) && (!record.vc_content)) {
|
505 |
+
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: ${window.i18n.MISSING} text/vc_content`)
|
506 |
+
return []
|
507 |
+
}
|
508 |
+
|
509 |
+
// Check that the game_id exists
|
510 |
+
if (!availableGames.includes(record.game_id)) {
|
511 |
+
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: game_id "${record.game_id}" ${window.i18n.BATCH_ERR_GAMEID} <br><br>(${availableGames.join(', ')})`)
|
512 |
+
return []
|
513 |
+
}
|
514 |
+
// Check that the voice_id exists
|
515 |
+
const gameVoices = []
|
516 |
+
window.games[record.game_id].models.forEach(model => {
|
517 |
+
model.variants.forEach(variant => gameVoices.push(variant.voiceId))
|
518 |
+
})
|
519 |
+
|
520 |
+
if (!gameVoices.includes(record.voice_id)) {
|
521 |
+
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: voice_id "${record.voice_id}" ${window.i18n.BATCH_ERR_VOICEID}: ${record.game_id}`)
|
522 |
+
return []
|
523 |
+
}
|
524 |
+
// Check that the vocoder exists
|
525 |
+
if (!["quickanddirty", "waveglow", "waveglowBIG", "hifi", "", "-", undefined].includes(record.vocoder)) {
|
526 |
+
window.errorModal(`[${window.i18n.LINE}: ${di+2}] ${window.i18n.ERROR}: ${window.i18n.BATCHH_VOCODER} "${record.vocoder}" ${window.i18n.BATCH_ERR_VOCODER1}: quickanddirty, waveglow, waveglowBIG, hifi ${window.i18n.BATCH_ERR_VOCODER2}`)
|
527 |
+
return []
|
528 |
+
}
|
529 |
+
|
530 |
+
data[di].modelType = undefined
|
531 |
+
let hasHifi = false
|
532 |
+
window.games[data[di].game_id].models.forEach(model => {
|
533 |
+
model.variants.forEach(variant => {
|
534 |
+
if (variant.voiceId==data[di].voice_id) {
|
535 |
+
data[di].modelType = variant.modelType || model.modelType
|
536 |
+
record.voiceName = model.voiceName // For easy access later on
|
537 |
+
if (variant.hifi) {
|
538 |
+
hasHifi = variant.hifi
|
539 |
+
}
|
540 |
+
if (variant.lang && !record.lang) {
|
541 |
+
record.lang = "en"
|
542 |
+
}
|
543 |
+
// TODO allow batch mode voice conversion/speech to speech by computing the embs of specified audio files instead of using the base voice embedding
|
544 |
+
// Also TODO, might need to allow for custom voice embeddings
|
545 |
+
if (variant.modelType=="xVAPitch") {
|
546 |
+
record.base_emb = variant.base_speaker_emb
|
547 |
+
record.vocoder = "-"
|
548 |
+
}
|
549 |
+
}
|
550 |
+
})
|
551 |
+
})
|
552 |
+
|
553 |
+
// Fill with defaults
|
554 |
+
// ==================
|
555 |
+
if (!record.out_path) {
|
556 |
+
record.out_path = window.userSettings.batchOutFolder
|
557 |
+
}
|
558 |
+
if (!record.pacing) {
|
559 |
+
record.pacing = 1
|
560 |
+
}
|
561 |
+
record.pacing = parseFloat(record.pacing)
|
562 |
+
|
563 |
+
|
564 |
+
if (!record.pitch_amp) {
|
565 |
+
record.pitch_amp = 1
|
566 |
+
}
|
567 |
+
record.pitch_amp = parseFloat(record.pitch_amp)
|
568 |
+
|
569 |
+
if (!record.out_path.includes(":/") && !record.out_path.startsWith("./")) {
|
570 |
+
record.out_path = `./${record.out_path}`
|
571 |
+
}
|
572 |
+
|
573 |
+
if (!record.vocoder || (record.vocoder=="hifi" && !hasHifi)) {
|
574 |
+
record.vocoder = "quickanddirty"
|
575 |
+
}
|
576 |
+
} catch (e) {
|
577 |
+
console.log(e)
|
578 |
+
window.appLogger.log(e)
|
579 |
+
console.log(data[di])
|
580 |
+
console.log(window.games[data[di].game_id])
|
581 |
+
}
|
582 |
+
}
|
583 |
+
|
584 |
+
return data
|
585 |
+
}
|
586 |
+
|
587 |
+
window.populateBatchRecordsList = records => {
|
588 |
+
batch_synthesizeBtn.style.display = "inline-block"
|
589 |
+
batchDropZoneNote.style.display = "none"
|
590 |
+
|
591 |
+
records.forEach((record, ri) => {
|
592 |
+
const row = createElem("div")
|
593 |
+
|
594 |
+
const rNumElem = createElem("div", batchRecordsContainer.children.length.toString())
|
595 |
+
const rStatusElem = createElem("div", "Ready")
|
596 |
+
const rActionsElem = createElem("div")
|
597 |
+
const rVoiceElem = createElem("div", record.voice_id)
|
598 |
+
const rTextElem = createElem("div", (record.vc_content?record.vc_content:record.text).toString())
|
599 |
+
rTextElem.title = rTextElem.innerText
|
600 |
+
if (record.vc_content) {
|
601 |
+
rTextElem.style.fontStyle = "italic"
|
602 |
+
}
|
603 |
+
const rGameElem = createElem("div", record.game_id)
|
604 |
+
const rOutPathElem = createElem("div", "‎"+record.out_path+"‎")
|
605 |
+
rOutPathElem.title = record.out_path
|
606 |
+
const rBaseLangElem = createElem("div", record.vc_content?"-":(record.lang||" ").toString())
|
607 |
+
const rVCStyleElem = createElem("div", (record.vc_style||" ").toString())
|
608 |
+
rVCStyleElem.title = rVCStyleElem.innerText
|
609 |
+
const rVocoderElem = createElem("div", record.vocoder)
|
610 |
+
const rPacingElem = createElem("div", record.vc_content?"-":(record.pacing||" ").toString())
|
611 |
+
const rPitchAmpElem = createElem("div", record.vc_content?"-":(record.pitch_amp||" ").toString())
|
612 |
+
|
613 |
+
|
614 |
+
row.appendChild(rNumElem)
|
615 |
+
row.appendChild(rStatusElem)
|
616 |
+
row.appendChild(rActionsElem)
|
617 |
+
row.appendChild(rVoiceElem)
|
618 |
+
row.appendChild(rTextElem)
|
619 |
+
row.appendChild(rGameElem)
|
620 |
+
row.appendChild(rOutPathElem)
|
621 |
+
row.appendChild(rBaseLangElem)
|
622 |
+
row.appendChild(rVCStyleElem)
|
623 |
+
row.appendChild(rVocoderElem)
|
624 |
+
row.appendChild(rPacingElem)
|
625 |
+
row.appendChild(rPitchAmpElem)
|
626 |
+
|
627 |
+
window.batch_state.lines.push([record, row, ri])
|
628 |
+
})
|
629 |
+
}
|
630 |
+
|
631 |
+
window.refreshBatchRecordsList = (finalOrder) => {
|
632 |
+
batchRecordsContainer.innerHTML = ""
|
633 |
+
finalOrder = finalOrder ? finalOrder : window.batch_state.lines
|
634 |
+
|
635 |
+
const startIndex = (window.batch_state.paginationIndex*window.userSettings.batch_paginationSize)
|
636 |
+
const endIndex = Math.min(startIndex+window.userSettings.batch_paginationSize, finalOrder.length)
|
637 |
+
|
638 |
+
for (let ri=startIndex; ri<endIndex; ri++) {
|
639 |
+
const recordAndElem = finalOrder[ri]
|
640 |
+
recordAndElem[1].children[0].innerHTML = (ri+1)//batchRecordsContainer.children.length.toString()
|
641 |
+
batchRecordsContainer.appendChild(recordAndElem[1])
|
642 |
+
}
|
643 |
+
window.toggleNumericalRecordsDisplay()
|
644 |
+
}
|
645 |
+
|
646 |
+
// Sort the lines by voice_id, and then by vocoder used
|
647 |
+
window.groupBatchLines = () => {
|
648 |
+
if (window.userSettings.batch_doGrouping) {
|
649 |
+
const voices_order = []
|
650 |
+
|
651 |
+
const lines = window.batch_state.lines.sort((a,b) => {
|
652 |
+
return a.voice_id - b.voice_id
|
653 |
+
})
|
654 |
+
|
655 |
+
const voices_groups = {}
|
656 |
+
|
657 |
+
// Get the order of the voice_id, and group them up
|
658 |
+
window.batch_state.lines.forEach(record => {
|
659 |
+
if (!voices_order.includes(record[0].voice_id)) {
|
660 |
+
voices_order.push(record[0].voice_id)
|
661 |
+
voices_groups[record[0].voice_id] = []
|
662 |
+
}
|
663 |
+
voices_groups[record[0].voice_id].push(record)
|
664 |
+
})
|
665 |
+
|
666 |
+
// Go through the voice groups and sort them by vocoder
|
667 |
+
if (window.userSettings.batch_doVocoderGrouping) {
|
668 |
+
voices_order.forEach(voice_id => {
|
669 |
+
voices_groups[voice_id] = voices_groups[voice_id].sort((a,b) => a[0].vocoder<b[0].vocoder?1:-1)
|
670 |
+
})
|
671 |
+
}
|
672 |
+
|
673 |
+
// Collate everything back into the final order
|
674 |
+
const finalOrder = []
|
675 |
+
voices_order.forEach(voice_id => {
|
676 |
+
voices_groups[voice_id].forEach(record => finalOrder.push(record))
|
677 |
+
})
|
678 |
+
|
679 |
+
return finalOrder
|
680 |
+
|
681 |
+
} else {
|
682 |
+
return window.batch_state.lines
|
683 |
+
}
|
684 |
+
}
|
685 |
+
|
686 |
+
|
687 |
+
batch_clearBtn.addEventListener("click", () => {
|
688 |
+
|
689 |
+
window.batch_state.lines = []
|
690 |
+
batch_main.style.display = "flex"
|
691 |
+
batchDropZoneNote.style.display = "block"
|
692 |
+
batchRecordsHeader.style.display = "none"
|
693 |
+
batch_clearBtn.style.display = "none"
|
694 |
+
batch_outputFolderInput.style.display = "inline-block"
|
695 |
+
batch_clearDirOpts.style.display = "flex"
|
696 |
+
batch_skipExistingOpts.style.display = "flex"
|
697 |
+
batch_useSR.style.display = "flex"
|
698 |
+
batch_useCleanup.style.display = "flex"
|
699 |
+
batch_outputNumericallyOpts.style.display = "flex"
|
700 |
+
batch_progressItems.style.display = "none"
|
701 |
+
batch_progressBar.style.display = "none"
|
702 |
+
|
703 |
+
batch_pauseBtn.style.display = "none"
|
704 |
+
batch_stopBtn.style.display = "none"
|
705 |
+
batch_synthesizeBtn.style.display = "none"
|
706 |
+
|
707 |
+
batchRecordsContainer.innerHTML = ""
|
708 |
+
})
|
709 |
+
|
710 |
+
|
711 |
+
window.startBatch = () => {
|
712 |
+
|
713 |
+
// Output directory
|
714 |
+
if (!fs.existsSync(window.userSettings.batchOutFolder)) {
|
715 |
+
window.userSettings.batchOutFolder.split("/")
|
716 |
+
.reduce((prevPath, folder) => {
|
717 |
+
const currentPath = path.join(prevPath, folder, path.sep);
|
718 |
+
if (!fs.existsSync(currentPath)){
|
719 |
+
try {
|
720 |
+
fs.mkdirSync(currentPath);
|
721 |
+
} catch (e) {
|
722 |
+
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}:<br><br>`+e.message)
|
723 |
+
throw ""
|
724 |
+
}
|
725 |
+
}
|
726 |
+
return currentPath;
|
727 |
+
}, '');
|
728 |
+
}
|
729 |
+
if (batch_clearDirFirstCkbx.checked) {
|
730 |
+
const filesAndFolders = fs.readdirSync(window.userSettings.batchOutFolder)
|
731 |
+
filesAndFolders.forEach(faf => {
|
732 |
+
// Ignore .csv and .txt files
|
733 |
+
if (faf.toLowerCase().endsWith(".csv") || faf.toLowerCase().endsWith(".txt")) {
|
734 |
+
return
|
735 |
+
}
|
736 |
+
if (fs.lstatSync(`${window.userSettings.batchOutFolder}/${faf}`).isDirectory()) {
|
737 |
+
window.deleteFolderRecursive(`${window.userSettings.batchOutFolder}/${faf}`, false)
|
738 |
+
} else {
|
739 |
+
fs.unlinkSync(`${window.userSettings.batchOutFolder}/${faf}`)
|
740 |
+
}
|
741 |
+
console.log(`${window.userSettings.batchOutFolder}/${faf}`, )
|
742 |
+
})
|
743 |
+
}
|
744 |
+
|
745 |
+
batch_synthesizeBtn.style.display = "none"
|
746 |
+
batch_clearBtn.style.display = "none"
|
747 |
+
batch_outputFolderInput.style.display = "none"
|
748 |
+
batch_clearDirOpts.style.display = "none"
|
749 |
+
batch_skipExistingOpts.style.display = "none"
|
750 |
+
batch_useSR.style.display = "none"
|
751 |
+
batch_useCleanup.style.display = "none"
|
752 |
+
batch_outputNumericallyOpts.style.display = "none"
|
753 |
+
batch_progressItems.style.display = "flex"
|
754 |
+
batch_progressBar.style.display = "flex"
|
755 |
+
batch_pauseBtn.style.display = "inline-block"
|
756 |
+
batch_stopBtn.style.display = "inline-block"
|
757 |
+
batch_openDirBtn.style.display = "none"
|
758 |
+
|
759 |
+
window.batch_state.lines.forEach(record => {
|
760 |
+
record[1].children[1].innerHTML = window.i18n.READY
|
761 |
+
record[1].children[1].style.background = "none"
|
762 |
+
})
|
763 |
+
|
764 |
+
window.batch_state.fastModeOutputPromises = []
|
765 |
+
window.batch_state.fastModeActuallyFinishedTasks = 0
|
766 |
+
window.batch_state.lineIndex = 0
|
767 |
+
window.batch_state.state = true
|
768 |
+
window.batch_state.outPathsChecked = []
|
769 |
+
window.batch_state.startTime = new Date()
|
770 |
+
window.batch_state.linesDoneSinceStart = 0
|
771 |
+
window.performSynthesis()
|
772 |
+
}
|
773 |
+
|
774 |
+
window.batchChangeVoice = (game, voice, modelType) => {
|
775 |
+
return new Promise((resolve) => {
|
776 |
+
if (!window.batch_state.state) {
|
777 |
+
return resolve()
|
778 |
+
}
|
779 |
+
// Update the main app with any changes, if a voice has already been selected
|
780 |
+
if (window.currentModel) {
|
781 |
+
generateVoiceButton.innerHTML = window.i18n.LOAD_MODEL
|
782 |
+
keepSampleButton.style.display = "none"
|
783 |
+
wavesurferContainer.innerHTML = ""
|
784 |
+
|
785 |
+
const modelGameFolder = window.currentModel.audioPreviewPath.split("/")[0]
|
786 |
+
const modelFileName = window.currentModel.audioPreviewPath.split("/")[1].split(".wav")[0]
|
787 |
+
generateVoiceButton.dataset.modelQuery = JSON.stringify({
|
788 |
+
outputs: parseInt(window.currentModel.outputs),
|
789 |
+
model: `${window.path}/models/${modelGameFolder}/${modelFileName}`,
|
790 |
+
model_speakers: window.currentModel.emb_size,
|
791 |
+
cmudict: window.currentModel.cmudict
|
792 |
+
})
|
793 |
+
}
|
794 |
+
|
795 |
+
|
796 |
+
if (window.batch_state.state) {
|
797 |
+
batch_progressNotes.innerHTML = `${window.i18n.BATCH_CHANGING_MODEL_TO}: ${voice}`
|
798 |
+
}
|
799 |
+
|
800 |
+
let model
|
801 |
+
window.games[game].models.forEach(gameModel => {
|
802 |
+
gameModel.variants.forEach(variant => {
|
803 |
+
if (variant.voiceId==voice) {
|
804 |
+
model = variant
|
805 |
+
}
|
806 |
+
})
|
807 |
+
})
|
808 |
+
|
809 |
+
doFetch(`http://localhost:8008/loadModel`, {
|
810 |
+
method: "Post",
|
811 |
+
body: JSON.stringify({
|
812 |
+
"modelType": modelType,
|
813 |
+
"outputs": null,
|
814 |
+
"model": `${window.userSettings[`modelspath_${game}`]}/${voice}`,
|
815 |
+
"model_speakers": model.num_speakers,
|
816 |
+
"base_lang": model.lang,
|
817 |
+
"pluginsContext": JSON.stringify(window.pluginsContext)
|
818 |
+
})
|
819 |
+
}).then(r=>r.text()).then(res => {
|
820 |
+
resolve()
|
821 |
+
}).catch(async e => {
|
822 |
+
if (e.code=="ECONNREFUSED" || e.code=="ECONNRESET") {
|
823 |
+
await window.batchChangeVoice(game, voice, modelType)
|
824 |
+
resolve()
|
825 |
+
} else {
|
826 |
+
console.log(e)
|
827 |
+
window.appLogger.log(e)
|
828 |
+
batch_pauseBtn.click()
|
829 |
+
|
830 |
+
if (document.getElementById("activeModal")) {
|
831 |
+
activeModal.remove()
|
832 |
+
}
|
833 |
+
if (e.code=="ENOENT") {
|
834 |
+
window.errorModal(window.i18n.ERR_SERVER)
|
835 |
+
} else {
|
836 |
+
window.errorModal(e.message)
|
837 |
+
}
|
838 |
+
resolve()
|
839 |
+
}
|
840 |
+
})
|
841 |
+
})
|
842 |
+
}
|
843 |
+
window.batchChangeVocoder = (vocoder, game, voice) => {
|
844 |
+
return new Promise((resolve) => {
|
845 |
+
if (!window.batch_state.state) {
|
846 |
+
return resolve()
|
847 |
+
}
|
848 |
+
console.log("Changing vocoder: ", vocoder)
|
849 |
+
if (window.batch_state.state) {
|
850 |
+
batch_progressNotes.innerHTML = `${window.i18n.BATCH_CHANGING_VOCODER_TO}: ${vocoder}`
|
851 |
+
}
|
852 |
+
|
853 |
+
const vocoderMappings = [["waveglow", "256_waveglow"], ["waveglowBIG", "big_waveglow"], ["quickanddirty", "qnd"], ["hifi", `${game}/${voice}.hg.pt`]]
|
854 |
+
const vocoderId = vocoderMappings.find(record => record[0]==vocoder)[1]
|
855 |
+
|
856 |
+
doFetch(`http://localhost:8008/setVocoder`, {
|
857 |
+
method: "Post",
|
858 |
+
body: JSON.stringify({
|
859 |
+
vocoder: vocoderId,
|
860 |
+
modelPath: vocoderId=="256_waveglow" ? window.userSettings.waveglow_path : window.userSettings.bigwaveglow_path,
|
861 |
+
})
|
862 |
+
}).then(r=>r.text()).then((res) => {
|
863 |
+
if (res=="ENOENT") {
|
864 |
+
closeModal(undefined, batchGenerationContainer).then(() => {
|
865 |
+
setTimeout(() => {
|
866 |
+
vocoder_select.value = window.userSettings.vocoder
|
867 |
+
window.errorModal(`${window.i18n.BATCH_MODEL_NOT_FOUND}.${vocoderId.includes("waveglow")?" "+window.i18n.BATCH_DOWNLOAD_WAVEGLOW:""}`)
|
868 |
+
batch_pauseBtn.click()
|
869 |
+
resolve()
|
870 |
+
}, 300)
|
871 |
+
})
|
872 |
+
} else {
|
873 |
+
window.batch_state.lastVocoder = vocoder
|
874 |
+
resolve()
|
875 |
+
}
|
876 |
+
}).catch(async e => {
|
877 |
+
if (e.code=="ECONNREFUSED" || e.code=="ECONNRESET") {
|
878 |
+
await window.batchChangeVocoder(vocoder, game, voice)
|
879 |
+
resolve()
|
880 |
+
} else {
|
881 |
+
console.log(e)
|
882 |
+
window.appLogger.log(e)
|
883 |
+
batch_pauseBtn.click()
|
884 |
+
|
885 |
+
if (document.getElementById("activeModal")) {
|
886 |
+
activeModal.remove()
|
887 |
+
}
|
888 |
+
if (e.code=="ENOENT") {
|
889 |
+
window.errorModal(window.i18n.ERR_SERVER)
|
890 |
+
} else {
|
891 |
+
window.errorModal(e.message)
|
892 |
+
}
|
893 |
+
resolve()
|
894 |
+
}
|
895 |
+
})
|
896 |
+
})
|
897 |
+
}
|
898 |
+
|
899 |
+
|
900 |
+
window.prepareLinesBatchForSynth = () => {
|
901 |
+
|
902 |
+
const linesBatch = []
|
903 |
+
const records = []
|
904 |
+
let firstItemVoiceId = undefined
|
905 |
+
let firstItemVocoder = undefined
|
906 |
+
let speaker_i = 0
|
907 |
+
|
908 |
+
for (let i=0; i<Math.min(window.userSettings.batch_batchSize, window.batch_state.lines.length-window.batch_state.lineIndex); i++) {
|
909 |
+
|
910 |
+
const record = window.batch_state.lines[window.batch_state.lineIndex+i]
|
911 |
+
|
912 |
+
const vocoderMappings = [["waveglow", "256_waveglow"], ["waveglowBIG", "big_waveglow"], ["quickanddirty", "qnd"], ["hifi", `${record[0].game_id}/${record[0].voice_id}.hg.pt`]]
|
913 |
+
const vocoder = record[0].vocoder=="-"?"-":vocoderMappings.find(voc => voc[0]==record[0].vocoder)[1]
|
914 |
+
|
915 |
+
if (firstItemVoiceId==undefined) firstItemVoiceId = record[0].voice_id
|
916 |
+
if (firstItemVocoder==undefined) firstItemVocoder = vocoder
|
917 |
+
|
918 |
+
if (record[0].voice_id!=firstItemVoiceId || vocoder!=firstItemVocoder) {
|
919 |
+
break
|
920 |
+
}
|
921 |
+
|
922 |
+
let model
|
923 |
+
window.games[record[0].game_id].models.forEach(gamesModel => {
|
924 |
+
gamesModel.variants.forEach(variant => {
|
925 |
+
if (variant.voiceId==record[0].voice_id) {
|
926 |
+
model = variant
|
927 |
+
}
|
928 |
+
})
|
929 |
+
})
|
930 |
+
|
931 |
+
const sequence = record[0].text
|
932 |
+
const pitch = undefined // maybe later
|
933 |
+
const duration = undefined // maybe later
|
934 |
+
speaker_i = model.emb_i || 0
|
935 |
+
let pace = record[0].pacing
|
936 |
+
pace = Number.isNaN(pace) ? 1.0 : pace
|
937 |
+
let pitch_amp = record[0].pitch_amp
|
938 |
+
pitch_amp = Number.isNaN(pitch_amp) ? 1.0 : pitch_amp
|
939 |
+
|
940 |
+
const tempFileNum = `${Math.random().toString().split(".")[1]}`
|
941 |
+
const tempFileLocation = `${window.path}/output/temp-${tempFileNum}.wav`
|
942 |
+
|
943 |
+
let outPath
|
944 |
+
let outFolder
|
945 |
+
|
946 |
+
outPath = record[0].out_path
|
947 |
+
outFolder = String(record[0].out_path).split("/").reverse().slice(1,10000).reverse().join("/")
|
948 |
+
outFolder = outFolder.length ? outFolder : window.userSettings.batchOutFolder
|
949 |
+
|
950 |
+
if (batch_outputNumerically.checked) {
|
951 |
+
outPath = `${window.userSettings.batchOutFolder}/${String(record[2]).padStart(10, '0')}.${outPath.split(".").reverse()[0]}`
|
952 |
+
} else {
|
953 |
+
outPath = outPath.startsWith("./") ? window.userSettings.batchOutFolder + outPath.slice(1,100000) : outPath
|
954 |
+
}
|
955 |
+
|
956 |
+
linesBatch.push([sequence, pitch, duration, pace, tempFileLocation, outPath, outFolder, pitch_amp, record[0].lang, record[0].base_emb, record[0].vc_content, record[0].vc_style])
|
957 |
+
records.push(record)
|
958 |
+
}
|
959 |
+
|
960 |
+
return [speaker_i, firstItemVoiceId, firstItemVocoder, linesBatch, records]
|
961 |
+
}
|
962 |
+
|
963 |
+
|
964 |
+
window.addActionButtons = (records, ri) => {
|
965 |
+
|
966 |
+
let audioPreview
|
967 |
+
const playButton = createElem("button.smallButton", window.i18n.PLAY)
|
968 |
+
playButton.style.background = `#${window.currentGame.themeColourPrimary}`
|
969 |
+
playButton.addEventListener("click", () => {
|
970 |
+
|
971 |
+
let audioPreviewPath = records[ri][0].fileOutputPath
|
972 |
+
if (audioPreviewPath.startsWith("./")) {
|
973 |
+
audioPreviewPath = window.userSettings.batchOutFolder + audioPreviewPath.replace("./", "/")
|
974 |
+
}
|
975 |
+
|
976 |
+
if (audioPreview==undefined) {
|
977 |
+
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", {
|
978 |
+
src: audioPreviewPath
|
979 |
+
}))
|
980 |
+
audioPreview.addEventListener("play", () => {
|
981 |
+
if (window.ctrlKeyIsPressed) {
|
982 |
+
audioPreview.setSinkId(window.userSettings.alt_speaker)
|
983 |
+
} else {
|
984 |
+
audioPreview.setSinkId(window.userSettings.base_speaker)
|
985 |
+
}
|
986 |
+
})
|
987 |
+
audioPreview.setSinkId(window.userSettings.base_speaker)
|
988 |
+
}
|
989 |
+
})
|
990 |
+
records[ri][1].children[2].appendChild(playButton)
|
991 |
+
|
992 |
+
// If not a Voice Conversion line
|
993 |
+
if (!records[ri][0].vc_content) {
|
994 |
+
const editButton = createElem("button.smallButton", window.i18n.EDIT)
|
995 |
+
editButton.style.background = `#${window.currentGame.themeColourPrimary}`
|
996 |
+
editButton.addEventListener("click", () => {
|
997 |
+
audioPreview = undefined
|
998 |
+
|
999 |
+
|
1000 |
+
if (window.batch_state.state) {
|
1001 |
+
window.errorModal(window.i18n.BATCH_ERR_EDIT)
|
1002 |
+
return
|
1003 |
+
}
|
1004 |
+
|
1005 |
+
// Change app theme to the voice's game
|
1006 |
+
if (window.currentGame.gameId!=records[ri][0].game_id) {
|
1007 |
+
window.changeGame(window.gameAssets[records[ri][0].game_id])
|
1008 |
+
}
|
1009 |
+
|
1010 |
+
dialogueInput.value = records[ri][0].text
|
1011 |
+
|
1012 |
+
// Simulate voice loading through the UI
|
1013 |
+
if (!window.currentModel || window.currentModel.voiceId != records[ri][0].voice_id) {
|
1014 |
+
const voiceName = records[ri][0].voiceName
|
1015 |
+
const voiceButton = Array.from(voiceTypeContainer.children).find(button => button.innerHTML==voiceName)
|
1016 |
+
voiceButton.click()
|
1017 |
+
vocoder_select.value = records[ri][0].vocoder=="hifi" ? `${records[ri][0].game_id}/${records[ri][0].voice_id}.hg.pt` : records[ri][0].vocoder
|
1018 |
+
generateVoiceButton.click()
|
1019 |
+
}
|
1020 |
+
window.closeModal(batchGenerationContainer)
|
1021 |
+
|
1022 |
+
setTimeout(() => {
|
1023 |
+
let audioPreviewPath = records[ri][0].fileOutputPath
|
1024 |
+
if (audioPreviewPath.startsWith("./")) {
|
1025 |
+
audioPreviewPath = window.userSettings.batchOutFolder + audioPreviewPath.replace("./", "/")
|
1026 |
+
}
|
1027 |
+
keepSampleButton.dataset.newFileLocation = "BATCH_EDIT"+audioPreviewPath
|
1028 |
+
generateVoiceButton.click()
|
1029 |
+
}, 500)
|
1030 |
+
})
|
1031 |
+
records[ri][1].children[2].appendChild(editButton)
|
1032 |
+
}
|
1033 |
+
}
|
1034 |
+
|
1035 |
+
|
1036 |
+
window.batchKickOffMPffmpegOutput = (records, tempPaths, outPaths, options, extraInfo) => {
|
1037 |
+
let hasShownError = false
|
1038 |
+
return new Promise((resolve, reject) => {
|
1039 |
+
doFetch(`http://localhost:8008/batchOutputAudio`, {
|
1040 |
+
method: "Post",
|
1041 |
+
body: JSON.stringify({
|
1042 |
+
input_paths: tempPaths,
|
1043 |
+
output_paths: outPaths,
|
1044 |
+
isBatchMode: true,
|
1045 |
+
pluginsContext: JSON.stringify(window.pluginsContext),
|
1046 |
+
processes: window.userSettings.batch_MPCount,
|
1047 |
+
extraInfo: extraInfo,
|
1048 |
+
options: JSON.stringify(options)
|
1049 |
+
})
|
1050 |
+
}).then(r=>r.text()).then(res => {
|
1051 |
+
res = res.split("\n")
|
1052 |
+
res.forEach((resItem, ri) => {
|
1053 |
+
if (resItem.length && resItem!="-") {
|
1054 |
+
window.appLogger.log(`Batch error, item ${ri} - ${resItem}`)
|
1055 |
+
if (window.batch_state.state) {
|
1056 |
+
batch_pauseBtn.click()
|
1057 |
+
}
|
1058 |
+
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}:<br><br>`+resItem)
|
1059 |
+
hasShownError = true
|
1060 |
+
|
1061 |
+
records[ri][1].children[1].innerHTML = window.i18n.FAILED
|
1062 |
+
records[ri][1].children[1].style.background = "red"
|
1063 |
+
|
1064 |
+
} else {
|
1065 |
+
|
1066 |
+
records[ri][1].children[1].innerHTML = window.i18n.DONE
|
1067 |
+
records[ri][1].children[1].style.background = "green"
|
1068 |
+
fs.unlinkSync(tempPaths[ri])
|
1069 |
+
window.addActionButtons(records, ri)
|
1070 |
+
}
|
1071 |
+
|
1072 |
+
// if (!window.userSettings.batch_fastMode) { // No more fast modde. TODO, remove completely
|
1073 |
+
window.batch_state.lineIndex += 1
|
1074 |
+
// }
|
1075 |
+
|
1076 |
+
window.batch_state.fastModeActuallyFinishedTasks += 1
|
1077 |
+
|
1078 |
+
})
|
1079 |
+
const percentDone = (window.batch_state.fastModeActuallyFinishedTasks) / window.batch_state.lines.length * 100
|
1080 |
+
batch_progressBar.style.background = `linear-gradient(90deg, green ${parseInt(percentDone)}%, rgba(255,255,255,0) ${parseInt(percentDone)}%)`
|
1081 |
+
batch_progressBar.innerHTML = `${parseInt(percentDone* 100)/100}%`
|
1082 |
+
window.batch_state.taskBarPercent = percentDone/100
|
1083 |
+
window.electronBrowserWindow.setProgressBar(window.batch_state.taskBarPercent)
|
1084 |
+
window.adjustETA()
|
1085 |
+
resolve()
|
1086 |
+
|
1087 |
+
}).catch(async e => {
|
1088 |
+
if (e.code=="ECONNREFUSED" || e.code=="ECONNRESET") {
|
1089 |
+
await window.batchKickOffMPffmpegOutput(records, tempPaths, outPaths, options, extraInfo)
|
1090 |
+
resolve()
|
1091 |
+
} else {
|
1092 |
+
console.log(e)
|
1093 |
+
window.appLogger.log(e.stack)
|
1094 |
+
if (document.getElementById("activeModal")) {
|
1095 |
+
activeModal.remove()
|
1096 |
+
}
|
1097 |
+
if (!hasShownError) {
|
1098 |
+
window.errorModal(e.message)
|
1099 |
+
}
|
1100 |
+
resolve()
|
1101 |
+
}
|
1102 |
+
})
|
1103 |
+
})
|
1104 |
+
}
|
1105 |
+
|
1106 |
+
|
1107 |
+
window.batchKickOffFfmpegOutput = (ri, linesBatch, records, tempFileLocation, body) => {
|
1108 |
+
return new Promise((resolve, reject) => {
|
1109 |
+
doFetch(`http://localhost:8008/outputAudio`, {
|
1110 |
+
method: "Post",
|
1111 |
+
body
|
1112 |
+
}).then(r=>r.text()).then(res => {
|
1113 |
+
if (res.length && res!="-") {
|
1114 |
+
window.appLogger.log("res", res)
|
1115 |
+
if (window.batch_state.state) {
|
1116 |
+
batch_pauseBtn.click()
|
1117 |
+
}
|
1118 |
+
|
1119 |
+
for (let ri2=ri; ri2<linesBatch.length; ri2++) {
|
1120 |
+
records[ri][1].children[1].innerHTML = window.i18n.FAILED
|
1121 |
+
records[ri][1].children[1].style.background = "red"
|
1122 |
+
}
|
1123 |
+
|
1124 |
+
reject(res)
|
1125 |
+
} else {
|
1126 |
+
records[ri][1].children[1].innerHTML = window.i18n.DONE
|
1127 |
+
records[ri][1].children[1].style.background = "green"
|
1128 |
+
fs.unlinkSync(tempFileLocation)
|
1129 |
+
window.addActionButtons(records, ri)
|
1130 |
+
window.batch_state.fastModeActuallyFinishedTasks += 1
|
1131 |
+
|
1132 |
+
const percentDone = (window.batch_state.fastModeActuallyFinishedTasks) / window.batch_state.lines.length * 100
|
1133 |
+
batch_progressBar.style.background = `linear-gradient(90deg, green ${parseInt(percentDone)}%, rgba(255,255,255,0) ${parseInt(percentDone)}%)`
|
1134 |
+
batch_progressBar.innerHTML = `${parseInt(percentDone* 100)/100}%`
|
1135 |
+
window.batch_state.taskBarPercent = percentDone/100
|
1136 |
+
window.electronBrowserWindow.setProgressBar(window.batch_state.taskBarPercent)
|
1137 |
+
window.adjustETA()
|
1138 |
+
resolve()
|
1139 |
+
}
|
1140 |
+
}).catch(async e => {
|
1141 |
+
if (e.code=="ECONNREFUSED" || e.code=="ECONNRESET") {
|
1142 |
+
await window.batchKickOffFfmpegOutput(ri, linesBatch, records, tempFileLocation, body)
|
1143 |
+
resolve()
|
1144 |
+
} else {
|
1145 |
+
console.log(e)
|
1146 |
+
window.appLogger.log(e)
|
1147 |
+
batch_pauseBtn.click()
|
1148 |
+
if (document.getElementById("activeModal")) {
|
1149 |
+
activeModal.remove()
|
1150 |
+
}
|
1151 |
+
window.errorModal(e.message)
|
1152 |
+
resolve()
|
1153 |
+
}
|
1154 |
+
})
|
1155 |
+
})
|
1156 |
+
}
|
1157 |
+
|
1158 |
+
window.batchKickOffGeneration = () => {
|
1159 |
+
return new Promise((resolve) => {
|
1160 |
+
if (!window.batch_state.state) {
|
1161 |
+
return resolve()
|
1162 |
+
}
|
1163 |
+
const [speaker_i, voice_id, vocoder, linesBatch, records] = window.prepareLinesBatchForSynth()
|
1164 |
+
|
1165 |
+
records.forEach((record, ri) => {
|
1166 |
+
record[1].children[1].innerHTML = window.i18n.RUNNING
|
1167 |
+
record[1].children[1].style.background = "goldenrod"
|
1168 |
+
record[0].fileOutputPath = linesBatch[ri][5]
|
1169 |
+
})
|
1170 |
+
|
1171 |
+
const record = window.batch_state.lines[window.batch_state.lineIndex]
|
1172 |
+
|
1173 |
+
if (window.batch_state.state) {
|
1174 |
+
if (linesBatch.length==1) {
|
1175 |
+
batch_progressNotes.innerHTML = `${window.i18n.SYNTHESIZING}: <i>${record[0].text}</i>`
|
1176 |
+
} else {
|
1177 |
+
batch_progressNotes.innerHTML = `${window.i18n.SYNTHESIZING} ${linesBatch.length} ${window.i18n.LINES}`
|
1178 |
+
}
|
1179 |
+
}
|
1180 |
+
const batchPostData = {
|
1181 |
+
modelType: records[0][0].modelType,
|
1182 |
+
batchSize: window.userSettings.batch_batchSize,
|
1183 |
+
defaultOutFolder: window.userSettings.batchOutFolder,
|
1184 |
+
pluginsContext: JSON.stringify(window.pluginsContext),
|
1185 |
+
outputJSON: window.userSettings.batch_json,
|
1186 |
+
useSR: batch_useSRCkbx.checked,
|
1187 |
+
useCleanup: batch_useCleanupCkbx.checked,
|
1188 |
+
speaker_i, vocoder, linesBatch
|
1189 |
+
}
|
1190 |
+
doFetch(`http://localhost:8008/synthesize_batch`, {
|
1191 |
+
method: "Post",
|
1192 |
+
body: JSON.stringify(batchPostData)
|
1193 |
+
}).then(r=>r.text()).then(async (res) => {
|
1194 |
+
|
1195 |
+
if (res && res!="-") {
|
1196 |
+
if (res=="CUDA OOM") {
|
1197 |
+
window.errorModal(window.i18n.BATCH_ERR_CUDA_OOM)
|
1198 |
+
} else {
|
1199 |
+
window.errorModal(res.replace(/\n/g, "<br>"))
|
1200 |
+
}
|
1201 |
+
if (window.batch_state.state) {
|
1202 |
+
batch_pauseBtn.click()
|
1203 |
+
}
|
1204 |
+
return
|
1205 |
+
}
|
1206 |
+
|
1207 |
+
|
1208 |
+
// Create the output directory if it does not exist
|
1209 |
+
linesBatch.forEach(record => {
|
1210 |
+
let outFolder = record[6].startsWith("./") ? window.userSettings.batchOutFolder + record[6].slice(1,100000) : record[6]
|
1211 |
+
|
1212 |
+
if (!window.batch_state.outPathsChecked.includes(outFolder)) {
|
1213 |
+
window.batch_state.outPathsChecked.push(outFolder)
|
1214 |
+
if (!fs.existsSync(outFolder)) {
|
1215 |
+
window.createFolderRecursive(outFolder)
|
1216 |
+
}
|
1217 |
+
}
|
1218 |
+
})
|
1219 |
+
|
1220 |
+
if (window.userSettings.audio.ffmpeg) {
|
1221 |
+
const options = {
|
1222 |
+
hz: window.userSettings.audio.hz,
|
1223 |
+
padStart: window.userSettings.audio.padStart,
|
1224 |
+
padEnd: window.userSettings.audio.padEnd,
|
1225 |
+
bit_depth: window.userSettings.audio.bitdepth,
|
1226 |
+
amplitude: window.userSettings.audio.amplitude,
|
1227 |
+
pitchMult: window.userSettings.audio.pitchMult,
|
1228 |
+
tempo: window.userSettings.audio.tempo,
|
1229 |
+
deessing: window.userSettings.audio.deessing,
|
1230 |
+
nr: window.userSettings.audio.nr,
|
1231 |
+
nf: window.userSettings.audio.nf,
|
1232 |
+
useNR: window.userSettings.audio.useNR,
|
1233 |
+
useSR: batch_useSRCkbx.checked,
|
1234 |
+
useCleanup: batch_useCleanupCkbx.checked,
|
1235 |
+
}
|
1236 |
+
|
1237 |
+
if (window.batch_state.state) {
|
1238 |
+
batch_progressNotes.innerHTML = window.i18n.BATCH_OUTPUTTING_FFMPEG
|
1239 |
+
}
|
1240 |
+
|
1241 |
+
const tempPaths = linesBatch.map(line => line[4])
|
1242 |
+
const outPaths = linesBatch.map((line, li) => {
|
1243 |
+
let outPath = linesBatch[li][5].includes(":") || linesBatch[li][5].includes("./") ? linesBatch[li][5] : `${linesBatch[li][6]}/${linesBatch[li][5]}`
|
1244 |
+
if (outPath.startsWith("./")) {
|
1245 |
+
outPath = window.userSettings.batchOutFolder + outPath.slice(1,100000)
|
1246 |
+
}
|
1247 |
+
return outPath
|
1248 |
+
})
|
1249 |
+
|
1250 |
+
if (window.userSettings.batch_useMP) {
|
1251 |
+
const extraInfo = {
|
1252 |
+
game: records.map(rec => rec[0].game_id),
|
1253 |
+
voiceId: records.map(rec => rec[0].voice_id),
|
1254 |
+
voiceName: records.map(rec => rec[0].voiceName),
|
1255 |
+
inputSequence: records.map(rec => rec[0].text)
|
1256 |
+
}
|
1257 |
+
if (window.userSettings.batch_fastMode && false) { // No more fast mode. TODO, remove completely
|
1258 |
+
window.batch_state.fastModeOutputPromises.push(window.batchKickOffMPffmpegOutput(records, tempPaths, outPaths, options, JSON.stringify(extraInfo)))
|
1259 |
+
window.batch_state.lineIndex += records.length
|
1260 |
+
} else {
|
1261 |
+
await window.batchKickOffMPffmpegOutput(records, tempPaths, outPaths, options, JSON.stringify(extraInfo))
|
1262 |
+
}
|
1263 |
+
} else {
|
1264 |
+
for (let ri=0; ri<linesBatch.length; ri++) {
|
1265 |
+
let tempFileLocation = tempPaths[ri]
|
1266 |
+
let outPath = outPaths[ri]
|
1267 |
+
try {
|
1268 |
+
if (window.batch_state.state) {
|
1269 |
+
records[ri][1].children[1].innerHTML = window.i18n.OUTPUTTING
|
1270 |
+
const extraInfo = {
|
1271 |
+
game: records[ri][0].game_id,
|
1272 |
+
voiceId: records[ri][0].voiceId,
|
1273 |
+
voiceName: records[ri][0].voiceName,
|
1274 |
+
letters: records[ri][0].text
|
1275 |
+
}
|
1276 |
+
|
1277 |
+
if (window.userSettings.batch_fastMode && false) { // No more fast modde. TODO, remove completely
|
1278 |
+
window.batch_state.fastModeOutputPromises.push(window.batchKickOffFfmpegOutput(ri, linesBatch, records, tempFileLocation, JSON.stringify({
|
1279 |
+
input_path: tempFileLocation,
|
1280 |
+
output_path: outPath,
|
1281 |
+
isBatchMode: true,
|
1282 |
+
pluginsContext: JSON.stringify(window.pluginsContext),
|
1283 |
+
extraInfo: JSON.stringify(extraInfo),
|
1284 |
+
options: JSON.stringify(options)
|
1285 |
+
})))
|
1286 |
+
} else {
|
1287 |
+
await window.batchKickOffFfmpegOutput(ri, linesBatch, records, tempFileLocation, JSON.stringify({
|
1288 |
+
input_path: tempFileLocation,
|
1289 |
+
output_path: outPath,
|
1290 |
+
isBatchMode: true,
|
1291 |
+
pluginsContext: JSON.stringify(window.pluginsContext),
|
1292 |
+
extraInfo: JSON.stringify(extraInfo),
|
1293 |
+
options: JSON.stringify(options)
|
1294 |
+
}))
|
1295 |
+
}
|
1296 |
+
window.batch_state.lineIndex += 1
|
1297 |
+
}
|
1298 |
+
} catch (e) {
|
1299 |
+
console.log(e)
|
1300 |
+
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}:<br><br>`+e)
|
1301 |
+
resolve()
|
1302 |
+
}
|
1303 |
+
}
|
1304 |
+
}
|
1305 |
+
window.batch_state.linesDoneSinceStart += linesBatch.length
|
1306 |
+
resolve()
|
1307 |
+
} else {
|
1308 |
+
linesBatch.forEach((lineRecord, li) => {
|
1309 |
+
let tempFileLocation = lineRecord[4]
|
1310 |
+
let outPath = lineRecord[5]
|
1311 |
+
try {
|
1312 |
+
fs.copyFileSync(tempFileLocation, outPath)
|
1313 |
+
records[li][1].children[1].innerHTML = window.i18n.DONE
|
1314 |
+
records[li][1].children[1].style.background = "green"
|
1315 |
+
|
1316 |
+
window.batch_state.lineIndex += 1
|
1317 |
+
|
1318 |
+
window.addActionButtons(records, li)
|
1319 |
+
|
1320 |
+
} catch (err) {
|
1321 |
+
console.log(err)
|
1322 |
+
window.appLogger.log(err)
|
1323 |
+
window.errorModal(err.message)
|
1324 |
+
batch_pauseBtn.click()
|
1325 |
+
}
|
1326 |
+
window.batch_state.linesDoneSinceStart += linesBatch.length
|
1327 |
+
resolve()
|
1328 |
+
})
|
1329 |
+
}
|
1330 |
+
}).catch(async e => {
|
1331 |
+
if (e.code=="ECONNREFUSED" || e.code=="ECONNRESET") {
|
1332 |
+
await window.batchKickOffGeneration()
|
1333 |
+
resolve()
|
1334 |
+
} else {
|
1335 |
+
console.log(e)
|
1336 |
+
window.appLogger.log(e)
|
1337 |
+
batch_pauseBtn.click()
|
1338 |
+
if (document.getElementById("activeModal")) {
|
1339 |
+
activeModal.remove()
|
1340 |
+
}
|
1341 |
+
console.log(e.message)
|
1342 |
+
window.errorModal(e.message).then(() => resolve())
|
1343 |
+
}
|
1344 |
+
})
|
1345 |
+
})
|
1346 |
+
}
|
1347 |
+
|
1348 |
+
window.performSynthesis = async () => {
|
1349 |
+
|
1350 |
+
if (batch_state.lineIndex-batch_state.fastModeActuallyFinishedTasks > window.userSettings.batch_fastModeMaxParallelizations) {
|
1351 |
+
console.log(`Ahead by ${batch_state.lineIndex-batch_state.fastModeActuallyFinishedTasks} tasks. Waiting...`)
|
1352 |
+
setTimeout(() => {window.performSynthesis()}, 1000)
|
1353 |
+
return
|
1354 |
+
}
|
1355 |
+
|
1356 |
+
if (!window.batch_state.state) {
|
1357 |
+
return
|
1358 |
+
}
|
1359 |
+
|
1360 |
+
if (window.batch_state.lineIndex==0) {
|
1361 |
+
const percentDone = (window.batch_state.lineIndex) / window.batch_state.lines.length * 100
|
1362 |
+
batch_progressBar.style.background = `linear-gradient(90deg, green ${parseInt(percentDone)}%, rgba(255,255,255,0) ${parseInt(percentDone)}%)`
|
1363 |
+
batch_progressBar.innerHTML = `${parseInt(percentDone* 100)/100}%`
|
1364 |
+
window.batch_state.taskBarPercent = percentDone/100
|
1365 |
+
window.electronBrowserWindow.setProgressBar(window.batch_state.taskBarPercent)
|
1366 |
+
}
|
1367 |
+
|
1368 |
+
|
1369 |
+
const record = window.batch_state.lines[window.batch_state.lineIndex]
|
1370 |
+
|
1371 |
+
// Change the voice model if the next line uses a different one
|
1372 |
+
if (window.batch_state.lastModel!=record[0].voice_id) {
|
1373 |
+
await window.batchChangeVoice(record[0].game_id, record[0].voice_id, record[0].modelType)
|
1374 |
+
window.batch_state.lastModel = record[0].voice_id
|
1375 |
+
}
|
1376 |
+
|
1377 |
+
// Change the vocoder if the next line uses a different one
|
1378 |
+
if (window.batch_state.lastVocoder!=record[0].vocoder && record[0].vocoder!="-") {
|
1379 |
+
await window.batchChangeVocoder(record[0].vocoder, record[0].game_id, record[0].voice_id)
|
1380 |
+
}
|
1381 |
+
|
1382 |
+
await window.batchKickOffGeneration()
|
1383 |
+
|
1384 |
+
if (window.batch_state.lineIndex==window.batch_state.lines.length) {
|
1385 |
+
// The end
|
1386 |
+
if (window.userSettings.batch_fastMode && false) { // No more fast modde. TODO, remove completely
|
1387 |
+
Promise.all(window.batch_state.fastModeOutputPromises).then(() => {
|
1388 |
+
window.stopBatch()
|
1389 |
+
batch_openDirBtn.style.display = "inline-block"
|
1390 |
+
})
|
1391 |
+
} else {
|
1392 |
+
window.stopBatch()
|
1393 |
+
batch_openDirBtn.style.display = "inline-block"
|
1394 |
+
}
|
1395 |
+
|
1396 |
+
} else {
|
1397 |
+
window.performSynthesis()
|
1398 |
+
}
|
1399 |
+
}
|
1400 |
+
|
1401 |
+
window.pauseResumeBatch = () => {
|
1402 |
+
|
1403 |
+
batch_progressNotes.innerHTML = window.i18n.PAUSED
|
1404 |
+
|
1405 |
+
const isRunning = window.batch_state.state
|
1406 |
+
batch_pauseBtn.innerHTML = isRunning ? window.i18n.RESUME : window.i18n.PAUSE
|
1407 |
+
window.batch_state.state = !isRunning
|
1408 |
+
|
1409 |
+
window.electronBrowserWindow.setProgressBar(window.batch_state.taskBarPercent?window.batch_state.taskBarPercent:1, {mode: isRunning ? "paused" : "normal"})
|
1410 |
+
|
1411 |
+
if (window.batch_state.state) {
|
1412 |
+
window.batch_state.startTime = new Date()
|
1413 |
+
window.batch_state.linesDoneSinceStart = 0
|
1414 |
+
}
|
1415 |
+
|
1416 |
+
|
1417 |
+
if (!isRunning) {
|
1418 |
+
window.performSynthesis()
|
1419 |
+
}
|
1420 |
+
}
|
1421 |
+
|
1422 |
+
window.stopBatch = (stoppedByUser) => {
|
1423 |
+
window.electronBrowserWindow.setProgressBar(0)
|
1424 |
+
window.batch_state.state = false
|
1425 |
+
window.batch_state.lineIndex = 0
|
1426 |
+
|
1427 |
+
batch_ETA_container.style.opacity = 0
|
1428 |
+
batch_synthesizeBtn.style.display = "inline-block"
|
1429 |
+
batch_clearBtn.style.display = "inline-block"
|
1430 |
+
batch_outputFolderInput.style.display = "inline-block"
|
1431 |
+
batch_clearDirOpts.style.display = "flex"
|
1432 |
+
batch_skipExistingOpts.style.display = "flex"
|
1433 |
+
batch_useSR.style.display = "flex"
|
1434 |
+
batch_useCleanup.style.display = "flex"
|
1435 |
+
batch_outputNumericallyOpts.style.display = "flex"
|
1436 |
+
batch_progressItems.style.display = "none"
|
1437 |
+
batch_progressBar.style.display = "none"
|
1438 |
+
batch_pauseBtn.style.display = "none"
|
1439 |
+
batch_stopBtn.style.display = "none"
|
1440 |
+
|
1441 |
+
window.batch_state.lines.forEach(record => {
|
1442 |
+
if (record[1].children[1].innerHTML==window.i18n.READY || record[1].children[1].innerHTML==window.i18n.RUNNING) {
|
1443 |
+
record[1].children[1].innerHTML = window.i18n.STOPPED
|
1444 |
+
record[1].children[1].style.background = "none"
|
1445 |
+
}
|
1446 |
+
})
|
1447 |
+
|
1448 |
+
const pluginData = {
|
1449 |
+
stoppedByUser: stoppedByUser
|
1450 |
+
}
|
1451 |
+
window.pluginsManager.runPlugins(window.pluginsManager.pluginsModules["batch-stop"]["post"], event="post batch-stop", pluginData)
|
1452 |
+
}
|
1453 |
+
|
1454 |
+
window.adjustETA = () => {
|
1455 |
+
if (window.batch_state.state && window.batch_state.fastModeActuallyFinishedTasks>=2) {
|
1456 |
+
batch_ETA_container.style.opacity = 1
|
1457 |
+
|
1458 |
+
// Lines per second
|
1459 |
+
const timeNow = new Date()
|
1460 |
+
const timeSinceStart = timeNow - window.batch_state.startTime
|
1461 |
+
const avgMSTimePerLine = timeSinceStart / window.batch_state.fastModeActuallyFinishedTasks
|
1462 |
+
batch_eta_lps.innerHTML = parseInt((1000/avgMSTimePerLine)*100)/100
|
1463 |
+
|
1464 |
+
|
1465 |
+
const remainingLines = window.batch_state.lines.length - window.batch_state.fastModeActuallyFinishedTasks
|
1466 |
+
let estTimeRemaining = avgMSTimePerLine*remainingLines
|
1467 |
+
|
1468 |
+
// Estimated finish time
|
1469 |
+
const finishTime = new Date(timeNow.getTime() + estTimeRemaining)
|
1470 |
+
let etaFinishTime = `${finishTime.getHours()}:${String(finishTime.getMinutes()).padStart(2, "0")}:${String(finishTime.getSeconds()).padStart(2, "0")}`
|
1471 |
+
const days = [window.i18n.SUNDAY, window.i18n.MONDAY, window.i18n.TUESDAY, window.i18n.WEDNESDAY, window.i18n.THURSDAY, window.i18n.FRIDAY, window.i18n.SATURDAY]
|
1472 |
+
etaFinishTime = `${days[finishTime.getDay()]} ${etaFinishTime}`
|
1473 |
+
|
1474 |
+
batch_eta_eta.innerHTML = etaFinishTime
|
1475 |
+
|
1476 |
+
// Time remaining
|
1477 |
+
let etaTimeDisplay = []
|
1478 |
+
if (estTimeRemaining > (1000*60*60)) { // hours
|
1479 |
+
const hours = parseInt(estTimeRemaining/(1000*60*60))
|
1480 |
+
etaTimeDisplay.push(hours+"h")
|
1481 |
+
estTimeRemaining -= hours*(1000*60*60)
|
1482 |
+
}
|
1483 |
+
if (estTimeRemaining > (1000*60)) { // minutes
|
1484 |
+
const minutes = parseInt(estTimeRemaining/(1000*60))
|
1485 |
+
etaTimeDisplay.push(String(minutes).padStart(2, "0")+"m")
|
1486 |
+
estTimeRemaining -= minutes*(1000*60)
|
1487 |
+
}
|
1488 |
+
if (estTimeRemaining > (1000)) { // seconds
|
1489 |
+
const seconds = parseInt(estTimeRemaining/(1000))
|
1490 |
+
etaTimeDisplay.push(String(seconds).padStart(2, "0")+"s")
|
1491 |
+
estTimeRemaining -= seconds*(1000)
|
1492 |
+
}
|
1493 |
+
batch_eta_time.innerHTML = etaTimeDisplay.join(" ")
|
1494 |
+
|
1495 |
+
} else {
|
1496 |
+
batch_ETA_container.style.opacity = 0
|
1497 |
+
}
|
1498 |
+
}
|
1499 |
+
|
1500 |
+
|
1501 |
+
const openOutput = () => {
|
1502 |
+
er.shell.showItemInFolder(window.userSettings.batchOutFolder+"/dummy.txt")
|
1503 |
+
spawn(`explorer`, [window.userSettings.batchOutFolder.replace(/\//g, "\\")], {stdio: "ignore"})
|
1504 |
+
}
|
1505 |
+
|
1506 |
+
|
1507 |
+
batch_paginationPrev.addEventListener("click", () => {
|
1508 |
+
batch_pageNum.value = Math.max(1, parseInt(batch_pageNum.value)-1)
|
1509 |
+
window.batch_state.paginationIndex = batch_pageNum.value-1
|
1510 |
+
window.refreshBatchRecordsList()
|
1511 |
+
})
|
1512 |
+
batch_paginationNext.addEventListener("click", () => {
|
1513 |
+
const numPages = Math.ceil(window.batch_state.lines.length/window.userSettings.batch_paginationSize)
|
1514 |
+
batch_pageNum.value = Math.min(parseInt(batch_pageNum.value)+1, numPages)
|
1515 |
+
window.batch_state.paginationIndex = batch_pageNum.value-1
|
1516 |
+
window.refreshBatchRecordsList()
|
1517 |
+
})
|
1518 |
+
batch_pageNum.addEventListener("change", () => {
|
1519 |
+
const numPages = Math.ceil(window.batch_state.lines.length/window.userSettings.batch_paginationSize)
|
1520 |
+
batch_pageNum.value = Math.max(1, Math.min(parseInt(batch_pageNum.value), numPages))
|
1521 |
+
window.batch_state.paginationIndex = batch_pageNum.value-1
|
1522 |
+
window.refreshBatchRecordsList()
|
1523 |
+
})
|
1524 |
+
setting_batch_paginationSize.addEventListener("change", () => {
|
1525 |
+
const numPages = Math.ceil(window.batch_state.lines.length/window.userSettings.batch_paginationSize)
|
1526 |
+
batch_pageNum.value = Math.max(1, Math.min(parseInt(batch_pageNum.value), numPages))
|
1527 |
+
window.batch_state.paginationIndex = batch_pageNum.value-1
|
1528 |
+
batch_total_pages.innerHTML = window.i18n.PAGINATION_TOTAL_OF.replace("_1", numPages)
|
1529 |
+
|
1530 |
+
window.refreshBatchRecordsList()
|
1531 |
+
})
|
1532 |
+
|
1533 |
+
window.toggleNumericalRecordsDisplay = () => {
|
1534 |
+
window.batch_state.lines.forEach(record => {
|
1535 |
+
record[1].children[6].innerHTML = batch_outputNumerically.checked ? `${window.userSettings.batchOutFolder}/${String(record[2]).padStart(10, '0')}` : record[0].out_path
|
1536 |
+
})
|
1537 |
+
}
|
1538 |
+
batch_outputNumerically.addEventListener("click", () => {
|
1539 |
+
window.toggleNumericalRecordsDisplay()
|
1540 |
+
})
|
1541 |
+
batch_saveToCSV.addEventListener("click", () => {
|
1542 |
+
|
1543 |
+
try {
|
1544 |
+
const csv_file = [`game_id|voice_id|text`]
|
1545 |
+
window.batch_state.lines.forEach(line => {
|
1546 |
+
csv_file.push(`${line[0].game_id}|${line[0].voice_id}|${line[0].text}`)
|
1547 |
+
})
|
1548 |
+
|
1549 |
+
const outFileName = JSON.stringify(new Date()).replace("\"","").replaceAll(":","_").split(".")[0]+"_batch.csv"
|
1550 |
+
|
1551 |
+
fs.writeFileSync(`${window.userSettings.batchOutFolder}/${outFileName}`, csv_file.join("\n"), "utf8")
|
1552 |
+
|
1553 |
+
window.createModal("error", `${window.i18n.BATCH_TOCSV_DONE}<br><br>${window.userSettings.batchOutFolder}/${outFileName}`)
|
1554 |
+
|
1555 |
+
} catch(e) {
|
1556 |
+
console.log(e)
|
1557 |
+
window.appLogger.log(e.stack)
|
1558 |
+
window.errorModal(e.stack)
|
1559 |
+
}
|
1560 |
+
|
1561 |
+
})
|
1562 |
+
|
1563 |
+
|
1564 |
+
batch_main.addEventListener("dragenter", event => window.uploadBatchCSVs("dragenter", event), false)
|
1565 |
+
batch_main.addEventListener("dragleave", event => window.uploadBatchCSVs("dragleave", event), false)
|
1566 |
+
batch_main.addEventListener("dragover", event => window.uploadBatchCSVs("dragover", event), false)
|
1567 |
+
batch_main.addEventListener("drop", event => window.uploadBatchCSVs("drop", event), false)
|
1568 |
+
|
1569 |
+
|
1570 |
+
batch_synthesizeBtn.addEventListener("click", window.startBatch)
|
1571 |
+
batch_pauseBtn.addEventListener("click", window.pauseResumeBatch)
|
1572 |
+
batch_stopBtn.addEventListener("click", () => window.stopBatch(true))
|
1573 |
+
batch_openDirBtn.addEventListener("click", openOutput)
|
javascript/dragdrop_model_install.js
ADDED
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
window.dragDropModelInstallation = (eType, event) => {
|
4 |
+
if (["dragenter", "dragover"].includes(eType)) {
|
5 |
+
left.style.background = "#5b5b5b"
|
6 |
+
left.style.color = "white"
|
7 |
+
}
|
8 |
+
if (["dragleave", "drop"].includes(eType)) {
|
9 |
+
left.style.background = "rgba(0,0,0,0)"
|
10 |
+
left.style.color = "white"
|
11 |
+
}
|
12 |
+
|
13 |
+
event.preventDefault()
|
14 |
+
event.stopPropagation()
|
15 |
+
|
16 |
+
const dataLines = []
|
17 |
+
|
18 |
+
if (eType=="drop") {
|
19 |
+
const dataTransfer = event.dataTransfer
|
20 |
+
const files = Array.from(dataTransfer.files)
|
21 |
+
|
22 |
+
const modelGroups = {} // Group up all files by their base name (for if loose files are given)
|
23 |
+
files.forEach(file => {
|
24 |
+
const baseName = file.name.split(".")[0]
|
25 |
+
if (!modelGroups[baseName]) {
|
26 |
+
modelGroups[baseName] = []
|
27 |
+
}
|
28 |
+
modelGroups[baseName].push(file.path)
|
29 |
+
})
|
30 |
+
|
31 |
+
const modelGroupsComplete = []
|
32 |
+
const modelGroupsNotComplete = []
|
33 |
+
Object.keys(modelGroups).forEach(key => {
|
34 |
+
if (modelGroups[key][0].split(".").at(-1)!="zip") {
|
35 |
+
const fileExtensions = modelGroups[key].map(filePath => filePath.split(".").at(-1))
|
36 |
+
if (fileExtensions.includes("json") && fileExtensions.includes("pt")) {
|
37 |
+
modelGroupsComplete.push(key)
|
38 |
+
} else {
|
39 |
+
modelGroupsNotComplete.push(key)
|
40 |
+
}
|
41 |
+
} else {
|
42 |
+
modelGroupsComplete.push(key)
|
43 |
+
}
|
44 |
+
})
|
45 |
+
|
46 |
+
if (modelGroupsNotComplete.length) {
|
47 |
+
return window.errorModal(window.i18n.MODEL_INSTALL_DRAGDROP_INCOMPLETE.replace("_1", modelGroupsNotComplete.join(", ")))
|
48 |
+
}
|
49 |
+
|
50 |
+
const modelsInstalledSuccessfully = []
|
51 |
+
const modelsFailedInstallation = []
|
52 |
+
let lastGameInstalledOkFor = undefined
|
53 |
+
|
54 |
+
|
55 |
+
const handleZip = (files, key) => {
|
56 |
+
return new Promise(resolve => {
|
57 |
+
let installedOk = false
|
58 |
+
let game = undefined
|
59 |
+
|
60 |
+
try {
|
61 |
+
if (fs.existsSync(`${window.path}/downloads`)) {
|
62 |
+
fs.readdirSync(`${window.path}/downloads`).forEach(fileName => {
|
63 |
+
fs.unlinkSync(`${window.path}/downloads/${fileName}`)
|
64 |
+
})
|
65 |
+
} else {
|
66 |
+
fs.mkdirSync(`${window.path}/downloads`)
|
67 |
+
}
|
68 |
+
|
69 |
+
window.unzipFileTo(files[0], `${window.path}/downloads`).then(() => {
|
70 |
+
const allFiles = fs.readdirSync(`${window.path}/downloads`)
|
71 |
+
const jsonFiles = allFiles.filter(fname => fname.endsWith(".json"))
|
72 |
+
|
73 |
+
jsonFiles.forEach(jsonFile => {
|
74 |
+
const jsonData = JSON.parse(fs.readFileSync(`${window.path}/downloads/${jsonFile}`))
|
75 |
+
|
76 |
+
game = jsonData.games[0].gameId
|
77 |
+
const voiceId = jsonData.games[0].voiceId
|
78 |
+
const modelsFolder = window.userSettings[`modelspath_${game}`]
|
79 |
+
|
80 |
+
const allFilesForThisModel = allFiles.filter(fname => fname.includes(voiceId))
|
81 |
+
allFilesForThisModel.forEach(fname => {
|
82 |
+
fs.copyFileSync(`${window.path}/downloads/${fname}`, `${modelsFolder}/${fname}`)
|
83 |
+
})
|
84 |
+
|
85 |
+
installedOk = true
|
86 |
+
})
|
87 |
+
|
88 |
+
resolve([game, key, installedOk])
|
89 |
+
})
|
90 |
+
|
91 |
+
} catch (e) {
|
92 |
+
resolve([game, key, false])
|
93 |
+
}
|
94 |
+
})
|
95 |
+
}
|
96 |
+
|
97 |
+
const handleLoose = (files, key) => {
|
98 |
+
let game = undefined
|
99 |
+
return new Promise(resolve => {
|
100 |
+
try {
|
101 |
+
const jsonFile = files.find(fname => fname.endsWith(".json"))
|
102 |
+
const parentFolder = jsonFile.replaceAll("\\", "/").split("/").reverse().slice(1, 100000).reverse().join("/")
|
103 |
+
// const jsonData = JSON.parse(fs.readFileSync(`${parentFolder}/${jsonFile}`))
|
104 |
+
const jsonData = JSON.parse(fs.readFileSync(`${jsonFile}`))
|
105 |
+
|
106 |
+
const game = jsonData.games[0].gameId
|
107 |
+
const voiceId = jsonData.games[0].voiceId
|
108 |
+
const modelsFolder = window.userSettings[`modelspath_${game}`]
|
109 |
+
|
110 |
+
const allFilesForThisModel = fs.readdirSync(parentFolder).filter(fname => fname.includes(voiceId))
|
111 |
+
allFilesForThisModel.forEach(fname => {
|
112 |
+
fs.copyFileSync(`${parentFolder}/${fname}`, `${modelsFolder}/${fname}`)
|
113 |
+
// fs.copyFileSync(`${fname}`, `${modelsFolder}/${fname}`)
|
114 |
+
})
|
115 |
+
|
116 |
+
// lastGameInstalledOkFor = game
|
117 |
+
// modelsInstalledSuccessfully.push(key)
|
118 |
+
resolve([game, key, true])
|
119 |
+
} catch (e) {
|
120 |
+
resolve([game, key, false])
|
121 |
+
}
|
122 |
+
})
|
123 |
+
}
|
124 |
+
|
125 |
+
const installPromises = []
|
126 |
+
|
127 |
+
modelGroupsComplete.forEach(key => {
|
128 |
+
try {
|
129 |
+
const files = modelGroups[key]
|
130 |
+
if (files[0].endsWith(".zip")) {
|
131 |
+
installPromises.push(handleZip(files, key))
|
132 |
+
} else {
|
133 |
+
installPromises.push(handleLoose(files, key))
|
134 |
+
}
|
135 |
+
|
136 |
+
} catch (e) {
|
137 |
+
console.log(e)
|
138 |
+
window.appLogger.log(e)
|
139 |
+
modelsFailedInstallation.push(key)
|
140 |
+
}
|
141 |
+
})
|
142 |
+
|
143 |
+
|
144 |
+
Promise.all(installPromises).then(responses => {
|
145 |
+
|
146 |
+
responses.forEach(([game, key, installedOk]) => {
|
147 |
+
if (installedOk) {
|
148 |
+
lastGameInstalledOkFor = game
|
149 |
+
modelsInstalledSuccessfully.push(key)
|
150 |
+
} else {
|
151 |
+
modelsFailedInstallation.push(key)
|
152 |
+
}
|
153 |
+
})
|
154 |
+
|
155 |
+
let outputMessage = ""
|
156 |
+
|
157 |
+
if (modelsInstalledSuccessfully.length) {
|
158 |
+
outputMessage += window.i18n.MODEL_INSTALL_DRAGDROP_SUCCESS.replace("_1", modelsInstalledSuccessfully.length)
|
159 |
+
}
|
160 |
+
if (modelsFailedInstallation.length) {
|
161 |
+
outputMessage += window.i18n.MODEL_INSTALL_DRAGDROP_FAILED.replace("_1", modelsFailedInstallation.length).replace("_2", modelsFailedInstallation.join(", "))
|
162 |
+
}
|
163 |
+
window.infoModal(outputMessage)
|
164 |
+
|
165 |
+
if (lastGameInstalledOkFor) {
|
166 |
+
window.changeGame(window.gameAssets[lastGameInstalledOkFor])
|
167 |
+
}
|
168 |
+
window.displayAllModels(true)
|
169 |
+
window.loadAllModels(true).then(() => {
|
170 |
+
changeGame(window.currentGame)
|
171 |
+
})
|
172 |
+
})
|
173 |
+
|
174 |
+
|
175 |
+
}
|
176 |
+
}
|
177 |
+
left.addEventListener("dragenter", event => window.dragDropModelInstallation("dragenter", event), false)
|
178 |
+
left.addEventListener("dragleave", event => window.dragDropModelInstallation("dragleave", event), false)
|
179 |
+
left.addEventListener("dragover", event => window.dragDropModelInstallation("dragover", event), false)
|
180 |
+
left.addEventListener("drop", event => window.dragDropModelInstallation("drop", event), false)
|
javascript/editor.js
ADDED
@@ -0,0 +1,2089 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
|
4 |
+
class Editor {
|
5 |
+
|
6 |
+
constructor () {
|
7 |
+
|
8 |
+
this.isCreated = false
|
9 |
+
|
10 |
+
this.hasChanged = false
|
11 |
+
this.autoInferTimer = null
|
12 |
+
|
13 |
+
this.adjustedLetters = new Set()
|
14 |
+
|
15 |
+
this.LEFT_RIGHT_SEQ_PADDING = 20
|
16 |
+
this.EDITOR_HEIGHT = 150
|
17 |
+
this.LETTERS_Y_OFFSET = 40
|
18 |
+
this.SLIDER_GRABBER_H = 15
|
19 |
+
this.MIN_LETTER_LENGTH = 20
|
20 |
+
this.MAX_LETTER_LENGTH = 100
|
21 |
+
this.SPACE_BETWEEN_LETTERS = 5
|
22 |
+
|
23 |
+
this.default_pitchSliderRange = 4
|
24 |
+
this.pitchSliderRange = 4
|
25 |
+
this.duration_visual_size_multiplier = 1
|
26 |
+
|
27 |
+
this.default_MIN_ENERGY = 3.45
|
28 |
+
this.MIN_ENERGY = 3.45
|
29 |
+
this.default_MAX_ENERGY = 4.35
|
30 |
+
this.MAX_ENERGY = 4.35
|
31 |
+
this.ENERGY_GRABBER_RADIUS = 8
|
32 |
+
this.EMOTION_STYLE_GRABBER_RADIUS = 8
|
33 |
+
|
34 |
+
this.MIN_EMOTIONS = 0
|
35 |
+
this.MAX_EMOTIONS = 1.06
|
36 |
+
|
37 |
+
this.MIN_STYLES = 0
|
38 |
+
this.MAX_STYLES = 1.06
|
39 |
+
|
40 |
+
this.clear() // And thus init
|
41 |
+
|
42 |
+
this.registeredStyleKeys = []
|
43 |
+
this.historyState = [] // TODO, add support for undo/redo across all editor functions
|
44 |
+
}
|
45 |
+
|
46 |
+
clear () {
|
47 |
+
this.sliderBoxes = []
|
48 |
+
this.grabbers = [] // Pitch (original single grabber)
|
49 |
+
this.energyGrabbers = []
|
50 |
+
this.emAngryGrabbers = []
|
51 |
+
this.emHappyGrabbers = []
|
52 |
+
this.emSadGrabbers = []
|
53 |
+
this.emSurpriseGrabbers = []
|
54 |
+
|
55 |
+
this.styleGrabbers = {}
|
56 |
+
this.styleValuesNew = {}
|
57 |
+
this.styleValuesReset = {}
|
58 |
+
this.multiLetterStyleDelta = {}
|
59 |
+
this.multiLetterStartStyleVals = {}
|
60 |
+
|
61 |
+
this.letters = []
|
62 |
+
this.pitchNew = []
|
63 |
+
this.dursNew = []
|
64 |
+
this.energyNew = []
|
65 |
+
this.emAngryNew = []
|
66 |
+
this.emHappyNew = []
|
67 |
+
this.emSadNew = []
|
68 |
+
this.emSurpriseNew = []
|
69 |
+
this.pacing = 1
|
70 |
+
this.ampFlatCounter = 0
|
71 |
+
|
72 |
+
this.inputSequence = undefined
|
73 |
+
this.currentVoice = undefined
|
74 |
+
this.letterFocus = []
|
75 |
+
this.lastSelected = 0
|
76 |
+
this.letterClasses = []
|
77 |
+
this.resetDurs = []
|
78 |
+
this.resetPitch = []
|
79 |
+
this.resetEnergy = []
|
80 |
+
this.resetEmAngry = []
|
81 |
+
this.resetEmHappy = []
|
82 |
+
this.resetEmSad = []
|
83 |
+
this.resetEmSurprise = []
|
84 |
+
|
85 |
+
this.multiLetterPitchDelta = undefined
|
86 |
+
this.multiLetterStartPitchVals = []
|
87 |
+
this.multiLetterStartDursVals = []
|
88 |
+
this.multiLetterEnergyDelta = undefined
|
89 |
+
this.multiLetterStartEnergyVals = []
|
90 |
+
this.multiLetterEmotionDelta = undefined
|
91 |
+
this.multiLetterStartEmotionVals = []
|
92 |
+
|
93 |
+
this.multiLetterLengthDelta = undefined
|
94 |
+
this.multiLetterStartLengthVals = []
|
95 |
+
}
|
96 |
+
|
97 |
+
loadStylesData (editorStyles) {
|
98 |
+
this.registeredStyleKeys = []
|
99 |
+
Object.keys(window.appState.currentModelEmbeddings).forEach(styleKey => {
|
100 |
+
if (styleKey=="default") return
|
101 |
+
this.registeredStyleKeys.push(styleKey)
|
102 |
+
|
103 |
+
this.styleGrabbers[styleKey] = []
|
104 |
+
this.styleValuesReset[styleKey] = this.resetPitch.map(v=>0)
|
105 |
+
this.styleValuesNew[styleKey] = (editorStyles&&editorStyles[styleKey]&&editorStyles[styleKey].sliders) ? editorStyles[styleKey].sliders : this.resetPitch.map(v=>0)
|
106 |
+
this.multiLetterStyleDelta[styleKey] = undefined
|
107 |
+
this.multiLetterStartStyleVals[styleKey] = []
|
108 |
+
})
|
109 |
+
}
|
110 |
+
|
111 |
+
init () {
|
112 |
+
|
113 |
+
// Clear away old instance
|
114 |
+
if (this.isCreated) {
|
115 |
+
editorContainer.innerHTML = ""
|
116 |
+
delete this.canvas
|
117 |
+
delete this.context
|
118 |
+
}
|
119 |
+
|
120 |
+
let canvasWidth = 0
|
121 |
+
if (this.sliderBoxes.length) {
|
122 |
+
canvasWidth = this.sliderBoxes.at(-1).getX() + this.SPACE_BETWEEN_LETTERS * 2 + 100
|
123 |
+
} else {
|
124 |
+
canvasWidth = this.LEFT_RIGHT_SEQ_PADDING*2 // Padding
|
125 |
+
this.dursNew.forEach((dur,di) => {
|
126 |
+
if (di) {
|
127 |
+
canvasWidth += this.SPACE_BETWEEN_LETTERS
|
128 |
+
}
|
129 |
+
|
130 |
+
let value = dur
|
131 |
+
value = value * this.pacing
|
132 |
+
value = Math.max(0.1, value)
|
133 |
+
value = Math.min(value, 20)
|
134 |
+
const percentAcross = value/20
|
135 |
+
const width = percentAcross * (this.MAX_LETTER_LENGTH-this.MIN_LETTER_LENGTH) + this.MIN_LETTER_LENGTH
|
136 |
+
canvasWidth += width
|
137 |
+
})
|
138 |
+
}
|
139 |
+
|
140 |
+
this.canvas = document.createElement("canvas")
|
141 |
+
this.context = this.canvas.getContext("2d")
|
142 |
+
this.context.textAlign = "center"
|
143 |
+
this.canvas.width = canvasWidth
|
144 |
+
this.canvas.height = 200
|
145 |
+
|
146 |
+
editorContainer.appendChild(this.canvas)
|
147 |
+
|
148 |
+
|
149 |
+
// Mouse cursor
|
150 |
+
this.canvas.addEventListener("mousemove", event => {
|
151 |
+
const mouseX = parseInt(event.offsetX)
|
152 |
+
const mouseY = parseInt(event.offsetY)
|
153 |
+
this.canvas.style.cursor = "default"
|
154 |
+
|
155 |
+
// Check energy grabber hover
|
156 |
+
const isOnEGrabber = seq_edit_view_select.value.includes("energy") && this.energyGrabbers.find((eGrabber, egi) => {
|
157 |
+
if (!this.enabled_disabled_items[egi]) return
|
158 |
+
const grabberX = eGrabber.getXLeft()+eGrabber.sliderBox.width/2-this.ENERGY_GRABBER_RADIUS
|
159 |
+
return (mouseX>grabberX && mouseX<grabberX+this.ENERGY_GRABBER_RADIUS*2+4) && (mouseY>eGrabber.topLeftY-this.ENERGY_GRABBER_RADIUS-2 && mouseY<eGrabber.topLeftY+this.ENERGY_GRABBER_RADIUS+2)
|
160 |
+
})
|
161 |
+
if (isOnEGrabber && isOnEGrabber!=undefined) {
|
162 |
+
this.canvas.style.cursor = "row-resize"
|
163 |
+
return
|
164 |
+
}
|
165 |
+
|
166 |
+
// One grabber type
|
167 |
+
if (seq_edit_view_select.value !== "pitch_energy") {
|
168 |
+
this.sliderBoxes.forEach((sbox, sboxi) => {
|
169 |
+
if (!this.enabled_disabled_items[sboxi]) return;
|
170 |
+
|
171 |
+
// is outside slider box => return
|
172 |
+
if (!(
|
173 |
+
mouseX>sbox.getXLeft()
|
174 |
+
&& mouseX<sbox.getXLeft() + sbox.width
|
175 |
+
)) {
|
176 |
+
return;
|
177 |
+
}
|
178 |
+
|
179 |
+
this.canvas.style.cursor = "row-resize"
|
180 |
+
})
|
181 |
+
}
|
182 |
+
|
183 |
+
// Check emotion grabber hover
|
184 |
+
const isHoveringOverEmotionGrabber = emotionGrabbers => {
|
185 |
+
return emotionGrabbers.find((eGrabber, egi) => {
|
186 |
+
if (!this.enabled_disabled_items[egi]) return
|
187 |
+
const grabberX = eGrabber.getXLeft()+eGrabber.sliderBox.width/2-this.EMOTION_STYLE_GRABBER_RADIUS
|
188 |
+
return (mouseX>grabberX && mouseX<grabberX+this.EMOTION_STYLE_GRABBER_RADIUS*2+4) && (mouseY>eGrabber.topLeftY-this.EMOTION_STYLE_GRABBER_RADIUS-2 && mouseY<eGrabber.topLeftY+this.EMOTION_STYLE_GRABBER_RADIUS+2)
|
189 |
+
})
|
190 |
+
}
|
191 |
+
if (window.currentModel.modelType=="xVAPitch") {
|
192 |
+
const isOnEmGrabber = seq_edit_view_select.value.startsWith("em") && (isHoveringOverEmotionGrabber(this.emAngryGrabbers) || isHoveringOverEmotionGrabber(this.emHappyGrabbers) || isHoveringOverEmotionGrabber(this.emSadGrabbers) || isHoveringOverEmotionGrabber(this.emSurpriseGrabbers))
|
193 |
+
if (isOnEmGrabber && isOnEmGrabber!=undefined) {
|
194 |
+
this.canvas.style.cursor = "row-resize"
|
195 |
+
return
|
196 |
+
}
|
197 |
+
}
|
198 |
+
// Check pitch grabber hover
|
199 |
+
const isOnGrabber = seq_edit_view_select.value.includes("pitch") && this.grabbers.find((grabber, gi) => {
|
200 |
+
if (!this.enabled_disabled_items[gi]) return
|
201 |
+
const grabberX = grabber.getXLeft()
|
202 |
+
return (mouseX>grabberX && mouseX<grabberX+grabber.width) && (mouseY>grabber.topLeftY && mouseY<grabber.topLeftY+grabber.height)
|
203 |
+
})
|
204 |
+
if (isOnGrabber && isOnGrabber!=undefined) {
|
205 |
+
this.canvas.style.cursor = "n-resize"
|
206 |
+
return
|
207 |
+
}
|
208 |
+
|
209 |
+
// Check styles grabbers
|
210 |
+
if (this.registeredStyleKeys && this.registeredStyleKeys.length) {
|
211 |
+
let isOnStyleGrabber
|
212 |
+
this.registeredStyleKeys.forEach(styleKey => {
|
213 |
+
if (isOnStyleGrabber) return // Skip unnecessary work if already found
|
214 |
+
if (seq_edit_view_select.value.startsWith("style_") && seq_edit_view_select.value.includes(styleKey)) {
|
215 |
+
isOnStyleGrabber = this.styleGrabbers[styleKey].find((grabber, gi) => {
|
216 |
+
if (!this.enabled_disabled_items[gi]) return
|
217 |
+
const grabberX = grabber.getXLeft()
|
218 |
+
return (mouseX>grabberX && mouseX<grabberX+grabber.width) && (mouseY>grabber.topLeftY && mouseY<grabber.topLeftY+grabber.height)
|
219 |
+
})
|
220 |
+
if (isOnStyleGrabber) {
|
221 |
+
this.canvas.style.cursor = "row-resize"
|
222 |
+
return
|
223 |
+
}
|
224 |
+
}
|
225 |
+
})
|
226 |
+
}
|
227 |
+
|
228 |
+
// Check letter hover
|
229 |
+
const isOnLetter = this.letterClasses.find((letter, l) => {
|
230 |
+
if (!this.enabled_disabled_items[l]) return
|
231 |
+
return (mouseY<this.LETTERS_Y_OFFSET) && (mouseX>this.sliderBoxes[l].getXLeft() && mouseX<this.sliderBoxes[l].getXLeft()+this.sliderBoxes[l].width)
|
232 |
+
})
|
233 |
+
if (isOnLetter!=undefined) {
|
234 |
+
this.canvas.style.cursor = "pointer"
|
235 |
+
return
|
236 |
+
}
|
237 |
+
// Check box length dragger
|
238 |
+
const isBetweenBoxes = this.sliderBoxes.find((box, bi) => {
|
239 |
+
if (!this.enabled_disabled_items[bi]) return
|
240 |
+
const boxX = box.getXLeft()
|
241 |
+
return (mouseY>box.topY && mouseY<box.topY+box.height) && (mouseX>(boxX+box.width-10) && mouseX<(boxX+box.width+10)+5)
|
242 |
+
})
|
243 |
+
if (isBetweenBoxes!=undefined) {
|
244 |
+
this.canvas.style.cursor = "w-resize"
|
245 |
+
return
|
246 |
+
}
|
247 |
+
})
|
248 |
+
|
249 |
+
let elemDragged = undefined
|
250 |
+
let mouseDownStart = {x: undefined, y: undefined}
|
251 |
+
this.canvas.addEventListener("mousedown", event => {
|
252 |
+
const mouseX = parseInt(event.offsetX)
|
253 |
+
const mouseY = parseInt(event.offsetY)
|
254 |
+
mouseDownStart.x = mouseX
|
255 |
+
mouseDownStart.y = mouseY
|
256 |
+
|
257 |
+
// Check up-down pitch dragging box first
|
258 |
+
const isOnGrabber = seq_edit_view_select.value.includes("pitch") && this.grabbers.find((grabber, gi) => {
|
259 |
+
if (!this.enabled_disabled_items[gi]) return
|
260 |
+
const grabberX = grabber.getXLeft()
|
261 |
+
return (mouseX>grabberX && mouseX<grabberX+grabber.width) && (mouseY>grabber.topLeftY && mouseY<grabber.topLeftY+grabber.height)
|
262 |
+
})
|
263 |
+
if (isOnGrabber) {
|
264 |
+
|
265 |
+
const slider = isOnGrabber
|
266 |
+
if (this.letterFocus.length <= 1 || (!this.letterFocus.includes(slider.index))) {
|
267 |
+
this.setLetterFocus(this.grabbers.indexOf(slider), event.ctrlKey, event.shiftKey, event.altKey)
|
268 |
+
}
|
269 |
+
this.multiLetterPitchDelta = slider.topLeftY
|
270 |
+
this.multiLetterStartPitchVals = this.grabbers.map(slider => slider.topLeftY)
|
271 |
+
|
272 |
+
elemDragged = isOnGrabber
|
273 |
+
return
|
274 |
+
}
|
275 |
+
|
276 |
+
// Check sideways dragging
|
277 |
+
const isBetweenBoxes = this.sliderBoxes.find((box, bi) => {
|
278 |
+
if (!this.enabled_disabled_items[bi]) return
|
279 |
+
const boxX = box.getXLeft()
|
280 |
+
return (mouseY>box.topY && mouseY<box.topY+box.height) && (mouseX>(boxX+box.width-10) && mouseX<(boxX+box.width+10)+5)
|
281 |
+
})
|
282 |
+
if (isBetweenBoxes) {
|
283 |
+
this.multiLetterStartDursVals = this.sliderBoxes.map(box => box.width)
|
284 |
+
|
285 |
+
isBetweenBoxes.dragStart.width = isBetweenBoxes.width
|
286 |
+
elemDragged = isBetweenBoxes
|
287 |
+
return
|
288 |
+
}
|
289 |
+
|
290 |
+
// Check up-down emotion dragging
|
291 |
+
const findGrabber = emotionGrabbers => {
|
292 |
+
return emotionGrabbers.find((eGrabber, egi) => {
|
293 |
+
if (!this.enabled_disabled_items[egi]) return
|
294 |
+
const boxX = eGrabber.sliderBox.getXLeft()
|
295 |
+
return (
|
296 |
+
(mouseX > boxX)
|
297 |
+
&& (mouseX < (boxX + eGrabber.sliderBox.width))
|
298 |
+
)
|
299 |
+
})
|
300 |
+
}
|
301 |
+
const handleEmGrabber = (emGrabber, grabbersList) => {
|
302 |
+
if (this.letterFocus.length <= 1 || (!this.letterFocus.includes(emGrabber.index))) {
|
303 |
+
this.setLetterFocus(grabbersList.indexOf(emGrabber), event.ctrlKey, event.shiftKey, event.altKey)
|
304 |
+
}
|
305 |
+
this.multiLetterEmotionDelta = emGrabber.topLeftY
|
306 |
+
this.multiLetterStartEmotionVals = grabbersList.map(emGrabber => emGrabber.topLeftY)
|
307 |
+
|
308 |
+
return emGrabber
|
309 |
+
}
|
310 |
+
const handleStyleGrabber = (styleGrabber, grabbersList, styleKey) => {
|
311 |
+
if (this.letterFocus.length <= 1 || (!this.letterFocus.includes(styleGrabber.index))) {
|
312 |
+
this.setLetterFocus(grabbersList.indexOf(styleGrabber), event.ctrlKey, event.shiftKey, event.altKey)
|
313 |
+
}
|
314 |
+
this.multiLetterStyleDelta[styleKey] = styleGrabber.topLeftY
|
315 |
+
this.multiLetterStartStyleVals[styleKey] = grabbersList.map(styleGrabber => styleGrabber.topLeftY)
|
316 |
+
return styleGrabber
|
317 |
+
}
|
318 |
+
|
319 |
+
// Check up-down energy dragging
|
320 |
+
const isOnEGrabber = seq_edit_view_select.value.includes("energy") && this.energyGrabbers.find((eGrabber, egi) => {
|
321 |
+
if (!this.enabled_disabled_items[egi]) return
|
322 |
+
const grabberX = eGrabber.getXLeft()+eGrabber.sliderBox.width/2-this.ENERGY_GRABBER_RADIUS
|
323 |
+
return (mouseX>grabberX && mouseX<grabberX+this.ENERGY_GRABBER_RADIUS*2+4) && (mouseY>eGrabber.topLeftY-this.ENERGY_GRABBER_RADIUS-2 && mouseY<eGrabber.topLeftY+this.ENERGY_GRABBER_RADIUS+2)
|
324 |
+
})
|
325 |
+
if (isOnEGrabber) {
|
326 |
+
|
327 |
+
const eGrabber = isOnEGrabber
|
328 |
+
if (this.letterFocus.length <= 1 || (!this.letterFocus.includes(eGrabber.index))) {
|
329 |
+
this.setLetterFocus(this.energyGrabbers.indexOf(eGrabber), event.ctrlKey, event.shiftKey, event.altKey)
|
330 |
+
}
|
331 |
+
this.multiLetterEnergyDelta = eGrabber.topLeftY
|
332 |
+
this.multiLetterStartEnergyVals = this.energyGrabbers.map(eGrabber => eGrabber.topLeftY)
|
333 |
+
|
334 |
+
elemDragged = isOnEGrabber
|
335 |
+
return
|
336 |
+
}
|
337 |
+
|
338 |
+
// Check clicking on the top letters
|
339 |
+
const isOnLetter = this.letterClasses.find((letter, l) => {
|
340 |
+
if (!this.enabled_disabled_items[l]) return
|
341 |
+
return (mouseY<this.LETTERS_Y_OFFSET) && (mouseX>this.sliderBoxes[l].getXLeft() && mouseX<this.sliderBoxes[l].getXLeft()+this.sliderBoxes[l].width)
|
342 |
+
})
|
343 |
+
|
344 |
+
if (isOnLetter) {
|
345 |
+
this.setLetterFocus(this.letterClasses.indexOf(isOnLetter), event.ctrlKey, event.shiftKey, event.altKey)
|
346 |
+
return;
|
347 |
+
}
|
348 |
+
|
349 |
+
// Not on letter
|
350 |
+
|
351 |
+
// Drag grabber when only single type of grabber
|
352 |
+
if (seq_edit_view_select.value === "pitch_energy")
|
353 |
+
{
|
354 |
+
return;
|
355 |
+
}
|
356 |
+
// Fetch any grabber within letter column
|
357 |
+
const isOnGrabberCol = seq_edit_view_select.value=="pitch" && findGrabber(this.grabbers)
|
358 |
+
if (isOnGrabberCol) {
|
359 |
+
const slider = isOnGrabberCol
|
360 |
+
this.multiLetterPitchDelta = slider.topLeftY
|
361 |
+
this.multiLetterStartPitchVals = this.grabbers.map(slider => slider.topLeftY)
|
362 |
+
|
363 |
+
elemDragged = isOnGrabberCol
|
364 |
+
return
|
365 |
+
}
|
366 |
+
const isOnEGrabberCol = seq_edit_view_select.value=="energy" && findGrabber(this.energyGrabbers)
|
367 |
+
if (isOnEGrabberCol) {
|
368 |
+
this.multiLetterEnergyDelta = isOnEGrabberCol.topLeftY
|
369 |
+
this.multiLetterStartEnergyVals = this.energyGrabbers.map(isOnEGrabberCol => isOnEGrabberCol.topLeftY)
|
370 |
+
|
371 |
+
elemDragged = isOnEGrabberCol
|
372 |
+
return
|
373 |
+
}
|
374 |
+
|
375 |
+
if (window.currentModel.modelType !== "xVAPitch") {
|
376 |
+
return
|
377 |
+
}
|
378 |
+
|
379 |
+
// v3 model
|
380 |
+
if (seq_edit_view_select.value.startsWith("style_") && this.registeredStyleKeys.length) {
|
381 |
+
this.registeredStyleKeys.forEach(styleKey => {
|
382 |
+
if (seq_edit_view_select.value.includes(styleKey)) {
|
383 |
+
const isOnStyleGrabber = findGrabber(this.styleGrabbers[styleKey])
|
384 |
+
if (isOnStyleGrabber) {
|
385 |
+
elemDragged = handleStyleGrabber(isOnStyleGrabber, this.styleGrabbers[styleKey], styleKey)
|
386 |
+
return
|
387 |
+
}
|
388 |
+
}
|
389 |
+
})
|
390 |
+
}
|
391 |
+
|
392 |
+
const isOnEmAngryGrabber = seq_edit_view_select.value=="emAngry" && findGrabber(this.emAngryGrabbers)
|
393 |
+
if (isOnEmAngryGrabber) {
|
394 |
+
elemDragged = handleEmGrabber(isOnEmAngryGrabber, this.emAngryGrabbers)
|
395 |
+
return
|
396 |
+
}
|
397 |
+
const isOnEmHappyGrabber = seq_edit_view_select.value=="emHappy" && findGrabber(this.emHappyGrabbers)
|
398 |
+
if (isOnEmHappyGrabber) {
|
399 |
+
elemDragged = handleEmGrabber(isOnEmHappyGrabber, this.emHappyGrabbers)
|
400 |
+
return
|
401 |
+
}
|
402 |
+
const isOnEmSadGrabber = seq_edit_view_select.value=="emSad" && findGrabber(this.emSadGrabbers)
|
403 |
+
if (isOnEmSadGrabber) {
|
404 |
+
elemDragged = handleEmGrabber(isOnEmSadGrabber, this.emSadGrabbers)
|
405 |
+
return
|
406 |
+
}
|
407 |
+
const isOnEmSurpriseGrabber = seq_edit_view_select.value=="emSurprise" && findGrabber(this.emSurpriseGrabbers)
|
408 |
+
if (isOnEmSurpriseGrabber) {
|
409 |
+
elemDragged = handleEmGrabber(isOnEmSurpriseGrabber, this.emSurpriseGrabbers)
|
410 |
+
return
|
411 |
+
}
|
412 |
+
})
|
413 |
+
|
414 |
+
this.canvas.addEventListener("mouseup", event => {
|
415 |
+
mouseDownStart = {x: undefined, y: undefined}
|
416 |
+
if (autoplay_ckbx.checked && this.hasChanged) {
|
417 |
+
generateVoiceButton.click()
|
418 |
+
}
|
419 |
+
this.init()
|
420 |
+
})
|
421 |
+
|
422 |
+
this.canvas.addEventListener("mousemove", event => {
|
423 |
+
if (mouseDownStart.x && mouseDownStart.y) {
|
424 |
+
|
425 |
+
if (elemDragged && (parseInt(event.offsetX)-mouseDownStart.x || parseInt(event.offsetY)-mouseDownStart.y)) {
|
426 |
+
this.hasChanged = true
|
427 |
+
this.letterFocus.forEach(index => this.adjustedLetters.add(index))
|
428 |
+
}
|
429 |
+
|
430 |
+
if (elemDragged) {
|
431 |
+
if (elemDragged.type=="slider") { // Pitch sliders, specifically
|
432 |
+
|
433 |
+
elemDragged.setValueFromCoords(parseInt(event.offsetY)-elemDragged.height/2)
|
434 |
+
|
435 |
+
// If there's a multi-selection, update all of their values, otherwise update the numerical input
|
436 |
+
if (this.letterFocus.length>1) {
|
437 |
+
this.letterFocus.forEach(li => {
|
438 |
+
if (li!=elemDragged.index) {
|
439 |
+
this.grabbers[li].setValueFromCoords(this.multiLetterStartPitchVals[li]+(elemDragged.topLeftY-this.multiLetterPitchDelta))
|
440 |
+
}
|
441 |
+
})
|
442 |
+
} else {
|
443 |
+
letterPitchNumb.value = parseInt(this.pitchNew[elemDragged.index]*100)/100
|
444 |
+
}
|
445 |
+
|
446 |
+
|
447 |
+
} else if (elemDragged.type=="box") { // Durations being dragged sideways
|
448 |
+
|
449 |
+
// If there's a multi-selection, update all of their values, otherwise update the numerical input
|
450 |
+
if (this.letterFocus.length>1) {
|
451 |
+
this.letterFocus.forEach(li => {
|
452 |
+
let newWidth = this.multiLetterStartDursVals[li] + parseInt(elemDragged.width - elemDragged.dragStart.width)
|
453 |
+
newWidth = Math.max(20, newWidth)
|
454 |
+
newWidth = Math.min(newWidth, this.MAX_LETTER_LENGTH)
|
455 |
+
|
456 |
+
this.sliderBoxes[li].width = newWidth
|
457 |
+
|
458 |
+
this.sliderBoxes[li].percentAcross = (this.sliderBoxes[li].width-20) / (this.MAX_LETTER_LENGTH-20)
|
459 |
+
this.dursNew[this.sliderBoxes[li].index] = Math.max(0.1, this.sliderBoxes[li].percentAcross*20)
|
460 |
+
|
461 |
+
this.sliderBoxes[li].grabber.width = this.sliderBoxes[li].width-2
|
462 |
+
this.sliderBoxes[li].letter.centerX = this.sliderBoxes[li].leftX + this.sliderBoxes[li].width/2
|
463 |
+
})
|
464 |
+
} else {
|
465 |
+
|
466 |
+
letterLengthNumb.value = parseInt(this.dursNew[elemDragged.index]*100)/100
|
467 |
+
}
|
468 |
+
|
469 |
+
|
470 |
+
let newWidth = elemDragged.dragStart.width + parseInt(event.offsetX)-mouseDownStart.x
|
471 |
+
newWidth = Math.max(20, newWidth)
|
472 |
+
newWidth = Math.min(newWidth, this.MAX_LETTER_LENGTH)
|
473 |
+
elemDragged.width = newWidth
|
474 |
+
|
475 |
+
elemDragged.percentAcross = (elemDragged.width-20) / (this.MAX_LETTER_LENGTH-20)
|
476 |
+
this.dursNew[elemDragged.index] = Math.max(0.1, elemDragged.percentAcross*20)
|
477 |
+
|
478 |
+
elemDragged.grabber.width = elemDragged.width-2
|
479 |
+
elemDragged.letter.centerX = elemDragged.leftX + elemDragged.width/2
|
480 |
+
|
481 |
+
} else if (elemDragged.type=="energy_slider") { // Energy sliders
|
482 |
+
|
483 |
+
elemDragged.setValueFromCoords(parseInt(event.offsetY)-elemDragged.height/2)
|
484 |
+
|
485 |
+
// If there's a multi-selection, update all of their values, otherwise update the numerical input
|
486 |
+
if (this.letterFocus.length>1) {
|
487 |
+
this.letterFocus.forEach(li => {
|
488 |
+
if (li!=elemDragged.index) {
|
489 |
+
this.energyGrabbers[li].setValueFromCoords(this.multiLetterStartEnergyVals[li]+(elemDragged.topLeftY-this.multiLetterEnergyDelta))
|
490 |
+
}
|
491 |
+
})
|
492 |
+
} else {
|
493 |
+
letterEnergyNumb.value = parseInt(this.energyNew[elemDragged.index]*100)/100
|
494 |
+
}
|
495 |
+
|
496 |
+
} else if (elemDragged.type=="emotion_slider") { // Emotion sliders
|
497 |
+
|
498 |
+
elemDragged.setValueFromCoords(parseInt(event.offsetY)-elemDragged.height/2)
|
499 |
+
|
500 |
+
// If there's a multi-selection, update all of their values, otherwise update the numerical input
|
501 |
+
if (this.letterFocus.length>1) {
|
502 |
+
this.letterFocus.forEach(li => {
|
503 |
+
if (li!=elemDragged.index) {
|
504 |
+
if (seq_edit_view_select.value=="emAngry") {
|
505 |
+
this.emAngryGrabbers[li].setValueFromCoords(this.multiLetterStartEmotionVals[li]+(elemDragged.topLeftY-this.multiLetterEmotionDelta))
|
506 |
+
} else if (seq_edit_view_select.value=="emHappy") {
|
507 |
+
this.emHappyGrabbers[li].setValueFromCoords(this.multiLetterStartEmotionVals[li]+(elemDragged.topLeftY-this.multiLetterEmotionDelta))
|
508 |
+
} else if (seq_edit_view_select.value=="emSad") {
|
509 |
+
this.emSadGrabbers[li].setValueFromCoords(this.multiLetterStartEmotionVals[li]+(elemDragged.topLeftY-this.multiLetterEmotionDelta))
|
510 |
+
} else if (seq_edit_view_select.value=="emSurprise") {
|
511 |
+
this.emSurpriseGrabbers[li].setValueFromCoords(this.multiLetterStartEmotionVals[li]+(elemDragged.topLeftY-this.multiLetterEmotionDelta))
|
512 |
+
}
|
513 |
+
}
|
514 |
+
})
|
515 |
+
} else {
|
516 |
+
if (seq_edit_view_select.value=="emAngry") {
|
517 |
+
letterEmotionNumb.value = parseFloat(this.emAngryNew[elemDragged.index]*100)/100
|
518 |
+
} else if (seq_edit_view_select.value=="emHappy") {
|
519 |
+
letterEmotionNumb.value = parseFloat(this.emHappyNew[elemDragged.index]*100)/100
|
520 |
+
} else if (seq_edit_view_select.value=="emSad") {
|
521 |
+
letterEmotionNumb.value = parseFloat(this.emSadNew[elemDragged.index]*100)/100
|
522 |
+
} else if (seq_edit_view_select.value=="emSurprise") {
|
523 |
+
letterEmotionNumb.value = parseFloat(this.emSurpriseNew[elemDragged.index]*100)/100
|
524 |
+
}
|
525 |
+
}
|
526 |
+
} else if (elemDragged.type=="style_slider") { // Style sliders
|
527 |
+
|
528 |
+
elemDragged.setValueFromCoords(parseInt(event.offsetY)-elemDragged.height/2)
|
529 |
+
|
530 |
+
if (this.registeredStyleKeys.length) {
|
531 |
+
this.registeredStyleKeys.forEach(styleKey => {
|
532 |
+
if (seq_edit_view_select.value.startsWith("style_") && seq_edit_view_select.value.includes(styleKey)) {
|
533 |
+
// If there's a multi-selection, update all of their values, otherwise update the numerical input
|
534 |
+
if (this.letterFocus.length>1) {
|
535 |
+
this.letterFocus.forEach(li => {
|
536 |
+
if (li!=elemDragged.index) {
|
537 |
+
this.styleGrabbers[styleKey][li].setValueFromCoords(this.multiLetterStartStyleVals[styleKey][li]+(elemDragged.topLeftY-this.multiLetterStyleDelta[styleKey]))
|
538 |
+
}
|
539 |
+
})
|
540 |
+
} else {
|
541 |
+
letterStyleNumb.value = parseInt(this.styleValuesNew[styleKey][elemDragged.index]*100)/100
|
542 |
+
}
|
543 |
+
}
|
544 |
+
})
|
545 |
+
}
|
546 |
+
}
|
547 |
+
}
|
548 |
+
}
|
549 |
+
})
|
550 |
+
|
551 |
+
if (!this.isCreated) {
|
552 |
+
this.render()
|
553 |
+
}
|
554 |
+
this.isCreated = true
|
555 |
+
}
|
556 |
+
|
557 |
+
|
558 |
+
|
559 |
+
|
560 |
+
render () {
|
561 |
+
if (this.context!=undefined) {
|
562 |
+
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
563 |
+
this.letterClasses.forEach((letter, li) => {
|
564 |
+
if (this.letters[li]=="<PAD>") return
|
565 |
+
letter.context = this.context
|
566 |
+
letter.render()
|
567 |
+
})
|
568 |
+
this.sliderBoxes.forEach((sliderBox, sbi) => {
|
569 |
+
if (this.letters[sbi]=="<PAD>") return
|
570 |
+
sliderBox.context = this.context
|
571 |
+
sliderBox.render()
|
572 |
+
})
|
573 |
+
if (seq_edit_view_select.value=="pitch_energy" || seq_edit_view_select.value=="pitch") {
|
574 |
+
this.grabbers.forEach((grabber,gi) => {
|
575 |
+
if (this.letters[gi]=="<PAD>") return
|
576 |
+
grabber.context = this.context
|
577 |
+
grabber.render()
|
578 |
+
})
|
579 |
+
}
|
580 |
+
if (seq_edit_view_select.value=="pitch_energy" || seq_edit_view_select.value=="energy") {
|
581 |
+
this.energyGrabbers.forEach((eGrabber, egi) => {
|
582 |
+
if (this.letters[egi]=="<PAD>") return
|
583 |
+
eGrabber.context = this.context
|
584 |
+
eGrabber.render()
|
585 |
+
})
|
586 |
+
}
|
587 |
+
if (window.currentModel.modelType=="xVAPitch") {
|
588 |
+
if (seq_edit_view_select.value=="emAngry") {
|
589 |
+
this.emAngryGrabbers.forEach((eGrabber, egi) => {
|
590 |
+
if (this.letters[egi]=="<PAD>") return
|
591 |
+
eGrabber.context = this.context
|
592 |
+
eGrabber.render()
|
593 |
+
})
|
594 |
+
}
|
595 |
+
if (seq_edit_view_select.value=="emHappy") {
|
596 |
+
this.emHappyGrabbers.forEach((eGrabber, egi) => {
|
597 |
+
if (this.letters[egi]=="<PAD>") return
|
598 |
+
eGrabber.context = this.context
|
599 |
+
eGrabber.render()
|
600 |
+
})
|
601 |
+
}
|
602 |
+
if (seq_edit_view_select.value=="emSad") {
|
603 |
+
this.emSadGrabbers.forEach((eGrabber, egi) => {
|
604 |
+
if (this.letters[egi]=="<PAD>") return
|
605 |
+
eGrabber.context = this.context
|
606 |
+
eGrabber.render()
|
607 |
+
})
|
608 |
+
}
|
609 |
+
if (seq_edit_view_select.value=="emSurprise") {
|
610 |
+
this.emSurpriseGrabbers.forEach((eGrabber, egi) => {
|
611 |
+
if (this.letters[egi]=="<PAD>") return
|
612 |
+
eGrabber.context = this.context
|
613 |
+
eGrabber.render()
|
614 |
+
})
|
615 |
+
}
|
616 |
+
if (this.registeredStyleKeys && this.registeredStyleKeys.length) {
|
617 |
+
this.registeredStyleKeys.forEach(styleKey => {
|
618 |
+
if (seq_edit_view_select.value.startsWith("style_") && seq_edit_view_select.value.includes(styleKey)) {
|
619 |
+
this.styleGrabbers[styleKey].forEach((styleGrabber, sgi) => {
|
620 |
+
if (this.letters[sgi]=="<PAD>") return
|
621 |
+
styleGrabber.context = this.context
|
622 |
+
styleGrabber.render()
|
623 |
+
})
|
624 |
+
}
|
625 |
+
})
|
626 |
+
}
|
627 |
+
}
|
628 |
+
}
|
629 |
+
requestAnimationFrame(() => {this.render()})
|
630 |
+
}
|
631 |
+
|
632 |
+
|
633 |
+
|
634 |
+
|
635 |
+
update (modelType=undefined, sliderRange=undefined) {
|
636 |
+
|
637 |
+
self.modelType = modelType
|
638 |
+
|
639 |
+
// Make model-specific adjustments
|
640 |
+
if (modelType=="xVAPitch") {
|
641 |
+
this.default_pitchSliderRange = 6
|
642 |
+
this.pitchSliderRange = sliderRange || 6
|
643 |
+
this.duration_visual_size_multiplier = 1
|
644 |
+
this.MAX_LETTER_LENGTH = 200
|
645 |
+
this.default_MIN_ENERGY = 0
|
646 |
+
this.MIN_ENERGY = 0
|
647 |
+
this.default_MAX_ENERGY = 1.07
|
648 |
+
this.MAX_ENERGY = 1.07
|
649 |
+
|
650 |
+
this.styleGrabbers = {}
|
651 |
+
this.registeredStyleKeys.forEach(styleKey => {
|
652 |
+
this.styleGrabbers[styleKey] = []
|
653 |
+
})
|
654 |
+
|
655 |
+
} else {
|
656 |
+
this.default_pitchSliderRange = 4
|
657 |
+
this.pitchSliderRange = sliderRange || 4
|
658 |
+
this.duration_visual_size_multiplier = 1
|
659 |
+
this.MAX_LETTER_LENGTH = 100
|
660 |
+
}
|
661 |
+
|
662 |
+
this.letterClasses = []
|
663 |
+
this.sliderBoxes = []
|
664 |
+
this.grabbers = []
|
665 |
+
this.energyGrabbers = []
|
666 |
+
this.emAngryGrabbers = []
|
667 |
+
this.emHappyGrabbers = []
|
668 |
+
this.emSadGrabbers = []
|
669 |
+
this.emSurpriseGrabbers = []
|
670 |
+
|
671 |
+
this.enabled_disabled_items = []
|
672 |
+
|
673 |
+
let xCounter = 0
|
674 |
+
let lastBox = undefined
|
675 |
+
let letter_counter = 0
|
676 |
+
this.letters.forEach((letter, li) => {
|
677 |
+
|
678 |
+
if (letter=="<PAD>") {
|
679 |
+
this.enabled_disabled_items.push(false)
|
680 |
+
letter_counter += 1
|
681 |
+
} else {
|
682 |
+
this.enabled_disabled_items.push(true)
|
683 |
+
}
|
684 |
+
letter_counter += 1
|
685 |
+
|
686 |
+
const dur = this.dursNew[li]
|
687 |
+
const width = Math.max(25, dur*10)
|
688 |
+
|
689 |
+
|
690 |
+
// Slider box
|
691 |
+
const sliderBox = new SliderBox(this.context, li, lastBox, this.LETTERS_Y_OFFSET, this.EDITOR_HEIGHT, this.MIN_LETTER_LENGTH, this.MAX_LETTER_LENGTH, letter_counter%2==0)
|
692 |
+
sliderBox.render()
|
693 |
+
if (lastBox) {
|
694 |
+
lastBox.rightBox = sliderBox
|
695 |
+
}
|
696 |
+
lastBox = sliderBox
|
697 |
+
this.sliderBoxes.push(sliderBox)
|
698 |
+
|
699 |
+
// Letter text
|
700 |
+
const letterClass = new Letter(this.context, li, letter, sliderBox, 20, 20+xCounter, width)
|
701 |
+
if (this.letterFocus.includes(li)) {
|
702 |
+
letterClass.colour = "red"
|
703 |
+
}
|
704 |
+
letterClass.render()
|
705 |
+
this.letterClasses.push(letterClass)
|
706 |
+
|
707 |
+
// Slider grabber thing
|
708 |
+
const pitchPercent = 1-(this.pitchNew[li]+this.pitchSliderRange)/(this.pitchSliderRange*2)
|
709 |
+
const grabber = new SliderGrabber(this.context, li, sliderBox, (this.LETTERS_Y_OFFSET+1)+(this.SLIDER_GRABBER_H/2)+((this.EDITOR_HEIGHT-2)-this.SLIDER_GRABBER_H)*pitchPercent-this.SLIDER_GRABBER_H/2, width-2, this.SLIDER_GRABBER_H, this.pitchSliderRange)
|
710 |
+
grabber.render()
|
711 |
+
this.grabbers.push(grabber)
|
712 |
+
|
713 |
+
if (this.energyNew && this.energyNew.length) {
|
714 |
+
// Energy round grabber
|
715 |
+
let energyPercent
|
716 |
+
if (modelType=="xVAPitch") {
|
717 |
+
energyPercent = ( (this.energyNew[li]-this.MIN_ENERGY) / (this.MAX_ENERGY-this.MIN_ENERGY) )
|
718 |
+
} else {
|
719 |
+
energyPercent = 1 - ( (this.energyNew[li]-this.MIN_ENERGY) / (this.MAX_ENERGY-this.MIN_ENERGY) )
|
720 |
+
}
|
721 |
+
energyPercent = Math.max(0, energyPercent)
|
722 |
+
energyPercent = Math.min(energyPercent, 1)
|
723 |
+
|
724 |
+
let topLeftY = (1 - energyPercent) * (this.EDITOR_HEIGHT-2-this.ENERGY_GRABBER_RADIUS) + (this.LETTERS_Y_OFFSET)
|
725 |
+
const energyGrabber = new EnergyEmotionGrabber(this.context, li, sliderBox, topLeftY, width-2, this.ENERGY_GRABBER_RADIUS, undefined, modelType, this.ENERGY_GRABBER_RADIUS, "energy")
|
726 |
+
energyGrabber.render()
|
727 |
+
this.energyGrabbers.push(energyGrabber)
|
728 |
+
}
|
729 |
+
|
730 |
+
if (modelType=="xVAPitch") {
|
731 |
+
if (this.emAngryNew && this.emAngryNew.length) {
|
732 |
+
let emotionPercent = ( (this.emAngryNew[li]-this.MIN_EMOTIONS) / (this.MAX_EMOTIONS-this.MIN_EMOTIONS) )
|
733 |
+
emotionPercent = Math.max(0, emotionPercent)
|
734 |
+
emotionPercent = Math.min(emotionPercent, 1)
|
735 |
+
|
736 |
+
let topLeftY = (1 - emotionPercent) * (this.EDITOR_HEIGHT-2-this.EMOTION_STYLE_GRABBER_RADIUS) + (this.LETTERS_Y_OFFSET)
|
737 |
+
const emAngryGrabber = new EnergyEmotionGrabber(this.context, li, sliderBox, topLeftY, width-2, this.EMOTION_STYLE_GRABBER_RADIUS, undefined, modelType, this.EMOTION_STYLE_GRABBER_RADIUS, "emotion")
|
738 |
+
emAngryGrabber.render()
|
739 |
+
this.emAngryGrabbers.push(emAngryGrabber)
|
740 |
+
}
|
741 |
+
if (this.emHappyNew && this.emHappyNew.length) {
|
742 |
+
let emotionPercent = ( (this.emHappyNew[li]-this.MIN_EMOTIONS) / (this.MAX_EMOTIONS-this.MIN_EMOTIONS) )
|
743 |
+
emotionPercent = Math.max(0, emotionPercent)
|
744 |
+
emotionPercent = Math.min(emotionPercent, 1)
|
745 |
+
|
746 |
+
let topLeftY = (1 - emotionPercent) * (this.EDITOR_HEIGHT-2-this.EMOTION_STYLE_GRABBER_RADIUS) + (this.LETTERS_Y_OFFSET)
|
747 |
+
const emHappyGrabber = new EnergyEmotionGrabber(this.context, li, sliderBox, topLeftY, width-2, this.EMOTION_STYLE_GRABBER_RADIUS, undefined, modelType, this.EMOTION_STYLE_GRABBER_RADIUS, "emotion")
|
748 |
+
emHappyGrabber.render()
|
749 |
+
this.emHappyGrabbers.push(emHappyGrabber)
|
750 |
+
}
|
751 |
+
if (this.emSadNew && this.emSadNew.length) {
|
752 |
+
let emotionPercent = ( (this.emSadNew[li]-this.MIN_EMOTIONS) / (this.MAX_EMOTIONS-this.MIN_EMOTIONS) )
|
753 |
+
emotionPercent = Math.max(0, emotionPercent)
|
754 |
+
emotionPercent = Math.min(emotionPercent, 1)
|
755 |
+
|
756 |
+
let topLeftY = (1 - emotionPercent) * (this.EDITOR_HEIGHT-2-this.EMOTION_STYLE_GRABBER_RADIUS) + (this.LETTERS_Y_OFFSET)
|
757 |
+
const emSadGrabber = new EnergyEmotionGrabber(this.context, li, sliderBox, topLeftY, width-2, this.EMOTION_STYLE_GRABBER_RADIUS, undefined, modelType, this.EMOTION_STYLE_GRABBER_RADIUS, "emotion")
|
758 |
+
emSadGrabber.render()
|
759 |
+
this.emSadGrabbers.push(emSadGrabber)
|
760 |
+
}
|
761 |
+
if (this.emSurpriseNew && this.emSurpriseNew.length) {
|
762 |
+
let emotionPercent = ( (this.emSurpriseNew[li]-this.MIN_EMOTIONS) / (this.MAX_EMOTIONS-this.MIN_EMOTIONS) )
|
763 |
+
emotionPercent = Math.max(0, emotionPercent)
|
764 |
+
emotionPercent = Math.min(emotionPercent, 1)
|
765 |
+
|
766 |
+
let topLeftY = (1 - emotionPercent) * (this.EDITOR_HEIGHT-2-this.EMOTION_STYLE_GRABBER_RADIUS) + (this.LETTERS_Y_OFFSET)
|
767 |
+
const emSurpriseGrabber = new EnergyEmotionGrabber(this.context, li, sliderBox, topLeftY, width-2, this.EMOTION_STYLE_GRABBER_RADIUS, undefined, modelType, this.EMOTION_STYLE_GRABBER_RADIUS, "emotion")
|
768 |
+
emSurpriseGrabber.render()
|
769 |
+
this.emSurpriseGrabbers.push(emSurpriseGrabber)
|
770 |
+
}
|
771 |
+
|
772 |
+
// Initialize grabbers dynamically for every style
|
773 |
+
this.registeredStyleKeys.forEach(styleKey => {
|
774 |
+
let stylePercent = ( (this.styleValuesNew[styleKey][li]-this.MIN_STYLES) / (this.MAX_STYLES-this.MIN_STYLES) )
|
775 |
+
stylePercent = Math.max(0, stylePercent)
|
776 |
+
stylePercent = Math.min(stylePercent, 1)
|
777 |
+
|
778 |
+
let topLeftY = (1 - stylePercent) * (this.EDITOR_HEIGHT-2-this.EMOTION_STYLE_GRABBER_RADIUS) + (this.LETTERS_Y_OFFSET)
|
779 |
+
const styleGrabber = new EnergyEmotionGrabber(this.context, li, sliderBox, topLeftY, width-2, this.EMOTION_STYLE_GRABBER_RADIUS, undefined, modelType, this.EMOTION_STYLE_GRABBER_RADIUS, "style")
|
780 |
+
styleGrabber.render()
|
781 |
+
this.styleGrabbers[styleKey].push(styleGrabber)
|
782 |
+
})
|
783 |
+
}
|
784 |
+
|
785 |
+
sliderBox.letter = letterClass
|
786 |
+
sliderBox.grabber = grabber
|
787 |
+
|
788 |
+
sliderBox.setValueFromValue(dur)
|
789 |
+
|
790 |
+
xCounter += width + 5
|
791 |
+
|
792 |
+
})
|
793 |
+
|
794 |
+
this.canvas.width = this.sliderBoxes.at(-1).getX() + this.SPACE_BETWEEN_LETTERS * 2 + 100
|
795 |
+
}
|
796 |
+
|
797 |
+
setLetterFocus (l, ctrlKey, shiftKey, altKey) {
|
798 |
+
|
799 |
+
// NONE = Clear selection, add l to selection
|
800 |
+
// Ctrl = Add l to existing selection
|
801 |
+
// Shift = Add all letters from the last selected letter up to and including l to existing selection
|
802 |
+
// Ctrl + Shift = (See Shift)
|
803 |
+
// Alt = Clear selection and select word surrounding l (space delimited)
|
804 |
+
// Ctrl + Alt = Add word surrounding l to existing selection
|
805 |
+
// Shift + Alt = Same as shift, then afterwards add word (space delimited) around l to selection
|
806 |
+
// Ctrl + Shift + Alt = (See Shift + Alt)
|
807 |
+
|
808 |
+
// If nothing is selected and we hold shift, we assume we start from the first letter: at position 0
|
809 |
+
|
810 |
+
// If we don't press shift or ctrl, we can clear our current selection.
|
811 |
+
if (!(ctrlKey || shiftKey) && this.letterFocus.length){
|
812 |
+
this.letterFocus.forEach(li => {
|
813 |
+
this.letterClasses[li].colour = "black"
|
814 |
+
})
|
815 |
+
this.letterFocus = []
|
816 |
+
this.lastSelected = 0
|
817 |
+
}
|
818 |
+
if (shiftKey){
|
819 |
+
if (l>this.lastSelected) {
|
820 |
+
for (let i=this.lastSelected; i<=l; i++) {
|
821 |
+
this.letterFocus.push(i)
|
822 |
+
}
|
823 |
+
} else {
|
824 |
+
for (let i=l; i<=this.lastSelected; i++) {
|
825 |
+
this.letterFocus.push(i)
|
826 |
+
}
|
827 |
+
}
|
828 |
+
}
|
829 |
+
this.letterFocus.push(l) // Push l
|
830 |
+
this.lastSelected = l
|
831 |
+
if (altKey){
|
832 |
+
let l2 = l
|
833 |
+
// Looking backwards
|
834 |
+
while (l2>=0) {
|
835 |
+
let prevLetter = this.letters[l2]
|
836 |
+
if (prevLetter!="_") {
|
837 |
+
this.letterFocus.push(l2)
|
838 |
+
} else {
|
839 |
+
break
|
840 |
+
}
|
841 |
+
l2--
|
842 |
+
}
|
843 |
+
l2 = l
|
844 |
+
// Looking forward
|
845 |
+
while (l2<this.letters.length) {
|
846 |
+
let nextLetter = this.letters[l2]
|
847 |
+
if (nextLetter!="_") {
|
848 |
+
this.letterFocus.push(l2)
|
849 |
+
} else {
|
850 |
+
break
|
851 |
+
}
|
852 |
+
l2++
|
853 |
+
}
|
854 |
+
}
|
855 |
+
|
856 |
+
this.letterFocus = Array.from(new Set(this.letterFocus.sort()))
|
857 |
+
this.letterFocus.forEach(li => {
|
858 |
+
this.letterClasses[li].colour = "red"
|
859 |
+
})
|
860 |
+
|
861 |
+
|
862 |
+
letterStyleNumb.value = ""
|
863 |
+
letterStyleNumb.disabled = true
|
864 |
+
if (this.letterFocus.length==1) {
|
865 |
+
if (this.energyNew.length) {
|
866 |
+
letterEnergyNumb.value = parseFloat(this.energyNew[this.letterFocus[0]])
|
867 |
+
letterEnergyNumb.disabled = false
|
868 |
+
}
|
869 |
+
if (this.emAngryNew && this.emAngryNew.length) {
|
870 |
+
letterEmotionNumb.value = parseFloat(this.emAngryNew[this.letterFocus[0]])
|
871 |
+
letterEmotionNumb.disabled = false
|
872 |
+
}
|
873 |
+
if (this.emHappyNew && this.emHappyNew.length) {
|
874 |
+
letterEmotionNumb.value = parseFloat(this.emHappyNew[this.letterFocus[0]])
|
875 |
+
letterEmotionNumb.disabled = false
|
876 |
+
}
|
877 |
+
if (this.emSadNew && this.emSadNew.length) {
|
878 |
+
letterEmotionNumb.value = parseFloat(this.emSadNew[this.letterFocus[0]])
|
879 |
+
letterEmotionNumb.disabled = false
|
880 |
+
}
|
881 |
+
if (this.emSurpriseNew && this.emSurpriseNew.length) {
|
882 |
+
letterEmotionNumb.value = parseFloat(this.emSurpriseNew[this.letterFocus[0]])
|
883 |
+
letterEmotionNumb.disabled = false
|
884 |
+
}
|
885 |
+
if (this.registeredStyleKeys) {
|
886 |
+
this.registeredStyleKeys.forEach(styleKey => {
|
887 |
+
if (seq_edit_view_select.value.startsWith("style_") && seq_edit_view_select.value.includes(styleKey)) {
|
888 |
+
letterStyleNumb.value = parseFloat(this.styleValuesNew[styleKey][this.letterFocus[0]])
|
889 |
+
letterStyleNumb.disabled = false
|
890 |
+
}
|
891 |
+
})
|
892 |
+
}
|
893 |
+
letterPitchNumb.value = parseInt(this.pitchNew[this.letterFocus[0]]*100)/100
|
894 |
+
letterLengthNumb.value = parseInt(parseFloat(this.dursNew[this.letterFocus[0]])*100)/100
|
895 |
+
|
896 |
+
letterPitchNumb.disabled = false
|
897 |
+
letterLengthNumb.disabled = false
|
898 |
+
} else {
|
899 |
+
letterEnergyNumb.disabled = true
|
900 |
+
letterEnergyNumb.value = ""
|
901 |
+
letterEmotionNumb.disabled = true
|
902 |
+
letterEmotionNumb.value = ""
|
903 |
+
letterPitchNumb.disabled = true
|
904 |
+
letterPitchNumb.value = ""
|
905 |
+
letterLengthNumb.disabled = true
|
906 |
+
letterLengthNumb.value = ""
|
907 |
+
}
|
908 |
+
}
|
909 |
+
|
910 |
+
|
911 |
+
getChangedTimeStamps (startI, endI, audioSDuration) {
|
912 |
+
|
913 |
+
const adjustedLetters = Array.from(this.adjustedLetters)
|
914 |
+
|
915 |
+
// Skip this if start/end indexes are not found (new sample)
|
916 |
+
if ((startI==-1 || endI==-1) && !adjustedLetters.length) {
|
917 |
+
return undefined
|
918 |
+
}
|
919 |
+
startI = startI==-1 ? this.letters.length : parseInt(startI)
|
920 |
+
endI = endI==-1 ? 0 : parseInt(endI)
|
921 |
+
|
922 |
+
// Check OUTSIDE of the given changed indexes for TEXT, to see if there were other changes, to eg pitch/duration
|
923 |
+
if (adjustedLetters.length) {
|
924 |
+
startI = Math.min(startI, Math.min(adjustedLetters))
|
925 |
+
endI = Math.max(endI, Math.max(adjustedLetters))
|
926 |
+
}
|
927 |
+
|
928 |
+
const newStartI = startI
|
929 |
+
const newEndI = endI
|
930 |
+
|
931 |
+
// Then, look through the duration values of the audio, and get a percent into the audio where those new start/end points are
|
932 |
+
const totalDuration = this.dursNew.reduce((p,c)=>p+c,0)
|
933 |
+
const durAtStart = this.dursNew.filter((v,vi) => vi<=newStartI).reduce((p,c)=>p+c,0)
|
934 |
+
const durAtEnd = this.dursNew.filter((v,vi) => vi<=newEndI).reduce((p,c)=>p+c,0)
|
935 |
+
const startPercent = durAtStart/totalDuration
|
936 |
+
const endPercent = durAtEnd/totalDuration
|
937 |
+
|
938 |
+
// Then, multiply this by the seconds duration of the generated audio, and pad with ~500ms, to get the final start/end of the section of the audio to play
|
939 |
+
const startSeconds = Math.max(0, startPercent*audioSDuration-0.5)
|
940 |
+
const endSeconds = Math.min(audioSDuration, endPercent*audioSDuration+0.5)
|
941 |
+
|
942 |
+
return [startSeconds, endSeconds]
|
943 |
+
}
|
944 |
+
}
|
945 |
+
|
946 |
+
class Letter {
|
947 |
+
constructor (context, index, letter, sliderBox, centerY, left, width) {
|
948 |
+
this.type = "letter"
|
949 |
+
this.context = context
|
950 |
+
this.letter = letter
|
951 |
+
this.sliderBox = sliderBox
|
952 |
+
this.centerY = centerY
|
953 |
+
this.index = index
|
954 |
+
|
955 |
+
this.left = left
|
956 |
+
this.width = width
|
957 |
+
this.colour = "black"
|
958 |
+
}
|
959 |
+
|
960 |
+
render () {
|
961 |
+
this.context.fillStyle = this.colour
|
962 |
+
this.context.font = "20pt Arial"
|
963 |
+
this.context.textAlign = "center"
|
964 |
+
this.context.textBaseline = "middle"
|
965 |
+
this.context.fillText(this.letter, this.sliderBox.getXLeft()+this.sliderBox.width/2, this.centerY)
|
966 |
+
}
|
967 |
+
}
|
968 |
+
|
969 |
+
class SliderGrabber {
|
970 |
+
|
971 |
+
constructor (context, index, sliderBox, topLeftY, width, height, sliderRange) {
|
972 |
+
this.type = "slider"
|
973 |
+
this.context = context
|
974 |
+
this.sliderBox = sliderBox
|
975 |
+
this.topLeftY = topLeftY
|
976 |
+
this.width = width
|
977 |
+
this.height = height
|
978 |
+
this.index = index
|
979 |
+
this.sliderRange = sliderRange
|
980 |
+
|
981 |
+
this.isBeingDragged = false
|
982 |
+
this.dragStart = {x: undefined, y: undefined}
|
983 |
+
|
984 |
+
this.fillStyle = `#${window.currentGame.themeColourPrimary}`
|
985 |
+
}
|
986 |
+
|
987 |
+
render () {
|
988 |
+
|
989 |
+
this.context.beginPath()
|
990 |
+
this.context.rect(this.sliderBox.getXLeft()+1, this.topLeftY, this.width, this.height)
|
991 |
+
this.context.stroke()
|
992 |
+
|
993 |
+
this.context.fillStyle = this.fillStyle
|
994 |
+
this.context.fillRect(this.sliderBox.getXLeft()+1, this.topLeftY, this.width, this.height)
|
995 |
+
}
|
996 |
+
|
997 |
+
getXLeft () {
|
998 |
+
return this.sliderBox.getXLeft()
|
999 |
+
}
|
1000 |
+
|
1001 |
+
setValueFromCoords (topLeftY) {
|
1002 |
+
|
1003 |
+
this.topLeftY = topLeftY
|
1004 |
+
this.topLeftY = Math.max(window.sequenceEditor.LETTERS_Y_OFFSET+1, this.topLeftY)
|
1005 |
+
this.topLeftY = Math.min(this.topLeftY, window.sequenceEditor.LETTERS_Y_OFFSET+window.sequenceEditor.EDITOR_HEIGHT-this.height-1)
|
1006 |
+
|
1007 |
+
this.percentUp = (this.topLeftY-window.sequenceEditor.LETTERS_Y_OFFSET) / (window.sequenceEditor.EDITOR_HEIGHT-this.height)
|
1008 |
+
window.sequenceEditor.pitchNew[this.index] = (1-this.percentUp)*(this.sliderRange*2)-this.sliderRange
|
1009 |
+
}
|
1010 |
+
|
1011 |
+
setValueFromValue (value) {
|
1012 |
+
value = Math.max(-this.sliderRange, value)
|
1013 |
+
value = Math.min(value, this.sliderRange)
|
1014 |
+
this.percentUp = (value+this.sliderRange)/(this.sliderRange*2)
|
1015 |
+
|
1016 |
+
this.topLeftY = (1-this.percentUp) * (window.sequenceEditor.EDITOR_HEIGHT-this.height) + window.sequenceEditor.LETTERS_Y_OFFSET
|
1017 |
+
}
|
1018 |
+
|
1019 |
+
}
|
1020 |
+
|
1021 |
+
|
1022 |
+
class EnergyEmotionGrabber extends SliderGrabber {
|
1023 |
+
|
1024 |
+
constructor (context, index, sliderBox, topLeftY, width, height, sliderRange, modelType, radius, sliderType) {
|
1025 |
+
super(context, index, sliderBox, topLeftY, width, height, sliderRange)
|
1026 |
+
this.type = `${sliderType}_slider`
|
1027 |
+
this.modelType = modelType
|
1028 |
+
this.radius = radius
|
1029 |
+
}
|
1030 |
+
|
1031 |
+
render () {
|
1032 |
+
this.context.fillStyle = this.fillStyle
|
1033 |
+
this.context.beginPath()
|
1034 |
+
this.context.lineWidth = 1
|
1035 |
+
let x = this.sliderBox.getXLeft()+1 + this.sliderBox.width/2 // Centered
|
1036 |
+
let y = this.topLeftY
|
1037 |
+
this.context.arc(x, y, this.radius, 0, 2 * Math.PI)
|
1038 |
+
this.context.fill()
|
1039 |
+
this.context.stroke()
|
1040 |
+
this.context.lineWidth = 1
|
1041 |
+
}
|
1042 |
+
|
1043 |
+
setValueFromCoords (topLeftY) {
|
1044 |
+
|
1045 |
+
this.topLeftY = topLeftY
|
1046 |
+
this.topLeftY = Math.max(window.sequenceEditor.LETTERS_Y_OFFSET+this.radius, this.topLeftY)
|
1047 |
+
this.topLeftY = Math.min(this.topLeftY, window.sequenceEditor.LETTERS_Y_OFFSET+(window.sequenceEditor.EDITOR_HEIGHT-2-this.radius/2))
|
1048 |
+
|
1049 |
+
if (this.type=="energy_slider") {
|
1050 |
+
if (this.modelType=="xVAPitch") {
|
1051 |
+
this.percentUp = (this.topLeftY-window.sequenceEditor.LETTERS_Y_OFFSET)/(window.sequenceEditor.EDITOR_HEIGHT-this.radius)
|
1052 |
+
} else {
|
1053 |
+
this.percentUp = 1-(this.topLeftY-window.sequenceEditor.LETTERS_Y_OFFSET)/(window.sequenceEditor.EDITOR_HEIGHT-this.radius)
|
1054 |
+
}
|
1055 |
+
window.sequenceEditor.energyNew[this.index] = window.sequenceEditor.MAX_ENERGY - (window.sequenceEditor.MAX_ENERGY-window.sequenceEditor.MIN_ENERGY)*this.percentUp
|
1056 |
+
} else if (this.type=="style_slider") {
|
1057 |
+
|
1058 |
+
this.percentUp = (this.topLeftY-window.sequenceEditor.LETTERS_Y_OFFSET)/(window.sequenceEditor.EDITOR_HEIGHT-this.radius)
|
1059 |
+
|
1060 |
+
window.sequenceEditor.registeredStyleKeys.forEach(styleKey => {
|
1061 |
+
if (seq_edit_view_select.value.startsWith("style_") && seq_edit_view_select.value.includes(styleKey)) {
|
1062 |
+
window.sequenceEditor.styleValuesNew[styleKey][this.index] = window.sequenceEditor.MAX_STYLES - (window.sequenceEditor.MAX_STYLES-window.sequenceEditor.MIN_STYLES)*this.percentUp
|
1063 |
+
}
|
1064 |
+
})
|
1065 |
+
|
1066 |
+
} else {
|
1067 |
+
this.percentUp = (this.topLeftY-window.sequenceEditor.LETTERS_Y_OFFSET)/(window.sequenceEditor.EDITOR_HEIGHT-this.radius)
|
1068 |
+
if (seq_edit_view_select.value=="emAngry") {
|
1069 |
+
window.sequenceEditor.emAngryNew[this.index] = window.sequenceEditor.MAX_EMOTIONS - (window.sequenceEditor.MAX_EMOTIONS-window.sequenceEditor.MIN_ENERGY)*this.percentUp
|
1070 |
+
} else if (seq_edit_view_select.value=="emHappy") {
|
1071 |
+
window.sequenceEditor.emHappyNew[this.index] = window.sequenceEditor.MAX_EMOTIONS - (window.sequenceEditor.MAX_EMOTIONS-window.sequenceEditor.MIN_ENERGY)*this.percentUp
|
1072 |
+
} else if (seq_edit_view_select.value=="emSad") {
|
1073 |
+
window.sequenceEditor.emSadNew[this.index] = window.sequenceEditor.MAX_EMOTIONS - (window.sequenceEditor.MAX_EMOTIONS-window.sequenceEditor.MIN_ENERGY)*this.percentUp
|
1074 |
+
} else if (seq_edit_view_select.value=="emSurprise") {
|
1075 |
+
window.sequenceEditor.emSurpriseNew[this.index] = window.sequenceEditor.MAX_EMOTIONS - (window.sequenceEditor.MAX_EMOTIONS-window.sequenceEditor.MIN_ENERGY)*this.percentUp
|
1076 |
+
}
|
1077 |
+
}
|
1078 |
+
}
|
1079 |
+
|
1080 |
+
setValueFromValue (value) {
|
1081 |
+
if (this.type=="energy_slider") {
|
1082 |
+
value = Math.max(window.sequenceEditor.MIN_ENERGY, value)
|
1083 |
+
value = Math.min(value, window.sequenceEditor.MAX_ENERGY)
|
1084 |
+
if (this.modelType=="xVAPitch") {
|
1085 |
+
this.percentUp = ( (value-window.sequenceEditor.MIN_ENERGY) / (window.sequenceEditor.MAX_ENERGY-window.sequenceEditor.MIN_ENERGY) )
|
1086 |
+
} else {
|
1087 |
+
this.percentUp = 1 - ( (value-window.sequenceEditor.MIN_ENERGY) / (window.sequenceEditor.MAX_ENERGY-window.sequenceEditor.MIN_ENERGY) )
|
1088 |
+
}
|
1089 |
+
} else if (this.type=="style_slider") {
|
1090 |
+
value = Math.max(window.sequenceEditor.MIN_STYLES, value)
|
1091 |
+
value = Math.min(value, window.sequenceEditor.MAX_STYLES)
|
1092 |
+
this.percentUp = ( (value-window.sequenceEditor.MIN_STYLES) / (window.sequenceEditor.MAX_STYLES-window.sequenceEditor.MIN_STYLES) )
|
1093 |
+
} else {
|
1094 |
+
value = Math.max(window.sequenceEditor.MIN_EMOTIONS, value)
|
1095 |
+
value = Math.min(value, window.sequenceEditor.MAX_EMOTIONS)
|
1096 |
+
this.percentUp = ( (value-window.sequenceEditor.MIN_EMOTIONS) / (window.sequenceEditor.MAX_EMOTIONS-window.sequenceEditor.MIN_EMOTIONS) )
|
1097 |
+
}
|
1098 |
+
|
1099 |
+
this.topLeftY = (1 - this.percentUp) * (window.sequenceEditor.EDITOR_HEIGHT-2-this.radius*2) + (window.sequenceEditor.LETTERS_Y_OFFSET+1)
|
1100 |
+
}
|
1101 |
+
}
|
1102 |
+
|
1103 |
+
|
1104 |
+
class SliderBox {
|
1105 |
+
constructor (context, index, leftBox, topY, height, minLetterLength, maxLetterLength, alternateColour=false) {
|
1106 |
+
this.type = "box"
|
1107 |
+
this.context = context
|
1108 |
+
this.leftBox = leftBox
|
1109 |
+
this.topY = topY
|
1110 |
+
this.height = height
|
1111 |
+
this.index = index
|
1112 |
+
this.alternateColour = alternateColour
|
1113 |
+
|
1114 |
+
this.LEFT_RIGHT_SEQ_PADDING = 20
|
1115 |
+
this.MIN_LETTER_LENGTH = minLetterLength
|
1116 |
+
this.MAX_LETTER_LENGTH = maxLetterLength
|
1117 |
+
|
1118 |
+
this.isBeingDragged = false
|
1119 |
+
this.dragStart = {width: undefined, y: undefined}
|
1120 |
+
}
|
1121 |
+
|
1122 |
+
render () {
|
1123 |
+
this.context.globalAlpha = 0.3
|
1124 |
+
this.context.fillStyle = this.alternateColour ? "white" : "black"
|
1125 |
+
this.context.fillRect(this.LEFT_RIGHT_SEQ_PADDING+ (this.getLeftBox()?this.getLeftBox().getX():0), this.topY, this.width, this.height)
|
1126 |
+
|
1127 |
+
this.context.beginPath()
|
1128 |
+
this.context.rect(this.LEFT_RIGHT_SEQ_PADDING+ (this.getLeftBox()?this.getLeftBox().getX():0), this.topY, this.width, this.height)
|
1129 |
+
this.context.stroke()
|
1130 |
+
|
1131 |
+
|
1132 |
+
this.context.globalAlpha = 1
|
1133 |
+
}
|
1134 |
+
|
1135 |
+
setValueFromValue (value) {
|
1136 |
+
|
1137 |
+
value = value * window.sequenceEditor.pacing
|
1138 |
+
value = Math.max(0.1, value)
|
1139 |
+
value = Math.min(value, 20)
|
1140 |
+
|
1141 |
+
this.percentAcross = value/20
|
1142 |
+
this.width = this.percentAcross * (this.MAX_LETTER_LENGTH-this.MIN_LETTER_LENGTH) + this.MIN_LETTER_LENGTH
|
1143 |
+
|
1144 |
+
this.grabber.width = this.width-2
|
1145 |
+
this.letter.centerX = this.leftX + this.width/2
|
1146 |
+
}
|
1147 |
+
|
1148 |
+
getLeftBox () {
|
1149 |
+
if (this.leftBox) {
|
1150 |
+
if (window.sequenceEditor.enabled_disabled_items[this.leftBox.index]) {
|
1151 |
+
return this.leftBox
|
1152 |
+
} else {
|
1153 |
+
return this.leftBox.leftBox
|
1154 |
+
}
|
1155 |
+
}
|
1156 |
+
}
|
1157 |
+
|
1158 |
+
|
1159 |
+
getX () {
|
1160 |
+
if (this.leftBox) {
|
1161 |
+
return this.getLeftBox().getX() + this.width + 5
|
1162 |
+
}
|
1163 |
+
return 0 + this.width + 5
|
1164 |
+
}
|
1165 |
+
getXLeft () {
|
1166 |
+
if (this.leftBox) {
|
1167 |
+
return this.LEFT_RIGHT_SEQ_PADDING+this.getLeftBox().getX()
|
1168 |
+
}
|
1169 |
+
return this.LEFT_RIGHT_SEQ_PADDING
|
1170 |
+
}
|
1171 |
+
}
|
1172 |
+
|
1173 |
+
|
1174 |
+
|
1175 |
+
|
1176 |
+
|
1177 |
+
|
1178 |
+
|
1179 |
+
const infer = () => {
|
1180 |
+
window.sequenceEditor.hasChanged = false
|
1181 |
+
if (!isGenerating) {
|
1182 |
+
generateVoiceButton.click()
|
1183 |
+
}
|
1184 |
+
}
|
1185 |
+
const kickOffAutoInferTimer = () => {
|
1186 |
+
if (window.sequenceEditor.autoInferTimer != null) {
|
1187 |
+
clearTimeout(window.sequenceEditor.autoInferTimer)
|
1188 |
+
window.sequenceEditor.autoInferTimer = null
|
1189 |
+
}
|
1190 |
+
if (autoplay_ckbx.checked) {
|
1191 |
+
window.sequenceEditor.autoInferTimer = setTimeout(infer, 500)
|
1192 |
+
}
|
1193 |
+
}
|
1194 |
+
|
1195 |
+
|
1196 |
+
// Un-select letters when clicking anywhere else
|
1197 |
+
right.addEventListener("click", event => {
|
1198 |
+
if (event.target.nodeName=="BUTTON" || event.target.nodeName=="INPUT" || event.target.nodeName=="SVG" || event.target.nodeName=="IMG" || event.target.nodeName=="path" || event.target == window.sequenceEditor.canvas || event.target.id=="dialogueInput" || (event.target.classList && event.target.classList.contains("autocomplete_option"))) {
|
1199 |
+
return
|
1200 |
+
}
|
1201 |
+
window.sequenceEditor.letterFocus.forEach(li => {
|
1202 |
+
window.sequenceEditor.letterClasses[li].colour = "black"
|
1203 |
+
})
|
1204 |
+
window.sequenceEditor.letterFocus = []
|
1205 |
+
|
1206 |
+
letterEnergyNumb.disabled = true
|
1207 |
+
letterEnergyNumb.value = ""
|
1208 |
+
letterPitchNumb.disabled = true
|
1209 |
+
letterPitchNumb.value = ""
|
1210 |
+
letterLengthNumb.disabled = true
|
1211 |
+
letterLengthNumb.value = ""
|
1212 |
+
letterEmotionNumb.disabled = true
|
1213 |
+
letterEmotionNumb.value = ""
|
1214 |
+
letterStyleNumb.disabled = true
|
1215 |
+
letterStyleNumb.value = ""
|
1216 |
+
})
|
1217 |
+
|
1218 |
+
letterEnergyNumb.addEventListener("click", () => {
|
1219 |
+
const lpnValue = parseFloat(letterEnergyNumb.value) || 0
|
1220 |
+
if (window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1221 |
+
window.sequenceEditor.hasChanged = true
|
1222 |
+
}
|
1223 |
+
})
|
1224 |
+
letterEnergyNumb.addEventListener("input", () => {
|
1225 |
+
const lpnValue = parseFloat(letterEnergyNumb.value) || 0
|
1226 |
+
if (window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1227 |
+
window.sequenceEditor.hasChanged = true
|
1228 |
+
}
|
1229 |
+
window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]] = lpnValue
|
1230 |
+
window.sequenceEditor.energyGrabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(lpnValue)
|
1231 |
+
kickOffAutoInferTimer()
|
1232 |
+
})
|
1233 |
+
letterEnergyNumb.addEventListener("change", () => {
|
1234 |
+
const lpnValue = parseFloat(letterEnergyNumb.value) || 0
|
1235 |
+
if (window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1236 |
+
window.sequenceEditor.hasChanged = true
|
1237 |
+
}
|
1238 |
+
window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]] = lpnValue
|
1239 |
+
window.sequenceEditor.energyGrabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(lpnValue)
|
1240 |
+
kickOffAutoInferTimer()
|
1241 |
+
})
|
1242 |
+
|
1243 |
+
letterEmotionNumb.addEventListener("click", () => {
|
1244 |
+
const lpnValue = parseFloat(letterEmotionNumb.value) || 0
|
1245 |
+
let [data, grabbers] = getSelectedEmotionDataAndGrabbers()
|
1246 |
+
if (data[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1247 |
+
window.sequenceEditor.hasChanged = true
|
1248 |
+
}
|
1249 |
+
})
|
1250 |
+
letterEmotionNumb.addEventListener("input", () => {
|
1251 |
+
const lpnValue = parseFloat(letterEmotionNumb.value) || 0
|
1252 |
+
let [data, grabbers] = getSelectedEmotionDataAndGrabbers()
|
1253 |
+
if (window.sequenceEditor.data[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1254 |
+
window.sequenceEditor.hasChanged = true
|
1255 |
+
}
|
1256 |
+
window.sequenceEditor.data[window.sequenceEditor.letterFocus[0]] = lpnValue
|
1257 |
+
grabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(lpnValue)
|
1258 |
+
kickOffAutoInferTimer()
|
1259 |
+
})
|
1260 |
+
letterEmotionNumb.addEventListener("change", () => {
|
1261 |
+
const lpnValue = parseFloat(letterEmotionNumb.value) || 0
|
1262 |
+
let [data, grabbers] = getSelectedEmotionDataAndGrabbers()
|
1263 |
+
if (data[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1264 |
+
window.sequenceEditor.hasChanged = true
|
1265 |
+
}
|
1266 |
+
data[window.sequenceEditor.letterFocus[0]] = lpnValue
|
1267 |
+
grabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(lpnValue)
|
1268 |
+
kickOffAutoInferTimer()
|
1269 |
+
})
|
1270 |
+
|
1271 |
+
const getSelectedStyleDataAndGrabbers = () => {
|
1272 |
+
let data, grabbers
|
1273 |
+
window.sequenceEditor.registeredStyleKeys.forEach(styleKey => {
|
1274 |
+
if (seq_edit_view_select.value.startsWith("style_") && seq_edit_view_select.value.includes(styleKey)) {
|
1275 |
+
data = window.sequenceEditor.styleValuesNew[styleKey]
|
1276 |
+
grabbers = window.sequenceEditor.styleGrabbers[styleKey]
|
1277 |
+
}
|
1278 |
+
})
|
1279 |
+
return [data, grabbers]
|
1280 |
+
}
|
1281 |
+
|
1282 |
+
letterStyleNumb.addEventListener("click", () => {
|
1283 |
+
const lpnValue = parseFloat(letterStyleNumb.value) || 0
|
1284 |
+
let [data, grabbers] = getSelectedStyleDataAndGrabbers()
|
1285 |
+
if (data && data[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1286 |
+
window.sequenceEditor.hasChanged = true
|
1287 |
+
}
|
1288 |
+
})
|
1289 |
+
letterStyleNumb.addEventListener("input", () => {
|
1290 |
+
const lpnValue = parseFloat(letterStyleNumb.value) || 0
|
1291 |
+
let [data, grabbers] = getSelectedStyleDataAndGrabbers()
|
1292 |
+
if (data[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1293 |
+
window.sequenceEditor.hasChanged = true
|
1294 |
+
}
|
1295 |
+
data[window.sequenceEditor.letterFocus[0]] = lpnValue
|
1296 |
+
grabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(lpnValue)
|
1297 |
+
setNewDataToSelectedStyle(data)
|
1298 |
+
kickOffAutoInferTimer()
|
1299 |
+
})
|
1300 |
+
letterStyleNumb.addEventListener("change", () => {
|
1301 |
+
const lpnValue = parseFloat(letterStyleNumb.value) || 0
|
1302 |
+
let [data, grabbers] = getSelectedStyleDataAndGrabbers()
|
1303 |
+
if (data[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1304 |
+
window.sequenceEditor.hasChanged = true
|
1305 |
+
}
|
1306 |
+
data[window.sequenceEditor.letterFocus[0]] = lpnValue
|
1307 |
+
grabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(lpnValue)
|
1308 |
+
setNewDataToSelectedStyle(data)
|
1309 |
+
kickOffAutoInferTimer()
|
1310 |
+
})
|
1311 |
+
|
1312 |
+
|
1313 |
+
letterPitchNumb.addEventListener("click", () => {
|
1314 |
+
const lpnValue = parseFloat(letterPitchNumb.value) || 0
|
1315 |
+
if (window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1316 |
+
window.sequenceEditor.hasChanged = true
|
1317 |
+
}
|
1318 |
+
})
|
1319 |
+
letterPitchNumb.addEventListener("input", () => {
|
1320 |
+
const lpnValue = parseFloat(letterPitchNumb.value) || 0
|
1321 |
+
if (window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1322 |
+
window.sequenceEditor.hasChanged = true
|
1323 |
+
}
|
1324 |
+
window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]] = lpnValue
|
1325 |
+
window.sequenceEditor.grabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(letterPitchNumb.value)
|
1326 |
+
kickOffAutoInferTimer()
|
1327 |
+
})
|
1328 |
+
letterPitchNumb.addEventListener("change", () => {
|
1329 |
+
const lpnValue = parseFloat(letterPitchNumb.value) || 0
|
1330 |
+
if (window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]!=lpnValue) {
|
1331 |
+
window.sequenceEditor.hasChanged = true
|
1332 |
+
}
|
1333 |
+
window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]] = lpnValue
|
1334 |
+
window.sequenceEditor.grabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(letterPitchNumb.value)
|
1335 |
+
kickOffAutoInferTimer()
|
1336 |
+
})
|
1337 |
+
|
1338 |
+
resetLetter_btn.addEventListener("click", () => {
|
1339 |
+
if (window.sequenceEditor.letterFocus.length==0) {
|
1340 |
+
return
|
1341 |
+
}
|
1342 |
+
|
1343 |
+
window.sequenceEditor.letterFocus.forEach(l => {
|
1344 |
+
if (window.sequenceEditor.dursNew[l] != window.sequenceEditor.resetDurs[l]) {
|
1345 |
+
window.sequenceEditor.hasChanged = true
|
1346 |
+
}
|
1347 |
+
window.sequenceEditor.dursNew[l] = window.sequenceEditor.resetDurs[l]
|
1348 |
+
window.sequenceEditor.pitchNew[l] = window.sequenceEditor.resetPitch[l]
|
1349 |
+
|
1350 |
+
window.sequenceEditor.grabbers[l].setValueFromValue(window.sequenceEditor.resetPitch[l])
|
1351 |
+
window.sequenceEditor.sliderBoxes[l].setValueFromValue(window.sequenceEditor.resetDurs[l])
|
1352 |
+
})
|
1353 |
+
|
1354 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1355 |
+
letterLengthNumb.value = parseFloat(window.sequenceEditor.dursNew[window.sequenceEditor.letterFocus[0]])
|
1356 |
+
letterPitchNumb.value = parseInt(window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1357 |
+
letterEnergyNumb.value = parseInt(window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]])
|
1358 |
+
|
1359 |
+
let [data, grabbers] = getSelectedEmotionDataAndGrabbers()
|
1360 |
+
|
1361 |
+
window.sequenceEditor.emAngryNew[window.sequenceEditor.letterFocus[0]] = window.sequenceEditor.resetEmAngry[window.sequenceEditor.letterFocus[0]]
|
1362 |
+
window.sequenceEditor.emAngryGrabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(window.sequenceEditor.emAngryNew[window.sequenceEditor.letterFocus[0]])
|
1363 |
+
window.sequenceEditor.emHappyNew[window.sequenceEditor.letterFocus[0]] = window.sequenceEditor.resetEmHappy[window.sequenceEditor.letterFocus[0]]
|
1364 |
+
window.sequenceEditor.emHappyGrabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(window.sequenceEditor.emHappyNew[window.sequenceEditor.letterFocus[0]])
|
1365 |
+
window.sequenceEditor.emSadNew[window.sequenceEditor.letterFocus[0]] = window.sequenceEditor.resetEmSad[window.sequenceEditor.letterFocus[0]]
|
1366 |
+
window.sequenceEditor.emSadGrabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(window.sequenceEditor.emSadNew[window.sequenceEditor.letterFocus[0]])
|
1367 |
+
window.sequenceEditor.emSurpriseNew[window.sequenceEditor.letterFocus[0]] = window.sequenceEditor.resetEmSurprise[window.sequenceEditor.letterFocus[0]]
|
1368 |
+
window.sequenceEditor.emSurpriseGrabbers[window.sequenceEditor.letterFocus[0]].setValueFromValue(window.sequenceEditor.emSurpriseNew[window.sequenceEditor.letterFocus[0]])
|
1369 |
+
|
1370 |
+
if (data) {
|
1371 |
+
letterEmotionNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]])
|
1372 |
+
}
|
1373 |
+
|
1374 |
+
window.sequenceEditor.registeredStyleKeys.forEach(styleKey => {
|
1375 |
+
window.sequenceEditor.styleValuesNew[styleKey][window.sequenceEditor.letterFocus[0]] = window.sequenceEditor.styleValuesReset[styleKey][window.sequenceEditor.letterFocus[0]]
|
1376 |
+
window.sequenceEditor.styleGrabbers[styleKey][window.sequenceEditor.letterFocus[0]].setValueFromValue(window.sequenceEditor.styleValuesNew[styleKey][window.sequenceEditor.letterFocus[0]])
|
1377 |
+
})
|
1378 |
+
|
1379 |
+
let [styleData, styleGrabbers] = getSelectedStyleDataAndGrabbers()
|
1380 |
+
if (styleData) {
|
1381 |
+
letterStyleNumb.value = parseInt(styleData[window.sequenceEditor.letterFocus[0]])
|
1382 |
+
}
|
1383 |
+
}
|
1384 |
+
})
|
1385 |
+
const updateLetterLengthFromInput = () => {
|
1386 |
+
if (window.sequenceEditor.dursNew[window.sequenceEditor.letterFocus[0]] != letterLengthNumb.value) {
|
1387 |
+
window.sequenceEditor.hasChanged = true
|
1388 |
+
}
|
1389 |
+
window.sequenceEditor.dursNew[window.sequenceEditor.letterFocus[0]] = parseFloat(letterLengthNumb.value)
|
1390 |
+
|
1391 |
+
window.sequenceEditor.letterFocus.forEach(l => {
|
1392 |
+
window.sequenceEditor.sliderBoxes[l].setValueFromValue(window.sequenceEditor.dursNew[l])
|
1393 |
+
})
|
1394 |
+
kickOffAutoInferTimer()
|
1395 |
+
}
|
1396 |
+
letterLengthNumb.addEventListener("input", () => {
|
1397 |
+
updateLetterLengthFromInput()
|
1398 |
+
})
|
1399 |
+
letterLengthNumb.addEventListener("change", () => {
|
1400 |
+
updateLetterLengthFromInput()
|
1401 |
+
})
|
1402 |
+
|
1403 |
+
// Reset button
|
1404 |
+
window.resetEnergy = () => {
|
1405 |
+
window.sequenceEditor.energyNew = window.sequenceEditor.resetEnergy.map(v => v)
|
1406 |
+
window.sequenceEditor.energyGrabbers.forEach((slider, l) => {
|
1407 |
+
slider.setValueFromValue(window.sequenceEditor.energyNew[l])
|
1408 |
+
})
|
1409 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1410 |
+
letterEnergyNumb.value = parseInt(window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1411 |
+
}
|
1412 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1413 |
+
letterEnergyNumb.value = parseInt(window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]])
|
1414 |
+
}
|
1415 |
+
}
|
1416 |
+
window.resetStyle = () => {
|
1417 |
+
window.sequenceEditor.registeredStyleKeys.forEach(styleKey => {
|
1418 |
+
window.sequenceEditor.styleValuesNew[styleKey] = window.sequenceEditor.styleValuesReset[styleKey].map(v => v)
|
1419 |
+
window.sequenceEditor.styleGrabbers[styleKey].forEach((slider, l) => slider.setValueFromValue(window.sequenceEditor.styleValuesNew[styleKey][l]))
|
1420 |
+
|
1421 |
+
})
|
1422 |
+
let [data, grabbers] = getSelectedStyleDataAndGrabbers()
|
1423 |
+
if (data) {
|
1424 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1425 |
+
letterStyleNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1426 |
+
}
|
1427 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1428 |
+
letterStyleNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]])
|
1429 |
+
}
|
1430 |
+
}
|
1431 |
+
}
|
1432 |
+
window.resetEmotion = () => {
|
1433 |
+
window.sequenceEditor.emAngryNew = window.sequenceEditor.resetEmAngry.map(v => v)
|
1434 |
+
window.sequenceEditor.emHappyNew = window.sequenceEditor.resetEmHappy.map(v => v)
|
1435 |
+
window.sequenceEditor.emSadNew = window.sequenceEditor.resetEmSad.map(v => v)
|
1436 |
+
window.sequenceEditor.emSurpriseNew = window.sequenceEditor.resetEmSurprise.map(v => v)
|
1437 |
+
|
1438 |
+
let [data, grabbers] = getSelectedEmotionDataAndGrabbers()
|
1439 |
+
|
1440 |
+
window.sequenceEditor.emAngryGrabbers.forEach((slider, l) => slider.setValueFromValue(window.sequenceEditor.emAngryNew[l]))
|
1441 |
+
window.sequenceEditor.emHappyGrabbers.forEach((slider, l) => slider.setValueFromValue(window.sequenceEditor.emHappyNew[l]))
|
1442 |
+
window.sequenceEditor.emSadGrabbers.forEach((slider, l) => slider.setValueFromValue(window.sequenceEditor.emSadNew[l]))
|
1443 |
+
window.sequenceEditor.emSurpriseGrabbers.forEach((slider, l) => slider.setValueFromValue(window.sequenceEditor.emSurpriseNew[l]))
|
1444 |
+
|
1445 |
+
if (data && window.sequenceEditor.letterFocus.length==1) {
|
1446 |
+
letterEmotionNumb.value = parseFloat(data[window.sequenceEditor.letterFocus[0]])
|
1447 |
+
}
|
1448 |
+
}
|
1449 |
+
window.resetPitch = () => {
|
1450 |
+
window.sequenceEditor.pitchNew = window.sequenceEditor.resetPitch.map(p=>p)
|
1451 |
+
// Update the editor pitch values
|
1452 |
+
window.sequenceEditor.grabbers.forEach((slider, i) => {
|
1453 |
+
slider.setValueFromValue(window.sequenceEditor.pitchNew[i])
|
1454 |
+
})
|
1455 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1456 |
+
letterPitchNumb.value = parseInt(window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1457 |
+
}
|
1458 |
+
}
|
1459 |
+
window.resetDursPace = () => {
|
1460 |
+
|
1461 |
+
pace_slid.value = 1
|
1462 |
+
paceNumbInput.value = 1
|
1463 |
+
window.sequenceEditor.pacing = parseFloat(pace_slid.value)
|
1464 |
+
|
1465 |
+
window.sequenceEditor.dursNew = window.sequenceEditor.resetDurs.map(v => v)
|
1466 |
+
// Update the editor lengths
|
1467 |
+
window.sequenceEditor.sliderBoxes.forEach((box,i) => {
|
1468 |
+
box.setValueFromValue(window.sequenceEditor.dursNew[i])
|
1469 |
+
})
|
1470 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1471 |
+
letterLengthNumb.value = parseFloat(window.sequenceEditor.dursNew[window.sequenceEditor.letterFocus[0]])
|
1472 |
+
}
|
1473 |
+
|
1474 |
+
}
|
1475 |
+
reset_btn.addEventListener("click", () => {
|
1476 |
+
|
1477 |
+
if (window.shiftKeyIsPressed) {
|
1478 |
+
if (seq_edit_edit_select.value=="energy") {
|
1479 |
+
resetEnergy()
|
1480 |
+
resetDursPace()
|
1481 |
+
|
1482 |
+
} else if (seq_edit_edit_select.value=="pitch") {
|
1483 |
+
resetPitch()
|
1484 |
+
resetDursPace()
|
1485 |
+
|
1486 |
+
} else if (seq_edit_edit_select.value=="emotion") {
|
1487 |
+
resetEmotion()
|
1488 |
+
resetDursPace()
|
1489 |
+
|
1490 |
+
} else if (seq_edit_edit_select.value=="style") {
|
1491 |
+
resetStyle()
|
1492 |
+
resetDursPace()
|
1493 |
+
}
|
1494 |
+
|
1495 |
+
window.sequenceEditor.init()
|
1496 |
+
} else {
|
1497 |
+
reset_what_open_btn.click()
|
1498 |
+
}
|
1499 |
+
})
|
1500 |
+
reset_what_confirm_btn.addEventListener("click", () => {
|
1501 |
+
resetContainer.click()
|
1502 |
+
if (reset_what_pitch.checked) {
|
1503 |
+
resetPitch()
|
1504 |
+
}
|
1505 |
+
if (reset_what_energy.checked) {
|
1506 |
+
resetEnergy()
|
1507 |
+
}
|
1508 |
+
if (reset_what_duration.checked) {
|
1509 |
+
resetDursPace()
|
1510 |
+
}
|
1511 |
+
if (reset_what_emotion.checked) {
|
1512 |
+
resetEmotion()
|
1513 |
+
}
|
1514 |
+
if (reset_what_style.checked) {
|
1515 |
+
resetStyle()
|
1516 |
+
}
|
1517 |
+
window.sequenceEditor.init()
|
1518 |
+
})
|
1519 |
+
|
1520 |
+
const getSelectedEmotionDataAndGrabbers = () => {
|
1521 |
+
let data, grabbers
|
1522 |
+
if (seq_edit_view_select.value=="emAngry") {
|
1523 |
+
data = window.sequenceEditor.emAngryNew
|
1524 |
+
grabbers = window.sequenceEditor.emAngryGrabbers
|
1525 |
+
} else if (seq_edit_view_select.value=="emHappy") {
|
1526 |
+
data = window.sequenceEditor.emHappyNew
|
1527 |
+
grabbers = window.sequenceEditor.emHappyGrabbers
|
1528 |
+
} else if (seq_edit_view_select.value=="emSad") {
|
1529 |
+
data = window.sequenceEditor.emSadNew
|
1530 |
+
grabbers = window.sequenceEditor.emSadGrabbers
|
1531 |
+
} else if (seq_edit_view_select.value=="emSurprise") {
|
1532 |
+
data = window.sequenceEditor.emSurpriseNew
|
1533 |
+
grabbers = window.sequenceEditor.emSurpriseGrabbers
|
1534 |
+
}
|
1535 |
+
return [data, grabbers]
|
1536 |
+
}
|
1537 |
+
const setNewDataToSelectedEmotion = (data) => {
|
1538 |
+
if (seq_edit_view_select.value=="emAngry") {
|
1539 |
+
window.sequenceEditor.emAngryNew = data
|
1540 |
+
} else if (seq_edit_view_select.value=="emHappy") {
|
1541 |
+
window.sequenceEditor.emHappyNew = data
|
1542 |
+
} else if (seq_edit_view_select.value=="emSad") {
|
1543 |
+
window.sequenceEditor.emSadNew = data
|
1544 |
+
} else if (seq_edit_view_select.value=="emSurprise") {
|
1545 |
+
window.sequenceEditor.emSurpriseNew = data
|
1546 |
+
}
|
1547 |
+
}
|
1548 |
+
const setNewDataToSelectedStyle = (data) => {
|
1549 |
+
window.sequenceEditor.registeredStyleKeys.forEach(styleKey => {
|
1550 |
+
if (seq_edit_view_select.value.startsWith("style_") && seq_edit_view_select.value.includes(styleKey)) {
|
1551 |
+
window.sequenceEditor.styleValuesNew[styleKey] = data
|
1552 |
+
}
|
1553 |
+
})
|
1554 |
+
}
|
1555 |
+
|
1556 |
+
amplify_btn.addEventListener("click", () => {
|
1557 |
+
if (seq_edit_edit_select.value=="pitch") {
|
1558 |
+
window.sequenceEditor.pitchNew = window.sequenceEditor.pitchNew.map((p, pi) => {
|
1559 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(pi)==-1) {
|
1560 |
+
return p
|
1561 |
+
}
|
1562 |
+
const newVal = p*1.025
|
1563 |
+
return newVal>0 ? Math.min(window.sequenceEditor.pitchSliderRange, newVal) : Math.max(-window.sequenceEditor.pitchSliderRange, newVal)
|
1564 |
+
})
|
1565 |
+
window.sequenceEditor.grabbers.forEach((slider, l) => {
|
1566 |
+
slider.setValueFromValue(window.sequenceEditor.pitchNew[l])
|
1567 |
+
})
|
1568 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1569 |
+
letterPitchNumb.value = parseInt(window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1570 |
+
}
|
1571 |
+
} else if (seq_edit_edit_select.value=="energy") {
|
1572 |
+
window.sequenceEditor.energyNew = window.sequenceEditor.energyNew.map((e, ei) => {
|
1573 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1574 |
+
return e
|
1575 |
+
}
|
1576 |
+
const distFromMiddle = (e-window.sequenceEditor.MIN_ENERGY) - (window.sequenceEditor.MAX_ENERGY-window.sequenceEditor.MIN_ENERGY)/2
|
1577 |
+
const newVal = e + distFromMiddle*0.025
|
1578 |
+
return newVal>0 ? Math.min(window.sequenceEditor.MAX_ENERGY, newVal) : Math.max(window.sequenceEditor.MIN_ENERGY, newVal)
|
1579 |
+
})
|
1580 |
+
window.sequenceEditor.energyGrabbers.forEach((slider, l) => {
|
1581 |
+
slider.setValueFromValue(window.sequenceEditor.energyNew[l])
|
1582 |
+
})
|
1583 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1584 |
+
letterEnergyNumb.value = parseInt(window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1585 |
+
}
|
1586 |
+
} else if (seq_edit_view_select.value.startsWith("style_")) {
|
1587 |
+
|
1588 |
+
let [data, grabbers] = getSelectedStyleDataAndGrabbers()
|
1589 |
+
|
1590 |
+
data = data.map((e, ei) => {
|
1591 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1592 |
+
return e
|
1593 |
+
}
|
1594 |
+
const distFromMiddle = (e-window.sequenceEditor.MIN_STYLES) - (window.sequenceEditor.MAX_STYLES-window.sequenceEditor.MIN_STYLES)/2
|
1595 |
+
const newVal = e + distFromMiddle*0.025
|
1596 |
+
return newVal>0 ? Math.min(window.sequenceEditor.MAX_STYLES, newVal) : Math.max(window.sequenceEditor.MIN_STYLES, newVal)
|
1597 |
+
})
|
1598 |
+
grabbers.forEach((slider, l) => {
|
1599 |
+
slider.setValueFromValue(data[l])
|
1600 |
+
})
|
1601 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1602 |
+
letterStyleNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1603 |
+
}
|
1604 |
+
setNewDataToSelectedStyle(data)
|
1605 |
+
|
1606 |
+
} else if (seq_edit_edit_select.value=="emotion") {
|
1607 |
+
|
1608 |
+
let [data, grabbers] = getSelectedEmotionDataAndGrabbers()
|
1609 |
+
|
1610 |
+
data = data.map((e, ei) => {
|
1611 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1612 |
+
return e
|
1613 |
+
}
|
1614 |
+
const distFromMiddle = (e-window.sequenceEditor.MIN_EMOTIONS) - (window.sequenceEditor.MAX_EMOTIONS-window.sequenceEditor.MIN_EMOTIONS)/2
|
1615 |
+
const newVal = e + distFromMiddle*0.025
|
1616 |
+
return newVal>0 ? Math.min(window.sequenceEditor.MAX_EMOTIONS, newVal) : Math.max(window.sequenceEditor.MIN_EMOTIONS, newVal)
|
1617 |
+
})
|
1618 |
+
grabbers.forEach((slider, l) => {
|
1619 |
+
slider.setValueFromValue(data[l])
|
1620 |
+
})
|
1621 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1622 |
+
letterEmotionNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1623 |
+
}
|
1624 |
+
setNewDataToSelectedEmotion(data)
|
1625 |
+
}
|
1626 |
+
kickOffAutoInferTimer()
|
1627 |
+
})
|
1628 |
+
flatten_btn.addEventListener("click", () => {
|
1629 |
+
if (seq_edit_edit_select.value=="pitch") {
|
1630 |
+
window.sequenceEditor.pitchNew = window.sequenceEditor.pitchNew.map((p,pi) => {
|
1631 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(pi)==-1) {
|
1632 |
+
return p
|
1633 |
+
}
|
1634 |
+
return p*(1-0.025)
|
1635 |
+
})
|
1636 |
+
window.sequenceEditor.grabbers.forEach((slider, l) => {
|
1637 |
+
slider.setValueFromValue(window.sequenceEditor.pitchNew[l])
|
1638 |
+
})
|
1639 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1640 |
+
letterPitchNumb.value = parseInt(window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1641 |
+
}
|
1642 |
+
|
1643 |
+
} else if (seq_edit_edit_select.value=="energy") {
|
1644 |
+
window.sequenceEditor.energyNew = window.sequenceEditor.energyNew.map((e,ei) => {
|
1645 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1646 |
+
return e
|
1647 |
+
}
|
1648 |
+
const distFromMiddle = (e-window.sequenceEditor.MIN_ENERGY) - (window.sequenceEditor.MAX_ENERGY-window.sequenceEditor.MIN_ENERGY)/2
|
1649 |
+
const newVal = e + distFromMiddle*-0.025
|
1650 |
+
return newVal>0 ? Math.min(window.sequenceEditor.MAX_ENERGY, newVal) : Math.max(window.sequenceEditor.MIN_ENERGY, newVal)
|
1651 |
+
})
|
1652 |
+
window.sequenceEditor.energyGrabbers.forEach((slider, l) => {
|
1653 |
+
slider.setValueFromValue(window.sequenceEditor.energyNew[l])
|
1654 |
+
})
|
1655 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1656 |
+
letterEnergyNumb.value = parseInt(window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1657 |
+
}
|
1658 |
+
} else if (seq_edit_view_select.value.startsWith("style_")) {
|
1659 |
+
|
1660 |
+
let [data, grabbers] = getSelectedStyleDataAndGrabbers()
|
1661 |
+
|
1662 |
+
data = data.map((e,ei) => {
|
1663 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1664 |
+
return e
|
1665 |
+
}
|
1666 |
+
const distFromMiddle = (e-window.sequenceEditor.MIN_STYLES) - (window.sequenceEditor.MAX_STYLES-window.sequenceEditor.MIN_STYLES)/2
|
1667 |
+
const newVal = e + distFromMiddle*-0.025
|
1668 |
+
return newVal>0 ? Math.min(window.sequenceEditor.MAX_STYLES, newVal) : Math.max(window.sequenceEditor.MIN_STYLES, newVal)
|
1669 |
+
})
|
1670 |
+
grabbers.forEach((slider, l) => {
|
1671 |
+
slider.setValueFromValue(data[l])
|
1672 |
+
})
|
1673 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1674 |
+
letterStyleNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1675 |
+
}
|
1676 |
+
setNewDataToSelectedStyle(data)
|
1677 |
+
|
1678 |
+
|
1679 |
+
} else if (seq_edit_edit_select.value=="emotion") {
|
1680 |
+
let [data, grabbers] = getSelectedEmotionDataAndGrabbers()
|
1681 |
+
|
1682 |
+
data = data.map((e,ei) => {
|
1683 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1684 |
+
return e
|
1685 |
+
}
|
1686 |
+
const distFromMiddle = (e-window.sequenceEditor.MIN_EMOTIONS) - (window.sequenceEditor.MAX_EMOTIONS-window.sequenceEditor.MIN_EMOTIONS)/2
|
1687 |
+
const newVal = e + distFromMiddle*-0.025
|
1688 |
+
return newVal>0 ? Math.min(window.sequenceEditor.MAX_EMOTIONS, newVal) : Math.max(window.sequenceEditor.MIN_EMOTIONS, newVal)
|
1689 |
+
})
|
1690 |
+
grabbers.forEach((slider, l) => {
|
1691 |
+
slider.setValueFromValue(data[l])
|
1692 |
+
})
|
1693 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1694 |
+
letterEmotionNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1695 |
+
}
|
1696 |
+
setNewDataToSelectedEmotion(data)
|
1697 |
+
}
|
1698 |
+
kickOffAutoInferTimer()
|
1699 |
+
})
|
1700 |
+
|
1701 |
+
|
1702 |
+
jitter_btn.addEventListener("click", () => {
|
1703 |
+
if (seq_edit_edit_select.value=="pitch") {
|
1704 |
+
window.sequenceEditor.pitchNew = window.sequenceEditor.pitchNew.map((p, pi) => {
|
1705 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(pi)==-1) {
|
1706 |
+
return p
|
1707 |
+
}
|
1708 |
+
let newVal
|
1709 |
+
if (p==0) {
|
1710 |
+
newVal = 1*(1+ (Math.random()*0.4+0.05) * ((Math.random()-0.5)>0 ? 1 : -1) )
|
1711 |
+
newVal -= 1
|
1712 |
+
} else {
|
1713 |
+
newVal = p*(1+ (Math.random()*0.2+0.05) * ((Math.random()-0.5)>0 ? 1 : -1) )
|
1714 |
+
}
|
1715 |
+
return newVal>0 ? Math.min(window.sequenceEditor.pitchSliderRange, newVal) : Math.max(-window.sequenceEditor.pitchSliderRange, newVal)
|
1716 |
+
})
|
1717 |
+
window.sequenceEditor.grabbers.forEach((slider, l) => {
|
1718 |
+
slider.setValueFromValue(window.sequenceEditor.pitchNew[l])
|
1719 |
+
})
|
1720 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1721 |
+
letterPitchNumb.value = parseInt(window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1722 |
+
}
|
1723 |
+
} else if (seq_edit_edit_select.value=="energy") {
|
1724 |
+
window.sequenceEditor.energyNew = window.sequenceEditor.energyNew.map((e, ei) => {
|
1725 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1726 |
+
return e
|
1727 |
+
}
|
1728 |
+
const distFromMiddle = (e-window.sequenceEditor.MIN_ENERGY) - (window.sequenceEditor.MAX_ENERGY-window.sequenceEditor.MIN_ENERGY)/2
|
1729 |
+
const newVal = e + distFromMiddle*(Math.random()*0.1+0.05) * ((Math.random()-0.5)>0 ? 1 : -1)
|
1730 |
+
return newVal>0 ? Math.min(window.sequenceEditor.MAX_ENERGY, newVal) : Math.max(window.sequenceEditor.MIN_ENERGY, newVal)
|
1731 |
+
})
|
1732 |
+
window.sequenceEditor.energyGrabbers.forEach((slider, l) => {
|
1733 |
+
slider.setValueFromValue(window.sequenceEditor.energyNew[l])
|
1734 |
+
})
|
1735 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1736 |
+
letterEnergyNumb.value = parseInt(window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1737 |
+
}
|
1738 |
+
} else if (seq_edit_view_select.value.startsWith("style_")) {
|
1739 |
+
|
1740 |
+
let [data, grabbers] = getSelectedStyleDataAndGrabbers()
|
1741 |
+
data = data.map((e, ei) => {
|
1742 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1743 |
+
return e
|
1744 |
+
}
|
1745 |
+
const distFromMiddle = (e-window.sequenceEditor.MIN_STYLES) - (window.sequenceEditor.MAX_STYLES-window.sequenceEditor.MIN_STYLES)/2
|
1746 |
+
const newVal = e + distFromMiddle*(Math.random()*0.1+0.05) * ((Math.random()-0.5)>0 ? 1 : -1)
|
1747 |
+
return newVal>0 ? Math.min(window.sequenceEditor.MAX_STYLES, newVal) : Math.max(window.sequenceEditor.MIN_STYLES, newVal)
|
1748 |
+
})
|
1749 |
+
grabbers.forEach((slider, l) => {
|
1750 |
+
slider.setValueFromValue(data[l])
|
1751 |
+
})
|
1752 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1753 |
+
letterStyleNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1754 |
+
}
|
1755 |
+
setNewDataToSelectedStyle(data)
|
1756 |
+
|
1757 |
+
} else if (seq_edit_edit_select.value=="emotion") {
|
1758 |
+
let [data, grabbers] = getSelectedEmotionDataAndGrabbers()
|
1759 |
+
data = data.map((e, ei) => {
|
1760 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1761 |
+
return e
|
1762 |
+
}
|
1763 |
+
const distFromMiddle = (e-window.sequenceEditor.MIN_EMOTIONS) - (window.sequenceEditor.MAX_EMOTIONS-window.sequenceEditor.MIN_EMOTIONS)/2
|
1764 |
+
const newVal = e + distFromMiddle*(Math.random()*0.1+0.05) * ((Math.random()-0.5)>0 ? 1 : -1)
|
1765 |
+
return newVal>0 ? Math.min(window.sequenceEditor.MAX_EMOTIONS, newVal) : Math.max(window.sequenceEditor.MIN_EMOTIONS, newVal)
|
1766 |
+
})
|
1767 |
+
grabbers.forEach((slider, l) => {
|
1768 |
+
slider.setValueFromValue(data[l])
|
1769 |
+
})
|
1770 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1771 |
+
letterEmotionNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1772 |
+
}
|
1773 |
+
setNewDataToSelectedEmotion(data)
|
1774 |
+
}
|
1775 |
+
kickOffAutoInferTimer()
|
1776 |
+
})
|
1777 |
+
|
1778 |
+
|
1779 |
+
increase_btn.addEventListener("click", () => {
|
1780 |
+
if (seq_edit_edit_select.value=="pitch") {
|
1781 |
+
window.sequenceEditor.pitchNew = window.sequenceEditor.pitchNew.map((p,pi) => {
|
1782 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(pi)==-1) {
|
1783 |
+
return p
|
1784 |
+
}
|
1785 |
+
return p+0.1
|
1786 |
+
})
|
1787 |
+
window.sequenceEditor.grabbers.forEach((slider, l) => {
|
1788 |
+
slider.setValueFromValue(window.sequenceEditor.pitchNew[l])
|
1789 |
+
})
|
1790 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1791 |
+
letterPitchNumb.value = parseInt(window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1792 |
+
}
|
1793 |
+
} else if (seq_edit_edit_select.value=="energy") {
|
1794 |
+
window.sequenceEditor.energyNew = window.sequenceEditor.energyNew.map((e,ei) => {
|
1795 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1796 |
+
return e
|
1797 |
+
}
|
1798 |
+
return window.currentModel.modelType=="xVAPitch" ? e+0.04 : e-0.04
|
1799 |
+
})
|
1800 |
+
window.sequenceEditor.energyGrabbers.forEach((slider, l) => {
|
1801 |
+
slider.setValueFromValue(window.sequenceEditor.energyNew[l])
|
1802 |
+
})
|
1803 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1804 |
+
letterEnergyNumb.value = parseInt(window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1805 |
+
}
|
1806 |
+
|
1807 |
+
} else if (seq_edit_view_select.value.startsWith("style_")) {
|
1808 |
+
|
1809 |
+
let [data, grabbers] = getSelectedStyleDataAndGrabbers()
|
1810 |
+
data = data.map((e,ei) => {
|
1811 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1812 |
+
return e
|
1813 |
+
}
|
1814 |
+
return e+0.04
|
1815 |
+
})
|
1816 |
+
grabbers.forEach((slider, l) => {
|
1817 |
+
slider.setValueFromValue(data[l])
|
1818 |
+
})
|
1819 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1820 |
+
letterStyleNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1821 |
+
}
|
1822 |
+
setNewDataToSelectedStyle(data)
|
1823 |
+
|
1824 |
+
|
1825 |
+
} else if (seq_edit_edit_select.value=="emotion") {
|
1826 |
+
let [data, grabbers] = getSelectedEmotionDataAndGrabbers()
|
1827 |
+
data = data.map((e,ei) => {
|
1828 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1829 |
+
return e
|
1830 |
+
}
|
1831 |
+
return e+0.04
|
1832 |
+
})
|
1833 |
+
grabbers.forEach((slider, l) => {
|
1834 |
+
slider.setValueFromValue(data[l])
|
1835 |
+
})
|
1836 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1837 |
+
letterEmotionNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1838 |
+
}
|
1839 |
+
setNewDataToSelectedEmotion(data)
|
1840 |
+
}
|
1841 |
+
kickOffAutoInferTimer()
|
1842 |
+
})
|
1843 |
+
decrease_btn.addEventListener("click", () => {
|
1844 |
+
if (seq_edit_edit_select.value=="pitch") {
|
1845 |
+
window.sequenceEditor.pitchNew = window.sequenceEditor.pitchNew.map((p,pi) => {
|
1846 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(pi)==-1) {
|
1847 |
+
return p
|
1848 |
+
}
|
1849 |
+
return p-0.1
|
1850 |
+
})
|
1851 |
+
window.sequenceEditor.grabbers.forEach((slider, l) => {
|
1852 |
+
slider.setValueFromValue(window.sequenceEditor.pitchNew[l])
|
1853 |
+
})
|
1854 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1855 |
+
letterPitchNumb.value = parseInt(window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1856 |
+
}
|
1857 |
+
} else if (seq_edit_edit_select.value=="energy") {
|
1858 |
+
window.sequenceEditor.energyNew = window.sequenceEditor.energyNew.map((e,ei) => {
|
1859 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1860 |
+
return e
|
1861 |
+
}
|
1862 |
+
return window.currentModel.modelType=="xVAPitch" ? e-0.04 : e+0.04
|
1863 |
+
})
|
1864 |
+
window.sequenceEditor.energyGrabbers.forEach((slider, l) => {
|
1865 |
+
slider.setValueFromValue(window.sequenceEditor.energyNew[l])
|
1866 |
+
})
|
1867 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1868 |
+
letterEnergyNumb.value = parseInt(window.sequenceEditor.energyNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
1869 |
+
}
|
1870 |
+
|
1871 |
+
} else if (seq_edit_view_select.value.startsWith("style_")) {
|
1872 |
+
|
1873 |
+
let [data, grabbers] = getSelectedStyleDataAndGrabbers()
|
1874 |
+
data = data.map((e,ei) => {
|
1875 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1876 |
+
return e
|
1877 |
+
}
|
1878 |
+
return e-0.04
|
1879 |
+
})
|
1880 |
+
grabbers.forEach((slider, l) => {
|
1881 |
+
slider.setValueFromValue(data[l])
|
1882 |
+
})
|
1883 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1884 |
+
letterStyleNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1885 |
+
}
|
1886 |
+
setNewDataToSelectedStyle(data)
|
1887 |
+
|
1888 |
+
|
1889 |
+
} else if (seq_edit_edit_select.value=="emotion") {
|
1890 |
+
let [data, grabbers] = getSelectedEmotionDataAndGrabbers()
|
1891 |
+
data = data.map((e,ei) => {
|
1892 |
+
if (window.sequenceEditor.letterFocus.length>1 && window.sequenceEditor.letterFocus.indexOf(ei)==-1) {
|
1893 |
+
return e
|
1894 |
+
}
|
1895 |
+
return e-0.04
|
1896 |
+
})
|
1897 |
+
grabbers.forEach((slider, l) => {
|
1898 |
+
slider.setValueFromValue(data[l])
|
1899 |
+
})
|
1900 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
1901 |
+
letterEmotionNumb.value = parseInt(data[window.sequenceEditor.letterFocus[0]]*100)/100
|
1902 |
+
}
|
1903 |
+
setNewDataToSelectedEmotion(data)
|
1904 |
+
}
|
1905 |
+
kickOffAutoInferTimer()
|
1906 |
+
})
|
1907 |
+
|
1908 |
+
pace_slid.addEventListener("input", () => {
|
1909 |
+
paceNumbInput.value = pace_slid.value
|
1910 |
+
})
|
1911 |
+
|
1912 |
+
pace_slid.addEventListener("change", () => {
|
1913 |
+
editorTooltip.style.display = "none"
|
1914 |
+
if (autoplay_ckbx.checked) {
|
1915 |
+
generateVoiceButton.click()
|
1916 |
+
}
|
1917 |
+
paceNumbInput.value = pace_slid.value
|
1918 |
+
window.sequenceEditor.pacing = parseFloat(pace_slid.value)
|
1919 |
+
window.sequenceEditor.init()
|
1920 |
+
})
|
1921 |
+
|
1922 |
+
pace_slid.addEventListener("input", () => {
|
1923 |
+
window.sequenceEditor.pacing = parseFloat(pace_slid.value)
|
1924 |
+
window.sequenceEditor.sliderBoxes.forEach((box, i) => {
|
1925 |
+
box.setValueFromValue(window.sequenceEditor.dursNew[i])
|
1926 |
+
})
|
1927 |
+
})
|
1928 |
+
paceNumbInput.addEventListener("change", () => {
|
1929 |
+
pace_slid.value = paceNumbInput.value
|
1930 |
+
if (autoplay_ckbx.checked) {
|
1931 |
+
generateVoiceButton.click()
|
1932 |
+
}
|
1933 |
+
window.sequenceEditor.sliderBoxes.forEach((box, i) => {box.setValueFromValue(window.sequenceEditor.dursNew[i])})
|
1934 |
+
window.sequenceEditor.pacing = parseFloat(pace_slid.value)
|
1935 |
+
window.sequenceEditor.init()
|
1936 |
+
})
|
1937 |
+
paceNumbInput.addEventListener("keyup", () => {
|
1938 |
+
pace_slid.value = paceNumbInput.value
|
1939 |
+
window.sequenceEditor.sliderBoxes.forEach((box, i) => {box.setValueFromValue(window.sequenceEditor.dursNew[i])})
|
1940 |
+
window.sequenceEditor.pacing = parseFloat(pace_slid.value)
|
1941 |
+
window.sequenceEditor.init()
|
1942 |
+
})
|
1943 |
+
autoplay_ckbx.addEventListener("change", () => {
|
1944 |
+
window.userSettings.autoplay = autoplay_ckbx.checked
|
1945 |
+
saveUserSettings()
|
1946 |
+
})
|
1947 |
+
|
1948 |
+
|
1949 |
+
// Populate the languages dropdown
|
1950 |
+
window.supportedLanguages = {
|
1951 |
+
// "am": "Amharic",
|
1952 |
+
"ar": "Arabic",
|
1953 |
+
"da": "Danish",
|
1954 |
+
"de": "German",
|
1955 |
+
"el": "Greek",
|
1956 |
+
"en": "English",
|
1957 |
+
"es": "Spanish",
|
1958 |
+
"fi": "Finnish",
|
1959 |
+
"fr": "French",
|
1960 |
+
"ha": "Hausa",
|
1961 |
+
"hi": "Hindi",
|
1962 |
+
"hu": "Hungarian",
|
1963 |
+
"it": "Italian",
|
1964 |
+
"jp": "Japanese",
|
1965 |
+
"ko": "Korean",
|
1966 |
+
"la": "Latin",
|
1967 |
+
"nl": "Dutch",
|
1968 |
+
"pl": "Polish",
|
1969 |
+
"pt": "Portuguese",
|
1970 |
+
"ro": "Romanian",
|
1971 |
+
"ru": "Russian",
|
1972 |
+
"sv": "Swedish",
|
1973 |
+
"sw": "Swahili",
|
1974 |
+
// "th": "Thai",
|
1975 |
+
"tr": "Turkish",
|
1976 |
+
"uk": "Ukrainian",
|
1977 |
+
"vi": "Vietnamese",
|
1978 |
+
"wo": "Wolof",
|
1979 |
+
"yo": "Yoruba",
|
1980 |
+
"zh": "Chinese"
|
1981 |
+
}
|
1982 |
+
window.populateLanguagesDropdownsFromModel = (dropdown, modelJson=undefined) => {
|
1983 |
+
dropdown.innerHTML = ""
|
1984 |
+
|
1985 |
+
Object.keys(window.supportedLanguages).sort((a,b)=>window.supportedLanguages[a]<window.supportedLanguages[b]?-1:1).forEach(key => {
|
1986 |
+
if (!modelJson || !modelJson.lang_capabilities || modelJson.lang_capabilities.includes(key)) {
|
1987 |
+
const opt = createElem("option", window.supportedLanguages[key])
|
1988 |
+
opt.value = key
|
1989 |
+
dropdown.appendChild(opt)
|
1990 |
+
}
|
1991 |
+
})
|
1992 |
+
}
|
1993 |
+
window.populateLanguagesDropdownsFromModel(base_lang_select)
|
1994 |
+
window.populateLanguagesDropdownsFromModel(voiceWorkbenchLanguageDropdown)
|
1995 |
+
base_lang_select.value = "en"
|
1996 |
+
voiceWorkbenchLanguageDropdown.value = "en"
|
1997 |
+
|
1998 |
+
|
1999 |
+
|
2000 |
+
// For copying the generated ARPAbet sequence to the clipboard
|
2001 |
+
editorContainer.addEventListener("contextmenu", event => {
|
2002 |
+
event.preventDefault()
|
2003 |
+
ipcRenderer.send('show-context-menu-editor')
|
2004 |
+
})
|
2005 |
+
ipcRenderer.on('context-menu-command', (e, command) => {
|
2006 |
+
|
2007 |
+
if (command=="context-copy-editor") {
|
2008 |
+
if (window.sequenceEditor && window.sequenceEditor.sequence && window.sequenceEditor.sequence.length && window.currentModel && window.currentModel.modelType=="xVAPitch") {
|
2009 |
+
|
2010 |
+
let seqARPAbet = window.sequenceEditor.sequence
|
2011 |
+
if (seqARPAbet[0]=="_") {
|
2012 |
+
seqARPAbet = seqARPAbet.slice(1, seqARPAbet.length)
|
2013 |
+
}
|
2014 |
+
if (seqARPAbet[seqARPAbet.length-1]=="_") {
|
2015 |
+
seqARPAbet = seqARPAbet.slice(0, seqARPAbet.length-1)
|
2016 |
+
}
|
2017 |
+
|
2018 |
+
seqARPAbet = seqARPAbet.filter(val => val!="<PAD>")
|
2019 |
+
seqARPAbet = seqARPAbet.map(v => {
|
2020 |
+
if (v=="_") {
|
2021 |
+
return "} {"
|
2022 |
+
}
|
2023 |
+
return v
|
2024 |
+
})
|
2025 |
+
|
2026 |
+
clipboard.writeText("{"+seqARPAbet.join(" ")+"}")
|
2027 |
+
}
|
2028 |
+
}
|
2029 |
+
})
|
2030 |
+
|
2031 |
+
|
2032 |
+
// Audio player
|
2033 |
+
window.initWaveSurfer = (src) => {
|
2034 |
+
if (window.wavesurfer) {
|
2035 |
+
window.wavesurfer.stop()
|
2036 |
+
wavesurferContainer.innerHTML = ""
|
2037 |
+
} else {
|
2038 |
+
window.wavesurfer = WaveSurfer.create({
|
2039 |
+
container: '#wavesurferContainer',
|
2040 |
+
backend: 'MediaElement',
|
2041 |
+
waveColor: `#${window.currentGame.themeColourPrimary}`,
|
2042 |
+
height: 100,
|
2043 |
+
progressColor: 'white',
|
2044 |
+
responsive: true,
|
2045 |
+
})
|
2046 |
+
}
|
2047 |
+
try {
|
2048 |
+
window.wavesurfer.setSinkId(window.userSettings.base_speaker)
|
2049 |
+
} catch (e) {
|
2050 |
+
console.log("Can't set sinkId")
|
2051 |
+
}
|
2052 |
+
if (src) {
|
2053 |
+
window.wavesurfer.load(src)
|
2054 |
+
}
|
2055 |
+
window.wavesurfer.on("finish", () => {
|
2056 |
+
samplePlayPause.innerHTML = window.i18n.PLAY
|
2057 |
+
})
|
2058 |
+
window.wavesurfer.on("seek", event => {
|
2059 |
+
if (event!=0) {
|
2060 |
+
window.wavesurfer.play()
|
2061 |
+
samplePlayPause.innerHTML = window.i18n.PAUSE
|
2062 |
+
}
|
2063 |
+
})
|
2064 |
+
}
|
2065 |
+
window.samplePlayPauseHandler = event => {
|
2066 |
+
if (window.wavesurfer) {
|
2067 |
+
if (event.ctrlKey) {
|
2068 |
+
if (window.wavesurfer.sink_id!=window.userSettings.alt_speaker) {
|
2069 |
+
window.wavesurfer.setSinkId(window.userSettings.alt_speaker)
|
2070 |
+
}
|
2071 |
+
} else {
|
2072 |
+
if (window.wavesurfer.sink_id!=window.userSettings.base_speaker) {
|
2073 |
+
window.wavesurfer.setSinkId(window.userSettings.base_speaker)
|
2074 |
+
}
|
2075 |
+
}
|
2076 |
+
|
2077 |
+
if (window.wavesurfer.isPlaying()) {
|
2078 |
+
samplePlayPause.innerHTML = window.i18n.PLAY
|
2079 |
+
window.wavesurfer.playPause()
|
2080 |
+
} else {
|
2081 |
+
samplePlayPause.innerHTML = window.i18n.PAUSE
|
2082 |
+
window.wavesurfer.playPause()
|
2083 |
+
}
|
2084 |
+
}
|
2085 |
+
}
|
2086 |
+
samplePlayPause.addEventListener("click", window.samplePlayPauseHandler)
|
2087 |
+
|
2088 |
+
|
2089 |
+
exports.Editor = Editor
|
javascript/embeddings.js
ADDED
@@ -0,0 +1,795 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
|
4 |
+
window.openEmbeddingsWindow = () => {
|
5 |
+
closeModal(undefined, embeddingsContainer).then(() => {
|
6 |
+
embeddingsContainer.style.opacity = 0
|
7 |
+
embeddingsContainer.style.display = "flex"
|
8 |
+
requestAnimationFrame(() => requestAnimationFrame(() => embeddingsContainer.style.opacity = 1))
|
9 |
+
requestAnimationFrame(() => requestAnimationFrame(() => chromeBar.style.opacity = 1))
|
10 |
+
})
|
11 |
+
}
|
12 |
+
|
13 |
+
window.embeddingsState = {
|
14 |
+
data: {},
|
15 |
+
allData: {},
|
16 |
+
clickedObject: undefined,
|
17 |
+
spritesOn: true,
|
18 |
+
gendersOn: false,
|
19 |
+
voiceCheckboxes: [],
|
20 |
+
isReady: false,
|
21 |
+
isOpen: false,
|
22 |
+
sceneData: {},
|
23 |
+
mouseIsDown: false,
|
24 |
+
rightMouseIsDown: false,
|
25 |
+
mousePos: {x: 0, y: 0}
|
26 |
+
}
|
27 |
+
|
28 |
+
|
29 |
+
|
30 |
+
function componentToHex(c) {
|
31 |
+
c = Math.min(255, c)
|
32 |
+
var hex = c.toString(16);
|
33 |
+
return hex.length == 1 ? "0" + hex : hex;
|
34 |
+
}
|
35 |
+
|
36 |
+
function rgbToHex(r, g, b) {
|
37 |
+
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
|
38 |
+
}
|
39 |
+
function hexToRgb(hex, normalize) {
|
40 |
+
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
41 |
+
const colour = result ? {
|
42 |
+
r: parseInt(result[1], 16),
|
43 |
+
g: parseInt(result[2], 16),
|
44 |
+
b: parseInt(result[3], 16)
|
45 |
+
} : null;
|
46 |
+
if (normalize && colour) {
|
47 |
+
colour.r /= 255
|
48 |
+
colour.g /= 255
|
49 |
+
colour.b /= 255
|
50 |
+
}
|
51 |
+
return colour
|
52 |
+
}
|
53 |
+
|
54 |
+
|
55 |
+
window.populateGamesList = () => {
|
56 |
+
embeddingsGamesListContainer.innerHTML = ""
|
57 |
+
Object.keys(window.gameAssets).sort((a,b)=>a<b?-1:1).forEach(gameId => {
|
58 |
+
const gameSelectContainer = createElem("div")
|
59 |
+
const gameCheckbox = createElem(`input#embs_${gameId}`, {type: "checkbox"})
|
60 |
+
gameCheckbox.checked = true
|
61 |
+
const gameButton = createElem("button.fixedColour")
|
62 |
+
gameButton.style.setProperty("background-color", `#${window.embeddingsState.gameColours[gameId]}`, "important")
|
63 |
+
gameButton.style.display = "flex"
|
64 |
+
gameButton.style.alignItems = "center"
|
65 |
+
gameButton.style.margin = "auto"
|
66 |
+
gameButton.style.marginTop = "8px"
|
67 |
+
const buttonLabel = createElem("span", window.embeddingsState.gameTitles[gameId])
|
68 |
+
|
69 |
+
gameButton.addEventListener("click", e => {
|
70 |
+
if (e.target==gameButton || e.target==buttonLabel) {
|
71 |
+
gameCheckbox.click()
|
72 |
+
window.populateVoicesList()
|
73 |
+
window.computeEmbsAndDimReduction()
|
74 |
+
}
|
75 |
+
})
|
76 |
+
gameButton.addEventListener("contextmenu", e => {
|
77 |
+
if (e.target==gameButton || e.target==buttonLabel) {
|
78 |
+
Array.from(embeddingsGamesListContainer.querySelectorAll("input")).forEach(ckbx => ckbx.checked = false)
|
79 |
+
gameCheckbox.click()
|
80 |
+
window.populateVoicesList()
|
81 |
+
window.computeEmbsAndDimReduction()
|
82 |
+
}
|
83 |
+
})
|
84 |
+
gameCheckbox.addEventListener("click", () => {
|
85 |
+
window.populateVoicesList()
|
86 |
+
window.computeEmbsAndDimReduction()
|
87 |
+
})
|
88 |
+
|
89 |
+
|
90 |
+
gameButton.appendChild(gameCheckbox)
|
91 |
+
gameButton.appendChild(buttonLabel)
|
92 |
+
gameSelectContainer.appendChild(gameButton)
|
93 |
+
embeddingsGamesListContainer.appendChild(gameSelectContainer)
|
94 |
+
})
|
95 |
+
}
|
96 |
+
|
97 |
+
|
98 |
+
|
99 |
+
window.populateVoicesList = () => {
|
100 |
+
const enabledGames = Array.from(embeddingsGamesListContainer.querySelectorAll("input"))
|
101 |
+
.map(elem => [elem.checked, elem.id.replace("embs_", "")])
|
102 |
+
.filter(checkedId => checkedId[0])
|
103 |
+
.map(checkedId => checkedId[1].replace("embs_", ""))
|
104 |
+
|
105 |
+
const checkboxes = []
|
106 |
+
|
107 |
+
embeddingsRecordsContainer.innerHTML = ""
|
108 |
+
Object.keys(window.games).forEach(gameId => {
|
109 |
+
if (!enabledGames.includes(gameId)) {
|
110 |
+
return
|
111 |
+
}
|
112 |
+
|
113 |
+
window.games[gameId].models.forEach(model => {
|
114 |
+
|
115 |
+
model.variants.forEach(variant => {
|
116 |
+
const voiceRowElem = createElem("div")
|
117 |
+
|
118 |
+
const voiceCkbx = createElem(`input#embsVoice_${variant.voiceId}`, {type: "checkbox"})
|
119 |
+
voiceCkbx.checked = true
|
120 |
+
voiceCkbx.addEventListener("click", () => {
|
121 |
+
window.computeEmbsAndDimReduction()
|
122 |
+
})
|
123 |
+
checkboxes.push(voiceCkbx)
|
124 |
+
const showVoiceBtn = createElem("button.smallButton.fixedColour", "Show")
|
125 |
+
showVoiceBtn.style.background = `#${window.embeddingsState.gameColours[model.gameId]}`
|
126 |
+
showVoiceBtn.addEventListener("click", () => {
|
127 |
+
if (!voiceCkbx.checked) {
|
128 |
+
return window.errorModal(window.i18n.VEMB_VOICE_NOT_ENABLED)
|
129 |
+
}
|
130 |
+
const point = window.embeddingsState.sceneData.points.find(point => point.data.voiceId==variant.voiceId)
|
131 |
+
window.embeddingsState.sceneData.controls.target.set(point.position.x, point.position.y, point.position.z)
|
132 |
+
|
133 |
+
const cameraPos = window.embeddingsState.sceneData.camera.position
|
134 |
+
const deltaX = (point.position.x - cameraPos.x)
|
135 |
+
const deltaY = (point.position.y - cameraPos.y)
|
136 |
+
const deltaZ = (point.position.z - cameraPos.z)
|
137 |
+
|
138 |
+
window.embeddingsState.sceneData.camera.position.set(cameraPos.x+deltaX/2, cameraPos.y+deltaY/2, cameraPos.z+deltaZ/2)
|
139 |
+
|
140 |
+
})
|
141 |
+
|
142 |
+
const nameElem = createElem("div", model.voiceName+(model.variants.length>1?` (${variant.variantName})`:""))
|
143 |
+
nameElem.title = model.voiceName
|
144 |
+
const gameElem = createElem("div", window.embeddingsState.gameTitles[model.gameId])
|
145 |
+
gameElem.title = window.embeddingsState.gameTitles[model.gameId]
|
146 |
+
const voiceGender = createElem("div", variant.gender)
|
147 |
+
voiceGender.title = variant.gender
|
148 |
+
|
149 |
+
voiceRowElem.appendChild(createElem("div", voiceCkbx))
|
150 |
+
voiceRowElem.appendChild(createElem("div", showVoiceBtn))
|
151 |
+
voiceRowElem.appendChild(nameElem)
|
152 |
+
voiceRowElem.appendChild(gameElem)
|
153 |
+
voiceRowElem.appendChild(voiceGender)
|
154 |
+
|
155 |
+
if (embeddingsSearchBar.value.length && !model.voiceName.toLowerCase().trim().includes(embeddingsSearchBar.value.toLowerCase().trim())) {
|
156 |
+
return
|
157 |
+
}
|
158 |
+
embeddingsRecordsContainer.appendChild(voiceRowElem)
|
159 |
+
})
|
160 |
+
})
|
161 |
+
})
|
162 |
+
|
163 |
+
window.embeddingsState.voiceCheckboxes = checkboxes
|
164 |
+
|
165 |
+
}
|
166 |
+
embeddingsSearchBar.addEventListener("keyup", () => window.populateVoicesList())
|
167 |
+
|
168 |
+
|
169 |
+
|
170 |
+
window.initDataMappings = () => {
|
171 |
+
|
172 |
+
window.embeddingsState.voiceIdToModel = {}
|
173 |
+
window.embeddingsState.gameShortIdToGameId = {}
|
174 |
+
window.embeddingsState.gameColours = {}
|
175 |
+
window.embeddingsState.gameTitles = {}
|
176 |
+
|
177 |
+
|
178 |
+
const idToGame = {}
|
179 |
+
Object.keys(window.gameAssets).forEach(gameId => {
|
180 |
+
const id = window.gameAssets[gameId].gameCode
|
181 |
+
idToGame[id] = gameId
|
182 |
+
})
|
183 |
+
|
184 |
+
Object.keys(window.gameAssets).forEach(gameId => {
|
185 |
+
// Short game ID to full game ID
|
186 |
+
const gameShortId = window.gameAssets[gameId].gameCode.toLowerCase()
|
187 |
+
window.embeddingsState.gameShortIdToGameId[gameShortId] = gameId.toLowerCase()
|
188 |
+
|
189 |
+
// Game title
|
190 |
+
const title = window.gameAssets[gameId].gameName
|
191 |
+
window.embeddingsState.gameTitles[gameId] = title
|
192 |
+
|
193 |
+
// Game colour
|
194 |
+
let colour = window.gameAssets[gameId].themeColourPrimary
|
195 |
+
colour = colour.length==3 ? `${colour[0]}${colour[0]}${colour[1]}${colour[1]}${colour[2]}${colour[2]}` : colour
|
196 |
+
window.embeddingsState.gameColours[gameId] = colour
|
197 |
+
})
|
198 |
+
|
199 |
+
Object.keys(window.games).forEach(gameId => {
|
200 |
+
// Voice Id to model data
|
201 |
+
window.games[gameId].models.forEach(model => {
|
202 |
+
model.variants.forEach(variant => {
|
203 |
+
window.embeddingsState.voiceIdToModel[variant.voiceId] = JSON.parse(JSON.stringify(model))
|
204 |
+
if (model.variants.length>1) {
|
205 |
+
let voiceName = window.embeddingsState.voiceIdToModel[variant.voiceId].voiceName
|
206 |
+
voiceName = `${voiceName} (${variant.variantName})`
|
207 |
+
window.embeddingsState.voiceIdToModel[variant.voiceId].voiceName = voiceName
|
208 |
+
}
|
209 |
+
})
|
210 |
+
})
|
211 |
+
})
|
212 |
+
}
|
213 |
+
|
214 |
+
|
215 |
+
|
216 |
+
|
217 |
+
window.initEmbeddingsScene = () => {
|
218 |
+
window.initDataMappings()
|
219 |
+
window.populateGamesList()
|
220 |
+
window.populateVoicesList()
|
221 |
+
|
222 |
+
embeddingsSceneContainer.addEventListener("mousedown", (event) => {
|
223 |
+
window.embeddingsState.mousePos.x = parseInt(event.layerX)
|
224 |
+
window.embeddingsState.mousePos.y = parseInt(event.layerY)
|
225 |
+
})
|
226 |
+
embeddingsSceneContainer.addEventListener("mouseup", (event) => {
|
227 |
+
const mouseX = parseInt(event.layerX)
|
228 |
+
const mouseY = parseInt(event.layerY)
|
229 |
+
|
230 |
+
if (event.button==0) {
|
231 |
+
if (Math.abs(mouseX-window.embeddingsState.mousePos.x)<10 && Math.abs(mouseY-window.embeddingsState.mousePos.y)<10) {
|
232 |
+
window.embeddingsState.mouseIsDown = true
|
233 |
+
setTimeout(() => {window.embeddingsState.mouseIsDown = false}, 100)
|
234 |
+
}
|
235 |
+
} else if (event.button==2) {
|
236 |
+
if (Math.abs(mouseX-window.embeddingsState.mousePos.x)<10 && Math.abs(mouseY-window.embeddingsState.mousePos.y)<10) {
|
237 |
+
window.embeddingsState.rightMouseIsDown = true
|
238 |
+
setTimeout(() => {window.embeddingsState.rightMouseIsDown = false}, 100)
|
239 |
+
}
|
240 |
+
}
|
241 |
+
})
|
242 |
+
window.embeddingsState.isReady = false
|
243 |
+
|
244 |
+
const SPHERE_RADIUS = 3
|
245 |
+
const SPHERE_V_COUNT = 50
|
246 |
+
|
247 |
+
// Renderer
|
248 |
+
window.embeddingsState.renderer = new THREE.WebGLRenderer({alpha: true, antialias: true})
|
249 |
+
window.embeddingsState.renderer.setPixelRatio( window.devicePixelRatio )
|
250 |
+
window.embeddingsState.renderer.setSize(embeddingsSceneContainer.offsetWidth, embeddingsSceneContainer.offsetHeight)
|
251 |
+
embeddingsSceneContainer.appendChild(window.embeddingsState.renderer.domElement)
|
252 |
+
|
253 |
+
// Scene and camera
|
254 |
+
const scene = new THREE.Scene()
|
255 |
+
window.embeddingsState.sceneData.camera = new THREE.PerspectiveCamera(60, embeddingsSceneContainer.offsetWidth/embeddingsSceneContainer.offsetHeight, 0.001, 100000)
|
256 |
+
window.embeddingsState.sceneData.camera.position.set( -100, 0, 0 )
|
257 |
+
|
258 |
+
// Controls
|
259 |
+
window.embeddingsState.sceneData.controls = new THREE.OrbitControls(window.embeddingsState.sceneData.camera, window.embeddingsState.renderer.domElement)
|
260 |
+
window.embeddingsState.sceneData.controls2 = new THREE.TrackballControls(window.embeddingsState.sceneData.camera, window.embeddingsState.renderer.domElement)
|
261 |
+
window.embeddingsState.sceneData.controls.target.set(window.embeddingsState.sceneData.camera.position.x+0.15, window.embeddingsState.sceneData.camera.position.y, window.embeddingsState.sceneData.camera.position.z)
|
262 |
+
|
263 |
+
|
264 |
+
window.embeddingsState.sceneData.controls.enableDamping = true
|
265 |
+
window.embeddingsState.sceneData.controls.dampingFactor = 0.025
|
266 |
+
window.embeddingsState.sceneData.controls.screenSpacePanning = true
|
267 |
+
window.embeddingsState.sceneData.controls.rotateSpeed = 1/6
|
268 |
+
window.embeddingsState.sceneData.controls.panSpeed = 1
|
269 |
+
window.embeddingsState.sceneData.controls.minDistance = 50
|
270 |
+
window.embeddingsState.sceneData.controls.maxDistance = 500
|
271 |
+
|
272 |
+
window.embeddingsState.sceneData.controls2.noRotate = true
|
273 |
+
window.embeddingsState.sceneData.controls2.noPan = true
|
274 |
+
window.embeddingsState.sceneData.controls2.noZoom = true
|
275 |
+
window.embeddingsState.sceneData.controls2.zoomSpeed = 1/2// 1.5
|
276 |
+
window.embeddingsState.sceneData.controls2.dynamicDampingFactor = 0.2
|
277 |
+
|
278 |
+
|
279 |
+
|
280 |
+
const light = new THREE.DirectionalLight( 0xffffff, 0.5 )
|
281 |
+
light.position.set( -1, 1, 1 ).normalize()
|
282 |
+
scene.add(light)
|
283 |
+
scene.add(new THREE.AmbientLight( 0xffffff, 0.5 ))
|
284 |
+
|
285 |
+
// Mouse event ray caster
|
286 |
+
const raycaster = new THREE.Raycaster()
|
287 |
+
const mouse = new THREE.Vector2()
|
288 |
+
window.embeddingsState.renderer.domElement.addEventListener("mousemove", event => {
|
289 |
+
const sizeY = event.target.height
|
290 |
+
const sizeX = event.target.width
|
291 |
+
mouse.x = event.offsetX / sizeX * 2 - 1
|
292 |
+
mouse.y = -event.offsetY / sizeY * 2 + 1
|
293 |
+
}, false)
|
294 |
+
|
295 |
+
|
296 |
+
window.embeddingsState.sceneData.sprites = []
|
297 |
+
window.embeddingsState.sceneData.points = []
|
298 |
+
|
299 |
+
window.refreshEmbeddingsScenePoints = () => {
|
300 |
+
|
301 |
+
const enabledGames = Array.from(embeddingsGamesListContainer.querySelectorAll("input"))
|
302 |
+
.map(elem => [elem.checked, elem.id.replace("embs_", "")])
|
303 |
+
.filter(checkedId => checkedId[0])
|
304 |
+
.map(checkedId => checkedId[1].replace("embs_", ""))
|
305 |
+
|
306 |
+
|
307 |
+
const data = window.embeddingsState.data
|
308 |
+
|
309 |
+
const newDataNames = Object.keys(data)
|
310 |
+
const oldDataKept = []
|
311 |
+
|
312 |
+
const newSprites = []
|
313 |
+
const newPoints = []
|
314 |
+
|
315 |
+
// Remove any existing data
|
316 |
+
;[window.embeddingsState.sceneData.points, window.embeddingsState.sceneData.sprites].forEach(dataList => {
|
317 |
+
dataList.forEach(object => {
|
318 |
+
const objectName = object.data.voiceId
|
319 |
+
if (newDataNames.includes(objectName)) {
|
320 |
+
oldDataKept.push(objectName)
|
321 |
+
const coords = {
|
322 |
+
x: parseFloat(data[objectName][0]),
|
323 |
+
y: parseFloat(data[objectName][1])-(object.data.type=="text"?SPHERE_RADIUS*1.5:0),
|
324 |
+
z: parseFloat(data[objectName][2])
|
325 |
+
}
|
326 |
+
object.data.isMoving = true
|
327 |
+
object.data.newPos = coords
|
328 |
+
if (object.data.type=="text") {
|
329 |
+
newSprites.push(object)
|
330 |
+
} else {
|
331 |
+
newPoints.push(object)
|
332 |
+
}
|
333 |
+
} else {
|
334 |
+
scene.remove(object)
|
335 |
+
}
|
336 |
+
})
|
337 |
+
})
|
338 |
+
|
339 |
+
// Add the new data
|
340 |
+
window.embeddingsState.sceneData.sprites = newSprites
|
341 |
+
window.embeddingsState.sceneData.points = newPoints
|
342 |
+
Object.keys(data).forEach(voiceId => {
|
343 |
+
|
344 |
+
if (oldDataKept.includes(voiceId)) {
|
345 |
+
return
|
346 |
+
}
|
347 |
+
const game = Object.keys(window.embeddingsState.gameShortIdToGameId).includes(voiceId.split("_")[0]) ? window.embeddingsState.gameShortIdToGameId[voiceId.split("_")[0]] : "other"
|
348 |
+
let gender
|
349 |
+
if (Object.keys(window.embeddingsState.voiceIdToModel).includes(voiceId)) {
|
350 |
+
gender = window.embeddingsState.voiceIdToModel[voiceId].gender || window.embeddingsState.voiceIdToModel[voiceId].variants[0].gender
|
351 |
+
} else {
|
352 |
+
gender = window.embeddingsState.allData[voiceId].voiceGender
|
353 |
+
}
|
354 |
+
gender = gender ? gender.toLowerCase() : "other"
|
355 |
+
|
356 |
+
// if (!enabledGames.includes(game)) {
|
357 |
+
// return
|
358 |
+
// }
|
359 |
+
|
360 |
+
// Filter out data by gender
|
361 |
+
// if (gender=="male" && !embeddingsMalesCkbx.checked) {
|
362 |
+
// return
|
363 |
+
// }
|
364 |
+
// if (gender=="female" && !embeddingsFemalesCkbx.checked) {
|
365 |
+
// return
|
366 |
+
// }
|
367 |
+
// if (gender=="other" && !embeddingsOtherGendersCkbx.checked) {
|
368 |
+
// return
|
369 |
+
// }
|
370 |
+
|
371 |
+
|
372 |
+
// Colour dict
|
373 |
+
const colour = hexToRgb("#"+window.embeddingsState.gameColours[game])
|
374 |
+
const genderColours = {
|
375 |
+
"f": {r: 200, g: 0, b: 0},
|
376 |
+
"m": {r: 0, g: 0, b: 200},
|
377 |
+
"o": {r: 85, g: 85, b: 85},
|
378 |
+
}
|
379 |
+
const coords = {
|
380 |
+
x: parseFloat(data[voiceId][0]),
|
381 |
+
y: parseFloat(data[voiceId][1]),
|
382 |
+
z: parseFloat(data[voiceId][2])
|
383 |
+
}
|
384 |
+
|
385 |
+
const genderColour = gender=="female" ? genderColours["f"] : (gender=="male" ? genderColours["m"] : genderColours["o"])
|
386 |
+
|
387 |
+
|
388 |
+
const pointGeometry = new THREE.SphereGeometry(SPHERE_RADIUS, SPHERE_V_COUNT, SPHERE_V_COUNT)
|
389 |
+
const pointMaterial = new THREE.MeshLambertMaterial({
|
390 |
+
color: window.embeddingsState.gendersOn ? rgbToHex(genderColour.r, genderColour.g, genderColour.b) : "#"+window.embeddingsState.gameColours[game],
|
391 |
+
transparent: true
|
392 |
+
})
|
393 |
+
pointMaterial.emissive.emissiveIntensity = 1
|
394 |
+
|
395 |
+
|
396 |
+
// Point sphere
|
397 |
+
const point = new THREE.Mesh(pointGeometry, pointMaterial)
|
398 |
+
point.position.x = coords.x
|
399 |
+
point.position.y = coords.y
|
400 |
+
point.position.z = coords.z
|
401 |
+
point.name = `point|${voiceId}`
|
402 |
+
point.data = {
|
403 |
+
type: "point",
|
404 |
+
voiceId: voiceId,
|
405 |
+
game: game,
|
406 |
+
gameColour: {r: colour.r, g: colour.g, b: colour.b},
|
407 |
+
genderColour: genderColour
|
408 |
+
}
|
409 |
+
window.embeddingsState.sceneData.points.push(point)
|
410 |
+
scene.add(point)
|
411 |
+
|
412 |
+
let voiceName
|
413 |
+
if (Object.keys(window.embeddingsState.voiceIdToModel).includes(voiceId)) {
|
414 |
+
voiceName = window.embeddingsState.voiceIdToModel[voiceId].voiceName
|
415 |
+
} else {
|
416 |
+
voiceName = window.embeddingsState.allData[voiceId].voiceName
|
417 |
+
}
|
418 |
+
|
419 |
+
// Text sprite
|
420 |
+
const sprite = new THREE.TextSprite({
|
421 |
+
text: voiceName,
|
422 |
+
fontFamily: 'Helvetica, sans-serif',
|
423 |
+
fontSize: 2,
|
424 |
+
strokeColor: '#ffffff',
|
425 |
+
strokeWidth: 0,
|
426 |
+
color: '#24ff00',
|
427 |
+
material: {color: "white"}
|
428 |
+
})
|
429 |
+
sprite.position.x = coords.x
|
430 |
+
sprite.position.y = coords.y-SPHERE_RADIUS*1.5
|
431 |
+
sprite.position.z = coords.z
|
432 |
+
sprite.name = `sprite|${voiceId}`
|
433 |
+
sprite.data = {type: "text", voiceId: voiceId, game: game}
|
434 |
+
window.embeddingsState.sceneData.sprites.push(sprite)
|
435 |
+
scene.add(sprite)
|
436 |
+
})
|
437 |
+
}
|
438 |
+
window.refreshEmbeddingsScenePoints()
|
439 |
+
|
440 |
+
let hoveredObject = undefined
|
441 |
+
let clickedObject = undefined
|
442 |
+
|
443 |
+
window.embeddings_render = () => {
|
444 |
+
if (!window.embeddingsState.isReady) {
|
445 |
+
return
|
446 |
+
}
|
447 |
+
requestAnimationFrame(window.embeddings_render)
|
448 |
+
|
449 |
+
const target = window.embeddingsState.sceneData.controls.target
|
450 |
+
window.embeddingsState.sceneData.controls.update()
|
451 |
+
window.embeddingsState.sceneData.controls2.target.set(target.x, target.y, target.z)
|
452 |
+
window.embeddingsState.sceneData.controls2.update()
|
453 |
+
|
454 |
+
window.embeddingsState.sceneData.camera.updateMatrixWorld()
|
455 |
+
|
456 |
+
raycaster.setFromCamera( mouse, window.embeddingsState.sceneData.camera );
|
457 |
+
|
458 |
+
// Move objects
|
459 |
+
[window.embeddingsState.sceneData.points, window.embeddingsState.sceneData.sprites].forEach(dataList => {
|
460 |
+
dataList.forEach(object => {
|
461 |
+
if (object.data.isMoving) {
|
462 |
+
if (Math.abs(object.position.x-object.data.newPos.x)>0.005) {
|
463 |
+
object.position.x += (object.data.newPos.x - object.position.x) / 20
|
464 |
+
object.position.y += (object.data.newPos.y - object.position.y) / 20
|
465 |
+
object.position.z += (object.data.newPos.z - object.position.z) / 20
|
466 |
+
|
467 |
+
} else {
|
468 |
+
object.data.isMoving = false
|
469 |
+
object.data.newPos = undefined
|
470 |
+
}
|
471 |
+
}
|
472 |
+
})
|
473 |
+
})
|
474 |
+
|
475 |
+
|
476 |
+
// Handle mouse events
|
477 |
+
let intersects = raycaster.intersectObjects(scene.children, true)
|
478 |
+
if (intersects.length) {
|
479 |
+
|
480 |
+
if (intersects.length>2) {
|
481 |
+
intersects = [intersects.find(it => it.object.data.type=="point")]
|
482 |
+
}
|
483 |
+
if (intersects.length==0 || intersects[0]==undefined || intersects[0].object==undefined || intersects[0].object.data.type=="text") {
|
484 |
+
window.embeddingsState.renderer.render(scene, window.embeddingsState.sceneData.camera)
|
485 |
+
return
|
486 |
+
}
|
487 |
+
|
488 |
+
window.embeddingsState.renderer.domElement.style.cursor = "pointer"
|
489 |
+
|
490 |
+
if (hoveredObject != undefined && hoveredObject.object.data.voiceId!=intersects[0].object.data.voiceId) {
|
491 |
+
const colour = window.embeddingsState.gendersOn ? hoveredObject.object.data.genderColour : hoveredObject.object.data.gameColour
|
492 |
+
hoveredObject.object.material.color.r = Math.min(1, colour.r/255)
|
493 |
+
hoveredObject.object.material.color.g = Math.min(1, colour.g/255)
|
494 |
+
hoveredObject.object.material.color.b = Math.min(1, colour.b/255)
|
495 |
+
}
|
496 |
+
hoveredObject = intersects[0]
|
497 |
+
|
498 |
+
const colour = window.embeddingsState.gendersOn ? hoveredObject.object.data.genderColour : hoveredObject.object.data.gameColour
|
499 |
+
hoveredObject.object.material.color.r = Math.min(1, colour.r/255*1.5)
|
500 |
+
hoveredObject.object.material.color.g = Math.min(1, colour.g/255*1.5)
|
501 |
+
hoveredObject.object.material.color.b = Math.min(1, colour.b/255*1.5)
|
502 |
+
|
503 |
+
|
504 |
+
// Right click does voice audio preview
|
505 |
+
if (window.embeddingsState.rightMouseIsDown) {
|
506 |
+
window.embeddingsState.rightMouseIsDown = false
|
507 |
+
const voiceId = hoveredObject.object.data.voiceId
|
508 |
+
const gameId = hoveredObject.object.data.game
|
509 |
+
const modelsPathForGame = window.userSettings[`modelspath_${gameId}`]
|
510 |
+
const audioPreviewPath = `${modelsPathForGame}/${voiceId}.wav`
|
511 |
+
|
512 |
+
if (fs.existsSync(audioPreviewPath)) {
|
513 |
+
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", {
|
514 |
+
src: audioPreviewPath
|
515 |
+
}))
|
516 |
+
audioPreview.setSinkId(window.userSettings.base_speaker)
|
517 |
+
} else {
|
518 |
+
window.errorModal(window.i18n.VEMB_NO_PREVIEW)
|
519 |
+
}
|
520 |
+
}
|
521 |
+
|
522 |
+
// Left click does voice click
|
523 |
+
if (window.embeddingsState.mouseIsDown) {
|
524 |
+
if (window.embeddingsState.clickedObject==undefined || window.embeddingsState.clickedObject.voiceId!=hoveredObject.object.data.voiceId) {
|
525 |
+
if (window.embeddingsState.clickedObject!=undefined) {
|
526 |
+
window.embeddingsState.clickedObject.object.material.emissive.setRGB(0,0,0)
|
527 |
+
}
|
528 |
+
window.embeddingsState.clickedObject = {
|
529 |
+
voiceId: hoveredObject.object.data.voiceId,
|
530 |
+
game: hoveredObject.object.data.game,
|
531 |
+
object: hoveredObject.object
|
532 |
+
}
|
533 |
+
hoveredObject.object.material.emissive.setRGB(0, 1, 0)
|
534 |
+
|
535 |
+
const voiceId = window.embeddingsState.clickedObject.object.data.voiceId
|
536 |
+
|
537 |
+
if (Object.keys(window.embeddingsState.voiceIdToModel).includes(voiceId)) {
|
538 |
+
embeddingsVoiceGameDisplay.innerHTML = window.embeddingsState.gameTitles[window.embeddingsState.clickedObject.object.data.game]
|
539 |
+
embeddingsVoiceNameDisplay.innerHTML = window.embeddingsState.voiceIdToModel[voiceId].voiceName
|
540 |
+
embeddingsVoiceGenderDisplay.innerHTML = window.embeddingsState.voiceIdToModel[voiceId].gender || window.embeddingsState.voiceIdToModel[voiceId].variants[0].gender
|
541 |
+
} else {
|
542 |
+
embeddingsVoiceGameDisplay.innerHTML = window.embeddingsState.gameTitles[window.embeddingsState.clickedObject.object.data.game]
|
543 |
+
embeddingsVoiceNameDisplay.innerHTML = window.embeddingsState.allData[voiceId].voiceName
|
544 |
+
embeddingsVoiceGenderDisplay.innerHTML = window.embeddingsState.allData[voiceId].voiceGender
|
545 |
+
}
|
546 |
+
}
|
547 |
+
}
|
548 |
+
|
549 |
+
|
550 |
+
} else {
|
551 |
+
window.embeddingsState.renderer.domElement.style.cursor = "default"
|
552 |
+
if (hoveredObject != undefined) {
|
553 |
+
const colour = window.embeddingsState.gendersOn ? hoveredObject.object.data.genderColour : hoveredObject.object.data.gameColour
|
554 |
+
hoveredObject.object.material.color.r = Math.min(1, colour.r/255)
|
555 |
+
hoveredObject.object.material.color.g = Math.min(1, colour.g/255)
|
556 |
+
hoveredObject.object.material.color.b = Math.min(1, colour.b/255)
|
557 |
+
}
|
558 |
+
if (window.embeddingsState.mouseIsDown && window.embeddingsState.clickedObject!=undefined) {
|
559 |
+
window.embeddingsState.clickedObject.object.material.emissive.setRGB(0,0,0)
|
560 |
+
window.embeddingsState.clickedObject = undefined
|
561 |
+
|
562 |
+
embeddingsVoiceGameDisplay.innerHTML = ""
|
563 |
+
embeddingsVoiceNameDisplay.innerHTML = ""
|
564 |
+
embeddingsVoiceGenderDisplay.innerHTML = ""
|
565 |
+
}
|
566 |
+
}
|
567 |
+
|
568 |
+
window.embeddingsState.renderer.render(scene, window.embeddingsState.sceneData.camera)
|
569 |
+
}
|
570 |
+
window.embeddingsState.isReady = true
|
571 |
+
window.embeddings_render()
|
572 |
+
|
573 |
+
|
574 |
+
window.toggleSprites = () => {
|
575 |
+
window.embeddingsState.spritesOn = !window.embeddingsState.spritesOn
|
576 |
+
window.embeddingsState.sceneData.sprites.forEach(sprite => {
|
577 |
+
sprite.material.visible = window.embeddingsState.spritesOn
|
578 |
+
})
|
579 |
+
}
|
580 |
+
|
581 |
+
window.toggleGenders = () => {
|
582 |
+
window.embeddingsState.gendersOn = !window.embeddingsState.gendersOn
|
583 |
+
window.embeddingsState.sceneData.points.forEach(point => {
|
584 |
+
const colour = window.embeddingsState.gendersOn ? point.data.genderColour : point.data.gameColour
|
585 |
+
point.material.color.r = Math.min(1, colour.r/255)
|
586 |
+
point.material.color.g = Math.min(1, colour.g/255)
|
587 |
+
point.material.color.b = Math.min(1, colour.b/255)
|
588 |
+
})
|
589 |
+
}
|
590 |
+
window.addEventListener("resize", () => {
|
591 |
+
if (window.embeddingsState.isOpen) {
|
592 |
+
window.embeddings_updateSize()
|
593 |
+
}
|
594 |
+
})
|
595 |
+
}
|
596 |
+
window.embeddings_updateSize = () => {
|
597 |
+
if (window.embeddingsState.isReady) {
|
598 |
+
window.embeddingsState.sceneData.camera.aspect = embeddingsSceneContainer.offsetWidth/embeddingsSceneContainer.offsetHeight
|
599 |
+
window.embeddingsState.sceneData.camera.updateProjectionMatrix()
|
600 |
+
window.embeddingsState.renderer.setSize(embeddingsSceneContainer.offsetWidth, embeddingsSceneContainer.offsetHeight)
|
601 |
+
}
|
602 |
+
}
|
603 |
+
|
604 |
+
|
605 |
+
embeddingsPreviewButton.addEventListener("click", () => {
|
606 |
+
if (window.embeddingsState.clickedObject) {
|
607 |
+
|
608 |
+
const voiceId = window.embeddingsState.clickedObject.object.data.voiceId
|
609 |
+
const gameId = window.embeddingsState.clickedObject.object.data.game
|
610 |
+
const modelsPathForGame = window.userSettings[`modelspath_${gameId}`]
|
611 |
+
const audioPreviewPath = `${modelsPathForGame}/${voiceId}.wav`
|
612 |
+
|
613 |
+
if (fs.existsSync(audioPreviewPath)) {
|
614 |
+
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", {
|
615 |
+
src: audioPreviewPath
|
616 |
+
}))
|
617 |
+
audioPreview.setSinkId(window.userSettings.base_speaker)
|
618 |
+
} else {
|
619 |
+
window.errorModal(window.i18n.VEMB_NO_PREVIEW)
|
620 |
+
}
|
621 |
+
} else {
|
622 |
+
window.errorModal(window.i18n.VEMB_SELECT_VOICE_FIRST)
|
623 |
+
}
|
624 |
+
})
|
625 |
+
embeddingsLoadButton.addEventListener("click", () => {
|
626 |
+
if (window.embeddingsState.clickedObject) {
|
627 |
+
|
628 |
+
const voiceId = window.embeddingsState.clickedObject.object.data.voiceId
|
629 |
+
const gameId = window.embeddingsState.clickedObject.object.data.game
|
630 |
+
const modelsPathForGame = window.userSettings[`modelspath_${gameId}`]
|
631 |
+
const modelPath = `${modelsPathForGame}/${voiceId}.pt`
|
632 |
+
|
633 |
+
if (fs.existsSync(modelPath)) {
|
634 |
+
|
635 |
+
window.changeGame(window.gameAssets[gameId])
|
636 |
+
|
637 |
+
// Simulate voice loading through the UI
|
638 |
+
let voiceName
|
639 |
+
window.games[gameId].models.forEach(model => {
|
640 |
+
model.variants.forEach(variant => {
|
641 |
+
if (variant.voiceId==voiceId) {
|
642 |
+
voiceName = model.voiceName
|
643 |
+
}
|
644 |
+
})
|
645 |
+
})
|
646 |
+
const voiceButton = Array.from(voiceTypeContainer.children).find(button => button.innerHTML==voiceName)
|
647 |
+
voiceButton.click()
|
648 |
+
closeModal().then(() => {
|
649 |
+
generateVoiceButton.click()
|
650 |
+
})
|
651 |
+
|
652 |
+
} else {
|
653 |
+
window.errorModal(window.i18n.VEMB_NO_MODEL)
|
654 |
+
}
|
655 |
+
} else {
|
656 |
+
window.errorModal(window.i18n.VEMB_SELECT_VOICE_FIRST)
|
657 |
+
}
|
658 |
+
})
|
659 |
+
|
660 |
+
embeddingsKey.addEventListener("change", () => {
|
661 |
+
if (embeddingsKey.value=="embsKey_game" && window.embeddingsState.gendersOn) {
|
662 |
+
window.toggleGenders()
|
663 |
+
} else if (embeddingsKey.value=="embsKey_gender" && !window.embeddingsState.gendersOn) {
|
664 |
+
window.toggleGenders()
|
665 |
+
}
|
666 |
+
})
|
667 |
+
embeddingsMalesCkbx.addEventListener("change", () => window.computeEmbsAndDimReduction())
|
668 |
+
embeddingsFemalesCkbx.addEventListener("change", () => window.computeEmbsAndDimReduction())
|
669 |
+
embeddingsOtherGendersCkbx.addEventListener("change", () => window.computeEmbsAndDimReduction())
|
670 |
+
embeddingsOnlyInstalledCkbx.addEventListener("change", () => window.computeEmbsAndDimReduction())
|
671 |
+
embeddingsAlgorithm.addEventListener("change", () => window.computeEmbsAndDimReduction())
|
672 |
+
|
673 |
+
|
674 |
+
window.computeEmbsAndDimReduction = (includeAllVoices=false) => {
|
675 |
+
|
676 |
+
const enabledGames = Array.from(embeddingsGamesListContainer.querySelectorAll("input"))
|
677 |
+
.map(elem => [elem.checked, elem.id.replace("embs_", "")])
|
678 |
+
.filter(checkedId => checkedId[0])
|
679 |
+
.map(checkedId => checkedId[1].replace("embs_", ""))
|
680 |
+
|
681 |
+
const enabledVoices = window.embeddingsState.voiceCheckboxes
|
682 |
+
.map(elem => [elem.checked, elem.id.replace("embsVoice_", "")])
|
683 |
+
.filter(checkedId => checkedId[0])
|
684 |
+
.map(checkedId => checkedId[1].replace("embsVoice_", ""))
|
685 |
+
|
686 |
+
if (enabledVoices.length<=2) {
|
687 |
+
return window.errorModal(window.i18n.EMBEDDINGS_NEED_AT_LEAST_3)
|
688 |
+
}
|
689 |
+
|
690 |
+
|
691 |
+
// Get together a list of voiceId->.wav path mappings
|
692 |
+
const mappings = []
|
693 |
+
|
694 |
+
if (includeAllVoices) {
|
695 |
+
|
696 |
+
Object.keys(window.games).forEach(gameId => {
|
697 |
+
const modelsPathForGame = window.userSettings[`modelspath_${gameId}`]
|
698 |
+
|
699 |
+
window.games[gameId].models.forEach(model => {
|
700 |
+
model.variants.forEach(variant => {
|
701 |
+
const audioPreviewPath = `${modelsPathForGame}/${variant.voiceId}.wav`
|
702 |
+
if (fs.existsSync(audioPreviewPath)) {
|
703 |
+
mappings.push(`${variant.voiceId}=${audioPreviewPath}=${model.voiceName}=${variant.gender}=${gameId}`)
|
704 |
+
}
|
705 |
+
})
|
706 |
+
})
|
707 |
+
})
|
708 |
+
|
709 |
+
} else {
|
710 |
+
Object.keys(window.embeddingsState.allData).forEach(voiceId => {
|
711 |
+
try {
|
712 |
+
const voiceMeta = window.embeddingsState.allData[voiceId]
|
713 |
+
const gender = voiceMeta.voiceGender.toLowerCase()
|
714 |
+
|
715 |
+
// Filter game-level voices
|
716 |
+
if (!enabledGames.includes(voiceMeta.gameId)) {
|
717 |
+
return
|
718 |
+
}
|
719 |
+
// Filter out by voice
|
720 |
+
if (!enabledVoices.includes(voiceId)) {
|
721 |
+
return
|
722 |
+
}
|
723 |
+
// Filter out data by gender
|
724 |
+
if (gender=="male" && !embeddingsMalesCkbx.checked) {
|
725 |
+
return
|
726 |
+
}
|
727 |
+
if (gender=="female" && !embeddingsFemalesCkbx.checked) {
|
728 |
+
return
|
729 |
+
}
|
730 |
+
if (gender=="other" && !embeddingsOtherGendersCkbx.checked) {
|
731 |
+
return
|
732 |
+
}
|
733 |
+
|
734 |
+
const modelsPathForGame = window.userSettings[`modelspath_${voiceMeta.gameId}`]
|
735 |
+
let audioPreviewPath = `${modelsPathForGame}/${voiceId}.wav`
|
736 |
+
|
737 |
+
if (!fs.existsSync(audioPreviewPath)) {
|
738 |
+
audioPreviewPath = ""
|
739 |
+
}
|
740 |
+
|
741 |
+
mappings.push(`${voiceId}=${audioPreviewPath}=${voiceMeta.voiceName}=${voiceMeta.voiceGender.toLowerCase()}=${voiceMeta.gameId}`)
|
742 |
+
|
743 |
+
} catch (e) {console.log(e)}
|
744 |
+
})
|
745 |
+
}
|
746 |
+
|
747 |
+
if (mappings.length<=2) {
|
748 |
+
return window.errorModal(window.i18n.EMBEDDINGS_NEED_AT_LEAST_3)
|
749 |
+
}
|
750 |
+
|
751 |
+
|
752 |
+
window.spinnerModal(window.i18n.VEMB_RECOMPUTING)
|
753 |
+
|
754 |
+
doFetch(`http://localhost:8008/computeEmbsAndDimReduction`, {
|
755 |
+
method: "Post",
|
756 |
+
body: JSON.stringify({
|
757 |
+
mappings: mappings.join("\n"),
|
758 |
+
onlyInstalled: embeddingsOnlyInstalledCkbx.checked,
|
759 |
+
algorithm: embeddingsAlgorithm.value.split("_")[1],
|
760 |
+
includeAllVoices
|
761 |
+
})
|
762 |
+
}).then(r=>r.text()).then(res => {
|
763 |
+
window.embeddingsState.data = {}
|
764 |
+
res.split("\n").forEach(voiceMetaAndCoords => {
|
765 |
+
const voiceId = voiceMetaAndCoords.split("=")[0]
|
766 |
+
const voiceName = voiceMetaAndCoords.split("=")[1]
|
767 |
+
const voiceGender = voiceMetaAndCoords.split("=")[2]
|
768 |
+
const gameId = voiceMetaAndCoords.split("=")[3]
|
769 |
+
const coords = voiceMetaAndCoords.split("=")[4].split(",").map(v => parseFloat(v))
|
770 |
+
window.embeddingsState.data[voiceId] = coords
|
771 |
+
if (includeAllVoices) {
|
772 |
+
window.embeddingsState.allData[voiceId] = {
|
773 |
+
voiceName,
|
774 |
+
voiceGender,
|
775 |
+
coords,
|
776 |
+
gameId
|
777 |
+
}
|
778 |
+
}
|
779 |
+
})
|
780 |
+
window.refreshEmbeddingsScenePoints()
|
781 |
+
closeModal(undefined, embeddingsContainer)
|
782 |
+
|
783 |
+
}).catch(e => {
|
784 |
+
console.log(e)
|
785 |
+
if (e.code =="ENOENT") {
|
786 |
+
closeModal(null, modalContainer).then(() => {
|
787 |
+
window.errorModal(window.i18n.ERR_SERVER)
|
788 |
+
})
|
789 |
+
}
|
790 |
+
})
|
791 |
+
}
|
792 |
+
|
793 |
+
embeddingsCloseHelpUI.addEventListener("click", () => {
|
794 |
+
embeddingsHelpUI.style.display = "none"
|
795 |
+
})
|
javascript/i18n.js
ADDED
@@ -0,0 +1,1070 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
window.i18n = {}
|
3 |
+
|
4 |
+
window.i18n.setEnglish = () => {
|
5 |
+
window.i18n.SELECT_GAME = "Select Game"
|
6 |
+
window.i18n.SEARCH_VOICES = "Search voices..."
|
7 |
+
window.i18n.SELECT_VOICE = "Select voice"
|
8 |
+
window.i18n.SELECT_VOICE_TYPE = "Select Voice Type"
|
9 |
+
window.i18n.KEEP_SAMPLE = "Keep Sample"
|
10 |
+
window.i18n.GENERATE = "Generate"
|
11 |
+
window.i18n.GENERATE_VOICE = "Generate Voice"
|
12 |
+
window.i18n.RENAME_THE_FILE = "Rename the file"
|
13 |
+
window.i18n.DELETE_FILE = "Delete file"
|
14 |
+
|
15 |
+
window.i18n.PITCH_AND_ENERGY = "Pitch+Energy"
|
16 |
+
window.i18n.PITCH = "Pitch"
|
17 |
+
window.i18n.ENERGY = "Energy"
|
18 |
+
window.i18n.EMOTION = "Emotion"
|
19 |
+
window.i18n.DURATIONS = "Durations"
|
20 |
+
window.i18n.MANAGE = "Manage"
|
21 |
+
window.i18n.DEFAULT = "Default"
|
22 |
+
window.i18n.ENABLED = "Enabled"
|
23 |
+
window.i18n.ACTIONS = "Actions"
|
24 |
+
window.i18n.DESCRIPTION = "Description"
|
25 |
+
window.i18n.UNKNOWN = "Unknown"
|
26 |
+
|
27 |
+
window.i18n.VIEW_IS = "View:"
|
28 |
+
window.i18n.PITCH_IS = "Pitch:"
|
29 |
+
window.i18n.ENERGY_IS = "Energy:"
|
30 |
+
window.i18n.EMOTION_IS_ANGRY = "Emotion: Angry"
|
31 |
+
window.i18n.EMOTION_IS_HAPPY = "Emotion: Happy"
|
32 |
+
window.i18n.EMOTION_IS_SAD = "Emotion: Sad"
|
33 |
+
window.i18n.EMOTION_IS_SURPRISE = "Emotion: Surprise"
|
34 |
+
window.i18n.DURATION_IS = "Duration:"
|
35 |
+
window.i18n.EMOTION_IS = "Emotion:"
|
36 |
+
window.i18n.LENGTH = "Length:"
|
37 |
+
window.i18n.RESET_LETTER = "Reset Letter"
|
38 |
+
window.i18n.AUTO_REGEN = "Auto regenerate"
|
39 |
+
window.i18n.VOCODER = "Vocoder:"
|
40 |
+
window.i18n.BASE_LANGUAGE = "Base Language"
|
41 |
+
window.i18n.BASE_LANGUAGE_IS = "Base Language:"
|
42 |
+
window.i18n.USE_SR = "Use SR"
|
43 |
+
window.i18n.USE_SR_IS = "Use SR:"
|
44 |
+
window.i18n.USE_CLEANUP = "Use Clean-up"
|
45 |
+
window.i18n.USE_CLEANUP_IS = "Use Clean-up:"
|
46 |
+
window.i18n.USE_SR_TITLE = "Super-resolution (Hz) - SLOW ON CPU"
|
47 |
+
window.i18n.USE_SR_HINT = "Super-resolution improves the quality of your audio through Super-resolution of 22050Hz audio into 48000Hz audio. To be able to hear the difference, you need to make sure your ffmpeg settings don't then convert the audio back down to something low like 22050, in the post-processing. Keep the Hz setting to something higher like 48000 or 44100.<br><br>Also to note, this is a fairly slow process on the CPU, but it's pretty quick on the GPU, so I'd recommend switching on GPU usage if you have an NVIDIA card.<br><br>Hide this notice in the future?"
|
48 |
+
window.i18n.BASE_STYLE_EMB_IS = "Base Style:"
|
49 |
+
window.i18n.STYLE_EMB_IS = "Style:"
|
50 |
+
window.i18n.VARIANT_IS = "Variant:"
|
51 |
+
|
52 |
+
window.i18n.SEARCH_GAMES = "Search games..."
|
53 |
+
window.i18n.SEARCH_SETTINGS = "Search settings..."
|
54 |
+
window.i18n.SEARCH_N_VOICES = "Search _ voices..."
|
55 |
+
window.i18n.SEARCH_N_GAMES_WITH_N2_VOICES = "Search _1 games with _2 voices..."
|
56 |
+
window.i18n.RESET = "Reset"
|
57 |
+
window.i18n.AMPLIFY = "Amplify"
|
58 |
+
window.i18n.JITTER = "Jitter"
|
59 |
+
window.i18n.FLATTEN = "Flatten"
|
60 |
+
window.i18n.RAISE = "Raise"
|
61 |
+
window.i18n.LOWER = "Lower"
|
62 |
+
window.i18n.PACING = "Pacing"
|
63 |
+
window.i18n.OPEN = "Open"
|
64 |
+
window.i18n.DOWNLOAD = "Download"
|
65 |
+
window.i18n.VRAM_USAGE = "VRAM usage:"
|
66 |
+
|
67 |
+
window.i18n.SETTINGS = "Settings"
|
68 |
+
window.i18n.SETTINGS_GPU = "Use GPU (requires CUDA)"
|
69 |
+
window.i18n.SETTINGS_AUTOPLAY = "Autoplay generated audio"
|
70 |
+
window.i18n.SETTINGS_DEFAULT_HIFI = "Default to loading the HiFi vocoder on voice change, if available"
|
71 |
+
window.i18n.SETTINGS_KEEP_PACING = "Keep the same pacing value on new text generations"
|
72 |
+
window.i18n.SETTINGS_TOOLTIP = "Show the sliders tooltip"
|
73 |
+
|
74 |
+
window.i18n.SETTINGS_SHOW_DISCORD = "Show Discord status"
|
75 |
+
window.i18n.SETTINGS_DARKMODE = "Dark mode text prompt"
|
76 |
+
window.i18n.SETTINGS_PROMPTSIZE = "Text prompt font size"
|
77 |
+
window.i18n.SETTINGS_BG_FADE = "Background image fade opacity"
|
78 |
+
window.i18n.SETTINGS_AUTORELOADVOICES = "Auto-reload voices on files changes"
|
79 |
+
window.i18n.SETTINGS_KEEPEDITORSTATE = "Keep editor state on voice change"
|
80 |
+
window.i18n.SETTINGS_PITCHRANGEOVERRIDE = "Pitch range over-ride"
|
81 |
+
window.i18n.SETTINGS_OUTPUTJSON = "Output .json (needed for editing)"
|
82 |
+
window.i18n.SETTINGS_SEQNUMBERING = "Use sequential numbering for file names"
|
83 |
+
window.i18n.SETTINGS_SPACEPADDING = "Automatically pad text sequence with spaces (better quality, usually)"
|
84 |
+
window.i18n.SETTINGS_BASE_SPEAKER = "Base app output device"
|
85 |
+
window.i18n.SETTINGS_ALT_SPEAKER = "Alternate output device (ctrl+click play)"
|
86 |
+
window.i18n.SETTINGS_EXTERNALEDIT = "External program for editing audio"
|
87 |
+
window.i18n.SETTINGS_FFMPEG = "Use ffmpeg post-processing"
|
88 |
+
window.i18n.SETTINGS_FFMPEG_FORMAT = "Audio format (wav, mp3, etc)"
|
89 |
+
window.i18n.SETTINGS_FFMPEG_HZ = "Audio sample rate (Hz)"
|
90 |
+
window.i18n.SETTINGS_FFMPEG_PADSTART = "Silence padding start (ms)"
|
91 |
+
window.i18n.SETTINGS_FFMPEG_PADEND = "Silence padding end (ms)"
|
92 |
+
window.i18n.SETTINGS_FFMPEG_PITCHMULT = "Pitch multiplier"
|
93 |
+
window.i18n.SETTINGS_FFMPEG_TEMPO = "Tempo"
|
94 |
+
window.i18n.SETTINGS_FFMPEG_DEESSING = "De-essing"
|
95 |
+
window.i18n.SETTINGS_FFMPEG_BITDEPTH = "Audio bit depth"
|
96 |
+
window.i18n.SETTINGS_FFMPEG_NR = "Noise Reduction (db)"
|
97 |
+
window.i18n.SETTINGS_FFMPEG_NF = "Noise Floor (db)"
|
98 |
+
window.i18n.SETTINGS_FFMPEG_AMPLITUDE = "Amplitude multiplier"
|
99 |
+
window.i18n.SETTINGS_BATCH_JSON = "Output .json editor data for batch lines"
|
100 |
+
// window.i18n.SETTINGS_BATCH_FASTMODE = "Use fast mode for Batch synth (start next batch in parallel to current batch outputting via ffmpeg)"
|
101 |
+
// window.i18n.SETTINGS_BATCH_FASTMODE_MAX_PARALLELIZATIONS = "Maximum parallelizations of lines for Fast mode (lower this if running out of RAM)"
|
102 |
+
window.i18n.SETTINGS_BATCH_USEMULTIP = "Use multi-processing for batch mode ffmpeg output"
|
103 |
+
window.i18n.SETTINGS_BATCH_MULTIPCOUNT = "Number of processes (0 for cpu threads count -1)"
|
104 |
+
window.i18n.SETTINGS_MICROPHONE = "Microphone"
|
105 |
+
// window.i18n.SETTINGS_S2S_VOICE = "Speech-to-Speech voice"
|
106 |
+
window.i18n.SETTINGS_AUTOGENERATEVOICE = "Automatically generate voice"
|
107 |
+
window.i18n.SETTINGS_S2S_PREADJUST_PITCH = "Pre-adjust the input audio average pitch to match the xVASpeech model voice's"
|
108 |
+
window.i18n.SETTINGS_S2S_BGNOISE = "Remove background noise from microphone. You need to record a background noise clip first. (requires sox >= v14.4.2) "
|
109 |
+
window.i18n.SETTINGS_S2S_RECNOISE = "Record noise"
|
110 |
+
window.i18n.SETTINGS_S2S_BGNOISE_STRENGTH = "Noise removal strength (0.2-0.3 recommended)"
|
111 |
+
window.i18n.SETTINGS_VC_STRENGTH = "Voice Conversion strength (1-2 recommended)"
|
112 |
+
window.i18n.SETTINGS_MODELS_PATH = "models path"
|
113 |
+
window.i18n.SETTINGS_OUTPUT_PATH = "output path"
|
114 |
+
window.i18n.SETTINGS_RESET_SETTINGS = "Reset Settings"
|
115 |
+
window.i18n.SETTINGS_RESET_PATHS = "Reset Paths"
|
116 |
+
window.i18n.RESET_WHAT_PROMPT = "What would you like to reset?"
|
117 |
+
window.i18n.RESET_WHAT_TIP = "Shift+click the Reset button to reset all 3."
|
118 |
+
window.i18n.BATCH_METADATA_CONFIRM = "Please confirm the voice details"
|
119 |
+
window.i18n.BATCH_METADATA_TIP = "Select the voice in the main app, to pre-fill these"
|
120 |
+
window.i18n.SAVE_TO_CSV = "Save to CSV"
|
121 |
+
|
122 |
+
window.i18n.UPDATES_VERSION = "This app version: 1.0.0"
|
123 |
+
window.i18n.THIS_APP_VERSION = "This app version"
|
124 |
+
window.i18n.CHECK_FOR_UPDATES = "Check for updates now"
|
125 |
+
window.i18n.CANT_REACH_SERVER = "Can't reach server"
|
126 |
+
window.i18n.CHECKING_FOR_UPDATES = "Checking for updates..."
|
127 |
+
window.i18n.UPDATE_AVAILABLE = "Update available"
|
128 |
+
window.i18n.UPTODATE = "Up-to-date."
|
129 |
+
window.i18n.UPDATES_LOG = "Updates log:"
|
130 |
+
window.i18n.UPDATES_CHECK = "Check for updates now"
|
131 |
+
|
132 |
+
window.i18n.AVAILABLE = "Available"
|
133 |
+
window.i18n.PLUGINS = "Plugins"
|
134 |
+
window.i18n.PLUGINS_TRUSTED = "Download plugins only from trusted sources"
|
135 |
+
window.i18n.PLUGINSH_ENABLED = "Enabled"
|
136 |
+
window.i18n.PLUGINSH_ORDER = "Order"
|
137 |
+
window.i18n.PLUGINSH_NAME = "Plugin Name"
|
138 |
+
window.i18n.PLUGINSH_AUTHOR = "Author"
|
139 |
+
window.i18n.PLUGINSH_VERSION = "Plugin Version"
|
140 |
+
window.i18n.PLUGINSH_TYPE = "Type"
|
141 |
+
window.i18n.PLUGINSH_MINV = "Min App Version"
|
142 |
+
window.i18n.PLUGINSH_MAXV = "Max App Version"
|
143 |
+
window.i18n.PLUGINSH_DESCRIPTION = "Description"
|
144 |
+
window.i18n.PLUGINSH_PLUGINID = "Plugin Id"
|
145 |
+
window.i18n.PLUGINS_MOVEUP = "Move Up"
|
146 |
+
window.i18n.PLUGINS_MOVEDOWN = "Move Down"
|
147 |
+
window.i18n.PLUGINS_APPLY = "Apply"
|
148 |
+
|
149 |
+
window.i18n.APP_INFO = "App info"
|
150 |
+
window.i18n.APP_INFO_INSTR_1 = "For instructions on how to use the app, please watch"
|
151 |
+
window.i18n.APP_INFO_INSTR_2 = "this short video"
|
152 |
+
window.i18n.APP_INFO_INSTR_3 = "showcase on YouTube."
|
153 |
+
window.i18n.APP_INFO_INSTR_4 = "You can also view and/or contribute to the community guide on GitHub "
|
154 |
+
window.i18n.APP_INFO_INSTR_5 = "here"
|
155 |
+
|
156 |
+
window.i18n.KEYBOARD_REFERENCE = "Keyboard shortcuts reference"
|
157 |
+
window.i18n.KEYBOARD_ENTER = "Enter"
|
158 |
+
window.i18n.KEYBOARD_ENTER_DO = "Generate the audio"
|
159 |
+
window.i18n.KEYBOARD_ESCAPE = "Escape"
|
160 |
+
window.i18n.KEYBOARD_ESCAPE_DO = "Close modals and menus"
|
161 |
+
window.i18n.KEYBOARD_SPACE = "Space"
|
162 |
+
window.i18n.KEYBOARD_SPACE_DO = "Bring focus to the input textarea"
|
163 |
+
window.i18n.KEYBOARD_CTRLS = "Ctrl+S"
|
164 |
+
window.i18n.KEYBOARD_CTRLS_DO = "Keep sample"
|
165 |
+
window.i18n.KEYBOARD_CTRLSHIFTS = "Ctrl+Shift-S"
|
166 |
+
window.i18n.KEYBOARD_CTRLSHIFTS_DO = "Keep sample (but with naming prompt)"
|
167 |
+
window.i18n.KEYBOARD_YN = "Y/N"
|
168 |
+
window.i18n.KEYBOARD_YN_DO = "Yes/No options in prompt modals"
|
169 |
+
window.i18n.KEYBOARD_LR = "Left/Right arrows"
|
170 |
+
window.i18n.KEYBOARD_LR_DO = "Move left/right along which letter is focused"
|
171 |
+
window.i18n.KEYBOARD_SHIFT_LR = "Shift-Left/Right arrows"
|
172 |
+
window.i18n.KEYBOARD_SHIFT_LR_DO = "Create multi-letter selection range"
|
173 |
+
window.i18n.KEYBOARD_ALT_CTRL_LR = "Alt-Ctrl-Left/Right arrows"
|
174 |
+
window.i18n.KEYBOARD_ALT_CTRL_LR_DO = "Adjust width of letter selection"
|
175 |
+
window.i18n.KEYBOARD_UD = "Up/Down arrows"
|
176 |
+
window.i18n.KEYBOARD_UD_DO = "Move pitch up/down for the letter(s) selected"
|
177 |
+
window.i18n.KEYBOARD_CTRL_LR = "Ctrl+Left/Right arrows"
|
178 |
+
window.i18n.KEYBOARD_CTRL_LR_DO = "Move the sequence-wide pacing slider"
|
179 |
+
window.i18n.KEYBOARD_CTRL_UD = "Ctrl+Up/Down arrows"
|
180 |
+
window.i18n.KEYBOARD_CTRL_UD_DO = "Pitch increase/decrease buttons"
|
181 |
+
window.i18n.KEYBOARD_CTRLSHIFTUD = "Ctrl+Shift+Up/Down arrows"
|
182 |
+
window.i18n.KEYBOARD_CTRLSHIFTUD_DO = "Pitch amplify/flatten buttons"
|
183 |
+
window.i18n.KEYBOARD_CTRLENTER = "Ctrl+Enter"
|
184 |
+
window.i18n.KEYBOARD_CTRLENTER_DO = "Manually re-generate a line"
|
185 |
+
window.i18n.KEYBOARD_CTRLA = "Ctrl+A"
|
186 |
+
window.i18n.KEYBOARD_CTRLA_DO = "Select all editor sequence letters"
|
187 |
+
|
188 |
+
window.i18n.SUPPORT = "Support"
|
189 |
+
window.i18n.SUPPORT_LINK = "You can support 'xVASynth' development on patreon"
|
190 |
+
window.i18n.SUPPORT_THANKS = "Special thanks to all xVASynth supporters:"
|
191 |
+
|
192 |
+
window.i18n.SUPPORT_GAMES = "Search games..."
|
193 |
+
|
194 |
+
window.i18n.EULA_ACCEPT = "I accept the EULA"
|
195 |
+
window.i18n.EULA_CLOSE = "Close"
|
196 |
+
|
197 |
+
window.i18n.BATCH_SYNTHESIS = "Batch Synthesis"
|
198 |
+
window.i18n.BATCH_SIZE = "Batch Size"
|
199 |
+
window.i18n.BATCH_INSTR1 = `Place the .csv batch file(s) into the box below. The mandatory columns are "game_id", "voice_id", and "text", but you can also specify output filename/filepath under "out_path", pacing under "pacing", and vocoder under "vocoder" (Available options: 'hifi', 'quickanddirty', 'waveglow', 'waveglowBIG'). Click the "Generate sample" button to generate an example .csv file if you need one. Watch`
|
200 |
+
window.i18n.BATCH_INSTR2 = "this short video"
|
201 |
+
window.i18n.BATCH_INSTR3 = "for a demo and more instructions."
|
202 |
+
window.i18n.BATCH_GEN_SAMPLE = "Generate Sample"
|
203 |
+
window.i18n.BATCH_INSTRUCTIONS = "Instructions"
|
204 |
+
window.i18n.BATCH_DROPZONE = "Drag and drop .csv files here"
|
205 |
+
|
206 |
+
window.i18n.BATCHH_NUM = "#"
|
207 |
+
window.i18n.BATCHH_STATUS = "Status"
|
208 |
+
window.i18n.BATCHH_ACTIONS = "Actions"
|
209 |
+
window.i18n.BATCHH_GAME = "Game"
|
210 |
+
window.i18n.BATCHH_VOICE = "Voice"
|
211 |
+
window.i18n.BATCHH_TEXT = "Text or <i>VC content</i>"
|
212 |
+
window.i18n.BATCHH_VC_STYLE = "VC Style"
|
213 |
+
window.i18n.BATCHH_VOCODER = "Vocoder"
|
214 |
+
window.i18n.BATCHH_OUTPATH = "Out Path"
|
215 |
+
window.i18n.BATCHH_PACING = "Pacing"
|
216 |
+
window.i18n.BATCHH_PITCH_AMP = "Pitch Amp."
|
217 |
+
window.i18n.BATCHH_BASE_LANG = "Base lang"
|
218 |
+
window.i18n.BATCH_ABS_DIR_PLACEHOLDER = "Complete absolute directory path to output"
|
219 |
+
|
220 |
+
window.i18n.BATCH_CLEAR_DIR = "Clear out the directory first"
|
221 |
+
window.i18n.BATCH_SKIP = "Skip existing output"
|
222 |
+
window.i18n.BATCH_OUTPUTNUMERICALLY = "Output file names in numerical order"
|
223 |
+
window.i18n.BATCH_CURRENTLYDOING = "currently doing..."
|
224 |
+
window.i18n.BATCH_SYNTHESIZE = "Synthesize Batch"
|
225 |
+
window.i18n.BATCH_PAUSE = "Pause"
|
226 |
+
window.i18n.BATCH_STOP = "Stop"
|
227 |
+
window.i18n.BATCH_CLEAR = "Clear"
|
228 |
+
window.i18n.BATCH_OPENOUT = "Open Output"
|
229 |
+
|
230 |
+
window.i18n.S2S_RECORD_SAMPLE = "Record sample"
|
231 |
+
window.i18n.FEMALE = "Female"
|
232 |
+
window.i18n.MALE = "Male"
|
233 |
+
window.i18n.S2S_OTHER = "Other"
|
234 |
+
window.i18n.VC_ONLY_FOR_V3 = "Voice conversion only available for v3 models."
|
235 |
+
|
236 |
+
|
237 |
+
window.i18n.VW_INPUT_TEXTAREA_PLACEHOLDER = "Enter a sentence to use for generating preview samples of your crafted voice with the current embedding and proposed delta change"
|
238 |
+
window.i18n.CURRENT = "Current"
|
239 |
+
window.i18n.CURRENT_EMB = "Current Embedding"
|
240 |
+
window.i18n.CURRENT_DELTA = "Current Delta"
|
241 |
+
window.i18n.STRENGTH = "Strength"
|
242 |
+
window.i18n.APPLY_DELTA = "Apply Delta"
|
243 |
+
window.i18n.VW_REF_FILE_A = "Reference Audio File A:"
|
244 |
+
window.i18n.VW_REF_FILE_B = "Reference Audio File B:"
|
245 |
+
window.i18n.VW_BASE_MODEL = "Base Model (v3 models only)"
|
246 |
+
window.i18n.NAME_OF_YOUR_VOICE = "Name of your voice"
|
247 |
+
window.i18n.UNIQUE_ID_FOR_VOICE = "A unique identifier for your voice (eg: f4_nate)"
|
248 |
+
window.i18n.YOUR_NAME_FOR_CREDITS = "Your name for credits"
|
249 |
+
|
250 |
+
|
251 |
+
|
252 |
+
|
253 |
+
|
254 |
+
// Dynamic
|
255 |
+
window.i18n.SOMETHING_WENT_WRONG = "Something went wrong"
|
256 |
+
window.i18n.THERE_WAS_A_PROBLEM = "There was a problem"
|
257 |
+
window.i18n.ENTER_DIR_PATH = "Please enter a directory path"
|
258 |
+
window.i18n.SURE_RESET_SETTINGS = `Are you sure you'd like to reset your settings?`
|
259 |
+
window.i18n.SURE_RESET_PATHS = `Are you sure you'd like to reset your paths? This includes the paths for models, and output.`
|
260 |
+
window.i18n.LOAD_MODEL = "Load model"
|
261 |
+
window.i18n.LOAD_TARGET_MODEL = "Please load a target voice from the panel on the left, first."
|
262 |
+
window.i18n.NO_XVASPEECH_MODELS = "No FastPitch1.1 models are installed"
|
263 |
+
window.i18n.ONLY_WAV_S2S = "Only .wav files are supported for speech-to-speech file input at the moment."
|
264 |
+
window.i18n.NO_MODELS_IN = "No models in"
|
265 |
+
window.i18n.NO_MODELS_FOUND = "No models found"
|
266 |
+
window.i18n.MODEL_REQUIRES_VERSION = `This model requires app version`
|
267 |
+
window.i18n.OPEN_CONTAINING_FOLDER = "Open containing folder"
|
268 |
+
window.i18n.ADJUST_SAMPLE_IN_EDITOR = "Adjust sample in the editor"
|
269 |
+
window.i18n.ENTER_NEW_FILENAME_UNCHANGED_CANCEL = "Enter new file name, or submit unchanged to cancel."
|
270 |
+
window.i18n.EDIT_IN_EXTERNAL_PROGRAM = "Edit in external program"
|
271 |
+
window.i18n.FOLLOWING_PATH_NOT_VALID = "The following program path is not valid"
|
272 |
+
window.i18n.SPECIFY_EDIT_TOOL = "Specify your audio editing tool in the settings"
|
273 |
+
window.i18n.SURE_DELETE = "Are you sure you'd like to delete this file?"
|
274 |
+
window.i18n.LOADING_VOICE = "Loading voice"
|
275 |
+
window.i18n.ERR_SERVER = "There was an issue connecting to the python server.<br><br>Try again in a few seconds. If the issue persists, make sure localhost port 8008 is free, or send the server.log file to me on GitHub or Nexus."
|
276 |
+
window.i18n.ABOUT_TO_SAVE_FROM_N1_TO_N2_WITH_OPTIONS = `About to save file from _1 to _2 with options`
|
277 |
+
window.i18n.SAVING_AUDIO_FILE = "Saving the audio file..."
|
278 |
+
window.i18n.TEMP_FILE_NOT_EXIST = "The temporary file does not exist at this file path"
|
279 |
+
window.i18n.OUT_DIR_NOT_EXIST = "The output directory does not exist at this file path"
|
280 |
+
window.i18n.YOU_CAN_CHANGE_IN_SETTINGS = "You can change this in the settings."
|
281 |
+
window.i18n.FILE_EXISTS_ADJUST = `File already exists. Adjust the file name here, or submit without changing to overwrite the old file.`
|
282 |
+
window.i18n.ENTER_FILE_NAME = `Enter file name`
|
283 |
+
window.i18n.WAVEGLOW_NOT_FOUND = "WaveGlow model not found. Download it also (separate download), and place the .pt file in the models folder."
|
284 |
+
window.i18n.BATCH_MODEL_NOT_FOUND = "Model not found."
|
285 |
+
window.i18n.BATCH_DOWNLOAD_WAVEGLOW = "Download WaveGlow files separately if you haven't, or check the path in the settings."
|
286 |
+
window.i18n.ERR_LOADING_MODELS_FOR_GAME = "ERROR loading models for game"
|
287 |
+
window.i18n.ERR_LOADING_MODELS_FOR_GAME_WITH_FILENAME = "ERROR loading models for game _1 with filename:"
|
288 |
+
window.i18n.ERR_XVASPEECH_MODEL_VERSION = `This xVASpeech model needs minimum app version _1. Your app version:`
|
289 |
+
window.i18n.ERR_ARPABET_NOT_EXIST = `The following ARPAbet symbol does not exist: _1`
|
290 |
+
|
291 |
+
window.i18n.ENTER_VOICE_NAME = "Please enter a voice name"
|
292 |
+
window.i18n.ENTER_VOICE_ID = "Please enter a voice ID"
|
293 |
+
window.i18n.VOICE_CREATED_AT = "Voice successfully saved at the following location:<br><br>_1"
|
294 |
+
window.i18n.CONFIRM_DELETE_CRAFTED_VOICE = "Are you sure you'd like to delete the crafted voice '_1' at the following location?<br><br>_2"
|
295 |
+
window.i18n.SUCCESSFULLY_DELETED_CRAFTED_VOICE = "Successfully deleted the crafted voice model."
|
296 |
+
window.i18n.ENTER_VOICE_CRAFTING_STARTING_EMB = "Please provide a starting embedding. Drag and drop a .wav audio file over the 'Current Embedding' field below."
|
297 |
+
|
298 |
+
window.i18n.CHANGING_MODELS = "Changing models..."
|
299 |
+
window.i18n.CHANGING_DEVICE = "Changing device..."
|
300 |
+
window.i18n.PROCESSING_DATA = "Processing data..."
|
301 |
+
window.i18n.DELETING_FILE = "Deleting file"
|
302 |
+
window.i18n.DELETING_NEW_FILE = "Deleting new file"
|
303 |
+
window.i18n.FAILED = "Failed"
|
304 |
+
window.i18n.DONE = "Done"
|
305 |
+
window.i18n.READY = "Ready"
|
306 |
+
window.i18n.RUNNING = "Running"
|
307 |
+
window.i18n.PAUSED = "Paused"
|
308 |
+
window.i18n.PAUSE = "Pause"
|
309 |
+
window.i18n.PLAY = "Play"
|
310 |
+
window.i18n.EDIT = "Edit"
|
311 |
+
window.i18n.EDIT_IS = "Edit:"
|
312 |
+
window.i18n.RESUME = "Resume"
|
313 |
+
window.i18n.STOPPED = "Stopped"
|
314 |
+
window.i18n.SYNTHESIZING = "Synthesizing"
|
315 |
+
window.i18n.LINES = "lines"
|
316 |
+
window.i18n.LINE = "Line"
|
317 |
+
window.i18n.ERROR = "Error"
|
318 |
+
window.i18n.MISSING = "Missing"
|
319 |
+
window.i18n.INPUT = "Input"
|
320 |
+
window.i18n.OUTPUT = "Output"
|
321 |
+
window.i18n.OUTPUTTING = "Outputting"
|
322 |
+
window.i18n.SUBMIT = "Submit"
|
323 |
+
window.i18n.CLOSE = "Close"
|
324 |
+
window.i18n.YES = "Yes"
|
325 |
+
window.i18n.NO = "No"
|
326 |
+
window.i18n.VOICE = "voice"
|
327 |
+
window.i18n.VOICE_PLURAL = "voices"
|
328 |
+
window.i18n.NEW = "new"
|
329 |
+
window.i18n.PAGE = "Page:"
|
330 |
+
window.i18n.NEXT = "Next"
|
331 |
+
window.i18n.PREVIOUS = "Previous"
|
332 |
+
window.i18n.LOADING = "Loading"
|
333 |
+
window.i18n.MAY_TAKE_A_MINUTE = "May take a minute (but not much more)"
|
334 |
+
window.i18n.BUILDING_FASTPITCH = "Building FastPitch model"
|
335 |
+
window.i18n.LOADING_WAVEGLOW = "Loading WaveGlow model"
|
336 |
+
window.i18n.STARTING_PYTHON = "Starting up the python backend"
|
337 |
+
window.i18n.NOT_USING_GPU = "Not using GPU"
|
338 |
+
|
339 |
+
window.i18n.BATCH_CHANGING_MODEL_TO = "Changing voice model to"
|
340 |
+
window.i18n.BATCH_CHANGING_VOCODER_TO = "Changing vocoder to"
|
341 |
+
window.i18n.BATCH_OUTPUTTING_FFMPEG = `Outputting audio via ffmpeg...`
|
342 |
+
|
343 |
+
window.i18n.BATCH_ERR_NO_VOICES = "No voice models available in the app. Load at least one."
|
344 |
+
window.i18n.BATCH_ERR_GAMEID = "does not match any available games"
|
345 |
+
window.i18n.BATCH_ERR_VOICEID = "does not match any in the game"
|
346 |
+
window.i18n.BATCH_ERR_VOCODER1 = "does not exist. Available options"
|
347 |
+
window.i18n.BATCH_ERR_VOCODER2 = "(or leaving it blank)"
|
348 |
+
window.i18n.BATCH_ERR_CUDA_OOM = "CUDA OOM: There is not enough VRAM to run this. Try lowering the batch size, or shortening very long sentences."
|
349 |
+
window.i18n.BATCH_ERR_IN_PROGRESS = "Batch synthesis is in progress. Loading a model in the main app now would break things."
|
350 |
+
window.i18n.BATCH_ERR_EDIT = "Batch synthesis is in progress. Pause or stop it first to enable editor."
|
351 |
+
window.i18n.BATCH_ERR_SKIPPEDALL = "No records imported, but _1 were skipped as they already exist."
|
352 |
+
|
353 |
+
window.i18n.ERR_LOADING_PLUGIN = "Error loading plugin"
|
354 |
+
window.i18n.SUCCESSFULLY_INITIALIZED = "Successfully initialized"
|
355 |
+
window.i18n.FAILED_INIT_FOLLOWING = "Failed to initialize the following"
|
356 |
+
window.i18n.CHECK_SERVERLOG = "Check the server.log file for detailed error traces"
|
357 |
+
window.i18n.SUCC_NO_ACTIVE_PLUGINS = "Success. No plugins active."
|
358 |
+
window.i18n.APP_RESTART_NEEDED = "App restart is required for at least one of the plugins to take effect."
|
359 |
+
window.i18n.ERR_LOADING_CSS = "Error loading style file for plugin"
|
360 |
+
window.i18n.PLUGIN = "Plugin"
|
361 |
+
window.i18n.PLUGINS = "Plugins"
|
362 |
+
window.i18n.CANT_IMPORT_FILE_FOR_HOOK_TASK_ENTRYPOINT = "Cannot import _1 file for _2 _3 entry-point"
|
363 |
+
window.i18n.ONLY_JS = "Only JavaScript files are supported right now."
|
364 |
+
window.i18n.PLUGIN_RUN_ERROR = "Plugin run error at event"
|
365 |
+
|
366 |
+
window.i18n.MONDAY = "Monday"
|
367 |
+
window.i18n.TUESDAY = "Tuesday"
|
368 |
+
window.i18n.WEDNESDAY = "Wednesday"
|
369 |
+
window.i18n.THURSDAY = "Thursday"
|
370 |
+
window.i18n.FRIDAY = "Friday"
|
371 |
+
window.i18n.SATURDAY = "Saturday"
|
372 |
+
window.i18n.SUNDAY = "Sunday"
|
373 |
+
|
374 |
+
window.i18n.EMBEDDINGS = "Embeddings"
|
375 |
+
window.i18n.EMB_NAME = "Embedding Name"
|
376 |
+
window.i18n.EMB_DESCRIPTION = "Embedding description"
|
377 |
+
window.i18n.EMB_ID = "Embedding ID"
|
378 |
+
window.i18n.STYLE_EMB_ID = "Embedding ID (Write a short, descriptive, alpha-numerical ID you think will be unique)"
|
379 |
+
window.i18n.EMB_ID = "Emb ID"
|
380 |
+
window.i18n.STYLE_EMBEDDINGS = "Style Embeddings"
|
381 |
+
window.i18n.STYLE_EMB_WAVPATH = "Wav file path"
|
382 |
+
window.i18n.STYLE_EMB_WAVPATH_PLACEHOLDER = "Drag+drop or full file path"
|
383 |
+
window.i18n.ERROR_FILE_MUST_BE_WAV = "File type must be .wav"
|
384 |
+
window.i18n.ERROR_NEED_WAV_FILE = "Add a wav file path"
|
385 |
+
window.i18n.STYLE_EMB_VALUES = "Style embedding values"
|
386 |
+
window.i18n.ERROR_MISSING_FIELDS = "Missing values for the following fields: _1"
|
387 |
+
window.i18n.CONFIRM_DELETE_STYLE_EMB = "Are you sure you want to delete this style embedding forever?"
|
388 |
+
|
389 |
+
|
390 |
+
window.i18n.TOTD_1 = "You can right-click a voice on the left to hear a preview of the voice"
|
391 |
+
window.i18n.TOTD_2 = "You can right-click the microphone icon after a recording, to hear back the audio you recorded/inserted"
|
392 |
+
window.i18n.TOTD_3 = "There are a number of keyboard shortcuts you can use. Check the info tab for a reference"
|
393 |
+
window.i18n.TOTD_4 = "Check the community guide for tips for how to get the best quality out of the tool. This is linked in the info (i) menu"
|
394 |
+
window.i18n.TOTD_5 = "You can create a multi-letter selection in the editor by Ctrl+clicking several letters"
|
395 |
+
window.i18n.TOTD_6 = "You can shift-click the 'Keep Sample' button (or Ctrl+Shift+S) to first give your file a custom name before saving"
|
396 |
+
window.i18n.TOTD_7 = "You can alt+click editor letters to make a multi-letter selection for the entire word you click on"
|
397 |
+
window.i18n.TOTD_8 = "You can drag+drop multiple .csv or .txt files into batch mode"
|
398 |
+
window.i18n.TOTD_9 = "You can use .txt files in batch mode instead of .csv files, if you first click a voice in the main app to assign the lines to"
|
399 |
+
window.i18n.TOTD_10 = "If you have a compatible NVIDIA GPU, and CUDA installed, you can switch to the CPU+GPU installation. Using the GPU is much faster, especially for batch mode."
|
400 |
+
window.i18n.TOTD_11 = "The HiFi-GAN vocoder (v1 and v2 models) is normally the best quality, but you can also download and use WaveGlow vocoders, if you'd like."
|
401 |
+
window.i18n.TOTD_12 = "(v1 and v2 models) If the 'Keep editor state on voice changes' option is ticked on, you can generate a line using one voice, then switch to a different voice, and click the 'Generate Voice' button again to generate a line using the new voice, but using a similar speaking style to the first voice."
|
402 |
+
window.i18n.TOTD_13 = "If you set the 'Alternative Output device' to something other than the default device, you can Ctrl-click when playing audio, to have it play on a different speaker. You can couple this with something like Voicemeeter Banana split, to have the app speak for you over the microphone, for voice chat, or other audio recording."
|
403 |
+
window.i18n.TOTD_14 = "If you add the path to an audio editing program to the 'External Program for Editing audio' setting, you can open generated audio straight in that program in one click, from the output records on the main page"
|
404 |
+
window.i18n.TOTD_15 = "FFmpeg automatically directly applies a few different audio post processing tasks on the generated audio. This can include Hz resampling, silence padding to the start and/or end of the audio, bit depth, loudness, noise reduction, de-essing, pitch and tempo modifiers, and different audio formats. Play with these to get the best quality for a particular voice"
|
405 |
+
window.i18n.TOTD_16 = "You can tick on the 'Fast mode' for batch mode to parallelize the audio generation and the audio output (via ffmpeg for example)"
|
406 |
+
window.i18n.TOTD_17 = "You can enable multiprocessing for ffmpeg file output in batch mode, to speed up the output process. This is especially useful if you use a large batch size, and your CPU has plenty of threads. This can be used together with Fast Mode."
|
407 |
+
window.i18n.TOTD_18 = "If you're having trouble formatting a .csv file for batch mode, you can change the delimiter in the settings to something else (for example a pipe symbol '|')"
|
408 |
+
window.i18n.TOTD_19 = "You can change the folder location of your output files, as well as the models. I'd recommend keeping your model files on an SSD, to reduce the loading time."
|
409 |
+
window.i18n.TOTD_20 = "Use the voice embeddings search menu to get a 3D visualisation of all the voices in the app (including some 'officially' trained voices not downloaded yet). You can use this as a reference for voice similarly search, to see what other voices there are, which sound similar to a particular voice."
|
410 |
+
window.i18n.TOTD_21 = "You can right click on the points in the 3D voice embeddings visualisation, to hear a preview of that voice. This will only work for the voices you have installed, locally."
|
411 |
+
window.i18n.TOTD_22 = "The app is customisable via third-party plugins. Plugins can be managed from the plugins menu, and they can change, or add to the front end app functionality/looks (the UI), as well as the python back-end (the machine learning code). If you're interested in developing such a plugin, there is a full developer reference on the GitHub wiki, here: https://github.com/DanRuta/xvasynth-community-guide"
|
412 |
+
window.i18n.TOTD_23 = "If you log into nexusmods.com from within the app, you can check for new and updated voice models on your chosen Nexus pages. You can also endorse these, as well as any plugins configured with a nexus link. If you have a premium membership for the Nexus, you can also download (or batch download) all available voices, and have them installed automatically."
|
413 |
+
window.i18n.TOTD_24 = "You can manage the list of Nexus pages to check for voice models by clicking the 'Manage Repos' button in the Nexus menu, or by editing the repositories.txt file"
|
414 |
+
window.i18n.TOTD_25 = "You can enable/disable error sounds in the settings. You can also pick a different sound, if you'd prefer something else"
|
415 |
+
window.i18n.TOTD_26 = "You can resize the window by dragging one of the bottom corners"
|
416 |
+
window.i18n.TOTD_27 = "You can right-click game buttons in the nexus window 'Games' list and voice embeddings 'Games' list, to de-select all other games apart from the one you right-clicked"
|
417 |
+
window.i18n.TOTD_28 = "With v3 models, you can change the default speaking style of your voice by creating an embedding for it. You do so by drag+dropping an example audio file (usually from the same original voice) into the Management menu."
|
418 |
+
window.i18n.TOTD_29 = "The v3 models don't pre-generate pitch or energy values. Instead, the values in the editor are multipliers rather than absolute values. So initially, tney are set to 1, and you can CHANGE what they are rather than setting values like for v1 and v2 models."
|
419 |
+
window.i18n.TOTD_30 = "To get the absolute highest quality from an audio file, you should enable the 'Use SR' option, to run super-resolution from the default 22050Hz into 48000Hz. It's best to use the GPU mode for this, else it can be quite slow. You also need to make sure that you didn't set the ffmpeg Hz post-processing value to something low like 22050, else you won't hear the benefits."
|
420 |
+
window.i18n.TOTD_31 = "With v3 models, you can right click the sliders editor to open the context menu, where you can select to copy the final symbol sequence to clipboard."
|
421 |
+
window.i18n.TOTD_32 = "You can use the Ctrl+Enter shortcut to manually kick offf re-generating a line."
|
422 |
+
|
423 |
+
window.i18n.TOTD_NO_UNSEEN = "There are no unseen tips left to show. Untick the 'Only show unseen tips' setting to show all tips."
|
424 |
+
|
425 |
+
|
426 |
+
window.i18n.LINES_PER_SECOND = "lines per second"
|
427 |
+
window.i18n.ETA_FINISHED = "Estimated time until finished:"
|
428 |
+
window.i18n.LOGGED_IN_AS = "Logged in as: "
|
429 |
+
window.i18n.GAMES = "Games"
|
430 |
+
window.i18n.MODELS = "Models"
|
431 |
+
window.i18n.SHOW_NEW_UPDATED = "Show only new/updated"
|
432 |
+
window.i18n.CHECK_NOW = "Check now"
|
433 |
+
window.i18n.MANAGE_REPOS = "Manage repos"
|
434 |
+
window.i18n.LOG_IN = "Log in"
|
435 |
+
window.i18n.LOG_OUT = "Log out"
|
436 |
+
window.i18n.NAME = "Name"
|
437 |
+
window.i18n.AUTHOR = "Author"
|
438 |
+
window.i18n.VERSION = "Version"
|
439 |
+
window.i18n.DATE = "Date"
|
440 |
+
window.i18n.TYPE = "Type"
|
441 |
+
window.i18n.NOTES = "Notes"
|
442 |
+
window.i18n.DOWNLOADING = "Downloading:"
|
443 |
+
window.i18n.INSTALLING = "Installing:"
|
444 |
+
window.i18n.FINISHED = "Finished:"
|
445 |
+
window.i18n.DOWNLOAD_ALL = "Download All"
|
446 |
+
window.i18n.REPOSITORIES = "Repositories"
|
447 |
+
window.i18n.ADD = "Add"
|
448 |
+
window.i18n.REMOVE = "Remove"
|
449 |
+
window.i18n.V_EMB_VIS = "Voice embeddings visualiser"
|
450 |
+
window.i18n.VOICES = "Voices"
|
451 |
+
window.i18n.SHOW = "Show"
|
452 |
+
window.i18n.GAME = "Game"
|
453 |
+
window.i18n.GENDER = "Gender"
|
454 |
+
window.i18n.GENDER_IS = "Gender:"
|
455 |
+
window.i18n.GAME_IS = "Game:"
|
456 |
+
window.i18n.PREVIEW = "Preview"
|
457 |
+
window.i18n.LOAD = "Load"
|
458 |
+
|
459 |
+
window.i18n.VOICE_NAME = "Voice Name"
|
460 |
+
window.i18n.VOICE_NAME_IS = "Voice Name:"
|
461 |
+
|
462 |
+
window.i18n.VEMB_INSTR_1 = "Left click drag to rotate"
|
463 |
+
window.i18n.VEMB_INSTR_2 = "Right click drag to pan"
|
464 |
+
window.i18n.VEMB_INSTR_3 = "Mouse wheel scroll to zoom"
|
465 |
+
window.i18n.VEMB_INSTR_4 = "Left click on voice to select"
|
466 |
+
window.i18n.VEMB_INSTR_5 = "Right click on voice to play sample"
|
467 |
+
|
468 |
+
window.i18n.MALES = "Males"
|
469 |
+
window.i18n.FEMALES = "Females"
|
470 |
+
window.i18n.OTHER = "Other"
|
471 |
+
|
472 |
+
window.i18n.SHOW_ONLY_INSTALED = "Show only installed voices"
|
473 |
+
window.i18n.KEY_IS = "Key:"
|
474 |
+
window.i18n.ALGORITHM = "Algorithm"
|
475 |
+
|
476 |
+
window.i18n.TOTD = "Tip of the day"
|
477 |
+
window.i18n.TOTD_SHOW = "Show tip of the day"
|
478 |
+
window.i18n.TOTD_SHOW_UNSEEN = "Only show unseen tips"
|
479 |
+
window.i18n.TOTD_PREV_TIP = "Previous tip"
|
480 |
+
window.i18n.TOTD_NEXT_TIP = "Next tip"
|
481 |
+
|
482 |
+
window.i18n.ENDORSE = "Endorse"
|
483 |
+
window.i18n.GET_MORE_VOICES = "Get more voices"
|
484 |
+
|
485 |
+
window.i18n.CURR_INSTALL = "Current installation:"
|
486 |
+
window.i18n.CHANGE_TO_GPU = "Change to CPU+GPU"
|
487 |
+
window.i18n.CHANGE_TO_CPU = "Change to CPU"
|
488 |
+
window.i18n.USE_SOUND_ERR = "Use sound for errors"
|
489 |
+
window.i18n.ERR_SOUNDFILE = "Error sound file"
|
490 |
+
window.i18n.SHOW_NOW = "Show now"
|
491 |
+
window.i18n.SETTINGS_PLAYCHANGEDAUDIO = "Play only changed audio, when regenerating"
|
492 |
+
window.i18n.SETTINGS_PREAPPLY_FFMPEG = "(recommended) Pre-apply ffmpeg effects to the preview sample"
|
493 |
+
window.i18n.SETTINGS_USE_NR = "Use noise reduction (recommended when using SR)"
|
494 |
+
window.i18n.SETTINGS_DOUBLE_AMP_DISPLAY = "Also display amplitude setting in the editor"
|
495 |
+
window.i18n.SETTINGS_CSV_DELIMITER = "CSV delimiter"
|
496 |
+
window.i18n.SETTINGS_PAGINATION_SIZE_BATCH = "Batch pagination size"
|
497 |
+
window.i18n.SETTINGS_PAGINATION_SIZE_ARPABET = "ARPAbet pagination size"
|
498 |
+
window.i18n.SETTINGS_MAX_FILENAME_LENGTH = "Maximum filename characters (trimming for maximum windows filepath length)"
|
499 |
+
window.i18n.SETTINGS_CLEAR_TEXT_AFTER_GENERATION = "Clear the text input after generation"
|
500 |
+
window.i18n.SETTINGS_GROUP_VOICEID = "Group voices by voiceId and vocoder in preprocessing to minimize model switching"
|
501 |
+
window.i18n.SETTINGS_GROUP_VOCODER = "Also do a secondary group by the vocoder - can take long to do with big files (100k+ lines)"
|
502 |
+
window.i18n.SETTING_HIGHLIGHT_ONLY_MODELS_V = "Highlight only models with at least this version"
|
503 |
+
window.i18n.SETTING_OUTPUTFILES_PAGINATION = "Output records pagination size"
|
504 |
+
|
505 |
+
|
506 |
+
window.i18n.SEARCH_OUTPUT = "Search output file names..."
|
507 |
+
window.i18n.SEARCH_OUTPUT_PROMPT = "Search prompts..."
|
508 |
+
window.i18n.DELETE = "Delete"
|
509 |
+
window.i18n.DELETE_ALL = "Delete all"
|
510 |
+
window.i18n.DELETE_ALL_FILES_CONFIRM = "Are you sure you'd like to delete all files for this voice? This will delete all _1 files in the following output directory:<br>_2"
|
511 |
+
window.i18n.DELETE_ALL_FILES_ERR_NO_FILES = "There are no files in the following output directory:<br>_1"
|
512 |
+
window.i18n.SORT_BY = "Sort by"
|
513 |
+
window.i18n.ASCENDING = "Ascending"
|
514 |
+
window.i18n.DESCENDING = "Descending"
|
515 |
+
window.i18n.TIME = "Time"
|
516 |
+
|
517 |
+
window.i18n.ERR_LOGGING_INTO_NEXUS = "Error attempting to log into nexusmods"
|
518 |
+
window.i18n.LOGGING_INTO_NEXUS = "Logging into nexusmods (check your browser)..."
|
519 |
+
window.i18n.NEXUS_PREMIUM = "Nexus requires premium membership for using their API for file downloads"
|
520 |
+
window.i18n.NEXUS_ORIG_ERR = "Original error message"
|
521 |
+
window.i18n.FAILED_DOWNLOAD = "Failed to download"
|
522 |
+
window.i18n.DONE_INSTALLING = "Done installing"
|
523 |
+
window.i18n.CHECKING_NEXUS = "Checking nexusmods.com..."
|
524 |
+
window.i18n.NEXUS_NOT_DOWNLOADED_MOD = "You need to first download something from this repo to be able to endorse it."
|
525 |
+
window.i18n.NEXUS_TOO_SOON_AFTER_DOWNLOAD = "Nexus requires you to wait at least 15 mins (at the time of writing) before you can endorse."
|
526 |
+
window.i18n.NEXUS_IS_OWN_MOD = "Nexus does not allow you to rate your own content."
|
527 |
+
window.i18n.YOURS = "Yours"
|
528 |
+
window.i18n.NEXUS_ENTER_LINK = "Enter the nexusmods.com link to use as a repository"
|
529 |
+
window.i18n.NEXUS_LINK_EXISTS = "This link already exists."
|
530 |
+
window.i18n.ERROR_FROM_NEXUS = "<h3>Error using Nexus API. Their response:</h3> <br>_1"
|
531 |
+
|
532 |
+
window.i18n.VEMB_VOICE_NOT_ENABLED = "This voice is not enabled"
|
533 |
+
window.i18n.VEMB_NO_PREVIEW = "No preview audio file available"
|
534 |
+
window.i18n.VEMB_SELECT_VOICE_FIRST = "Select a voice from the scene below first."
|
535 |
+
window.i18n.VEMB_NO_MODEL = "No model file available. Download it if you haven't already."
|
536 |
+
window.i18n.VEMB_RECOMPUTING = "Re-computing embeddings and dimensionality reduction on voices. May take a minute the first time, subsequent runs should be instant."
|
537 |
+
|
538 |
+
window.i18n.SETTINGS_FOR_PLUGIN = "Settings for plugin: <i>_1</i>"
|
539 |
+
window.i18n.EMBEDDINGS_NEED_AT_LEAST_3 = "You need at least 3 voices to run dimensionality reduction for the plot"
|
540 |
+
|
541 |
+
|
542 |
+
|
543 |
+
window.i18n.ARPABET_ERROR_BAD_SYMBOLS = "Found non-ARPAbet symbols: _1"
|
544 |
+
window.i18n.ARPABET_ERROR_EMPTY_INPUT = "Words or ARPAbet symbols can't be left empty"
|
545 |
+
window.i18n.PAGINATION_X_OF_Y = "_1 of _2"
|
546 |
+
window.i18n.ARPABET_CONFIRM_ENABLE_ALL = "Are you sure you'd like to enable ALL words for the following dictionary?<br><br><i>_1</i>"
|
547 |
+
window.i18n.ARPABET_CONFIRM_DISABLE_ALL = "Are you sure you'd like to disable ALL words for the following dictionary?<br><br><i>_1</i>"
|
548 |
+
window.i18n.ARPABET_CONFIRM_DELETE_WORD = "Are you sure you'd like to delete the following word?<br><br><i>_1</i>"
|
549 |
+
window.i18n.ARPABET_CONFIRM_SAME_WORD = "The word '_1' already exists in the following dictionaries:<br><br><i>_2</i><br><br>Are you sure you'd like to add it?"
|
550 |
+
|
551 |
+
window.i18n.ONLY_ENABLED = "Only enabled"
|
552 |
+
|
553 |
+
window.i18n.DICTIONARIES = "Dictionaries"
|
554 |
+
window.i18n.CANCEL = "Cancel"
|
555 |
+
window.i18n.START = "Start"
|
556 |
+
window.i18n.SAVE = "Save"
|
557 |
+
window.i18n.WORDS = "Words"
|
558 |
+
window.i18n.WORD_IS = "Word:"
|
559 |
+
window.i18n.WORD = "Word"
|
560 |
+
window.i18n.REFERENCE = "Reference"
|
561 |
+
window.i18n.SEARCH_WORDS = "Search words..."
|
562 |
+
window.i18n.ENABLE_ALL = "Enable All"
|
563 |
+
window.i18n.DISABLE_ALL = "Disable All"
|
564 |
+
window.i18n.PREV = "Prev"
|
565 |
+
window.i18n.LOADING_DICTIONARIES = "Loading ARPAbet dictionaries..."
|
566 |
+
|
567 |
+
window.i18n.ALL = "All"
|
568 |
+
window.i18n.MOD_NAME = "Mod name"
|
569 |
+
window.i18n.MOD_TITLE = "Mod title"
|
570 |
+
window.i18n.SEARCH_NEXUS = "Search Nexus"
|
571 |
+
window.i18n.MOD_REPOS_USED = "Mod repos used"
|
572 |
+
window.i18n.LINK = "Link"
|
573 |
+
window.i18n.ENDORSEMENTS = "Endorsements"
|
574 |
+
window.i18n.DOWNLOADS = "Downloads"
|
575 |
+
|
576 |
+
window.i18n.CONFIRM = "Confirm"
|
577 |
+
window.i18n.GAME_ID = "Game ID"
|
578 |
+
window.i18n.VOICE_ID = "Voice ID"
|
579 |
+
window.i18n.VOICE_ID_IS = "Voice ID:"
|
580 |
+
window.i18n.APP_VERSION_IS = "App version:"
|
581 |
+
window.i18n.MODEL_VERSION_IS = "Model version:"
|
582 |
+
window.i18n.MODEL_TYPE_IS = "Model type:"
|
583 |
+
window.i18n.LANGUAGE_IS = "Language:"
|
584 |
+
window.i18n.TRAINED_BY_IS = "Trained by:"
|
585 |
+
window.i18n.LICENSE_IS = "License:"
|
586 |
+
|
587 |
+
window.i18n.X_WORKSHOP_VOICES_INSTALLED = "_1 workshop voices installed"
|
588 |
+
window.i18n.WORKSHOP_GAMES_NOT_RECOGNISED = "The following workshop games were not recognised. Do you have the asset file installed?<i>_1</i>"
|
589 |
+
|
590 |
+
window.i18n.YOU_MUST_BE_LOGGED_IN = "You must be logged in to check what voices there are available on the nexus."
|
591 |
+
window.i18n.JOIN_DISCORD = "Join xVASynth server"
|
592 |
+
|
593 |
+
window.i18n.GETTING_SPEAKER_EMBEDDING = "Getting speaker embedding..."
|
594 |
+
|
595 |
+
window.i18n.INFO = "Info"
|
596 |
+
window.i18n.VOICE_CRAFTING_WORKBENCH = "Voice Crafting Workbench"
|
597 |
+
window.i18n.WORKBENCH = "Workbench"
|
598 |
+
window.i18n.FROM_FILE_IS_DRAG_N_DROP = "From file: (Drag and drop a .wav file)"
|
599 |
+
window.i18n.FROM_FILE_IS_FILEPATH = "From file: _1"
|
600 |
+
|
601 |
+
|
602 |
+
window.i18n.BATCH_CHANGE_DELIMITER = "The .csv delimiter is not found in the data. The delimiter in the settings is '_1', but the one in the .csv file is potentially '_2'. Do you want to change the delimiter used, and try again using this?"
|
603 |
+
window.i18n.BATCH_TOCSV_DONE = "Saved all lines to csv file at:"
|
604 |
+
window.i18n.PAGINATION_TOTAL_OF = "of _1"
|
605 |
+
|
606 |
+
|
607 |
+
window.i18n.VC_TOO_SHORT = "Recorded sample is too short and/or empty"
|
608 |
+
|
609 |
+
window.i18n.MODEL_INSTALL_DRAGDROP_INCOMPLETE = "Some of the loose files given were not complete models. Each model needs at least a .json file and a .pt file. Loose model files not complete:<br><br>_1"
|
610 |
+
window.i18n.MODEL_INSTALL_DRAGDROP_SUCCESS = "_1 models installed successfully. "
|
611 |
+
window.i18n.MODEL_INSTALL_DRAGDROP_FAILED = "_1 models failed to install:<br><br>_2"
|
612 |
+
|
613 |
+
// Useful during developing, to see if there are any strings left un-i18n-ed
|
614 |
+
// Object.keys(window.i18n).forEach(key => {
|
615 |
+
// if (!["setEnglish", "updateUI"].includes(key)) {
|
616 |
+
// window.i18n[key] = ""
|
617 |
+
// }
|
618 |
+
// })
|
619 |
+
}
|
620 |
+
|
621 |
+
|
622 |
+
window.i18n.updateUI = () => {
|
623 |
+
|
624 |
+
|
625 |
+
|
626 |
+
i18n_voiceInfo_name.innerHTML = window.i18n.VOICE_NAME_IS
|
627 |
+
i18n_voiceInfo_id.innerHTML = window.i18n.VOICE_ID_IS
|
628 |
+
i18n_voiceInfo_gender.innerHTML = window.i18n.GENDER_IS
|
629 |
+
i18n_voiceInfo_appVersion.innerHTML = window.i18n.APP_VERSION_IS
|
630 |
+
i18n_voiceInfo_modelVersion.innerHTML = window.i18n.MODEL_VERSION_IS
|
631 |
+
i18n_voiceInfo_modelType.innerHTML = window.i18n.MODEL_TYPE_IS
|
632 |
+
i18n_voiceInfo_lang.innerHTML = window.i18n.LANGUAGE_IS
|
633 |
+
i18n_voiceInfo_author.innerHTML = window.i18n.TRAINED_BY_IS
|
634 |
+
i18n_voiceInfo_license.innerHTML = window.i18n.LICENSE_IS
|
635 |
+
|
636 |
+
|
637 |
+
i18n_nexusRepos_mod_name.innerHTML = window.i18n.MOD_NAME
|
638 |
+
nexusReposSearchBar.placeholder = window.i18n.MOD_TITLE
|
639 |
+
i18n_nexusRepos_all.innerHTML = window.i18n.ALL
|
640 |
+
searchNexusButton.innerHTML = window.i18n.SEARCH_NEXUS
|
641 |
+
i18n_nexusRepos_game.innerHTML = window.i18n.GAME_IS
|
642 |
+
i18n_nexusRepos_modReposUsed.innerHTML = window.i18n.MOD_REPOS_USED
|
643 |
+
|
644 |
+
i18n_nexus_searchh_add.innerHTML = window.i18n.ADD
|
645 |
+
i18n_nexus_searchh_link.innerHTML = window.i18n.LINK
|
646 |
+
i18n_nexus_searchh_game.innerHTML = window.i18n.GAME
|
647 |
+
i18n_nexus_searchh_name.innerHTML = window.i18n.NAME
|
648 |
+
i18n_nexus_searchh_author.innerHTML = window.i18n.AUTHOR
|
649 |
+
i18n_nexus_searchh_endorsements.innerHTML = window.i18n.ENDORSEMENTS
|
650 |
+
i18n_nexus_searchh_downloads.innerHTML = window.i18n.DOWNLOADS
|
651 |
+
|
652 |
+
i18n_nexus_reposUsedh_link.innerHTML = window.i18n.LINK
|
653 |
+
i18n_nexus_reposUsedh_game.innerHTML = window.i18n.GAME
|
654 |
+
i18n_nexus_reposUsedh_name.innerHTML = window.i18n.NAME
|
655 |
+
i18n_nexus_reposUsedh_author.innerHTML = window.i18n.AUTHOR
|
656 |
+
i18n_nexus_reposUsedh_endorsements.innerHTML = window.i18n.ENDORSEMENTS
|
657 |
+
i18n_nexus_reposUsedh_downloads.innerHTML = window.i18n.DOWNLOADS
|
658 |
+
i18n_nexus_reposUsedh_remove.innerHTML = window.i18n.REMOVE
|
659 |
+
|
660 |
+
|
661 |
+
|
662 |
+
i18n_arpabet_dictionaries.innerHTML = window.i18n.DICTIONARIES
|
663 |
+
i18n_arpabet_words.innerHTML = window.i18n.WORDS
|
664 |
+
i18n_arpabet_reference.innerHTML = window.i18n.REFERENCE
|
665 |
+
arpabet_word_search_input.placeholder = window.i18n.SEARCH_WORDS
|
666 |
+
i18n_arpabet_ckbx_only_enabled.placeholder = window.i18n.ONLY_ENABLED
|
667 |
+
i18n_arpabet_word_is.innerHTML = window.i18n.WORD_IS
|
668 |
+
arpabet_save.innerHTML = window.i18n.SAVE
|
669 |
+
i18n_arpabetWordsListh_word.innerHTML = window.i18n.WORD
|
670 |
+
i18n_arpabetWordsListh_delete.innerHTML = window.i18n.DELETE
|
671 |
+
arpabet_enableall_button.innerHTML = window.i18n.ENABLE_ALL
|
672 |
+
arpabet_disableall_button.innerHTML = window.i18n.DISABLE_ALL
|
673 |
+
arpabet_prev_btn.innerHTML = window.i18n.PREV
|
674 |
+
arpabet_next_btn.innerHTML = window.i18n.NEXT
|
675 |
+
|
676 |
+
|
677 |
+
selectedGameDisplay.innerHTML = window.i18n.SELECT_GAME
|
678 |
+
voiceSearchInput.placeholder = window.i18n.SEARCH_VOICES
|
679 |
+
titleName.innerHTML = window.i18n.SELECT_VOICE_TYPE
|
680 |
+
generateVoiceButton.innerHTML = window.i18n.GENERATE_VOICE
|
681 |
+
keepSampleButton.innerHTML = window.i18n.KEEP_SAMPLE
|
682 |
+
|
683 |
+
i18n_seq_edit_edit.innerHTML = window.i18n.EDIT_IS
|
684 |
+
i18n_seq_edit_view.innerHTML = window.i18n.VIEW_IS
|
685 |
+
i18n_pitch.innerHTML = window.i18n.PITCH_IS
|
686 |
+
i18n_energy.innerHTML = window.i18n.ENERGY_IS
|
687 |
+
i18n_duration.innerHTML = window.i18n.DURATION_IS
|
688 |
+
i18n_emotion.innerHTML = window.i18n.EMOTION_IS
|
689 |
+
i18n_emotion_is.innerHTML = window.i18n.EMOTION_IS
|
690 |
+
seq_edit_view_pitch_energy.innerHTML = window.i18n.PITCH_AND_ENERGY
|
691 |
+
seq_edit_view_pitch.innerHTML = window.i18n.PITCH
|
692 |
+
seq_edit_view_energy.innerHTML = window.i18n.ENERGY
|
693 |
+
seq_edit_view_emAngry.innerHTML = window.i18n.EMOTION_IS_ANGRY
|
694 |
+
seq_edit_view_emHappy.innerHTML = window.i18n.EMOTION_IS_HAPPY
|
695 |
+
seq_edit_view_emSad.innerHTML = window.i18n.EMOTION_IS_SAD
|
696 |
+
seq_edit_view_emSurprise.innerHTML = window.i18n.EMOTION_IS_SURPRISE
|
697 |
+
seq_edit_edit_pitch.innerHTML = window.i18n.PITCH
|
698 |
+
seq_edit_edit_energy.innerHTML = window.i18n.ENERGY
|
699 |
+
seq_edit_edit_emotion.innerHTML = window.i18n.EMOTION
|
700 |
+
|
701 |
+
i18n_vramUsage.innerHTML = window.i18n.VRAM_USAGE
|
702 |
+
i18n_length.innerHTML = window.i18n.LENGTH
|
703 |
+
resetLetter_btn.innerHTML = window.i18n.RESET_LETTER
|
704 |
+
i18n_autoregen.innerHTML = window.i18n.AUTO_REGEN
|
705 |
+
i18n_vocoder.innerHTML = window.i18n.VOCODER
|
706 |
+
i18n_use_SR.innerHTML = window.i18n.USE_SR_IS
|
707 |
+
i18n_batch_useSR.innerHTML = window.i18n.USE_SR
|
708 |
+
i18n_use_SR.title = window.i18n.USE_SR_TITLE
|
709 |
+
i18n_use_cleanup.innerHTML = window.i18n.USE_CLEANUP_IS
|
710 |
+
i18n_batch_useCleanUp.innerHTML = window.i18n.USE_CLEANUP
|
711 |
+
i18n_base_lang.innerHTML = window.i18n.BASE_LANGUAGE_IS
|
712 |
+
i18n_style_emb_is.innerHTML = window.i18n.STYLE_EMB_IS
|
713 |
+
i18n_style.innerHTML = window.i18n.STYLE_EMB_IS
|
714 |
+
i18n_base_style_emb_is.innerHTML = window.i18n.BASE_STYLE_EMB_IS
|
715 |
+
default_opt_style_emb.innerHTML = window.i18n.DEFAULT
|
716 |
+
style_emb_manage_btn.innerHTML = window.i18n.MANAGE
|
717 |
+
|
718 |
+
batch_paginationPrev.innerHTML = window.i18n.PREVIOUS
|
719 |
+
main_paginationPrev.innerHTML = window.i18n.PREVIOUS
|
720 |
+
batch_paginationNext.innerHTML = window.i18n.NEXT
|
721 |
+
main_paginationNext.innerHTML = window.i18n.NEXT
|
722 |
+
i18n_page.innerHTML = window.i18n.PAGE
|
723 |
+
i18n_page_main.innerHTML = window.i18n.PAGE
|
724 |
+
i18n_batchLPS.innerHTML = window.i18n.LINES_PER_SECOND
|
725 |
+
i18n_etaFinished.innerHTML = window.i18n.ETA_FINISHED
|
726 |
+
nexusNameDisplay.innerHTML = window.i18n.LOGGED_IN_AS
|
727 |
+
i18n_games.innerHTML = window.i18n.GAMES
|
728 |
+
nexusGamesListEnableAllBtn.innerHTML = window.i18n.ENABLE_ALL
|
729 |
+
nexusGamesListDisableAllBtn.innerHTML = window.i18n.DISABLE_ALL
|
730 |
+
i18n_models.innerHTML = window.i18n.MODELS
|
731 |
+
i18n_showNewUpdated.innerHTML = window.i18n.SHOW_NEW_UPDATED
|
732 |
+
nexusCheckNow.innerHTML = window.i18n.CHECK_NOW
|
733 |
+
nexusManageReposButton.innerHTML = window.i18n.MANAGE_REPOS
|
734 |
+
nexusLogInButton.innerHTML = window.i18n.LOG_IN
|
735 |
+
i18n_nexush_name.innerHTML = window.i18n.NAME
|
736 |
+
i18n_nexush_author.innerHTML = window.i18n.AUTHOR
|
737 |
+
i18n_nexush_version.innerHTML = window.i18n.VERSION
|
738 |
+
i18n_nexush_date.innerHTML = window.i18n.DATE
|
739 |
+
i18n_nexush_type.innerHTML = window.i18n.TYPE
|
740 |
+
i18n_nexush_notes.innerHTML = window.i18n.NOTES
|
741 |
+
i18n_nexusDownloading.innerHTML = window.i18n.DOWNLOADING
|
742 |
+
i18n_nexusInstalling.innerHTML = window.i18n.INSTALLING
|
743 |
+
i18n_nexusFinished.innerHTML = window.i18n.FINISHED
|
744 |
+
nexusDownloadAllBtn.innerHTML = window.i18n.DOWNLOAD_ALL
|
745 |
+
i18n_repositories.innerHTML = window.i18n.REPOSITORIES
|
746 |
+
|
747 |
+
|
748 |
+
|
749 |
+
i18n_settings_curr_install.innerHTML = window.i18n.CURR_INSTALL
|
750 |
+
setting_change_installation.innerHTML = window.i18n.CHANGE_TO_GPU
|
751 |
+
i18n_settings_useSound.innerHTML = window.i18n.USE_SOUND_ERR
|
752 |
+
i18n_settings_err_soundfile.innerHTML = window.i18n.ERR_SOUNDFILE
|
753 |
+
i18n_settings_showTOTD.innerHTML = window.i18n.TOTD_SHOW
|
754 |
+
setting_btnShowTOTD.innerHTML = window.i18n.SHOW_NOW
|
755 |
+
i18n_settings_unseenTOTD.innerHTML = window.i18n.TOTD_SHOW_UNSEEN
|
756 |
+
i18n_settings_playChangedAudio.innerHTML = window.i18n.SETTINGS_PLAYCHANGEDAUDIO
|
757 |
+
// i18n_setting_ffmpeg_preapply.innerHTML = window.i18n.SETTINGS_PREAPPLY_FFMPEG
|
758 |
+
i18n_setting_useNR.innerHTML = window.i18n.SETTINGS_USE_NR
|
759 |
+
i18n_settings_doubleAmpDisplay.innerHTML = window.i18n.SETTINGS_DOUBLE_AMP_DISPLAY
|
760 |
+
i18n_settings_csv_delimiter.innerHTML = window.i18n.SETTINGS_CSV_DELIMITER
|
761 |
+
i18n_settings_paginationSize.innerHTML = window.i18n.SETTINGS_PAGINATION_SIZE_BATCH
|
762 |
+
i18n_settings_arpabetPagination.innerHTML = window.i18n.SETTINGS_PAGINATION_SIZE_ARPABET
|
763 |
+
i18n_settings_max_filename_chars.innerHTML = window.i18n.SETTINGS_MAX_FILENAME_LENGTH
|
764 |
+
i18n_settings_clear_text_after_synth.innerHTML = window.i18n.SETTINGS_CLEAR_TEXT_AFTER_GENERATION
|
765 |
+
i18n_settings_groupVoiceID.innerHTML = window.i18n.SETTINGS_GROUP_VOICEID
|
766 |
+
i18n_settings_groupVocoder.innerHTML = window.i18n.SETTINGS_GROUP_VOCODER
|
767 |
+
|
768 |
+
|
769 |
+
voiceSamplesSearch.placeholder = window.i18n.SEARCH_OUTPUT
|
770 |
+
voiceSamplesSearchPrompt.placeholder = window.i18n.SEARCH_OUTPUT_PROMPT
|
771 |
+
i18n_sortByOutput.innerHTML = window.i18n.SORT_BY
|
772 |
+
voiceRecordsOrderByButton.innerHTML = window.i18n.NAME
|
773 |
+
voiceRecordsOrderByOrderButton.innerHTML = window.i18n.ASCENDING
|
774 |
+
voiceRecordsDeleteAllButton.innerHTML = window.i18n.DELETE_ALL
|
775 |
+
|
776 |
+
i18n_pluginsh_endorse.innerHTML = window.i18n.ENDORSE
|
777 |
+
|
778 |
+
i18n_vembVis.innerHTML = window.i18n.V_EMB_VIS
|
779 |
+
i18n_games_vemb.innerHTML = window.i18n.GAMES
|
780 |
+
i18n_voices.innerHTML = window.i18n.VOICES
|
781 |
+
i18n_vembShow.innerHTML = window.i18n.SHOW
|
782 |
+
i18n_vembName.innerHTML = window.i18n.NAME
|
783 |
+
i18n_vembGame.innerHTML = window.i18n.GAME
|
784 |
+
i18n_vembGender.innerHTML = window.i18n.GENDER
|
785 |
+
embeddingsSearchBar.placeholder = window.i18n.SEARCH_VOICES
|
786 |
+
nexusSearchBar.placeholder = window.i18n.SEARCH_VOICES
|
787 |
+
i18n_voiceName.innerHTML = window.i18n.VOICE_NAME_IS
|
788 |
+
i18n_genderIs.innerHTML = window.i18n.GENDER_IS
|
789 |
+
i18n_vemb_game.innerHTML = window.i18n.GAME_IS
|
790 |
+
embeddingsPreviewButton.innerHTML = window.i18n.PREVIEW
|
791 |
+
embeddingsLoadButton.innerHTML = window.i18n.LOAD
|
792 |
+
|
793 |
+
i18n_vemb_instr1.innerHTML = window.i18n.VEMB_INSTR_1
|
794 |
+
i18n_vemb_instr2.innerHTML = window.i18n.VEMB_INSTR_2
|
795 |
+
i18n_vemb_instr3.innerHTML = window.i18n.VEMB_INSTR_3
|
796 |
+
i18n_vemb_instr4.innerHTML = window.i18n.VEMB_INSTR_4
|
797 |
+
i18n_vemb_instr5.innerHTML = window.i18n.VEMB_INSTR_5
|
798 |
+
|
799 |
+
i18n_vemb_males.innerHTML = window.i18n.MALES
|
800 |
+
i18n_vemb_females.innerHTML = window.i18n.FEMALES
|
801 |
+
i18n_vemb_other.innerHTML = window.i18n.OTHER
|
802 |
+
|
803 |
+
i18n_showOnlyInstalled.innerHTML = window.i18n.SHOW_ONLY_INSTALED
|
804 |
+
i18n_vemb_keyIs.innerHTML = window.i18n.KEY_IS
|
805 |
+
i18n_vemb_game_option.innerHTML = window.i18n.GAME
|
806 |
+
i18n_vemb_gender_option.innerHTML = window.i18n.GENDER
|
807 |
+
i18n_algorithm.innerHTML = window.i18n.ALGORITHM
|
808 |
+
|
809 |
+
i18n_totd.innerHTML = window.i18n.TOTD
|
810 |
+
i18n_totd_show.innerHTML = window.i18n.TOTD_SHOW
|
811 |
+
i18n_totd_show_unseen.innerHTML = window.i18n.TOTD_SHOW_UNSEEN
|
812 |
+
totdPrevTipBtn.innerHTML = window.i18n.TOTD_PREV_TIP
|
813 |
+
totdNextTipBtn.innerHTML = window.i18n.TOTD_NEXT_TIP
|
814 |
+
totd_close.innerHTML = window.i18n.CLOSE
|
815 |
+
embeddingsCloseHelpUI.innerHTML = window.i18n.CLOSE
|
816 |
+
nexusMenuButton.innerHTML = window.i18n.GET_MORE_VOICES
|
817 |
+
|
818 |
+
i18n_embeddings.innerHTML = window.i18n.STYLE_EMBEDDINGS
|
819 |
+
i18n_style_emb_wavpath.innerHTML = window.i18n.STYLE_EMB_WAVPATH
|
820 |
+
i18n_style_emb_author.innerHTML = window.i18n.AUTHOR
|
821 |
+
i18n_style_emb_gameId.innerHTML = window.i18n.GAME_ID
|
822 |
+
i18n_style_emb_voiceId.innerHTML = window.i18n.VOICE_ID
|
823 |
+
i18n_style_emb_name.innerHTML = window.i18n.EMB_NAME
|
824 |
+
i18n_style_emb_description.innerHTML = window.i18n.EMB_DESCRIPTION
|
825 |
+
i18n_style_emb_id.innerHTML = window.i18n.STYLE_EMB_ID
|
826 |
+
wavFilepathForEmbComputeInput.placeholder = window.i18n.STYLE_EMB_WAVPATH_PLACEHOLDER
|
827 |
+
i18n_style_emb_values.innerHTML = window.i18n.STYLE_EMB_VALUES
|
828 |
+
i18n_styleembsh_enabled.innerHTML = window.i18n.ENABLED
|
829 |
+
i18n_styleembsh_name.innerHTML = window.i18n.EMB_NAME
|
830 |
+
i18n_styleembsh_gameID.innerHTML = window.i18n.GAME_ID
|
831 |
+
i18n_styleembsh_voiceID.innerHTML = window.i18n.VOICE_ID
|
832 |
+
// i18n_styleembsh_actions.innerHTML = window.i18n.ACTIONS
|
833 |
+
i18n_styleembsh_description.innerHTML = window.i18n.DESCRIPTION
|
834 |
+
i18n_styleembsh_embId.innerHTML = window.i18n.EMB_ID
|
835 |
+
i18n_styleembsh_version.innerHTML = window.i18n.VERSION
|
836 |
+
// i18n_styleembsh_endorse.innerHTML = window.i18n.ENDORSE
|
837 |
+
styleEmbSave.innerHTML = window.i18n.SAVE
|
838 |
+
styleEmbDelete.innerHTML = window.i18n.DELETE
|
839 |
+
|
840 |
+
|
841 |
+
reset_btn.innerHTML = window.i18n.RESET
|
842 |
+
amplify_btn.innerHTML = window.i18n.AMPLIFY
|
843 |
+
jitter_btn.innerHTML = window.i18n.JITTER
|
844 |
+
flatten_btn.innerHTML = window.i18n.FLATTEN
|
845 |
+
increase_btn.innerHTML = window.i18n.RAISE
|
846 |
+
decrease_btn.innerHTML = window.i18n.LOWER
|
847 |
+
i18n_pacing.innerHTML = window.i18n.PACING
|
848 |
+
|
849 |
+
i18n_settings.innerHTML = window.i18n.SETTINGS
|
850 |
+
i18n_setting_gpu.innerHTML = window.i18n.SETTINGS_GPU
|
851 |
+
i18n_setting_autoplay.innerHTML = window.i18n.SETTINGS_AUTOPLAY
|
852 |
+
i18n_setting_defaulthifi.innerHTML = window.i18n.SETTINGS_DEFAULT_HIFI
|
853 |
+
i18n_setting_keeppacing.innerHTML = window.i18n.SETTINGS_KEEP_PACING
|
854 |
+
// i18n_setting_tooltip.innerHTML = window.i18n.SETTINGS_TOOLTIP
|
855 |
+
|
856 |
+
i18n_showDiscordStatus.innerHTML = window.i18n.SETTINGS_SHOW_DISCORD
|
857 |
+
// i18n_setting_darkmode.innerHTML = window.i18n.SETTINGS_DARKMODE
|
858 |
+
i18n_setting_promptfontsize.innerHTML = window.i18n.SETTINGS_PROMPTSIZE
|
859 |
+
i18n_setting_bg_fade.innerHTML = window.i18n.SETTINGS_BG_FADE
|
860 |
+
i18n_setting_autoreloadvoices.innerHTML = window.i18n.SETTINGS_AUTORELOADVOICES
|
861 |
+
i18n_setting_keepeditorstate.innerHTML = window.i18n.SETTINGS_KEEPEDITORSTATE
|
862 |
+
i18n_setting_pitchrangeoverride.innerHTML = window.i18n.SETTINGS_PITCHRANGEOVERRIDE
|
863 |
+
i18n_setting_outputjson.innerHTML = window.i18n.SETTINGS_OUTPUTJSON
|
864 |
+
i18n_setting_seqnumbering.innerHTML = window.i18n.SETTINGS_SEQNUMBERING
|
865 |
+
i18n_setting_spacepadding.innerHTML = window.i18n.SETTINGS_SPACEPADDING
|
866 |
+
i18n_setting_base_speaker.innerHTML = window.i18n.SETTINGS_BASE_SPEAKER
|
867 |
+
i18n_setting_alt_speaker.innerHTML = window.i18n.SETTINGS_ALT_SPEAKER
|
868 |
+
i18n_setting_external_edit.innerHTML = window.i18n.SETTINGS_EXTERNALEDIT
|
869 |
+
i18n_setting_ffmpeg.innerHTML = window.i18n.SETTINGS_FFMPEG
|
870 |
+
i18n_setting_ffmpeg_format.innerHTML = window.i18n.SETTINGS_FFMPEG_FORMAT
|
871 |
+
i18n_setting_ffmpeg_hz.innerHTML = window.i18n.SETTINGS_FFMPEG_HZ
|
872 |
+
i18n_setting_ffmpeg_padstart.innerHTML = window.i18n.SETTINGS_FFMPEG_PADSTART
|
873 |
+
i18n_setting_ffmpeg_padend.innerHTML = window.i18n.SETTINGS_FFMPEG_PADEND
|
874 |
+
i18n_setting_ffmpeg_pitchMult.innerHTML = window.i18n.SETTINGS_FFMPEG_PITCHMULT
|
875 |
+
i18n_setting_ffmpeg_tempo.innerHTML = window.i18n.SETTINGS_FFMPEG_TEMPO
|
876 |
+
i18n_setting_ffmpeg_deessing.innerHTML = window.i18n.SETTINGS_FFMPEG_DEESSING
|
877 |
+
i18n_setting_ffmpeg_bitdepth.innerHTML = window.i18n.SETTINGS_FFMPEG_BITDEPTH
|
878 |
+
i18n_setting_ffmpeg_nr.innerHTML = window.i18n.SETTINGS_FFMPEG_NR
|
879 |
+
i18n_setting_ffmpeg_nf.innerHTML = window.i18n.SETTINGS_FFMPEG_NF
|
880 |
+
i18n_setting_ffmpeg_amplitude.innerHTML = window.i18n.SETTINGS_FFMPEG_AMPLITUDE
|
881 |
+
i18n_setting_batch_json.innerHTML = window.i18n.SETTINGS_BATCH_JSON
|
882 |
+
// i18n_setting_batch_fastmode.innerHTML = window.i18n.SETTINGS_BATCH_FASTMODE
|
883 |
+
// i18n_settings_batch_mp_max_parallelizations.innerHTML = window.i18n.SETTINGS_BATCH_FASTMODE_MAX_PARALLELIZATIONS
|
884 |
+
i18n_setting_batch_multip.innerHTML = window.i18n.SETTINGS_BATCH_USEMULTIP
|
885 |
+
i18n_setting_batch_multip_count.innerHTML = window.i18n.SETTINGS_BATCH_MULTIPCOUNT
|
886 |
+
i18n_setting_microphone.innerHTML = window.i18n.SETTINGS_MICROPHONE
|
887 |
+
// i18n_setting_autogeneratevoice.innerHTML = window.i18n.SETTINGS_AUTOGENERATEVOICE
|
888 |
+
i18n_setting_s2s_bgnoise.innerHTML = window.i18n.SETTINGS_S2S_BGNOISE
|
889 |
+
s2s_settingsRecNoiseBtn.innerHTML = window.i18n.SETTINGS_S2S_RECNOISE
|
890 |
+
// i18n_setting_s2s_bgnoise_strength.innerHTML = window.i18n.SETTINGS_S2S_BGNOISE_STRENGTH
|
891 |
+
i18n_vc_strength.innerHTML = window.i18n.SETTINGS_VC_STRENGTH
|
892 |
+
reset_settings_btn.innerHTML = window.i18n.SETTINGS_RESET_SETTINGS
|
893 |
+
reset_paths_btn.innerHTML = window.i18n.SETTINGS_RESET_PATHS
|
894 |
+
|
895 |
+
updatesVersions.innerHTML = window.i18n.UPDATES_VERSION
|
896 |
+
i18n_updateslog.innerHTML = window.i18n.UPDATES_LOG
|
897 |
+
checkUpdates.innerHTML = window.i18n.UPDATES_CHECK
|
898 |
+
|
899 |
+
i18n_plugins.innerHTML = window.i18n.PLUGINS
|
900 |
+
i18n_plugins_trusted.innerHTML = window.i18n.PLUGINS_TRUSTED
|
901 |
+
|
902 |
+
i18n_pluginsh_enabled.innerHTML = window.i18n.PLUGINSH_ENABLED
|
903 |
+
i18n_pluginsh_order.innerHTML = window.i18n.PLUGINSH_ORDER
|
904 |
+
i18n_pluginsh_name.innerHTML = window.i18n.PLUGINSH_NAME
|
905 |
+
i18n_pluginsh_author.innerHTML = window.i18n.PLUGINSH_AUTHOR
|
906 |
+
i18n_pluginsh_version.innerHTML = window.i18n.PLUGINSH_VERSION
|
907 |
+
i18n_pluginsh_type.innerHTML = window.i18n.PLUGINSH_TYPE
|
908 |
+
i18n_pluginsh_minv.innerHTML = window.i18n.PLUGINSH_MINV
|
909 |
+
i18n_pluginsh_maxv.innerHTML = window.i18n.PLUGINSH_MAXV
|
910 |
+
i18n_pluginsh_description.innerHTML = window.i18n.PLUGINSH_DESCRIPTION
|
911 |
+
i18n_pluginsh_pluginid.innerHTML = window.i18n.PLUGINSH_PLUGINID
|
912 |
+
plugins_moveUpBtn.innerHTML = window.i18n.PLUGINS_MOVEUP
|
913 |
+
plugins_moveDownBtn.innerHTML = window.i18n.PLUGINS_MOVEDOWN
|
914 |
+
plugins_applyBtn.innerHTML = window.i18n.PLUGINS_APPLY
|
915 |
+
|
916 |
+
i18n_appinfo.innerHTML = window.i18n.APP_INFO
|
917 |
+
i18n_appinfo_instr_1.innerHTML = window.i18n.APP_INFO_INSTR_1
|
918 |
+
i18n_appinfo_instr_2.innerHTML = window.i18n.APP_INFO_INSTR_2
|
919 |
+
i18n_appinfo_instr_3.innerHTML = window.i18n.APP_INFO_INSTR_3
|
920 |
+
i18n_appinfo_instr_4.innerHTML = window.i18n.APP_INFO_INSTR_4
|
921 |
+
i18n_appinfo_instr_5.innerHTML = window.i18n.APP_INFO_INSTR_5
|
922 |
+
|
923 |
+
i18n_keyboard_reference.innerHTML = window.i18n.KEYBOARD_REFERENCE
|
924 |
+
i18n_keyboard_enter.innerHTML = window.i18n.KEYBOARD_ENTER
|
925 |
+
i18n_keyboard_enter_do.innerHTML = window.i18n.KEYBOARD_ENTER_DO
|
926 |
+
i18n_keyboard_escape.innerHTML = window.i18n.KEYBOARD_ESCAPE
|
927 |
+
i18n_keyboard_escape_do.innerHTML = window.i18n.KEYBOARD_ESCAPE_DO
|
928 |
+
i18n_keyboard_space.innerHTML = window.i18n.KEYBOARD_SPACE
|
929 |
+
i18n_keyboard_space_do.innerHTML = window.i18n.KEYBOARD_SPACE_DO
|
930 |
+
i18n_keyboard_ctrls.innerHTML = window.i18n.KEYBOARD_CTRLS
|
931 |
+
i18n_keyboard_ctrls_do.innerHTML = window.i18n.KEYBOARD_CTRLS_DO
|
932 |
+
i18n_keyboard_ctrlshifts.innerHTML = window.i18n.KEYBOARD_CTRLSHIFTS
|
933 |
+
i18n_keyboard_ctrlshifts_do.innerHTML = window.i18n.KEYBOARD_CTRLSHIFTS_DO
|
934 |
+
i18n_keyboard_yn.innerHTML = window.i18n.KEYBOARD_YN
|
935 |
+
i18n_keyboard_yn_do.innerHTML = window.i18n.KEYBOARD_YN_DO
|
936 |
+
i18n_keyboard_lr.innerHTML = window.i18n.KEYBOARD_LR
|
937 |
+
i18n_keyboard_lr_do.innerHTML = window.i18n.KEYBOARD_LR_DO
|
938 |
+
i18n_keyboard_shift_lr.innerHTML = window.i18n.KEYBOARD_SHIFT_LR
|
939 |
+
i18n_keyboard_shift_lr_do.innerHTML = window.i18n.KEYBOARD_SHIFT_LR_DO
|
940 |
+
|
941 |
+
i18n_keyboard_alt_ctrl_lr.innerHTML = window.i18n.KEYBOARD_ALT_CTRL_LR
|
942 |
+
i18n_keyboard_alt_ctrl_lr_do.innerHTML = window.i18n.KEYBOARD_ALT_CTRL_LR_DO
|
943 |
+
i18n_keyboard_ctrla.innerHTML = window.i18n.KEYBOARD_CTRLA
|
944 |
+
i18n_keyboard_ctrla_do.innerHTML = window.i18n.KEYBOARD_CTRLA_DO
|
945 |
+
|
946 |
+
i18n_keyboard_ud.innerHTML = window.i18n.KEYBOARD_UD
|
947 |
+
i18n_keyboard_ud_do.innerHTML = window.i18n.KEYBOARD_UD_DO
|
948 |
+
i18n_keyboard_ctrl_lr.innerHTML = window.i18n.KEYBOARD_CTRL_LR
|
949 |
+
i18n_keyboard_ctrl_lr_do.innerHTML = window.i18n.KEYBOARD_CTRL_LR_DO
|
950 |
+
i18n_keyboard_ctrl_ud.innerHTML = window.i18n.KEYBOARD_CTRL_UD
|
951 |
+
i18n_keyboard_ctrl_ud_do.innerHTML = window.i18n.KEYBOARD_CTRL_UD_DO
|
952 |
+
i18n_keyboard_ctrlshiftud.innerHTML = window.i18n.KEYBOARD_CTRLSHIFTUD
|
953 |
+
i18n_keyboard_ctrlshiftud_do.innerHTML = window.i18n.KEYBOARD_CTRLSHIFTUD_DO
|
954 |
+
i18n_keyboard_ctrlenter.innerHTML = window.i18n.KEYBOARD_CTRLENTER
|
955 |
+
i18n_keyboard_ctrlenter_do.innerHTML = window.i18n.KEYBOARD_CTRLENTER_DO
|
956 |
+
|
957 |
+
i18n_support.innerHTML = window.i18n.SUPPORT
|
958 |
+
// i18n_support_link.innerHTML = window.i18n.SUPPORT_LINK
|
959 |
+
i18n_support_thanks.innerHTML = window.i18n.SUPPORT_THANKS
|
960 |
+
|
961 |
+
searchGameInput.placeholder = window.i18n.SEARCH_GAMES
|
962 |
+
searchSettingsInput.placeholder = window.i18n.SEARCH_SETTINGS
|
963 |
+
|
964 |
+
i18n_eula_accept.innerHTML = window.i18n.EULA_ACCEPT
|
965 |
+
EULA_closeButon.innerHTML = window.i18n.EULA_CLOSE
|
966 |
+
|
967 |
+
i18n_batch_synthesis.innerHTML = window.i18n.BATCH_SYNTHESIS
|
968 |
+
i18n_batchsize.innerHTML = window.i18n.BATCH_SIZE
|
969 |
+
batch_generateSample.innerHTML = window.i18n.BATCH_GEN_SAMPLE
|
970 |
+
batch_instructions_btn.innerHTML = window.i18n.BATCH_INSTRUCTIONS
|
971 |
+
batchDropZoneNote.innerHTML = window.i18n.BATCH_DROPZONE
|
972 |
+
|
973 |
+
i18n_batchh_num.innerHTML = window.i18n.BATCHH_NUM
|
974 |
+
i18n_batchh_status.innerHTML = window.i18n.BATCHH_STATUS
|
975 |
+
i18n_batchh_actions.innerHTML = window.i18n.BATCHH_ACTIONS
|
976 |
+
i18n_nexush_actions.innerHTML = window.i18n.BATCHH_ACTIONS
|
977 |
+
i18n_batchh_game.innerHTML = window.i18n.BATCHH_GAME
|
978 |
+
i18n_nexush_game.innerHTML = window.i18n.BATCHH_GAME
|
979 |
+
i18n_batchh_voice.innerHTML = window.i18n.BATCHH_VOICE
|
980 |
+
i18n_batchh_text.innerHTML = window.i18n.BATCHH_TEXT
|
981 |
+
i18n_batchh_vocoder.innerHTML = window.i18n.BATCHH_VOCODER
|
982 |
+
i18n_batchh_vc_style.innerHTML = window.i18n.BATCHH_VC_STYLE
|
983 |
+
i18n_batchh_outpath.innerHTML = window.i18n.BATCHH_OUTPATH
|
984 |
+
i18n_batchh_pacing.innerHTML = window.i18n.BATCHH_PACING
|
985 |
+
i18n_batchh_pitch_amp.innerHTML = window.i18n.BATCHH_PITCH_AMP
|
986 |
+
i18n_batchh_base_lang.innerHTML = window.i18n.BATCHH_BASE_LANG
|
987 |
+
batch_outputFolderInput.placeholder = window.i18n.BATCH_ABS_DIR_PLACEHOLDER
|
988 |
+
|
989 |
+
i18n_batch_cleardir.innerHTML = window.i18n.BATCH_CLEAR_DIR
|
990 |
+
i18n_batch_skip.innerHTML = window.i18n.BATCH_SKIP
|
991 |
+
i18n_batch_outputNumerically.innerHTML = window.i18n.BATCH_OUTPUTNUMERICALLY
|
992 |
+
batch_progressNotes.innerHTML = window.i18n.BATCH_CURRENTLYDOING
|
993 |
+
batch_synthesizeBtn.innerHTML = window.i18n.BATCH_SYNTHESIZE
|
994 |
+
batch_pauseBtn.innerHTML = window.i18n.BATCH_PAUSE
|
995 |
+
batch_stopBtn.innerHTML = window.i18n.BATCH_STOP
|
996 |
+
batch_clearBtn.innerHTML = window.i18n.BATCH_CLEAR
|
997 |
+
batch_openDirBtn.innerHTML = window.i18n.BATCH_OPENOUT
|
998 |
+
|
999 |
+
s2s_voiceId_selected_label.innerHTML = window.i18n.VC_ONLY_FOR_V3
|
1000 |
+
i18n_settings_model_version_highlight.innerHTML = window.i18n.SETTING_HIGHLIGHT_ONLY_MODELS_V
|
1001 |
+
i18n_setting_output_files_pagination_size.innerHTML = window.i18n.SETTING_OUTPUTFILES_PAGINATION
|
1002 |
+
|
1003 |
+
openDiscord.innerHTML = window.i18n.JOIN_DISCORD
|
1004 |
+
|
1005 |
+
i18n_workbench.innerHTML = window.i18n.VOICE_CRAFTING_WORKBENCH
|
1006 |
+
voiceWorkbenchRefAFilePath.innerHTML = window.i18n.FROM_FILE_IS_DRAG_N_DROP
|
1007 |
+
voiceWorkbenchRefBFilePath.innerHTML = window.i18n.FROM_FILE_IS_DRAG_N_DROP
|
1008 |
+
|
1009 |
+
voiceWorkbenchInputTextArea.innerHTML = window.i18n.VW_INPUT_TEXTAREA_PLACEHOLDER
|
1010 |
+
|
1011 |
+
voiceWorkbenchGenerateSampleButton.innerHTML = window.i18n.GENERATE
|
1012 |
+
i18n_vw_current.innerHTML = window.i18n.CURRENT
|
1013 |
+
voiceWorkbenchAudioCurrentPlayPauseBtn.innerHTML = window.i18n.PLAY
|
1014 |
+
voiceWorkbenchAudioCurrentSaveBtn.innerHTML = window.i18n.SAVE
|
1015 |
+
voiceWorkbenchAudioNewPlayBtn.innerHTML = window.i18n.PLAY
|
1016 |
+
voiceWorkbenchAudioNewSaveBtn.innerHTML = window.i18n.SAVE
|
1017 |
+
|
1018 |
+
i18n_vw_current_emb.innerHTML = window.i18n.CURRENT_EMB
|
1019 |
+
i18n_vw_current_delta.innerHTML = window.i18n.CURRENT_DELTA
|
1020 |
+
i18n_vw_strength.innerHTML = window.i18n.STRENGTH
|
1021 |
+
voiceWorkshopApplyDeltaButton.innerHTML = window.i18n.APPLY_DELTA
|
1022 |
+
i18n_refAF_a.innerHTML = window.i18n.VW_REF_FILE_A
|
1023 |
+
i18n_refAF_b.innerHTML = window.i18n.VW_REF_FILE_B
|
1024 |
+
|
1025 |
+
i18n_vw_basemodel.innerHTML = window.i18n.VW_BASE_MODEL
|
1026 |
+
i18n_vw_game.innerHTML = window.i18n.GAME
|
1027 |
+
i18n_vw_voicename.innerHTML = window.i18n.VOICE_NAME
|
1028 |
+
i18n_vw_voiceid.innerHTML = window.i18n.VOICE_ID
|
1029 |
+
voiceWorkbenchVoiceNameInput.placeholder = window.i18n.NAME_OF_YOUR_VOICE
|
1030 |
+
voiceWorkbenchVoiceIDInput.placeholder = window.i18n.UNIQUE_ID_FOR_VOICE
|
1031 |
+
i18n_vw_gender.innerHTML = window.i18n.GENDER
|
1032 |
+
i18n_vw_author.innerHTML = window.i18n.AUTHOR
|
1033 |
+
|
1034 |
+
voiceWorkbenchAuthorInput.placeholder = window.i18n.YOUR_NAME_FOR_CREDITS
|
1035 |
+
|
1036 |
+
i18n_vw_baselang.innerHTML = window.i18n.BASE_LANGUAGE
|
1037 |
+
voiceWorkbenchStartButton.innerHTML = window.i18n.START
|
1038 |
+
voiceWorkbenchCancelButton.innerHTML = window.i18n.CANCEL
|
1039 |
+
voiceWorkbenchDeleteButton.innerHTML = window.i18n.DELETE
|
1040 |
+
voiceWorkbenchSaveButton.innerHTML = window.i18n.SAVE
|
1041 |
+
|
1042 |
+
|
1043 |
+
splashNextButton1.innerHTML = window.i18n.NEXT
|
1044 |
+
|
1045 |
+
i18n_variant.innerHTML = window.i18n.VARIANT_IS
|
1046 |
+
i18n_reset_what_prompt.innerHTML = window.i18n.RESET_WHAT_PROMPT
|
1047 |
+
i18n_reset_what_tip.innerHTML = window.i18n.RESET_WHAT_TIP
|
1048 |
+
reset_what_confirm_btn.innerHTML = window.i18n.RESET
|
1049 |
+
i18n_batch_metadata_confirm.innerHTML = window.i18n.BATCH_METADATA_CONFIRM
|
1050 |
+
i18n_batch_metadata_tip.innerHTML = window.i18n.BATCH_METADATA_TIP
|
1051 |
+
i18n_batch_metadata_voiceID.innerHTML = window.i18n.VOICE_ID
|
1052 |
+
i18n_batch_metadata_gameID.innerHTML = window.i18n.GAME_ID
|
1053 |
+
i18n_batch_metadata_confirm_btn.innerHTML = window.i18n.CONFIRM
|
1054 |
+
batch_saveToCSV.innerHTML = window.i18n.SAVE_TO_CSV
|
1055 |
+
|
1056 |
+
arpabetIcon.title = "ARPAbet"
|
1057 |
+
embeddingsIcon.title = "Embeddings visualiser"
|
1058 |
+
pluginsIcon.title = window.i18n.PLUGINS
|
1059 |
+
batchIcon.title = "Batch mode"
|
1060 |
+
updatesIcon.title = "Changelog"
|
1061 |
+
patreonIcon.title = "Patreon"
|
1062 |
+
infoIcon.title = window.i18n.INFO
|
1063 |
+
settingsCog.title = "Settings"
|
1064 |
+
workbenchIcon.title = window.i18n.WORKBENCH
|
1065 |
+
|
1066 |
+
}
|
1067 |
+
|
1068 |
+
|
1069 |
+
window.i18n.setEnglish()
|
1070 |
+
window.i18n.updateUI()
|
javascript/nexus.js
ADDED
@@ -0,0 +1,983 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
window.nexusModelsList = []
|
4 |
+
window.endorsedRepos = new Set()
|
5 |
+
window.nexusState = {
|
6 |
+
key: null,
|
7 |
+
applicationSlug: "xvasynth",
|
8 |
+
socket: null,
|
9 |
+
uuid: null,
|
10 |
+
token: null,
|
11 |
+
downloadQueue: [],
|
12 |
+
installQueue: [],
|
13 |
+
finished: 0,
|
14 |
+
repoLinks: [],
|
15 |
+
primaryColumnSort: "game",
|
16 |
+
columnSortModifier: -1
|
17 |
+
}
|
18 |
+
|
19 |
+
// TEMP, maybe move to utils
|
20 |
+
// ==========================
|
21 |
+
const http = require("http")
|
22 |
+
const https = require("https")
|
23 |
+
|
24 |
+
// Utility for printing out in the dev console all the numerical game IDs on the Nexus
|
25 |
+
window.nexusGameIdToGameName = {}
|
26 |
+
window.getAllNexusGameIDs = (gameName) => {
|
27 |
+
return new Promise((resolve) => {
|
28 |
+
getData("", undefined, "GET").then(results => {
|
29 |
+
results = gameName ? results.filter(x=>x.name.toLowerCase().includes(gameName)) : results
|
30 |
+
resolve(results)
|
31 |
+
})
|
32 |
+
})
|
33 |
+
}
|
34 |
+
|
35 |
+
window.mod_search_nexus = (game_id, query) => {
|
36 |
+
return new Promise(resolve => {
|
37 |
+
doFetch(`https://search.nexusmods.com/mods/?game_id=${game_id}&terms=${encodeURI(query.split(' ').toString())}&include_adult=true`)
|
38 |
+
.then(r=>r.text())
|
39 |
+
.then(r => {
|
40 |
+
try {
|
41 |
+
const data = JSON.parse(r).results.map(res => {
|
42 |
+
return {
|
43 |
+
downloads: res.downloads,
|
44 |
+
endorsements: res.endorsements,
|
45 |
+
game_id: res.game_id,
|
46 |
+
name: res.name,
|
47 |
+
author: res.username,
|
48 |
+
url: `https://www.nexusmods.com/${res.game_name}/mods/${res.mod_id}`
|
49 |
+
}
|
50 |
+
})
|
51 |
+
|
52 |
+
resolve([r.total, data])
|
53 |
+
} catch (e) {
|
54 |
+
window.appLogger.log(window.i18n.ERROR_FROM_NEXUS.replace("_1", r))
|
55 |
+
window.errorModal(window.i18n.ERROR_FROM_NEXUS.replace("_1", r))
|
56 |
+
}
|
57 |
+
})
|
58 |
+
})
|
59 |
+
}
|
60 |
+
|
61 |
+
window.nexusDownload = (url, dest) => {
|
62 |
+
return new Promise((resolve, reject) => {
|
63 |
+
const file = fs.createWriteStream(dest)
|
64 |
+
|
65 |
+
const request = https.get(url.replace("http:", "https:"), (response) => {
|
66 |
+
// check if response is success
|
67 |
+
if (response.statusCode !== 200) {
|
68 |
+
console.log("url", url)
|
69 |
+
console.log("Response status was " + response.statusCode, response)
|
70 |
+
resolve()
|
71 |
+
return
|
72 |
+
}
|
73 |
+
|
74 |
+
response.pipe(file)
|
75 |
+
})
|
76 |
+
|
77 |
+
file.on("finish", () => {
|
78 |
+
file.close()
|
79 |
+
resolve()
|
80 |
+
})
|
81 |
+
|
82 |
+
// check for request error too
|
83 |
+
request.on("error", (err) => {
|
84 |
+
fs.unlink(dest)
|
85 |
+
return reject(err.message)
|
86 |
+
})
|
87 |
+
|
88 |
+
file.on("error", (err) => { // Handle errors
|
89 |
+
fs.unlink(dest) // Delete the file async. (But we don't check the result)
|
90 |
+
return reject(err.message)
|
91 |
+
})
|
92 |
+
})
|
93 |
+
}
|
94 |
+
|
95 |
+
window.initNexus = () => {
|
96 |
+
const data = fs.readFileSync(`${window.path}/repositories.json`, "utf8")
|
97 |
+
window.nexusReposList = JSON.parse(data)
|
98 |
+
return new Promise((resolve) => {
|
99 |
+
|
100 |
+
window.nexusState.key = localStorage.getItem("nexus_API_key")
|
101 |
+
|
102 |
+
if (window.nexusState.key) {
|
103 |
+
window.showUserName()
|
104 |
+
nexusLogInButton.innerHTML = window.i18n.LOG_OUT
|
105 |
+
resolve()
|
106 |
+
} else {
|
107 |
+
try {
|
108 |
+
window.nexusState.socket = new WebSocket("wss://sso.nexusmods.com")
|
109 |
+
} catch (e) {
|
110 |
+
console.log(e)
|
111 |
+
}
|
112 |
+
|
113 |
+
window.nexusState.socket.onclose = event => {
|
114 |
+
console.log("socket closed")
|
115 |
+
if (!window.nexusState.key) {
|
116 |
+
setTimeout(() => {window.initNexus()}, 5000)
|
117 |
+
}
|
118 |
+
}
|
119 |
+
|
120 |
+
window.nexusState.socket.onmessage = event => {
|
121 |
+
const response = JSON.parse(event.data)
|
122 |
+
|
123 |
+
if (response && response.success) {
|
124 |
+
if (response.data.hasOwnProperty('connection_token')) {
|
125 |
+
localStorage.setItem('connection_token', response.data.connection_token)
|
126 |
+
} else if (response.data.hasOwnProperty('api_key')) {
|
127 |
+
console.log("API Key Received: " + response.data.api_key)
|
128 |
+
window.nexusState.key = response.data.api_key
|
129 |
+
localStorage.setItem('uuid', window.nexusState.uuid)
|
130 |
+
localStorage.setItem('nexus_API_key', window.nexusState.key)
|
131 |
+
window.showUserName()
|
132 |
+
window.pluginsManager.updateUI()
|
133 |
+
closeModal(undefined, nexusContainer)
|
134 |
+
nexusLogInButton.innerHTML = window.i18n.LOG_OUT
|
135 |
+
resolve()
|
136 |
+
}
|
137 |
+
} else {
|
138 |
+
window.errorModal(`${window.i18n.ERR_LOGGING_INTO_NEXUS}: ${response.error}`)
|
139 |
+
reject()
|
140 |
+
}
|
141 |
+
}
|
142 |
+
}
|
143 |
+
})
|
144 |
+
}
|
145 |
+
nexusLogInButton.addEventListener("click", () => {
|
146 |
+
|
147 |
+
if (nexusLogInButton.innerHTML==window.i18n.LOG_IN) {
|
148 |
+
window.spinnerModal(window.i18n.LOGGING_INTO_NEXUS)
|
149 |
+
|
150 |
+
window.nexusState.uuid = localStorage.getItem("uuid")
|
151 |
+
window.nexusState.token = localStorage.getItem("connection_token")
|
152 |
+
|
153 |
+
if (window.nexusState.uuid==null) {
|
154 |
+
window.nexusState.uuid = uuidv4()
|
155 |
+
}
|
156 |
+
|
157 |
+
const data = {
|
158 |
+
id: window.nexusState.uuid,
|
159 |
+
token: window.nexusState.token,
|
160 |
+
protocol: 2
|
161 |
+
}
|
162 |
+
window.nexusState.socket.send(JSON.stringify(data))
|
163 |
+
|
164 |
+
shell.openExternal(`https://www.nexusmods.com/sso?id=${window.nexusState.uuid}&application=${window.nexusState.applicationSlug}`)
|
165 |
+
|
166 |
+
} else {
|
167 |
+
nexusNameDisplayContainer.style.opacity = 0
|
168 |
+
localStorage.removeItem("nexus_API_key")
|
169 |
+
localStorage.removeItem("uuid")
|
170 |
+
localStorage.removeItem("connection_token")
|
171 |
+
nexusAvatar.innerHTML = ""
|
172 |
+
nexusUserName.innerHTML = ""
|
173 |
+
nexusLogInButton.innerHTML = window.i18n.LOG_IN
|
174 |
+
window.nexusState.uuid = null
|
175 |
+
window.nexusState.key = null
|
176 |
+
window.pluginsManager.updateUI()
|
177 |
+
}
|
178 |
+
})
|
179 |
+
|
180 |
+
|
181 |
+
|
182 |
+
window.downloadFile = ([nexusGameId, nexusRepoId, outputFileName, fileId]) => {
|
183 |
+
nexusDownloadLog.appendChild(createElem("div", `Downloading: ${outputFileName}`))
|
184 |
+
return new Promise(async (resolve, reject) => {
|
185 |
+
if (!fs.existsSync(`${window.path}/downloads`)) {
|
186 |
+
fs.mkdirSync(`${window.path}/downloads`)
|
187 |
+
}
|
188 |
+
|
189 |
+
const downloadLink = await getData(`${nexusGameId}/mods/${nexusRepoId}/files/${fileId}/download_link.json`)
|
190 |
+
if (!downloadLink.length && downloadLink.code==403) {
|
191 |
+
|
192 |
+
window.errorModal(`${window.i18n.NEXUS_PREMIUM}<br><br>${window.i18n.NEXUS_ORIG_ERR}:<br>${downloadLink.message}`).then(() => {
|
193 |
+
const queueIndex = window.nexusState.downloadQueue.findIndex(it => it[1]==fileId)
|
194 |
+
window.nexusState.downloadQueue.splice(queueIndex, 1)
|
195 |
+
nexusDownloadingCount.innerHTML = window.nexusState.downloadQueue.length
|
196 |
+
|
197 |
+
nexusDownloadLog.appendChild(createElem("div", `${window.i18n.FAILED_DOWNLOAD}: ${outputFileName}`))
|
198 |
+
|
199 |
+
reject()
|
200 |
+
})
|
201 |
+
|
202 |
+
} else {
|
203 |
+
await window.nexusDownload(downloadLink[0].URI.replace("https", "http"), `${window.path}/downloads/${outputFileName}.zip`)
|
204 |
+
|
205 |
+
const queueIndex = window.nexusState.downloadQueue.findIndex(it => it[1]==fileId)
|
206 |
+
window.nexusState.downloadQueue.splice(queueIndex, 1)
|
207 |
+
nexusDownloadingCount.innerHTML = window.nexusState.downloadQueue.length
|
208 |
+
|
209 |
+
resolve()
|
210 |
+
}
|
211 |
+
})
|
212 |
+
|
213 |
+
}
|
214 |
+
window.installDownloadedModel = ([game, zipName]) => {
|
215 |
+
nexusDownloadLog.appendChild(createElem("div", `${window.i18n.INSTALLING} ${zipName}`))
|
216 |
+
return new Promise(resolve => {
|
217 |
+
try {
|
218 |
+
const modelsFolder = window.userSettings[`modelspath_${game}`]
|
219 |
+
|
220 |
+
const unzipper = require('unzipper')
|
221 |
+
const zipPath = `${window.path}/downloads/${zipName}.zip`
|
222 |
+
|
223 |
+
if (!fs.existsSync(modelsFolder)) {
|
224 |
+
fs.mkdirSync(modelsFolder)
|
225 |
+
}
|
226 |
+
|
227 |
+
if (!fs.existsSync(`${window.path}/downloads`)) {
|
228 |
+
fs.mkdirSync(`${window.path}/downloads`)
|
229 |
+
}
|
230 |
+
|
231 |
+
fs.createReadStream(zipPath).pipe(unzipper.Parse()).on("entry", entry => {
|
232 |
+
const fileName = entry.path
|
233 |
+
const dirOrFile = entry.type
|
234 |
+
|
235 |
+
if (/\/$/.test(fileName)) { // It's a directory
|
236 |
+
return
|
237 |
+
}
|
238 |
+
|
239 |
+
let fileContainerFolderPath = fileName.split("/").reverse()
|
240 |
+
const justFileName = fileContainerFolderPath[0]
|
241 |
+
|
242 |
+
entry.pipe(fs.createWriteStream(`${modelsFolder}/${justFileName}`))
|
243 |
+
})
|
244 |
+
.promise()
|
245 |
+
.then(() => {
|
246 |
+
window.appLogger.log(`${window.i18n.DONE_INSTALLING} ${zipName}`)
|
247 |
+
|
248 |
+
const queueIndex = window.nexusState.installQueue.findIndex(it => it[1]==zipName)
|
249 |
+
window.nexusState.installQueue.splice(queueIndex, 1)
|
250 |
+
nexusInstallingCount.innerHTML = window.nexusState.installQueue.length
|
251 |
+
|
252 |
+
nexusDownloadLog.appendChild(createElem("div", `${window.i18n.FINISHED} ${zipName}`))
|
253 |
+
resolve()
|
254 |
+
}, e => {
|
255 |
+
console.log(e)
|
256 |
+
window.appLogger.log(e)
|
257 |
+
window.errorModal(e.message)
|
258 |
+
})
|
259 |
+
} catch (e) {
|
260 |
+
console.log(e)
|
261 |
+
window.appLogger.log(e)
|
262 |
+
window.errorModal(e.message)
|
263 |
+
resolve()
|
264 |
+
}
|
265 |
+
})
|
266 |
+
}
|
267 |
+
|
268 |
+
nexusDownloadAllBtn.addEventListener("click", async () => {
|
269 |
+
|
270 |
+
for (let mi=0; mi<window.nexusState.filteredDownloadableModels.length; mi++) {
|
271 |
+
const modelMeta = window.nexusState.filteredDownloadableModels[mi]
|
272 |
+
window.nexusState.downloadQueue.push([modelMeta.voiceId, modelMeta.nexus_file_id])
|
273 |
+
nexusDownloadingCount.innerHTML = window.nexusState.downloadQueue.length
|
274 |
+
}
|
275 |
+
|
276 |
+
for (let mi=0; mi<window.nexusState.filteredDownloadableModels.length; mi++) {
|
277 |
+
|
278 |
+
const modelMeta = window.nexusState.filteredDownloadableModels[mi]
|
279 |
+
await window.downloadFile([modelMeta.nexusGameId, modelMeta.nexusRepoId, modelMeta.voiceId, modelMeta.nexus_file_id])
|
280 |
+
|
281 |
+
// Install the downloaded voice
|
282 |
+
window.nexusState.installQueue.push([modelMeta.game, modelMeta.voiceId])
|
283 |
+
nexusInstallingCount.innerHTML = window.nexusState.installQueue.length
|
284 |
+
await window.installDownloadedModel([modelMeta.game, modelMeta.voiceId])
|
285 |
+
|
286 |
+
fs.unlinkSync(`${window.path}/downloads/${modelMeta.voiceId}.zip`)
|
287 |
+
|
288 |
+
window.nexusState.finished += 1
|
289 |
+
nexusFinishedCount.innerHTML = window.nexusState.finished
|
290 |
+
window.displayAllModels(true)
|
291 |
+
window.loadAllModels(true).then(() => {
|
292 |
+
changeGame(window.currentGame)
|
293 |
+
})
|
294 |
+
}
|
295 |
+
})
|
296 |
+
|
297 |
+
|
298 |
+
const getJSONData = (url) => {
|
299 |
+
return new Promise(resolve => {
|
300 |
+
doFetch(url).then(r=>r.json())
|
301 |
+
})
|
302 |
+
}
|
303 |
+
|
304 |
+
window.showUserName = async () => {
|
305 |
+
const data = await getuserData("validate.json")
|
306 |
+
const img = createElem("img")
|
307 |
+
img.src = data.profile_url
|
308 |
+
img.style.height = "40px"
|
309 |
+
nexusAvatar.innerHTML = ""
|
310 |
+
img.addEventListener("load", () => {
|
311 |
+
nexusAvatar.appendChild(img)
|
312 |
+
nexusUserName.innerHTML = data.name
|
313 |
+
nexusNameDisplayContainer.style.opacity = 1
|
314 |
+
})
|
315 |
+
}
|
316 |
+
|
317 |
+
const getuserData = (url, data) => {
|
318 |
+
return new Promise(resolve => {
|
319 |
+
doFetch(`https://api.nexusmods.com/v1/users/${url}`, {
|
320 |
+
method: "GET",
|
321 |
+
headers: {
|
322 |
+
apikey: window.nexusState.key
|
323 |
+
}
|
324 |
+
})
|
325 |
+
.then(r=>r.text())
|
326 |
+
.then(r => {
|
327 |
+
try {
|
328 |
+
resolve(JSON.parse(r))
|
329 |
+
} catch (e) {
|
330 |
+
window.appLogger.log(window.i18n.ERROR_FROM_NEXUS.replace("_1", r))
|
331 |
+
window.errorModal(window.i18n.ERROR_FROM_NEXUS.replace("_1", r))
|
332 |
+
}
|
333 |
+
})
|
334 |
+
.catch(err => {
|
335 |
+
console.log("err", err)
|
336 |
+
resolve()
|
337 |
+
})
|
338 |
+
})
|
339 |
+
}
|
340 |
+
const getData = (url, data, type="GET") => {
|
341 |
+
return new Promise(resolve => {
|
342 |
+
const payload = {
|
343 |
+
method: type,
|
344 |
+
headers: {
|
345 |
+
apikey: window.nexusState.key
|
346 |
+
}
|
347 |
+
}
|
348 |
+
if (type=="POST") {
|
349 |
+
const params = new URLSearchParams()
|
350 |
+
Object.keys(data).forEach(key => {
|
351 |
+
params.append(key, data[key])
|
352 |
+
})
|
353 |
+
payload.body = params
|
354 |
+
}
|
355 |
+
|
356 |
+
doFetch(`https://api.nexusmods.com/v1/games/${url}`, payload)
|
357 |
+
.then(r=>r.text())
|
358 |
+
.then(r => {
|
359 |
+
try {
|
360 |
+
resolve(JSON.parse(r))
|
361 |
+
} catch (e) {
|
362 |
+
window.appLogger.log(window.i18n.ERROR_FROM_NEXUS.replace("_1", r))
|
363 |
+
window.errorModal(window.i18n.ERROR_FROM_NEXUS.replace("_1", r))
|
364 |
+
}
|
365 |
+
})
|
366 |
+
.catch(err => {
|
367 |
+
console.log("err", err)
|
368 |
+
resolve()
|
369 |
+
})
|
370 |
+
})
|
371 |
+
}
|
372 |
+
window.nexus_getData = getData
|
373 |
+
// ==========================
|
374 |
+
|
375 |
+
|
376 |
+
|
377 |
+
|
378 |
+
let hasPopulatedNexusGameListDropdown = false
|
379 |
+
window.openNexusWindow = () => {
|
380 |
+
closeModal(undefined, nexusContainer).then(() => {
|
381 |
+
nexusContainer.style.opacity = 0
|
382 |
+
nexusContainer.style.display = "flex"
|
383 |
+
requestAnimationFrame(() => requestAnimationFrame(() => nexusContainer.style.opacity = 1))
|
384 |
+
requestAnimationFrame(() => requestAnimationFrame(() => chromeBar.style.opacity = 1))
|
385 |
+
})
|
386 |
+
|
387 |
+
const gameColours = {}
|
388 |
+
Object.keys(window.gameAssets).forEach(gameId => {
|
389 |
+
const colour = window.gameAssets[gameId].themeColourPrimary
|
390 |
+
gameColours[gameId] = colour
|
391 |
+
})
|
392 |
+
|
393 |
+
nexusGamesList.innerHTML = ""
|
394 |
+
Object.keys(window.gameAssets).sort((a,b)=>a<b?-1:1).forEach(gameId => {
|
395 |
+
const gameSelectContainer = createElem("div")
|
396 |
+
const gameCheckbox = createElem(`input#ngl_${gameId}`, {type: "checkbox"})
|
397 |
+
gameCheckbox.checked = true
|
398 |
+
const gameButton = createElem("button.fixedColour")
|
399 |
+
gameButton.style.setProperty("background-color", `#${gameColours[gameId]}`, "important")
|
400 |
+
gameButton.style.display = "flex"
|
401 |
+
gameButton.style.alignItems = "center"
|
402 |
+
gameButton.style.margin = "auto"
|
403 |
+
gameButton.style.marginTop = "8px"
|
404 |
+
const buttonLabel = createElem("span", window.gameAssets[gameId].gameName)
|
405 |
+
|
406 |
+
gameButton.addEventListener("contextmenu", e => {
|
407 |
+
if (e.target==gameButton || e.target==buttonLabel) {
|
408 |
+
Array.from(nexusGamesList.querySelectorAll("input")).forEach(ckbx => ckbx.checked = false)
|
409 |
+
gameCheckbox.click()
|
410 |
+
window.displayAllModels()
|
411 |
+
}
|
412 |
+
})
|
413 |
+
|
414 |
+
gameButton.addEventListener("click", e => {
|
415 |
+
if (e.target==gameButton || e.target==buttonLabel) {
|
416 |
+
gameCheckbox.click()
|
417 |
+
window.displayAllModels()
|
418 |
+
}
|
419 |
+
})
|
420 |
+
gameCheckbox.addEventListener("change", () => {
|
421 |
+
window.displayAllModels()
|
422 |
+
})
|
423 |
+
|
424 |
+
gameButton.appendChild(gameCheckbox)
|
425 |
+
gameButton.appendChild(buttonLabel)
|
426 |
+
gameSelectContainer.appendChild(gameButton)
|
427 |
+
nexusGamesList.appendChild(gameSelectContainer)
|
428 |
+
})
|
429 |
+
|
430 |
+
// Populate the game IDs for the Nexus repo searching
|
431 |
+
if (!hasPopulatedNexusGameListDropdown) {
|
432 |
+
window.getAllNexusGameIDs().then(results => {
|
433 |
+
if (results && results.length) {
|
434 |
+
results = results.sort((a,b)=>a.name.toLowerCase()<b.name.toLowerCase()?-1:1)
|
435 |
+
results.forEach(res => {
|
436 |
+
|
437 |
+
window.nexusGameIdToGameName[res.id] = res.name
|
438 |
+
|
439 |
+
const opt = createElem("option", {value: res.id})
|
440 |
+
opt.innerHTML = res.name
|
441 |
+
nexusAllGamesSelect.appendChild(opt)
|
442 |
+
})
|
443 |
+
if (fs.existsSync(`${window.path}/repositories.json`)) {
|
444 |
+
const data = fs.readFileSync(`${window.path}/repositories.json`, "utf8")
|
445 |
+
window.nexusReposList = JSON.parse(data)
|
446 |
+
window.nexusUpdateModsUsedPanel()
|
447 |
+
}
|
448 |
+
hasPopulatedNexusGameListDropdown = true
|
449 |
+
}
|
450 |
+
})
|
451 |
+
}
|
452 |
+
|
453 |
+
}
|
454 |
+
window.setupModal(nexusMenuButton, nexusContainer, window.openNexusWindow)
|
455 |
+
|
456 |
+
nexusGamesListEnableAllBtn.addEventListener("click", () => {
|
457 |
+
Array.from(nexusGamesList.querySelectorAll("input")).forEach(ckbx => ckbx.checked = true)
|
458 |
+
window.displayAllModels()
|
459 |
+
})
|
460 |
+
nexusGamesListDisableAllBtn.addEventListener("click", () => {
|
461 |
+
Array.from(nexusGamesList.querySelectorAll("input")).forEach(ckbx => ckbx.checked = false)
|
462 |
+
window.displayAllModels()
|
463 |
+
})
|
464 |
+
|
465 |
+
|
466 |
+
|
467 |
+
nexusSearchBar.addEventListener("keyup", () => {
|
468 |
+
window.displayAllModels()
|
469 |
+
})
|
470 |
+
|
471 |
+
|
472 |
+
window.getLatestModelsList = async () => {
|
473 |
+
if (!nexusState.key) {
|
474 |
+
return window.errorModal(window.i18n.YOU_MUST_BE_LOGGED_IN)
|
475 |
+
} else {
|
476 |
+
try {
|
477 |
+
window.spinnerModal(window.i18n.CHECKING_NEXUS)
|
478 |
+
window.nexusModelsList = []
|
479 |
+
|
480 |
+
const idToGame = {}
|
481 |
+
Object.keys(window.gameAssets).forEach(gameId => {
|
482 |
+
const id = window.gameAssets[gameId].gameCode.toLowerCase()
|
483 |
+
idToGame[id] = gameId
|
484 |
+
})
|
485 |
+
|
486 |
+
const repoLinks = window.nexusReposList.repos.filter(r=>r.enabled).map(r=>r.url)
|
487 |
+
|
488 |
+
for (let li=0; li<repoLinks.length; li++) {
|
489 |
+
|
490 |
+
const link = repoLinks[li].replace("\r","")
|
491 |
+
const repoInfo = await getData(`${link.split(".com/")[1]}.json`)
|
492 |
+
const author = repoInfo.author
|
493 |
+
const nexusRepoId = repoInfo.mod_id
|
494 |
+
const nexusRepoVersion = repoInfo.version
|
495 |
+
const nexusGameId = repoInfo.domain_name
|
496 |
+
|
497 |
+
const files = await getData(`${link.split(".com/")[1]}/files.json`)
|
498 |
+
files["files"].forEach(file => {
|
499 |
+
|
500 |
+
if (file.category_name=="OPTIONAL" || file.category_name=="OPTIONAL") {
|
501 |
+
|
502 |
+
if (!file.description.includes("Voice model")) {
|
503 |
+
return
|
504 |
+
}
|
505 |
+
|
506 |
+
const description = file.description
|
507 |
+
const parts = description.split("<br />")
|
508 |
+
let voiceId = parts.filter(line => line.startsWith("Voice ID:") || line.startsWith("VoiceID:"))[0]
|
509 |
+
voiceId = voiceId.includes("Voice ID: ") ? voiceId.split("Voice ID: ")[1].split(" ")[0] : voiceId.split("VoiceID: ")[1].split(" ")[0]
|
510 |
+
const game = idToGame[voiceId.split("_")[0]]
|
511 |
+
const name = parts.filter(line => line.startsWith("Voice model"))[0].split(" - ")[1]
|
512 |
+
const date = file.uploaded_time
|
513 |
+
const nexus_file_id = file.file_id
|
514 |
+
|
515 |
+
if (repoInfo.endorsement.endorse_status=="Endorsed") {
|
516 |
+
window.endorsedRepos.add(game)
|
517 |
+
}
|
518 |
+
|
519 |
+
const hasT2 = description.includes("Tacotron2")
|
520 |
+
const hasHiFi = description.includes("HiFi-GAN")
|
521 |
+
const version = file.version
|
522 |
+
|
523 |
+
let type
|
524 |
+
if (description.includes("Model:")) {
|
525 |
+
type = parts.filter(line => line.startsWith("Model: "))[0].split("Model: ")[1]
|
526 |
+
} else {
|
527 |
+
type = "FastPitch"
|
528 |
+
if (type=="FastPitch") {
|
529 |
+
if (hasT2) {
|
530 |
+
type = "T2+"+type
|
531 |
+
}
|
532 |
+
}
|
533 |
+
if (hasHiFi) {
|
534 |
+
type += "+HiFi"
|
535 |
+
}
|
536 |
+
}
|
537 |
+
|
538 |
+
const notes = description.includes("Notes:") ? parts.filter(line => line.startsWith("Notes: "))[0].split("Notes: ")[1] : ""
|
539 |
+
const meta = {author, description, version, voiceId, game, name, type, notes, date, nexusRepoId, nexusRepoVersion, nexusGameId, nexus_file_id, repoLink: link}
|
540 |
+
window.nexusModelsList.push(meta)
|
541 |
+
}
|
542 |
+
})
|
543 |
+
}
|
544 |
+
|
545 |
+
window.closeModal(undefined, nexusContainer)
|
546 |
+
window.displayAllModels()
|
547 |
+
|
548 |
+
} catch (e) {
|
549 |
+
console.log(e)
|
550 |
+
window.appLogger.log(e)
|
551 |
+
window.errorModal(e.message)
|
552 |
+
}
|
553 |
+
}
|
554 |
+
}
|
555 |
+
|
556 |
+
const clearColumsFocus = () => {
|
557 |
+
nexusRecordsHeader.querySelectorAll("div").forEach(elem => elem.style.textDecoration = "none")
|
558 |
+
}
|
559 |
+
const setColumnSort = (elem, key) => {
|
560 |
+
clearColumsFocus()
|
561 |
+
elem.style.textDecoration = "underline"
|
562 |
+
if (window.nexusState.primaryColumnSort==key) {
|
563 |
+
window.nexusState.columnSortModifier = window.nexusState.columnSortModifier * -1
|
564 |
+
}
|
565 |
+
window.nexusState.primaryColumnSort = key
|
566 |
+
window.displayAllModels()
|
567 |
+
}
|
568 |
+
i18n_nexush_game.addEventListener("click", () => setColumnSort(i18n_nexush_game, "game"))
|
569 |
+
i18n_nexush_name.addEventListener("click", () => setColumnSort(i18n_nexush_game, "name"))
|
570 |
+
i18n_nexush_author.addEventListener("click", () => setColumnSort(i18n_nexush_author, "author"))
|
571 |
+
i18n_nexush_version.addEventListener("click", () => setColumnSort(i18n_nexush_version, "version"))
|
572 |
+
i18n_nexush_date.addEventListener("click", () => setColumnSort(i18n_nexush_date, "date"))
|
573 |
+
i18n_nexush_type.addEventListener("click", () => setColumnSort(i18n_nexush_type, "type"))
|
574 |
+
|
575 |
+
|
576 |
+
const runNestedSort = (modelsList, primKey) => {
|
577 |
+
// Perform the primary sorting
|
578 |
+
const primaryGroup = {}
|
579 |
+
let modelsOrder = []
|
580 |
+
modelsList.forEach(item => {
|
581 |
+
if (!Object.keys(primaryGroup).includes(item[primKey]||"")) {
|
582 |
+
modelsOrder.push(item[primKey]||"")
|
583 |
+
primaryGroup[item[primKey]||""] = []
|
584 |
+
}
|
585 |
+
primaryGroup[item[primKey]||""].push(item)
|
586 |
+
})
|
587 |
+
|
588 |
+
// Sort the primary key in the correct direction
|
589 |
+
modelsOrder = modelsOrder.sort((a,b) => a<b?window.nexusState.columnSortModifier:-window.nexusState.columnSortModifier)
|
590 |
+
|
591 |
+
// Sort the secondary criteria (the voice names) within the primary groups
|
592 |
+
modelsOrder.forEach(primaryKey => {
|
593 |
+
primaryGroup[primaryKey] = primaryGroup[primaryKey].sort((a,b) => (a.name||"").toLowerCase()<(b.name||"").toLowerCase()?window.nexusState.columnSortModifier:-window.nexusState.columnSortModifier)
|
594 |
+
})
|
595 |
+
// Collate everything back into the final order
|
596 |
+
const finalOrder = []
|
597 |
+
modelsOrder.forEach(primaryKey => {
|
598 |
+
primaryGroup[primaryKey].forEach(record => finalOrder.push(record))
|
599 |
+
})
|
600 |
+
return finalOrder
|
601 |
+
}
|
602 |
+
|
603 |
+
window.displayAllModels = (forceUpdate=false) => {
|
604 |
+
|
605 |
+
if (!forceUpdate && window.nexusState.installQueue.length) {
|
606 |
+
return
|
607 |
+
}
|
608 |
+
|
609 |
+
const enabledGames = Array.from(nexusGamesList.querySelectorAll("input"))
|
610 |
+
.map(elem => [elem.checked, elem.id.replace("ngl_", "")])
|
611 |
+
.filter(checkedId => checkedId[0])
|
612 |
+
.map(checkedId => checkedId[1])
|
613 |
+
|
614 |
+
const gameColours = {}
|
615 |
+
Object.keys(window.gameAssets).forEach(gameId => {
|
616 |
+
const colour = window.gameAssets[gameId].themeColourPrimary
|
617 |
+
gameColours[gameId] = colour
|
618 |
+
})
|
619 |
+
const gameTitles = {}
|
620 |
+
Object.keys(window.gameAssets).forEach(gameId => {
|
621 |
+
const title = window.gameAssets[gameId].gameName
|
622 |
+
gameTitles[gameId] = title
|
623 |
+
})
|
624 |
+
|
625 |
+
nexusRecordsContainer.innerHTML = ""
|
626 |
+
|
627 |
+
window.nexusState.filteredDownloadableModels = []
|
628 |
+
|
629 |
+
|
630 |
+
let sortedModelsList = []
|
631 |
+
// Allow sorting by another column. But should still sort based on voice name alphabetically, as a secondary criteria
|
632 |
+
// Primary sortable columns: Game, VoiceName, Author, Version, Date, Type
|
633 |
+
if (window.nexusState.primaryColumnSort=="name") {
|
634 |
+
sortedModelsList = window.nexusModelsList.sort((a,b) => (a.name||"").toLowerCase()<(b.name||"").toLowerCase()?window.nexusState.columnSortModifier:-window.nexusState.columnSortModifier)
|
635 |
+
} else {
|
636 |
+
sortedModelsList = runNestedSort(window.nexusModelsList, window.nexusState.primaryColumnSort)
|
637 |
+
}
|
638 |
+
|
639 |
+
sortedModelsList.forEach(modelMeta => {
|
640 |
+
if (!enabledGames.includes(modelMeta.game)) {
|
641 |
+
return
|
642 |
+
}
|
643 |
+
if (nexusSearchBar.value.toLowerCase().trim().length && !modelMeta.name.toLowerCase().includes(nexusSearchBar.value.toLowerCase().trim())) {
|
644 |
+
return
|
645 |
+
}
|
646 |
+
let existingModel = undefined
|
647 |
+
if (Object.keys(window.games).includes(modelMeta.game)) {
|
648 |
+
for (let mi=0; mi<window.games[modelMeta.game].models.length; mi++) {
|
649 |
+
if (existingModel) continue
|
650 |
+
|
651 |
+
const variants = window.games[modelMeta.game].models[mi].variants
|
652 |
+
|
653 |
+
for (let vi=0; vi<variants.length; vi++) {
|
654 |
+
if (variants[vi].voiceId==modelMeta.voiceId) {
|
655 |
+
existingModel = variants[vi]
|
656 |
+
break
|
657 |
+
}
|
658 |
+
}
|
659 |
+
}
|
660 |
+
}
|
661 |
+
|
662 |
+
if (existingModel && nexusOnlyNewUpdatedCkbx.checked && (window.checkVersionRequirements(modelMeta.version, String(existingModel.modelVersion)) || (modelMeta.version.replace(".0","")==String(existingModel.modelVersion))) ){
|
663 |
+
return
|
664 |
+
}
|
665 |
+
|
666 |
+
|
667 |
+
|
668 |
+
const recordRow = createElem("div")
|
669 |
+
const actionsElem = createElem("div")
|
670 |
+
|
671 |
+
// Open link to the repo in the browser
|
672 |
+
const openButton = createElem("button.smallButton.fixedColour", window.i18n.OPEN)
|
673 |
+
openButton.style.setProperty("background-color", `#${gameColours[modelMeta.game]}`, "important")
|
674 |
+
openButton.addEventListener("click", () => {
|
675 |
+
shell.openExternal(modelMeta.repoLink)
|
676 |
+
})
|
677 |
+
actionsElem.appendChild(openButton)
|
678 |
+
|
679 |
+
|
680 |
+
|
681 |
+
// Download
|
682 |
+
const downloadButton = createElem("button.smallButton.fixedColour", window.i18n.DOWNLOAD)
|
683 |
+
downloadButton.style.setProperty("background-color", `#${gameColours[modelMeta.game]}`, "important")
|
684 |
+
downloadButton.addEventListener("click", async () => {
|
685 |
+
// Download the voice
|
686 |
+
window.nexusState.downloadQueue.push([modelMeta.voiceId, modelMeta.nexus_file_id])
|
687 |
+
nexusDownloadingCount.innerHTML = window.nexusState.downloadQueue.length
|
688 |
+
try {
|
689 |
+
await window.downloadFile([modelMeta.nexusGameId, modelMeta.nexusRepoId, modelMeta.voiceId, modelMeta.nexus_file_id])
|
690 |
+
|
691 |
+
// Install the downloaded voice
|
692 |
+
window.nexusState.installQueue.push([modelMeta.game, modelMeta.voiceId])
|
693 |
+
nexusInstallingCount.innerHTML = window.nexusState.installQueue.length
|
694 |
+
await window.installDownloadedModel([modelMeta.game, modelMeta.voiceId])
|
695 |
+
|
696 |
+
window.nexusState.finished += 1
|
697 |
+
nexusFinishedCount.innerHTML = window.nexusState.finished
|
698 |
+
window.displayAllModels()
|
699 |
+
window.loadAllModels(true).then(() => {
|
700 |
+
changeGame(window.currentGame)
|
701 |
+
})
|
702 |
+
|
703 |
+
} catch (e) {}
|
704 |
+
|
705 |
+
})
|
706 |
+
if (existingModel && (modelMeta.version.replace(".0","")==String(existingModel.modelVersion) || window.checkVersionRequirements(modelMeta.version, String(existingModel.modelVersion)) ) ) {
|
707 |
+
} else {
|
708 |
+
window.nexusState.filteredDownloadableModels.push(modelMeta)
|
709 |
+
actionsElem.appendChild(downloadButton)
|
710 |
+
}
|
711 |
+
|
712 |
+
|
713 |
+
// Endorse
|
714 |
+
const endorsed = window.endorsedRepos.has(modelMeta.game)
|
715 |
+
const endorseButton = createElem("button.smallButton.fixedColour", endorsed?"Unendorse":"Endorse")
|
716 |
+
if (endorsed) {
|
717 |
+
endorseButton.style.background = "none"
|
718 |
+
endorseButton.style.border = `2px solid #${gameColours[modelMeta.game]}`
|
719 |
+
} else {
|
720 |
+
endorseButton.style.setProperty("background-color", `#${gameColours[modelMeta.game]}`, "important")
|
721 |
+
}
|
722 |
+
endorseButton.addEventListener("click", async () => {
|
723 |
+
let response
|
724 |
+
if (endorsed) {
|
725 |
+
response = await getData(`${modelMeta.nexusGameId}/mods/${modelMeta.nexusRepoId}/abstain.json`, {
|
726 |
+
game_domain_name: modelMeta.nexusGameId,
|
727 |
+
id: modelMeta.nexusRepoId,
|
728 |
+
version: modelMeta.nexusRepoVersion
|
729 |
+
}, "POST")
|
730 |
+
} else {
|
731 |
+
response = await getData(`${modelMeta.nexusGameId}/mods/${modelMeta.nexusRepoId}/endorse.json`, {
|
732 |
+
game_domain_name: modelMeta.nexusGameId,
|
733 |
+
id: modelMeta.nexusRepoId,
|
734 |
+
version: modelMeta.nexusRepoVersion
|
735 |
+
}, "POST")
|
736 |
+
}
|
737 |
+
if (response && response.message && response.status=="Error") {
|
738 |
+
if (response.message=="NOT_DOWNLOADED_MOD") {
|
739 |
+
response.message = window.i18n.NEXUS_NOT_DOWNLOADED_MOD
|
740 |
+
} else if (response.message=="TOO_SOON_AFTER_DOWNLOAD") {
|
741 |
+
response.message = window.i18n.NEXUS_TOO_SOON_AFTER_DOWNLOAD
|
742 |
+
} else if (response.message=="IS_OWN_MOD") {
|
743 |
+
response.message = window.i18n.NEXUS_IS_OWN_MOD
|
744 |
+
}
|
745 |
+
|
746 |
+
window.errorModal(response.message)
|
747 |
+
} else {
|
748 |
+
if (endorsed) {
|
749 |
+
window.endorsedRepos.delete(modelMeta.game)
|
750 |
+
} else {
|
751 |
+
window.endorsedRepos.add(modelMeta.game)
|
752 |
+
}
|
753 |
+
window.displayAllModels()
|
754 |
+
}
|
755 |
+
})
|
756 |
+
actionsElem.appendChild(endorseButton)
|
757 |
+
|
758 |
+
|
759 |
+
|
760 |
+
const gameElem = createElem("div", gameTitles[modelMeta.game])
|
761 |
+
gameElem.title = gameTitles[modelMeta.game]
|
762 |
+
const nameElem = createElem("div", modelMeta.name)
|
763 |
+
nameElem.title = modelMeta.name
|
764 |
+
const authorElem = createElem("div", modelMeta.author)
|
765 |
+
authorElem.title = modelMeta.author
|
766 |
+
let versionElemText
|
767 |
+
if (existingModel) {
|
768 |
+
const yoursVersion = String(existingModel.modelVersion).includes(".") ? existingModel.modelVersion : existingModel.modelVersion+".0"
|
769 |
+
versionElemText = `${modelMeta.version} (${window.i18n.YOURS}: ${yoursVersion})`
|
770 |
+
} else {
|
771 |
+
versionElemText = modelMeta.version
|
772 |
+
}
|
773 |
+
const versionElem = createElem("div", versionElemText)
|
774 |
+
versionElem.title = versionElemText
|
775 |
+
|
776 |
+
const date = new Date(modelMeta.date)
|
777 |
+
const dateString = `${date.getDate()}/${date.getMonth()+1}/${date.getYear()+1900}`
|
778 |
+
const dateElem = createElem("div", dateString)
|
779 |
+
dateElem.title = dateString
|
780 |
+
|
781 |
+
const typeElem = createElem("div", modelMeta.type)
|
782 |
+
typeElem.title = modelMeta.type
|
783 |
+
const notesElem = createElem("div", modelMeta.notes)
|
784 |
+
notesElem.title = modelMeta.notes
|
785 |
+
|
786 |
+
|
787 |
+
recordRow.appendChild(actionsElem)
|
788 |
+
recordRow.appendChild(gameElem)
|
789 |
+
recordRow.appendChild(nameElem)
|
790 |
+
recordRow.appendChild(authorElem)
|
791 |
+
recordRow.appendChild(versionElem)
|
792 |
+
recordRow.appendChild(dateElem)
|
793 |
+
recordRow.appendChild(typeElem)
|
794 |
+
recordRow.appendChild(notesElem)
|
795 |
+
|
796 |
+
nexusRecordsContainer.appendChild(recordRow)
|
797 |
+
})
|
798 |
+
}
|
799 |
+
|
800 |
+
|
801 |
+
nexusCheckNow.addEventListener("click", () => window.getLatestModelsList())
|
802 |
+
|
803 |
+
window.setupModal(nexusManageReposButton, nexusReposContainer)
|
804 |
+
|
805 |
+
window.nexusUpdateModsUsedPanel = () => {
|
806 |
+
nexusReposUsedContainer.innerHTML = ""
|
807 |
+
|
808 |
+
window.nexusReposList.repos.forEach((repo, ri) => {
|
809 |
+
const row = createElem("div")
|
810 |
+
|
811 |
+
const enabledCkbx = createElem("input", {type: "checkbox"})
|
812 |
+
enabledCkbx.checked = repo.enabled
|
813 |
+
enabledCkbx.addEventListener("click", () => {
|
814 |
+
window.nexusReposList.repos[ri].enabled = enabledCkbx.checked
|
815 |
+
fs.writeFileSync(`${window.path}/repositories.json`, JSON.stringify(window.nexusReposList, null, 4), "utf8")
|
816 |
+
})
|
817 |
+
const enabledCkbxElem = createElem("div", enabledCkbx)
|
818 |
+
|
819 |
+
const removeButton = createElem("button.smallButton", window.i18n.REMOVE)
|
820 |
+
removeButton.style.background = `#${window.currentGame.themeColourPrimary}`
|
821 |
+
const removeButtonElem = createElem("div", removeButton)
|
822 |
+
const linkButton = createElem("button.smallButton", window.i18n.OPEN)
|
823 |
+
linkButton.style.background = `#${window.currentGame.themeColourPrimary}`
|
824 |
+
linkButton.addEventListener("click", () => {
|
825 |
+
shell.openExternal(repo.url)
|
826 |
+
})
|
827 |
+
const linkButtonElem = createElem("div", linkButton)
|
828 |
+
const gameElem = createElem("div", window.nexusGameIdToGameName[repo.game_id])
|
829 |
+
gameElem.title = window.nexusGameIdToGameName[repo.game_id]
|
830 |
+
const nameElem = createElem("div", repo.name)
|
831 |
+
nameElem.title = repo.name
|
832 |
+
const authorElem = createElem("div", repo.author)
|
833 |
+
authorElem.title = repo.author
|
834 |
+
const endorsementsElem = createElem("div", String(repo.endorsements))
|
835 |
+
const downloadsElem = createElem("div", String(repo.downloads))
|
836 |
+
|
837 |
+
endorsementsElem.style.display = "none" // TEMP
|
838 |
+
downloadsElem.style.display = "none" // TEMP
|
839 |
+
|
840 |
+
row.appendChild(enabledCkbxElem)
|
841 |
+
row.appendChild(linkButtonElem)
|
842 |
+
row.appendChild(gameElem)
|
843 |
+
row.appendChild(nameElem)
|
844 |
+
row.appendChild(authorElem)
|
845 |
+
row.appendChild(endorsementsElem)
|
846 |
+
row.appendChild(downloadsElem)
|
847 |
+
row.appendChild(removeButtonElem)
|
848 |
+
nexusReposUsedContainer.appendChild(row)
|
849 |
+
})
|
850 |
+
}
|
851 |
+
|
852 |
+
|
853 |
+
window.addRepoToApp = (repo) => {
|
854 |
+
repo.enabled = true
|
855 |
+
window.nexusReposList.repos.push(repo)
|
856 |
+
window.nexusReposList.repos = window.nexusReposList.repos.sort((a,b)=>a.endorsements<b.endorsements?1:-1)
|
857 |
+
fs.writeFileSync(`${window.path}/repositories.json`, JSON.stringify(window.nexusReposList, null, 4), "utf8")
|
858 |
+
window.nexusUpdateModsUsedPanel()
|
859 |
+
}
|
860 |
+
|
861 |
+
|
862 |
+
|
863 |
+
|
864 |
+
nexusReposSearchBar.addEventListener("keydown", e => {
|
865 |
+
if (e.key.toLowerCase()=="enter" && nexusReposSearchBar.value.length) {
|
866 |
+
searchNexusButton.click()
|
867 |
+
}
|
868 |
+
})
|
869 |
+
searchNexusButton.addEventListener("click", () => {
|
870 |
+
const gameId = nexusAllGamesSelect.value ? parseInt(nexusAllGamesSelect.value) : undefined
|
871 |
+
const query = nexusReposSearchBar.value
|
872 |
+
nexusSearchContainer.innerHTML = ""
|
873 |
+
window.mod_search_nexus(gameId, query).then(results => {
|
874 |
+
|
875 |
+
const numResults = results[0]
|
876 |
+
results = results[1]
|
877 |
+
|
878 |
+
results.forEach(repo => {
|
879 |
+
|
880 |
+
const row = createElem("div")
|
881 |
+
const addButton = createElem("button.smallButton", window.i18n.ADD)
|
882 |
+
const addButtonElem = createElem("div", addButton)
|
883 |
+
addButton.style.background = `#${window.currentGame.themeColourPrimary}`
|
884 |
+
if (window.nexusReposList.repos.find(r=>r.url==repo.url)) {
|
885 |
+
addButton.disabled = true
|
886 |
+
}
|
887 |
+
addButton.addEventListener("click", () => {
|
888 |
+
window.addRepoToApp(repo)
|
889 |
+
addButton.disabled = true
|
890 |
+
})
|
891 |
+
|
892 |
+
const linkButton = createElem("button.smallButton", window.i18n.OPEN)
|
893 |
+
linkButton.style.background = `#${window.currentGame.themeColourPrimary}`
|
894 |
+
linkButton.addEventListener("click", () => {
|
895 |
+
shell.openExternal(repo.url)
|
896 |
+
})
|
897 |
+
const linkButtonElem = createElem("div", linkButton)
|
898 |
+
const gameElem = createElem("div", window.nexusGameIdToGameName[repo.game_id])
|
899 |
+
gameElem.title = window.nexusGameIdToGameName[repo.game_id]
|
900 |
+
const nameElem = createElem("div", repo.name)
|
901 |
+
nameElem.title = repo.name
|
902 |
+
const authorElem = createElem("div", repo.author)
|
903 |
+
authorElem.title = repo.author
|
904 |
+
const endorsementsElem = createElem("div", String(repo.endorsements))
|
905 |
+
const downloadsElem = createElem("div", String(repo.downloads))
|
906 |
+
|
907 |
+
|
908 |
+
row.appendChild(addButtonElem)
|
909 |
+
row.appendChild(linkButtonElem)
|
910 |
+
row.appendChild(gameElem)
|
911 |
+
row.appendChild(nameElem)
|
912 |
+
row.appendChild(authorElem)
|
913 |
+
row.appendChild(endorsementsElem)
|
914 |
+
row.appendChild(downloadsElem)
|
915 |
+
nexusSearchContainer.appendChild(row)
|
916 |
+
})
|
917 |
+
|
918 |
+
})
|
919 |
+
})
|
920 |
+
|
921 |
+
nexusOnlyNewUpdatedCkbx.addEventListener("change", () => window.displayAllModels())
|
922 |
+
window.initNexus()
|
923 |
+
|
924 |
+
|
925 |
+
// The app will support voice installation via Steam workshop. However, workshop installations can only install voices into the game directory
|
926 |
+
// Moreover, users can pick their own locations for voice models. To handle this, I'll have all voices go into the "workshop" folder. From here,
|
927 |
+
// the app will (on start-up) check if there's anything there, and it will move it to the correct location
|
928 |
+
window.checkForWorkshopInstallations = () => {
|
929 |
+
|
930 |
+
let voicesInstalled = 0
|
931 |
+
let badGameIDs = []
|
932 |
+
|
933 |
+
if (fs.existsSync(`${window.path}/workshop`) && fs.existsSync(`${window.path}/workshop/voices`)) {
|
934 |
+
|
935 |
+
const gameFolders = fs.readdirSync(`${window.path}/workshop/voices`)
|
936 |
+
|
937 |
+
gameFolders.forEach(gameId => {
|
938 |
+
|
939 |
+
const userModelDir = window.userSettings[`modelspath_${gameId}`]
|
940 |
+
|
941 |
+
if (!userModelDir) {
|
942 |
+
badGameIDs.push(gameId)
|
943 |
+
return
|
944 |
+
}
|
945 |
+
|
946 |
+
const voiceIDs_jsons = fs.readdirSync(`${window.path}/workshop/voices/${gameId}`).filter(fName => fName.endsWith(".json"))
|
947 |
+
|
948 |
+
voiceIDs_jsons.forEach(voiceIDs_json => {
|
949 |
+
const voiceID = voiceIDs_json.replace(".json", "")
|
950 |
+
const voiceFiles = fs.readdirSync(`${window.path}/workshop/voices/${gameId}`).filter(fName => fName.includes(voiceID))
|
951 |
+
|
952 |
+
voiceFiles.forEach(voiceFileName => {
|
953 |
+
if (!fs.existsSync(`${userModelDir}/${voiceFileName}`)) {
|
954 |
+
fs.copyFileSync(`${window.path}/workshop/voices/${gameId}/${voiceFileName}`, `${userModelDir}/${voiceFileName}`)
|
955 |
+
}
|
956 |
+
fs.unlinkSync(`${window.path}/workshop/voices/${gameId}/${voiceFileName}`)
|
957 |
+
})
|
958 |
+
|
959 |
+
voicesInstalled++
|
960 |
+
})
|
961 |
+
})
|
962 |
+
}
|
963 |
+
|
964 |
+
if (voicesInstalled || badGameIDs.length) {
|
965 |
+
setTimeout(() => {
|
966 |
+
|
967 |
+
let modalMessage = ""
|
968 |
+
|
969 |
+
if (voicesInstalled) {
|
970 |
+
modalMessage += window.i18n.X_WORKSHOP_VOICES_INSTALLED.replace("_1", voicesInstalled)
|
971 |
+
}
|
972 |
+
if (voicesInstalled && badGameIDs.length) {
|
973 |
+
modalMessage += "<br><br>"
|
974 |
+
}
|
975 |
+
if (badGameIDs) {
|
976 |
+
modalMessage += window.i18n.WORKSHOP_GAMES_NOT_RECOGNISED.replace("_1", badGameIDs.join(","))
|
977 |
+
}
|
978 |
+
|
979 |
+
createModal("error", modalMessage)
|
980 |
+
window.updateGameList()
|
981 |
+
}, 1000)
|
982 |
+
}
|
983 |
+
}
|
javascript/outputFiles.js
ADDED
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
const spawn = require("child_process").spawn
|
4 |
+
|
5 |
+
window.outputFilesState = {
|
6 |
+
paginationIndex: 0,
|
7 |
+
records: [],
|
8 |
+
totalPages: 0
|
9 |
+
}
|
10 |
+
|
11 |
+
window.initMainPagePagination = (directory) => {
|
12 |
+
window.outputFilesState.records = []
|
13 |
+
window.outputFilesState.totalPages = 0
|
14 |
+
window.resetPagination()
|
15 |
+
|
16 |
+
if (!fs.existsSync(directory)) {
|
17 |
+
return
|
18 |
+
}
|
19 |
+
|
20 |
+
const records = []
|
21 |
+
const files = fs.readdirSync(directory)
|
22 |
+
files.forEach(file => {
|
23 |
+
if (!["wav", "mp3", "ogg", "opus", "wma", "xwm"].includes(file.split(".").reverse()[0].toLowerCase())) {
|
24 |
+
return
|
25 |
+
}
|
26 |
+
|
27 |
+
let jsonData
|
28 |
+
|
29 |
+
if (fs.existsSync(`${directory}/${file}.json`)) {
|
30 |
+
try {
|
31 |
+
const lineMeta = fs.readFileSync(`${directory}/${file}.json`, "utf8")
|
32 |
+
jsonData = JSON.parse(lineMeta)
|
33 |
+
} catch (e) {
|
34 |
+
// console.log(e)
|
35 |
+
}
|
36 |
+
}
|
37 |
+
|
38 |
+
const record = {}
|
39 |
+
record.fileName = file
|
40 |
+
record.lastChanged = fs.statSync(`${directory}/${file}`).mtime
|
41 |
+
record.jsonPath = `${directory}/${file}`
|
42 |
+
records.push([record, jsonData])
|
43 |
+
})
|
44 |
+
|
45 |
+
window.outputFilesState.records = records
|
46 |
+
window.reOrderMainPageRecords()
|
47 |
+
}
|
48 |
+
window.resetPagination = () => {
|
49 |
+
window.outputFilesState.paginationIndex = 0
|
50 |
+
const numPages = Math.ceil(window.outputFilesState.records.length/window.userSettings.output_files_pagination_size)
|
51 |
+
main_total_pages.innerHTML = window.i18n.PAGINATION_TOTAL_OF.replace("_1", numPages)
|
52 |
+
window.outputFilesState.totalPages = numPages
|
53 |
+
main_pageNum.value = 1
|
54 |
+
}
|
55 |
+
window.reOrderMainPageRecords = () => {
|
56 |
+
const reverse = window.userSettings.voiceRecordsOrderByOrder=="ascending"
|
57 |
+
const sortBy = window.userSettings.voiceRecordsOrderBy
|
58 |
+
|
59 |
+
window.outputFilesState.records = window.outputFilesState.records.sort((a,b) => {
|
60 |
+
if (sortBy=="name") {
|
61 |
+
return a[0].fileName.toLowerCase()<b[0].fileName.toLowerCase() ? (reverse?-1:1) : (reverse?1:-1)
|
62 |
+
} else if (sortBy=="time") {
|
63 |
+
return a[0].lastChanged<b[0].lastChanged ? (reverse?-1:1) : (reverse?1:-1)
|
64 |
+
} else {
|
65 |
+
console.warn("sort by type not recognised", sortBy)
|
66 |
+
}
|
67 |
+
})
|
68 |
+
}
|
69 |
+
|
70 |
+
window.makeSample = (src, newSample) => {
|
71 |
+
const fileName = src.split("/").reverse()[0].split("%20").join(" ")
|
72 |
+
const fileFormat = fileName.split(".").reverse()[0]
|
73 |
+
const fileNameElem = createElem("div", fileName)
|
74 |
+
const promptText = createElem("div.samplePromptText")
|
75 |
+
|
76 |
+
if (fs.existsSync(src+".json")) {
|
77 |
+
try {
|
78 |
+
const lineMeta = fs.readFileSync(src+".json", "utf8")
|
79 |
+
promptText.innerHTML = JSON.parse(lineMeta).inputSequence
|
80 |
+
if (promptText.innerHTML.length > 130) {
|
81 |
+
promptText.innerHTML = promptText.innerHTML.slice(0, 130)+"..."
|
82 |
+
}
|
83 |
+
} catch (e) {
|
84 |
+
// console.log(e)
|
85 |
+
}
|
86 |
+
}
|
87 |
+
const sample = createElem("div.sample", createElem("div", fileNameElem, promptText))
|
88 |
+
const audioControls = createElem("div.sampleAudioControls")
|
89 |
+
const audio = createElem("audio", {controls: true}, createElem("source", {
|
90 |
+
src: src,
|
91 |
+
type: `audio/${fileFormat}`
|
92 |
+
}))
|
93 |
+
audio.addEventListener("play", () => {
|
94 |
+
if (window.ctrlKeyIsPressed) {
|
95 |
+
audio.setSinkId(window.userSettings.alt_speaker)
|
96 |
+
} else {
|
97 |
+
audio.setSinkId(window.userSettings.base_speaker)
|
98 |
+
}
|
99 |
+
})
|
100 |
+
audio.setSinkId(window.userSettings.base_speaker)
|
101 |
+
|
102 |
+
const audioSVG = window.getAudioPlayTriangleSVG()
|
103 |
+
audioSVG.addEventListener("click", () => {
|
104 |
+
audio.play()
|
105 |
+
})
|
106 |
+
|
107 |
+
const openFileLocationButton = createElem("div", {title: window.i18n.OPEN_CONTAINING_FOLDER})
|
108 |
+
openFileLocationButton.innerHTML = `<svg class="openFolderSVG" id="svg" version="1.1" xmlns="http:\/\/www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="350" viewBox="0, 0, 400,350"><g id="svgg"><path id="path0" d="M39.960 53.003 C 36.442 53.516,35.992 53.635,30.800 55.422 C 15.784 60.591,3.913 74.835,0.636 91.617 C -0.372 96.776,-0.146 305.978,0.872 310.000 C 5.229 327.228,16.605 339.940,32.351 345.172 C 40.175 347.773,32.175 347.630,163.000 347.498 L 281.800 347.378 285.600 346.495 C 304.672 342.065,321.061 332.312,330.218 319.944 C 330.648 319.362,332.162 317.472,333.581 315.744 C 335.001 314.015,336.299 312.420,336.467 312.200 C 336.634 311.980,337.543 310.879,338.486 309.753 C 340.489 307.360,342.127 305.341,343.800 303.201 C 344.460 302.356,346.890 299.375,349.200 296.575 C 351.510 293.776,353.940 290.806,354.600 289.975 C 355.260 289.144,356.561 287.505,357.492 286.332 C 358.422 285.160,359.952 283.267,360.892 282.126 C 362.517 280.153,371.130 269.561,375.632 264.000 C 376.789 262.570,380.427 258.097,383.715 254.059 C 393.790 241.689,396.099 237.993,398.474 230.445 C 403.970 212.972,394.149 194.684,376.212 188.991 C 369.142 186.747,368.803 186.724,344.733 186.779 C 330.095 186.812,322.380 186.691,322.216 186.425 C 322.078 186.203,321.971 178.951,321.977 170.310 C 321.995 146.255,321.401 141.613,317.200 133.000 C 314.009 126.457,307.690 118.680,303.142 115.694 C 302.560 115.313,301.300 114.438,300.342 113.752 C 295.986 110.631,288.986 107.881,282.402 106.704 C 280.540 106.371,262.906 106.176,220.400 106.019 L 161.000 105.800 160.763 98.800 C 159.961 75.055,143.463 56.235,120.600 52.984 C 115.148 52.208,45.292 52.225,39.960 53.003 M120.348 80.330 C 130.472 83.988,133.993 90.369,133.998 105.071 C 134.003 120.968,137.334 127.726,147.110 131.675 L 149.400 132.600 213.800 132.807 C 272.726 132.996,278.392 133.071,280.453 133.690 C 286.872 135.615,292.306 141.010,294.261 147.400 C 294.928 149.578,294.996 151.483,294.998 168.000 L 295.000 186.200 292.800 186.449 C 291.590 186.585,254.330 186.725,210.000 186.759 C 163.866 186.795,128.374 186.977,127.000 187.186 C 115.800 188.887,104.936 192.929,96.705 198.458 C 95.442 199.306,94.302 200.000,94.171 200.000 C 93.815 200.000,89.287 203.526,87.000 205.583 C 84.269 208.039,80.083 212.649,76.488 217.159 C 72.902 221.657,72.598 222.031,70.800 224.169 C 70.030 225.084,68.770 226.620,68.000 227.582 C 67.230 228.544,66.054 229.977,65.387 230.766 C 64.720 231.554,62.727 234.000,60.957 236.200 C 59.188 238.400,56.346 241.910,54.642 244.000 C 52.938 246.090,50.163 249.510,48.476 251.600 C 44.000 257.146,36.689 266.126,36.212 266.665 C 35.985 266.921,34.900 268.252,33.800 269.623 C 32.700 270.994,30.947 273.125,29.904 274.358 C 28.861 275.591,28.006 276.735,28.004 276.900 C 28.002 277.065,27.728 277.200,27.395 277.200 C 26.428 277.200,26.700 96.271,27.670 93.553 C 30.020 86.972,35.122 81.823,40.800 80.300 C 44.238 79.378,47.793 79.296,81.800 79.351 L 117.800 79.410 120.348 80.330 M369.400 214.800 C 374.239 217.220,374.273 222.468,369.489 228.785 C 367.767 231.059,364.761 234.844,364.394 235.200 C 364.281 235.310,362.373 237.650,360.154 240.400 C 357.936 243.150,354.248 247.707,351.960 250.526 C 347.732 255.736,346.053 257.821,343.202 261.400 C 341.505 263.530,340.849 264.336,334.600 271.965 C 332.400 274.651,330.204 277.390,329.720 278.053 C 329.236 278.716,328.246 279.945,327.520 280.785 C 326.794 281.624,325.300 283.429,324.200 284.794 C 323.100 286.160,321.726 287.845,321.147 288.538 C 320.568 289.232,318.858 291.345,317.347 293.233 C 308.372 304.449,306.512 306.609,303.703 309.081 C 299.300 312.956,290.855 317.633,286.000 318.886 C 277.958 320.960,287.753 320.819,159.845 320.699 C 33.557 320.581,42.330 320.726,38.536 318.694 C 34.021 316.276,35.345 310.414,42.386 301.647 C 44.044 299.583,45.940 297.210,46.600 296.374 C 47.260 295.538,48.340 294.169,49.000 293.332 C 49.660 292.495,51.550 290.171,53.200 288.167 C 54.850 286.164,57.100 283.395,58.200 282.015 C 59.300 280.635,60.920 278.632,61.800 277.564 C 62.680 276.496,64.210 274.617,65.200 273.389 C 66.190 272.162,67.188 270.942,67.418 270.678 C 67.649 270.415,71.591 265.520,76.179 259.800 C 80.767 254.080,84.634 249.310,84.773 249.200 C 84.913 249.090,87.117 246.390,89.673 243.200 C 92.228 240.010,95.621 235.780,97.213 233.800 C 106.328 222.459,116.884 215.713,128.200 213.998 C 129.300 213.832,183.570 213.719,248.800 213.748 L 367.400 213.800 369.400 214.800 " stroke="none" fill="#050505" fill-rule="evenodd"></path><path id="path1" d="M0.000 46.800 C 0.000 72.540,0.072 93.600,0.159 93.600 C 0.246 93.600,0.516 92.460,0.759 91.066 C 3.484 75.417,16.060 60.496,30.800 55.422 C 35.953 53.648,36.338 53.550,40.317 52.981 C 46.066 52.159,114.817 52.161,120.600 52.984 C 143.463 56.235,159.961 75.055,160.763 98.800 L 161.000 105.800 220.400 106.019 C 262.906 106.176,280.540 106.371,282.402 106.704 C 288.986 107.881,295.986 110.631,300.342 113.752 C 301.300 114.438,302.560 115.313,303.142 115.694 C 307.690 118.680,314.009 126.457,317.200 133.000 C 321.401 141.613,321.995 146.255,321.977 170.310 C 321.971 178.951,322.078 186.203,322.216 186.425 C 322.380 186.691,330.095 186.812,344.733 186.779 C 368.803 186.724,369.142 186.747,376.212 188.991 C 381.954 190.814,388.211 194.832,391.662 198.914 C 395.916 203.945,397.373 206.765,399.354 213.800 C 399.842 215.533,399.922 201.399,399.958 107.900 L 400.000 0.000 200.000 0.000 L 0.000 0.000 0.000 46.800 M44.000 79.609 C 35.903 81.030,30.492 85.651,27.670 93.553 C 26.700 96.271,26.428 277.200,27.395 277.200 C 27.728 277.200,28.002 277.065,28.004 276.900 C 28.006 276.735,28.861 275.591,29.904 274.358 C 30.947 273.125,32.700 270.994,33.800 269.623 C 34.900 268.252,35.985 266.921,36.212 266.665 C 36.689 266.126,44.000 257.146,48.476 251.600 C 50.163 249.510,52.938 246.090,54.642 244.000 C 56.346 241.910,59.188 238.400,60.957 236.200 C 62.727 234.000,64.720 231.554,65.387 230.766 C 66.054 229.977,67.230 228.544,68.000 227.582 C 68.770 226.620,70.030 225.084,70.800 224.169 C 72.598 222.031,72.902 221.657,76.488 217.159 C 80.083 212.649,84.269 208.039,87.000 205.583 C 89.287 203.526,93.815 200.000,94.171 200.000 C 94.302 200.000,95.442 199.306,96.705 198.458 C 104.936 192.929,115.800 188.887,127.000 187.186 C 128.374 186.977,163.866 186.795,210.000 186.759 C 254.330 186.725,291.590 186.585,292.800 186.449 L 295.000 186.200 294.998 168.000 C 294.996 151.483,294.928 149.578,294.261 147.400 C 292.306 141.010,286.872 135.615,280.453 133.690 C 278.392 133.071,272.726 132.996,213.800 132.807 L 149.400 132.600 147.110 131.675 C 137.334 127.726,134.003 120.968,133.998 105.071 C 133.993 90.369,130.472 83.988,120.348 80.330 L 117.800 79.410 81.800 79.351 C 62.000 79.319,44.990 79.435,44.000 79.609 M128.200 213.998 C 116.884 215.713,106.328 222.459,97.213 233.800 C 95.621 235.780,92.228 240.010,89.673 243.200 C 87.117 246.390,84.913 249.090,84.773 249.200 C 84.634 249.310,80.767 254.080,76.179 259.800 C 71.591 265.520,67.649 270.415,67.418 270.678 C 67.188 270.942,66.190 272.162,65.200 273.389 C 64.210 274.617,62.680 276.496,61.800 277.564 C 60.920 278.632,59.300 280.635,58.200 282.015 C 57.100 283.395,54.850 286.164,53.200 288.167 C 51.550 290.171,49.660 292.495,49.000 293.332 C 48.340 294.169,47.260 295.538,46.600 296.374 C 45.940 297.210,44.044 299.583,42.386 301.647 C 35.345 310.414,34.021 316.276,38.536 318.694 C 42.330 320.726,33.557 320.581,159.845 320.699 C 287.753 320.819,277.958 320.960,286.000 318.886 C 290.855 317.633,299.300 312.956,303.703 309.081 C 306.512 306.609,308.372 304.449,317.347 293.233 C 318.858 291.345,320.568 289.232,321.147 288.538 C 321.726 287.845,323.100 286.160,324.200 284.794 C 325.300 283.429,326.794 281.624,327.520 280.785 C 328.246 279.945,329.236 278.716,329.720 278.053 C 330.204 277.390,332.400 274.651,334.600 271.965 C 340.849 264.336,341.505 263.530,343.202 261.400 C 346.053 257.821,347.732 255.736,351.960 250.526 C 354.248 247.707,357.936 243.150,360.154 240.400 C 362.373 237.650,364.281 235.310,364.394 235.200 C 364.761 234.844,367.767 231.059,369.489 228.785 C 374.273 222.468,374.239 217.220,369.400 214.800 L 367.400 213.800 248.800 213.748 C 183.570 213.719,129.300 213.832,128.200 213.998 M399.600 225.751 C 399.600 231.796,394.623 240.665,383.715 254.059 C 380.427 258.097,376.789 262.570,375.632 264.000 C 371.130 269.561,362.517 280.153,360.892 282.126 C 359.952 283.267,358.422 285.160,357.492 286.332 C 356.561 287.505,355.260 289.144,354.600 289.975 C 353.940 290.806,351.510 293.776,349.200 296.575 C 346.890 299.375,344.460 302.356,343.800 303.201 C 342.127 305.341,340.489 307.360,338.486 309.753 C 337.543 310.879,336.634 311.980,336.467 312.200 C 336.299 312.420,335.001 314.015,333.581 315.744 C 332.162 317.472,330.648 319.362,330.218 319.944 C 321.061 332.312,304.672 342.065,285.600 346.495 L 281.800 347.378 163.000 347.498 C 32.175 347.630,40.175 347.773,32.351 345.172 C 16.471 339.895,3.810 325.502,0.820 309.326 C 0.591 308.085,0.312 306.979,0.202 306.868 C 0.091 306.757,-0.000 327.667,-0.000 353.333 L 0.000 400.000 200.000 400.000 L 400.000 400.000 400.000 312.400 C 400.000 264.220,399.910 224.800,399.800 224.800 C 399.690 224.800,399.600 225.228,399.600 225.751 " stroke="none" fill="#fbfbfb" fill-rule="evenodd"></path></g></svg>`
|
109 |
+
openFileLocationButton.addEventListener("click", () => {
|
110 |
+
const containingFolder = src.split("/").slice(0, -1).join("/").replaceAll("/", "\\")
|
111 |
+
// shell.showItemInFolder(src)
|
112 |
+
// er.shell.showItemInFolder(src)
|
113 |
+
|
114 |
+
// Electron suddenly isn't working anymore, to open folders, since updating from v2 to v19
|
115 |
+
// Couldn't figure it out, so I'm just gonna do it myself, manually. It doesn't show the file, but at least it opens the folder
|
116 |
+
|
117 |
+
er.shell.showItemInFolder(src)
|
118 |
+
spawn(`explorer`, [containingFolder], {stdio: "ignore"})
|
119 |
+
})
|
120 |
+
|
121 |
+
if (fs.existsSync(`${src}.json`)) {
|
122 |
+
const editButton = createElem("div", {title: window.i18n.ADJUST_SAMPLE_IN_EDITOR})
|
123 |
+
editButton.innerHTML = `<svg class="renameSVG" version="1.0" xmlns="http:\/\/www.w3.org/2000/svg" width="344.000000pt" height="344.000000pt" viewBox="0 0 344.000000 344.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,344.000000) scale(0.100000,-0.100000)" fill="#555555" stroke="none"><path d="M1489 2353 l-936 -938 -197 -623 c-109 -343 -195 -626 -192 -629 2 -3 284 84 626 193 l621 198 937 938 c889 891 937 940 934 971 -11 108 -86 289 -167 403 -157 219 -395 371 -655 418 l-34 6 -937 -937z m1103 671 c135 -45 253 -135 337 -257 41 -61 96 -178 112 -241 l12 -48 -129 -129 -129 -129 -287 287 -288 288 127 127 c79 79 135 128 148 128 11 0 55 -12 97 -26z m-1798 -1783 c174 -79 354 -248 436 -409 59 -116 72 -104 -213 -196 l-248 -80 -104 104 c-58 58 -105 109 -105 115 0 23 154 495 162 495 5 0 37 -13 72 -29z"/></g></svg>`
|
124 |
+
editButton.addEventListener("click", () => {
|
125 |
+
|
126 |
+
const doTheRest = () => {
|
127 |
+
let editData = fs.readFileSync(`${src}.json`, "utf8")
|
128 |
+
editData = JSON.parse(editData)
|
129 |
+
|
130 |
+
generateVoiceButton.dataset.modelIDLoaded = editData.pitchEditor ? editData.pitchEditor.currentVoice : editData.currentVoice
|
131 |
+
|
132 |
+
window.sequenceEditor.historyState.push(editData.inputSequence.trim())
|
133 |
+
window.sequenceEditor.isEditingFromFile = true
|
134 |
+
window.sequenceEditor.inputSequence = editData.inputSequence
|
135 |
+
window.sequenceEditor.pacing = editData.pacing
|
136 |
+
window.sequenceEditor.letters = editData.pitchEditor ? editData.pitchEditor.letters : editData.letters
|
137 |
+
window.sequenceEditor.currentVoice = editData.pitchEditor ? editData.pitchEditor.currentVoice : editData.currentVoice
|
138 |
+
window.sequenceEditor.resetEnergy = (editData.pitchEditor && editData.pitchEditor.resetEnergy) ? editData.pitchEditor.resetEnergy : editData.resetEnergy
|
139 |
+
window.sequenceEditor.resetPitch = editData.pitchEditor ? editData.pitchEditor.resetPitch : editData.resetPitch
|
140 |
+
window.sequenceEditor.resetDurs = editData.pitchEditor ? editData.pitchEditor.resetDurs : editData.resetDurs
|
141 |
+
window.sequenceEditor.resetEmAngry = editData.resetEmAngry
|
142 |
+
window.sequenceEditor.resetEmHappy = editData.resetEmHappy
|
143 |
+
window.sequenceEditor.resetEmSad = editData.resetEmSad
|
144 |
+
window.sequenceEditor.resetEmSurprise = editData.resetEmSurprise
|
145 |
+
window.sequenceEditor.letterFocus = []
|
146 |
+
window.sequenceEditor.ampFlatCounter = 0
|
147 |
+
window.sequenceEditor.hasChanged = false
|
148 |
+
window.sequenceEditor.sequence = editData.pitchEditor ? editData.pitchEditor.sequence : editData.sequence
|
149 |
+
window.sequenceEditor.energyNew = (editData.pitchEditor && editData.pitchEditor.energyNew) ? editData.pitchEditor.energyNew : editData.energyNew
|
150 |
+
window.sequenceEditor.pitchNew = editData.pitchEditor ? editData.pitchEditor.pitchNew : editData.pitchNew
|
151 |
+
window.sequenceEditor.dursNew = editData.pitchEditor ? editData.pitchEditor.dursNew : editData.dursNew
|
152 |
+
window.sequenceEditor.emAngryNew = editData.emAngryNew
|
153 |
+
window.sequenceEditor.emHappyNew = editData.emHappyNew
|
154 |
+
window.sequenceEditor.emSadNew = editData.emSadNew
|
155 |
+
window.sequenceEditor.emSurpriseNew = editData.emSurpriseNew
|
156 |
+
|
157 |
+
if (editData.styleValuesReset) {
|
158 |
+
window.sequenceEditor.loadStylesData()
|
159 |
+
|
160 |
+
window.sequenceEditor.registeredStyleKeys.forEach(styleKey => {
|
161 |
+
window.sequenceEditor.styleValuesReset[styleKey] = editData.styleValuesReset[styleKey]
|
162 |
+
window.sequenceEditor.styleValuesNew[styleKey] = editData.styleValuesNew[styleKey]
|
163 |
+
})
|
164 |
+
}
|
165 |
+
|
166 |
+
|
167 |
+
window.sequenceEditor.init()
|
168 |
+
window.sequenceEditor.update(window.currentModel.modelType)
|
169 |
+
window.sequenceEditor.autoInferTimer = null
|
170 |
+
|
171 |
+
dialogueInput.value = editData.inputSequence
|
172 |
+
window.refreshText()
|
173 |
+
paceNumbInput.value = editData.pacing
|
174 |
+
pace_slid.value = editData.pacing
|
175 |
+
|
176 |
+
window.sequenceEditor.sliderBoxes.forEach((box, i) => {box.setValueFromValue(window.sequenceEditor.dursNew[i])})
|
177 |
+
window.sequenceEditor.update(window.currentModel.modelType)
|
178 |
+
|
179 |
+
if (!window.wavesurfer) {
|
180 |
+
window.initWaveSurfer(src)
|
181 |
+
} else {
|
182 |
+
window.wavesurfer.load(src)
|
183 |
+
}
|
184 |
+
|
185 |
+
samplePlayPause.style.display = "block"
|
186 |
+
}
|
187 |
+
|
188 |
+
if (window.currentModel.loaded) {
|
189 |
+
doTheRest()
|
190 |
+
} else {
|
191 |
+
window.loadModel().then(() => {
|
192 |
+
doTheRest()
|
193 |
+
})
|
194 |
+
}
|
195 |
+
})
|
196 |
+
audioControls.appendChild(editButton)
|
197 |
+
}
|
198 |
+
|
199 |
+
const renameButton = createElem("div", {title: window.i18n.RENAME_THE_FILE})
|
200 |
+
renameButton.innerHTML = `<svg class="renameSVG" version="1.0" xmlns="http://www.w3.org/2000/svg" width="166.000000pt" height="336.000000pt" viewBox="0 0 166.000000 336.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,336.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"> <path d="M165 3175 c-30 -31 -35 -42 -35 -84 0 -34 6 -56 21 -75 42 -53 58 -56 324 -56 l245 0 0 -1290 0 -1290 -245 0 c-266 0 -282 -3 -324 -56 -15 -19 -21 -41 -21 -75 0 -42 5 -53 35 -84 l36 -35 281 0 280 0 41 40 c30 30 42 38 48 28 5 -7 9 -16 9 -21 0 -4 15 -16 33 -27 30 -19 51 -20 319 -20 l287 0 36 35 c30 31 35 42 35 84 0 34 -6 56 -21 75 -42 53 -58 56 -324 56 l-245 0 0 1290 0 1290 245 0 c266 0 282 3 324 56 15 19 21 41 21 75 0 42 -5 53 -35 84 l-36 35 -287 0 c-268 0 -289 -1 -319 -20 -18 -11 -33 -23 -33 -27 0 -5 -4 -14 -9 -21 -6 -10 -18 -2 -48 28 l-41 40 -280 0 -281 0 -36 -35z"/></g></svg>`
|
201 |
+
|
202 |
+
renameButton.addEventListener("click", () => {
|
203 |
+
createModal("prompt", {
|
204 |
+
prompt: window.i18n.ENTER_NEW_FILENAME_UNCHANGED_CANCEL,
|
205 |
+
value: sample.querySelector("div").innerHTML
|
206 |
+
}).then(newFileName => {
|
207 |
+
if (newFileName!=fileName) {
|
208 |
+
const oldPath = src.split("/").reverse()
|
209 |
+
const newPath = src.split("/").reverse()
|
210 |
+
oldPath[0] = sample.querySelector("div").innerHTML
|
211 |
+
newPath[0] = newFileName
|
212 |
+
|
213 |
+
const oldPathComposed = oldPath.reverse().join("/")
|
214 |
+
const newPathComposed = newPath.reverse().join("/")
|
215 |
+
fs.renameSync(oldPathComposed, newPathComposed)
|
216 |
+
|
217 |
+
if (fs.existsSync(`${oldPathComposed}.json`)) {
|
218 |
+
fs.renameSync(oldPathComposed+".json", newPathComposed+".json")
|
219 |
+
}
|
220 |
+
if (fs.existsSync(`${oldPathComposed.replace(/\.wav$/, "")}.lip`)) {
|
221 |
+
fs.renameSync(oldPathComposed.replace(/\.wav$/, "")+".lip", newPathComposed.replace(/\.wav$/, "")+".lip")
|
222 |
+
}
|
223 |
+
if (fs.existsSync(`${oldPathComposed.replace(/\.wav$/, "")}.fuz`)) {
|
224 |
+
fs.renameSync(oldPathComposed.replace(/\.wav$/, "")+".fuz", newPathComposed.replace(/\.wav$/, "")+".fuz")
|
225 |
+
}
|
226 |
+
|
227 |
+
oldPath.reverse()
|
228 |
+
oldPath.splice(0,1)
|
229 |
+
refreshRecordsList(oldPath.reverse().join("/"))
|
230 |
+
}
|
231 |
+
})
|
232 |
+
})
|
233 |
+
|
234 |
+
const editInProgramButton = createElem("div", {title: window.i18n.EDIT_IN_EXTERNAL_PROGRAM})
|
235 |
+
editInProgramButton.innerHTML = `<svg class="renameSVG" version="1.0" width="175.000000pt" height="240.000000pt" viewBox="0 0 175.000000 240.000000" preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,240.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"><path d="M615 2265 l-129 -125 -68 0 c-95 0 -98 -4 -98 -150 0 -146 3 -150 98 -150 l68 0 129 -125 c128 -123 165 -145 179 -109 8 20 8 748 0 768 -14 36 -51 14 -179 -109z"/> <path d="M1016 2344 c-22 -21 -20 -30 10 -51 66 -45 126 -109 151 -162 22 -47 27 -69 27 -141 0 -72 -5 -94 -27 -141 -25 -53 -85 -117 -151 -162 -30 -20 -33 -39 -11 -57 22 -18 64 3 132 64 192 173 164 491 -54 636 -54 35 -56 35 -77 14z"/> <path d="M926 2235 c-8 -22 1 -37 46 -70 73 -53 104 -149 78 -241 -13 -44 -50 -92 -108 -136 -26 -21 -27 -31 -6 -52 37 -38 150 68 179 167 27 91 13 181 -41 259 -49 70 -133 112 -148 73z"/> <path d="M834 2115 c-9 -23 2 -42 33 -57 53 -25 56 -108 4 -134 -35 -18 -44 -30 -36 -53 8 -25 34 -27 76 -6 92 48 92 202 0 250 -38 19 -70 19 -77 0z"/> <path d="M1381 1853 c-33 -47 -182 -253 -264 -364 -100 -137 -187 -262 -187 -270 0 -8 140 -204 177 -249 5 -6 41 41 109 141 30 45 60 86 65 93 48 54 197 276 226 336 33 68 37 83 37 160 1 71 -3 93 -23 130 -53 101 -82 106 -140 23z"/> <path d="M211 1861 c-56 -60 -68 -184 -27 -283 15 -38 106 -168 260 -371 130 -173 236 -320 236 -328 0 -8 -9 -25 -20 -39 -11 -14 -20 -29 -20 -33 0 -5 -10 -23 -23 -40 -12 -18 -27 -41 -33 -52 -13 -24 -65 -114 -80 -138 -10 -17 -13 -16 -60 7 -98 49 -209 43 -305 -17 -83 -51 -129 -141 -129 -251 0 -161 115 -283 275 -294 101 -6 173 22 243 96 56 58 79 97 133 227 46 112 101 203 164 274 l53 60 42 -45 c27 -29 69 -103 124 -217 86 -176 133 -250 197 -306 157 -136 405 -73 478 123 37 101 21 202 -46 290 -91 118 -275 147 -402 63 -30 -20 -42 -23 -49 -14 -5 7 -48 82 -96 167 -47 85 -123 202 -168 260 -45 58 -111 143 -146 190 -85 110 -251 326 -321 416 -31 40 -65 84 -76 100 -11 15 -35 46 -54 68 -19 23 -45 58 -59 79 -30 45 -54 47 -91 8z m653 -943 c20 -28 20 -33 0 -52 -42 -43 -109 10 -69 54 24 26 50 25 69 -2z m653 -434 c49 -20 87 -85 87 -149 -2 -135 -144 -209 -257 -134 -124 82 -89 265 58 299 33 8 64 4 112 -16z m-1126 -20 c47 -24 73 -71 77 -139 3 -50 0 -65 -20 -94 -34 -50 -71 -73 -125 -78 -99 -9 -173 53 -181 152 -11 135 126 223 249 159z"/></g></svg>`
|
236 |
+
editInProgramButton.addEventListener("click", () => {
|
237 |
+
|
238 |
+
if (window.userSettings.externalAudioEditor && window.userSettings.externalAudioEditor.length) {
|
239 |
+
const fileName = audio.children[0].src.split("file:///")[1].split("%20").join(" ")
|
240 |
+
const sp = spawn(window.userSettings.externalAudioEditor, [fileName], {'detached': true}, (err, data) => {
|
241 |
+
if (err) {
|
242 |
+
console.log(err)
|
243 |
+
console.log(err.message)
|
244 |
+
window.errorModal(err.message)
|
245 |
+
}
|
246 |
+
})
|
247 |
+
|
248 |
+
sp.on("error", err => {
|
249 |
+
if (err.message.includes("ENOENT")) {
|
250 |
+
window.errorModal(`${window.i18n.FOLLOWING_PATH_NOT_VALID}:<br><br> ${window.userSettings.externalAudioEditor}`)
|
251 |
+
} else {
|
252 |
+
window.errorModal(err.message)
|
253 |
+
}
|
254 |
+
})
|
255 |
+
|
256 |
+
} else {
|
257 |
+
window.errorModal(window.i18n.SPECIFY_EDIT_TOOL)
|
258 |
+
}
|
259 |
+
})
|
260 |
+
|
261 |
+
|
262 |
+
const deleteFileButton = createElem("div", {title: window.i18n.DELETE_FILE})
|
263 |
+
deleteFileButton.innerHTML = "❌"
|
264 |
+
deleteFileButton.addEventListener("click", () => {
|
265 |
+
confirmModal(`${window.i18n.SURE_DELETE}<br><br><i>${fileName}</i>`).then(confirmation => {
|
266 |
+
if (confirmation) {
|
267 |
+
window.appLogger.log(`${newSample?window.i18n.DELETING_NEW_FILE:window.i18n.DELETING}: ${src}`)
|
268 |
+
if (fs.existsSync(src)) {
|
269 |
+
fs.unlinkSync(src)
|
270 |
+
}
|
271 |
+
sample.remove()
|
272 |
+
if (fs.existsSync(`${src}.json`)) {
|
273 |
+
fs.unlinkSync(`${src}.json`)
|
274 |
+
}
|
275 |
+
}
|
276 |
+
})
|
277 |
+
})
|
278 |
+
audioControls.appendChild(renameButton)
|
279 |
+
audioControls.appendChild(audioSVG)
|
280 |
+
audioControls.appendChild(editInProgramButton)
|
281 |
+
audioControls.appendChild(openFileLocationButton)
|
282 |
+
audioControls.appendChild(deleteFileButton)
|
283 |
+
sample.appendChild(audioControls)
|
284 |
+
return sample
|
285 |
+
}
|
286 |
+
|
287 |
+
|
288 |
+
window.refreshRecordsList = () => {
|
289 |
+
voiceSamples.innerHTML = ""
|
290 |
+
const outputFilesPaginationSize = window.userSettings.output_files_pagination_size
|
291 |
+
|
292 |
+
|
293 |
+
const filteredRecords = window.outputFilesState.records.filter(recordAndJson => {
|
294 |
+
if (!recordAndJson[0].fileName.toLowerCase().includes(voiceSamplesSearch.value.toLowerCase().trim())) {
|
295 |
+
return
|
296 |
+
}
|
297 |
+
if (voiceSamplesSearchPrompt.value.length) {
|
298 |
+
if (!recordAndJson[1] || !recordAndJson[1].inputSequence.toLowerCase().includes(voiceSamplesSearchPrompt.value.toLowerCase().trim())) {
|
299 |
+
return
|
300 |
+
}
|
301 |
+
}
|
302 |
+
return recordAndJson
|
303 |
+
})
|
304 |
+
|
305 |
+
|
306 |
+
const startIndex = (window.outputFilesState.paginationIndex*outputFilesPaginationSize)
|
307 |
+
const endIndex = Math.min(startIndex+outputFilesPaginationSize, filteredRecords.length)
|
308 |
+
|
309 |
+
for (let ri=startIndex; ri<endIndex; ri++) {
|
310 |
+
voiceSamples.appendChild(window.makeSample(filteredRecords[ri][0].jsonPath))
|
311 |
+
}
|
312 |
+
const numPages = Math.ceil(filteredRecords.length/outputFilesPaginationSize)
|
313 |
+
main_total_pages.innerHTML = window.i18n.PAGINATION_TOTAL_OF.replace("_1", numPages)
|
314 |
+
window.outputFilesState.totalPages = numPages
|
315 |
+
}
|
316 |
+
main_paginationPrev.addEventListener("click", () => {
|
317 |
+
main_pageNum.value = Math.max(1, parseInt(main_pageNum.value)-1)
|
318 |
+
window.outputFilesState.paginationIndex = main_pageNum.value-1
|
319 |
+
window.refreshRecordsList()
|
320 |
+
})
|
321 |
+
main_paginationNext.addEventListener("click", () => {
|
322 |
+
main_pageNum.value = Math.min(parseInt(main_pageNum.value)+1, window.outputFilesState.totalPages)
|
323 |
+
window.outputFilesState.paginationIndex = main_pageNum.value-1
|
324 |
+
window.refreshRecordsList()
|
325 |
+
})
|
326 |
+
|
327 |
+
|
328 |
+
// Delete all output files for a voice
|
329 |
+
voiceRecordsDeleteAllButton.addEventListener("click", () => {
|
330 |
+
if (window.currentModel) {
|
331 |
+
const outDir = window.userSettings[`outpath_${window.currentGame.gameId}`]+`/${currentModel.voiceId}`
|
332 |
+
|
333 |
+
const files = fs.readdirSync(outDir)
|
334 |
+
if (files.length) {
|
335 |
+
window.confirmModal(window.i18n.DELETE_ALL_FILES_CONFIRM.replace("_1", files.length).replace("_2", outDir)).then(resp => {
|
336 |
+
if (resp) {
|
337 |
+
window.deleteFolderRecursive(outDir, true)
|
338 |
+
window.initMainPagePagination(outDir)
|
339 |
+
window.refreshRecordsList()
|
340 |
+
}
|
341 |
+
})
|
342 |
+
} else {
|
343 |
+
window.errorModal(window.i18n.DELETE_ALL_FILES_ERR_NO_FILES.replace("_1", outDir))
|
344 |
+
}
|
345 |
+
}
|
346 |
+
})
|
347 |
+
|
348 |
+
voiceRecordsOrderByButton.addEventListener("click", () => {
|
349 |
+
window.userSettings.voiceRecordsOrderBy = window.userSettings.voiceRecordsOrderBy=="name" ? "time" : "name"
|
350 |
+
saveUserSettings()
|
351 |
+
const labels = {
|
352 |
+
"name": window.i18n.NAME,
|
353 |
+
"time": window.i18n.TIME
|
354 |
+
}
|
355 |
+
voiceRecordsOrderByButton.innerHTML = labels[window.userSettings.voiceRecordsOrderBy]
|
356 |
+
if (window.currentModel) {
|
357 |
+
const voiceRecordsList = window.userSettings[`outpath_${window.currentGame.gameId}`]+`/${window.currentModel.voiceId}`
|
358 |
+
window.reOrderMainPageRecords()
|
359 |
+
window.refreshRecordsList()
|
360 |
+
}
|
361 |
+
})
|
362 |
+
voiceRecordsOrderByOrderButton.addEventListener("click", () => {
|
363 |
+
window.userSettings.voiceRecordsOrderByOrder = window.userSettings.voiceRecordsOrderByOrder=="ascending" ? "descending" : "ascending"
|
364 |
+
saveUserSettings()
|
365 |
+
const labels = {
|
366 |
+
"ascending": window.i18n.ASCENDING,
|
367 |
+
"descending": window.i18n.DESCENDING
|
368 |
+
}
|
369 |
+
voiceRecordsOrderByOrderButton.innerHTML = labels[window.userSettings.voiceRecordsOrderByOrder]
|
370 |
+
if (window.currentModel) {
|
371 |
+
const voiceRecordsList = window.userSettings[`outpath_${window.currentGame.gameId}`]+`/${window.currentModel.voiceId}`
|
372 |
+
window.reOrderMainPageRecords()
|
373 |
+
window.refreshRecordsList()
|
374 |
+
}
|
375 |
+
})
|
376 |
+
voiceSamplesSearch.addEventListener("keyup", () => {
|
377 |
+
if (window.currentModel) {
|
378 |
+
window.outputFilesState.paginationIndex = 0
|
379 |
+
main_pageNum.value = 1
|
380 |
+
const voiceRecordsList = window.userSettings[`outpath_${window.currentGame.gameId}`]+`/${window.currentModel.voiceId}`
|
381 |
+
window.refreshRecordsList()
|
382 |
+
}
|
383 |
+
})
|
384 |
+
voiceSamplesSearchPrompt.addEventListener("keyup", () => {
|
385 |
+
if (window.currentModel) {
|
386 |
+
window.outputFilesState.paginationIndex = 0
|
387 |
+
main_pageNum.value = 1
|
388 |
+
const voiceRecordsList = window.userSettings[`outpath_${window.currentGame.gameId}`]+`/${window.currentModel.voiceId}`
|
389 |
+
window.refreshRecordsList()
|
390 |
+
}
|
391 |
+
})
|
392 |
+
|
393 |
+
if (Object.keys(window.userSettings).includes("voiceRecordsOrderBy")) {
|
394 |
+
const labels = {
|
395 |
+
"name": window.i18n.NAME,
|
396 |
+
"time": window.i18n.TIME
|
397 |
+
}
|
398 |
+
voiceRecordsOrderByButton.innerHTML = labels[window.userSettings.voiceRecordsOrderBy]
|
399 |
+
} else {
|
400 |
+
window.userSettings.voiceRecordsOrderBy = "name"
|
401 |
+
saveUserSettings()
|
402 |
+
}
|
403 |
+
if (Object.keys(window.userSettings).includes("voiceRecordsOrderByOrder")) {
|
404 |
+
const labels = {
|
405 |
+
"ascending": window.i18n.ASCENDING,
|
406 |
+
"descending": window.i18n.DESCENDING
|
407 |
+
}
|
408 |
+
voiceRecordsOrderByOrderButton.innerHTML = labels[window.userSettings.voiceRecordsOrderByOrder]
|
409 |
+
} else {
|
410 |
+
window.userSettings.voiceRecordsOrderByOrder = "ascending"
|
411 |
+
saveUserSettings()
|
412 |
+
}
|
javascript/plugins_manager.js
ADDED
@@ -0,0 +1,594 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
const fs = require("fs")
|
4 |
+
const er = require('@electron/remote')
|
5 |
+
|
6 |
+
class PluginsManager {
|
7 |
+
|
8 |
+
constructor (path, appLogger, appVersion) {
|
9 |
+
|
10 |
+
this.path = `${__dirname.replace(/\\/g,"/").replace("/javascript", "")}/`.replace("/resources/app/resources/app", "/resources/app")
|
11 |
+
this.appVersion = appVersion
|
12 |
+
this.appLogger = appLogger
|
13 |
+
this.plugins = []
|
14 |
+
this.selectedPlugin = undefined
|
15 |
+
this.hasRunPostStartPlugins = false
|
16 |
+
this.changesToApply = {
|
17 |
+
ticked: [],
|
18 |
+
unticked: []
|
19 |
+
}
|
20 |
+
this.teardownModules = {}
|
21 |
+
this.resetModules()
|
22 |
+
|
23 |
+
|
24 |
+
this.scanPlugins()
|
25 |
+
this.savePlugins()
|
26 |
+
this.appLogger.log(`${this.path}/plugins`)
|
27 |
+
if (fs.existsSync(`${this.path}/plugins`)) {
|
28 |
+
fs.watch(`${this.path}/plugins`, {recursive: false, persistent: true}, (eventType, filename) => {
|
29 |
+
this.scanPlugins()
|
30 |
+
this.updateUI()
|
31 |
+
this.savePlugins()
|
32 |
+
})
|
33 |
+
}
|
34 |
+
|
35 |
+
plugins_moveUpBtn.addEventListener("click", () => {
|
36 |
+
|
37 |
+
if (!this.selectedPlugin || this.selectedPlugin[1]==0) return
|
38 |
+
|
39 |
+
const plugin = this.plugins.splice(this.selectedPlugin[1], 1)[0]
|
40 |
+
this.plugins.splice(this.selectedPlugin[1]-1, 0, plugin)
|
41 |
+
this.selectedPlugin[1] -= 1
|
42 |
+
this.updateUI()
|
43 |
+
plugins_applyBtn.disabled = false
|
44 |
+
})
|
45 |
+
|
46 |
+
plugins_moveDownBtn.addEventListener("click", () => {
|
47 |
+
|
48 |
+
if (!this.selectedPlugin || this.selectedPlugin[1]==this.plugins.length-1) return
|
49 |
+
|
50 |
+
const plugin = this.plugins.splice(this.selectedPlugin[1], 1)[0]
|
51 |
+
this.plugins.splice(this.selectedPlugin[1]+1, 0, plugin)
|
52 |
+
this.selectedPlugin[1] += 1
|
53 |
+
this.updateUI()
|
54 |
+
plugins_applyBtn.disabled = false
|
55 |
+
})
|
56 |
+
|
57 |
+
plugins_applyBtn.addEventListener("click", () => this.apply())
|
58 |
+
plugins_main.addEventListener("click", (e) => {
|
59 |
+
if (e.target == plugins_main) {
|
60 |
+
this.selectedPlugin = undefined
|
61 |
+
this.updateUI()
|
62 |
+
}
|
63 |
+
})
|
64 |
+
|
65 |
+
window.pluginsManager = this
|
66 |
+
this.loadModules()
|
67 |
+
}
|
68 |
+
|
69 |
+
resetModules () {
|
70 |
+
this.setupModules = new Set()
|
71 |
+
this.pluginsModules = {
|
72 |
+
"start": {
|
73 |
+
"pre": [],
|
74 |
+
"post": []
|
75 |
+
},
|
76 |
+
"keep-sample": {
|
77 |
+
"pre": [],
|
78 |
+
"mid": [],
|
79 |
+
"post": []
|
80 |
+
},
|
81 |
+
"batch-stop": {
|
82 |
+
"post": []
|
83 |
+
},
|
84 |
+
"generate-voice": {
|
85 |
+
"pre": []
|
86 |
+
}
|
87 |
+
}
|
88 |
+
pluginsCSS.innerHTML = ""
|
89 |
+
}
|
90 |
+
|
91 |
+
scanPlugins () {
|
92 |
+
|
93 |
+
const plugins = []
|
94 |
+
|
95 |
+
try {
|
96 |
+
const pluginIDs = fs.readdirSync(`${this.path}/plugins`)
|
97 |
+
pluginIDs.forEach(pluginId => {
|
98 |
+
try {
|
99 |
+
const pluginData = JSON.parse(fs.readFileSync(`${this.path}/plugins/${pluginId}/plugin.json`))
|
100 |
+
|
101 |
+
const minVersionOk = window.checkVersionRequirements(pluginData["min-app-version"], this.appVersion)
|
102 |
+
const maxVersionOk = window.checkVersionRequirements(pluginData["max-app-version"], this.appVersion, true)
|
103 |
+
|
104 |
+
plugins.push([pluginId, pluginData, false, minVersionOk, maxVersionOk])
|
105 |
+
|
106 |
+
} catch (e) {
|
107 |
+
this.appLogger.log(`${window.i18n.ERR_LOADING_PLUGIN} ${pluginId}: ${e}`)
|
108 |
+
}
|
109 |
+
})
|
110 |
+
|
111 |
+
|
112 |
+
} catch (e) {
|
113 |
+
console.log(e)
|
114 |
+
}
|
115 |
+
|
116 |
+
const orderedPlugins = []
|
117 |
+
|
118 |
+
// Order the found known plugins
|
119 |
+
window.userSettings.plugins.loadOrder.split(",").forEach(pluginId => {
|
120 |
+
for (let i=0; i<plugins.length; i++) {
|
121 |
+
if (pluginId.replace("*", "")==plugins[i][0]) {
|
122 |
+
plugins[i][2] = pluginId.includes("*") && plugins[i][3] && plugins[i][4]
|
123 |
+
orderedPlugins.push(plugins[i])
|
124 |
+
plugins.splice(i,1)
|
125 |
+
break
|
126 |
+
}
|
127 |
+
}
|
128 |
+
})
|
129 |
+
|
130 |
+
// Add any remaining (new) plugins at the bottom of the list
|
131 |
+
plugins.forEach(p => orderedPlugins.push(p))
|
132 |
+
this.plugins = orderedPlugins
|
133 |
+
}
|
134 |
+
|
135 |
+
updateUI () {
|
136 |
+
|
137 |
+
pluginsRecordsContainer.innerHTML = ""
|
138 |
+
|
139 |
+
this.plugins.forEach(([pluginId, pluginData, isEnabled, minVersionOk, maxVersionOk], pi) => {
|
140 |
+
const record = createElem("div")
|
141 |
+
const enabledCkbx = createElem("input", {type: "checkbox"})
|
142 |
+
enabledCkbx.checked = isEnabled
|
143 |
+
record.appendChild(createElem("div", enabledCkbx))
|
144 |
+
record.appendChild(createElem("div", `${pi}`))
|
145 |
+
|
146 |
+
const pluginNameElem = createElem("div", pluginData["plugin-name"])
|
147 |
+
pluginNameElem.title = pluginData["plugin-name"]
|
148 |
+
record.appendChild(pluginNameElem)
|
149 |
+
|
150 |
+
const pluginAuthorElem = createElem("div", pluginData["author"]||"")
|
151 |
+
pluginAuthorElem.title = pluginData["author"]||""
|
152 |
+
record.appendChild(pluginAuthorElem)
|
153 |
+
|
154 |
+
const endorseButtonContainer = createElem("div")
|
155 |
+
record.appendChild(endorseButtonContainer)
|
156 |
+
if (pluginData["nexus-link"] && window.nexusState.key) {
|
157 |
+
|
158 |
+
if (window.nexusState.key) {
|
159 |
+
window.nexus_getData(`${pluginData["nexus-link"].split(".com/")[1]}.json`).then(repoInfo => {
|
160 |
+
const endorseButton = createElem("button.smallButton", "Endorse")
|
161 |
+
const gameId = repoInfo.game_id
|
162 |
+
const nexusRepoId = repoInfo.mod_id
|
163 |
+
|
164 |
+
if (repoInfo.endorsement.endorse_status=="Endorsed") {
|
165 |
+
window.endorsedRepos.add(`plugin:${pluginId}`)
|
166 |
+
endorseButton.innerHTML = "Unendorse"
|
167 |
+
endorseButton.style.background = "none"
|
168 |
+
endorseButton.style.border = `2px solid #${window.currentGame ? currentGame.themeColourPrimary : "aaa"}`
|
169 |
+
} else {
|
170 |
+
endorseButton.style.setProperty("background-color", `#${window.currentGame ? currentGame.themeColourPrimary : "aaa"}`, "important")
|
171 |
+
}
|
172 |
+
|
173 |
+
endorseButtonContainer.appendChild(endorseButton)
|
174 |
+
endorseButton.addEventListener("click", async () => {
|
175 |
+
let response
|
176 |
+
if (window.endorsedRepos.has(`plugin:${pluginId}`)) {
|
177 |
+
response = await window.nexus_getData(`${gameId}/mods/${nexusRepoId}/abstain.json`, {
|
178 |
+
game_domain_name: gameId,
|
179 |
+
id: nexusRepoId,
|
180 |
+
version: repoInfo.version
|
181 |
+
}, "POST")
|
182 |
+
} else {
|
183 |
+
response = await window.nexus_getData(`${gameId}/mods/${nexusRepoId}/endorse.json`, {
|
184 |
+
game_domain_name: gameId,
|
185 |
+
id: nexusRepoId,
|
186 |
+
version: repoInfo.version
|
187 |
+
}, "POST")
|
188 |
+
}
|
189 |
+
if (response && response.message && response.status=="Error") {
|
190 |
+
if (response.message=="NOT_DOWNLOADED_MOD") {
|
191 |
+
response.message = "You need to first download something from this repo to be able to endorse it."
|
192 |
+
} else if (response.message=="TOO_SOON_AFTER_DOWNLOAD") {
|
193 |
+
response.message = "Nexus requires you to wait at least 15 mins (at the time of writing) before you can endorse."
|
194 |
+
} else if (response.message=="IS_OWN_MOD") {
|
195 |
+
response.message = "Nexus does not allow you to rate your own content."
|
196 |
+
}
|
197 |
+
|
198 |
+
window.errorModal(response.message)
|
199 |
+
} else {
|
200 |
+
|
201 |
+
if (window.endorsedRepos.has(`plugin:${pluginId}`)) {
|
202 |
+
window.endorsedRepos.delete(`plugin:${pluginId}`)
|
203 |
+
} else {
|
204 |
+
window.endorsedRepos.add(`plugin:${pluginId}`)
|
205 |
+
}
|
206 |
+
this.updateUI()
|
207 |
+
}
|
208 |
+
})
|
209 |
+
})
|
210 |
+
}
|
211 |
+
}
|
212 |
+
|
213 |
+
|
214 |
+
const hasBackendScript = !!Object.keys(pluginData["back-end-hooks"]).find(key => {
|
215 |
+
return (key=="custom-event" && pluginData["back-end-hooks"]["custom-event"]["file"]) ||
|
216 |
+
(pluginData["back-end-hooks"][key]["pre"] && pluginData["back-end-hooks"][key]["pre"]["file"]) ||
|
217 |
+
(pluginData["back-end-hooks"][key]["mid"] && pluginData["back-end-hooks"][key]["mid"]["file"]) ||
|
218 |
+
(pluginData["back-end-hooks"][key]["post"] && pluginData["back-end-hooks"][key]["post"]["file"])
|
219 |
+
})
|
220 |
+
const hasFrontendScript = !!pluginData["front-end-hooks"]
|
221 |
+
const type = hasFrontendScript && hasBackendScript ? "Both": (!hasFrontendScript && !hasBackendScript ? "None" : (hasFrontendScript ? "Front" : "Back"))
|
222 |
+
|
223 |
+
record.appendChild(createElem("div", pluginData["plugin-version"]))
|
224 |
+
record.appendChild(createElem("div", type))
|
225 |
+
// Min app version requirement
|
226 |
+
const minAppVersionElem = createElem("div", pluginData["min-app-version"])
|
227 |
+
record.appendChild(minAppVersionElem)
|
228 |
+
if (pluginData["min-app-version"] && !minVersionOk) {
|
229 |
+
minAppVersionElem.style.color = "red"
|
230 |
+
enabledCkbx.checked = false
|
231 |
+
enabledCkbx.disabled = true
|
232 |
+
}
|
233 |
+
|
234 |
+
// Max app version requirement
|
235 |
+
const maxAppVersionElem = createElem("div", pluginData["max-app-version"])
|
236 |
+
record.appendChild(maxAppVersionElem)
|
237 |
+
if (pluginData["max-app-version"] && !maxVersionOk) {
|
238 |
+
maxAppVersionElem.style.color = "red"
|
239 |
+
enabledCkbx.checked = false
|
240 |
+
enabledCkbx.disabled = true
|
241 |
+
}
|
242 |
+
|
243 |
+
const shortDescriptionElem = createElem("div", pluginData["plugin-short-description"])
|
244 |
+
shortDescriptionElem.title = pluginData["plugin-short-description"]
|
245 |
+
record.appendChild(shortDescriptionElem)
|
246 |
+
|
247 |
+
const pluginIdElem = createElem("div", pluginId)
|
248 |
+
pluginIdElem.title = pluginId
|
249 |
+
record.appendChild(pluginIdElem)
|
250 |
+
|
251 |
+
pluginsRecordsContainer.appendChild(record)
|
252 |
+
|
253 |
+
enabledCkbx.addEventListener("click", () => {
|
254 |
+
this.plugins[pi][2] = enabledCkbx.checked
|
255 |
+
plugins_applyBtn.disabled = false
|
256 |
+
})
|
257 |
+
|
258 |
+
record.addEventListener("click", (e) => {
|
259 |
+
|
260 |
+
if (e.target==enabledCkbx || e.target.nodeName=="BUTTON") {
|
261 |
+
return
|
262 |
+
}
|
263 |
+
|
264 |
+
if (this.selectedPlugin) {
|
265 |
+
this.selectedPlugin[0].style.background = "none"
|
266 |
+
Array.from(this.selectedPlugin[0].children).forEach(child => child.style.color = "white")
|
267 |
+
}
|
268 |
+
|
269 |
+
this.selectedPlugin = [record, pi, pluginData]
|
270 |
+
this.selectedPlugin[0].style.background = "white"
|
271 |
+
Array.from(this.selectedPlugin[0].children).forEach(child => child.style.color = "black")
|
272 |
+
|
273 |
+
plugins_moveUpBtn.disabled = false
|
274 |
+
plugins_moveDownBtn.disabled = false
|
275 |
+
|
276 |
+
})
|
277 |
+
if (this.selectedPlugin && pi==this.selectedPlugin[1]) {
|
278 |
+
this.selectedPlugin = [record, pi, pluginData]
|
279 |
+
this.selectedPlugin[0].style.background = "white"
|
280 |
+
Array.from(this.selectedPlugin[0].children).forEach(child => child.style.color = "black")
|
281 |
+
}
|
282 |
+
|
283 |
+
})
|
284 |
+
}
|
285 |
+
|
286 |
+
savePlugins () {
|
287 |
+
window.userSettings.plugins.loadOrder = this.plugins.map(([pluginId, pluginData, isEnabled]) => `${isEnabled?"*":""}${pluginId}`).join(",")
|
288 |
+
saveUserSettings()
|
289 |
+
fs.writeFileSync(`./plugins.txt`, window.userSettings.plugins.loadOrder.replace(/,/g, "\n"))
|
290 |
+
}
|
291 |
+
|
292 |
+
apply () {
|
293 |
+
|
294 |
+
const enabledPlugins = this.plugins.filter(([pluginId, pluginData, isEnabled]) => isEnabled).map(([pluginId, pluginData, isEnabled]) => pluginId)
|
295 |
+
const newPlugins = enabledPlugins.filter(pluginId => !window.userSettings.plugins.loadOrder.includes(`*${pluginId}`))
|
296 |
+
const removedPlugins = window.userSettings.plugins.loadOrder.split(",").filter(pluginId => pluginId.startsWith("*") && !enabledPlugins.includes(pluginId.slice(1, 100000)) ).map(pluginId => pluginId.slice(1, 100000))
|
297 |
+
|
298 |
+
removedPlugins.forEach(pluginId => {
|
299 |
+
if (this.teardownModules[pluginId]) {
|
300 |
+
this.teardownModules[pluginId].forEach(func => func())
|
301 |
+
}
|
302 |
+
})
|
303 |
+
|
304 |
+
const pluginLoadStatus = this.loadModules()
|
305 |
+
if (pluginLoadStatus) {
|
306 |
+
window.errorModal(`${window.i18n.FAILED_INIT_FOLLOWING} ${window.i18n.PLUGIN.toLowerCase()}: ${pluginLoadStatus}`)
|
307 |
+
return
|
308 |
+
}
|
309 |
+
this.savePlugins()
|
310 |
+
this.resetModules()
|
311 |
+
plugins_applyBtn.disabled = true
|
312 |
+
|
313 |
+
doFetch(`http://localhost:8008/refreshPlugins`, {
|
314 |
+
method: "Post",
|
315 |
+
body: "{}"
|
316 |
+
}).then(r=>r.text()).then(status => {
|
317 |
+
|
318 |
+
const plugins = status.split(",")
|
319 |
+
const successful = plugins.filter(p => p=="OK")
|
320 |
+
const failed = plugins.filter(p => p!="OK")
|
321 |
+
|
322 |
+
let message = `${window.i18n.SUCCESSFULLY_INITIALIZED} ${successful.length} ${successful.length>1||successful.length==0?window.i18n.PLUGINS:window.i18n.PLUGIN}.`
|
323 |
+
if (failed.length) {
|
324 |
+
if (successful.length==0) {
|
325 |
+
message = ""
|
326 |
+
}
|
327 |
+
message += ` ${window.i18n.FAILED_INIT_FOLLOWING} ${failed.length>1?window.i18n.PLUGINS:window.i18n.PLUGIN}: <br>${failed.join("<br>")} <br><br>${window.i18n.CHECK_SERVERLOG}`
|
328 |
+
}
|
329 |
+
|
330 |
+
if (!status.length || successful.length==0 && failed.length==0) {
|
331 |
+
message = window.i18n.SUCC_NO_ACTIVE_PLUGINS
|
332 |
+
}
|
333 |
+
|
334 |
+
const restartRequired = newPlugins.map(newPluginId => this.plugins.find(([pluginId, pluginData, isEnabled]) => pluginId==newPluginId))
|
335 |
+
.filter(([pluginId, pluginData, isEnabled]) => !!pluginData["install-requires-restart"]).length +
|
336 |
+
removedPlugins.map(removedPluginId => this.plugins.find(([pluginId, pluginData, isEnabled]) => pluginId==removedPluginId))
|
337 |
+
.filter(([pluginId, pluginData, isEnabled]) => !!pluginData["uninstall-requires-restart"]).length
|
338 |
+
if (restartRequired) {
|
339 |
+
message += `<br><br> ${window.i18n.APP_RESTART_NEEDED}`
|
340 |
+
}
|
341 |
+
|
342 |
+
// Don't use window.errorModal, otherwise you get the error sound
|
343 |
+
createModal("error", message)
|
344 |
+
})
|
345 |
+
}
|
346 |
+
|
347 |
+
|
348 |
+
loadModules () {
|
349 |
+
for (let pi=0; pi<this.plugins.length; pi++) {
|
350 |
+
const [pluginId, pluginData, enabled] = this.plugins[pi]
|
351 |
+
if (!enabled) continue
|
352 |
+
let failed
|
353 |
+
|
354 |
+
failed = this.loadModuleFns(pluginId, pluginData, "start", "pre")
|
355 |
+
if (failed) return `${pluginId}->start->pre<br><br>${failed}`
|
356 |
+
|
357 |
+
failed = this.loadModuleFns(pluginId, pluginData, "start", "post")
|
358 |
+
if (failed) return `${pluginId}->start->post<br><br>${failed}`
|
359 |
+
|
360 |
+
this.loadModuleFns(pluginId, pluginData, "keep-sample", "pre")
|
361 |
+
if (failed) return `${pluginId}->keep-sample->pre<br><br>${failed}`
|
362 |
+
|
363 |
+
this.loadModuleFns(pluginId, pluginData, "keep-sample", "mid")
|
364 |
+
if (failed) return `${pluginId}->keep-sample->mid<br><br>${failed}`
|
365 |
+
|
366 |
+
this.loadModuleFns(pluginId, pluginData, "keep-sample", "post")
|
367 |
+
if (failed) return `${pluginId}->keep-sample->post<br><br>${failed}`
|
368 |
+
|
369 |
+
this.loadModuleFns(pluginId, pluginData, "generate-voice", "pre")
|
370 |
+
if (failed) return `${pluginId}->generate-voice->pre<br><br>${failed}`
|
371 |
+
|
372 |
+
this.loadModuleFns(pluginId, pluginData, "batch-stop", "post")
|
373 |
+
if (failed) return `${pluginId}->batch-stop->post<br><br>${failed}`
|
374 |
+
|
375 |
+
|
376 |
+
if (Object.keys(pluginData).includes("front-end-style-files") && pluginData["front-end-style-files"].length) {
|
377 |
+
pluginData["front-end-style-files"].forEach(styleFile => {
|
378 |
+
try {
|
379 |
+
if (styleFile.endsWith(".css")) {
|
380 |
+
const styleData = fs.readFileSync(`${this.path}/plugins/${pluginId}/${styleFile}`)
|
381 |
+
pluginsCSS.innerHTML += styleData
|
382 |
+
}
|
383 |
+
} catch (e) {
|
384 |
+
window.appLogger.log(`${window.i18n.ERR_LOADING_CSS} ${pluginId}: ${e}`)
|
385 |
+
}
|
386 |
+
})
|
387 |
+
}
|
388 |
+
}
|
389 |
+
}
|
390 |
+
|
391 |
+
loadModuleFns (pluginId, pluginData, task, hookTime) {
|
392 |
+
try {
|
393 |
+
if (Object.keys(pluginData).includes("front-end-hooks") && Object.keys(pluginData["front-end-hooks"]).includes(task) && Object.keys(pluginData["front-end-hooks"][task]).includes(hookTime) ) {
|
394 |
+
|
395 |
+
const file = pluginData["front-end-hooks"][task][hookTime]["file"]
|
396 |
+
const functionName = pluginData["front-end-hooks"][task][hookTime]["function"]
|
397 |
+
|
398 |
+
if (!file.endsWith(".js")) {
|
399 |
+
window.appLogger.log(`[${window.i18n.PLUGIN}: ${pluginId}]: ${window.i18n.CANT_IMPORT_FILE_FOR_HOOK_TASK_ENTRYPOINT.replace("_1", file).replace("_2", hookTime).replace("_3", task)}: ${window.i18n.ONLY_JS}`)
|
400 |
+
return
|
401 |
+
}
|
402 |
+
|
403 |
+
if (file && functionName) {
|
404 |
+
const module = require(`${this.path}/plugins/${pluginId}/${file}`)
|
405 |
+
|
406 |
+
if (module.teardown) {
|
407 |
+
if (!Object.keys(this.teardownModules).includes(pluginId)) {
|
408 |
+
this.teardownModules[pluginId] = []
|
409 |
+
}
|
410 |
+
this.teardownModules[pluginId].push(module.teardown)
|
411 |
+
}
|
412 |
+
|
413 |
+
if (module.setup && !this.setupModules.has(`${pluginId}/${file}`)) {
|
414 |
+
window.appLogger.setPrefix(pluginId)
|
415 |
+
module.setup(window)
|
416 |
+
window.appLogger.setPrefix("")
|
417 |
+
this.setupModules.add(`${pluginId}/${file}`)
|
418 |
+
}
|
419 |
+
|
420 |
+
this.pluginsModules[task][hookTime].push([pluginId, module[functionName]])
|
421 |
+
}
|
422 |
+
}
|
423 |
+
} catch (e) {
|
424 |
+
console.log(`${window.i18n.ERR_LOADING_PLUGIN} ${pluginId}->${task}->${hookTime}: ` + e.stack)
|
425 |
+
window.appLogger.log(`${window.i18n.ERR_LOADING_PLUGIN} ${pluginId}->${task}->${hookTime}: ` + e)
|
426 |
+
return e.stack
|
427 |
+
}
|
428 |
+
|
429 |
+
}
|
430 |
+
|
431 |
+
runPlugins (pList, event, data) {
|
432 |
+
if (pList.length) {
|
433 |
+
console.log(`Running plugin for event: ${event}`)
|
434 |
+
}
|
435 |
+
pList.forEach(([pluginId, pluginFn]) => {
|
436 |
+
try {
|
437 |
+
window.appLogger.setPrefix(pluginId)
|
438 |
+
pluginFn(window, data)
|
439 |
+
window.appLogger.setPrefix("")
|
440 |
+
|
441 |
+
} catch (e) {
|
442 |
+
console.log(e, pluginFn)
|
443 |
+
window.appLogger.log(`[${window.i18n.PLUGIN_RUN_ERROR} "${event}": ${pluginId}]: ${e}`)
|
444 |
+
}
|
445 |
+
})
|
446 |
+
}
|
447 |
+
|
448 |
+
|
449 |
+
_saveINIFile (IniSettings, settingsKey, pluginId, filePath) {
|
450 |
+
const outputIni = []
|
451 |
+
settingsOptionsContainer.querySelectorAll(`.${pluginId}_plugin_setting>div>input, .${pluginId}_plugin_setting>div>select`).forEach(input => {
|
452 |
+
|
453 |
+
if (input.tagName=="SELECT") {
|
454 |
+
const select = input
|
455 |
+
const optionsList = Array.from(select.querySelectorAll("option")).map(option => {
|
456 |
+
return [option.innerHTML, option.value]
|
457 |
+
})
|
458 |
+
const optionsListString = `{${optionsList.map(kv => kv.join(":")).join(";")}}`
|
459 |
+
|
460 |
+
outputIni.push(`${select.name.toLowerCase()}=${select.value} # ${optionsListString} ${select.getAttribute("comment")!="undefined" ? select.getAttribute("comment") : ""}`)
|
461 |
+
IniSettings[select.name.toLowerCase()] = select.value
|
462 |
+
|
463 |
+
} else {
|
464 |
+
const value = input.type=="checkbox" ? (input.checked ? true : false) : input.value
|
465 |
+
outputIni.push(`${input.name.toLowerCase()}=${value}${input.getAttribute("comment")!="undefined" ? " # "+input.getAttribute("comment") : ""}`)
|
466 |
+
IniSettings[input.name.toLowerCase()] = value
|
467 |
+
}
|
468 |
+
})
|
469 |
+
|
470 |
+
fs.writeFileSync(filePath, outputIni.join("\n"), "utf8")
|
471 |
+
window.pluginsContext[settingsKey] = IniSettings
|
472 |
+
}
|
473 |
+
|
474 |
+
registerINIFile (pluginId, settingsKey, filePath) {
|
475 |
+
|
476 |
+
if (!pluginId || !settingsKey || !filePath) {
|
477 |
+
return window.appLogger.log(`You must provide the following to register an ini file: pluginId, settingsKey, filePath`)
|
478 |
+
}
|
479 |
+
|
480 |
+
if (fs.existsSync(filePath)) {
|
481 |
+
|
482 |
+
if (document.querySelectorAll(`.${pluginId}_plugin_setting`).length) {
|
483 |
+
return
|
484 |
+
}
|
485 |
+
|
486 |
+
const IniSettings = {}
|
487 |
+
const iniFileData = fs.readFileSync(filePath, "utf8").split("\n")
|
488 |
+
|
489 |
+
const hr = createElem(`hr.${pluginId}_plugin_setting`)
|
490 |
+
settingsOptionsContainer.appendChild(hr)
|
491 |
+
settingsOptionsContainer.appendChild(createElem(`div.centeredSettingsSectionPlugins.${pluginId}_plugin_setting`, createElem("div", window.i18n.SETTINGS_FOR_PLUGIN.replace("_1", pluginId)) ))
|
492 |
+
|
493 |
+
iniFileData.forEach(keyVal => {
|
494 |
+
if (!keyVal.trim().length) {
|
495 |
+
return
|
496 |
+
}
|
497 |
+
let comment = keyVal.includes("#") ? keyVal.split("#")[1].trim() : undefined
|
498 |
+
keyVal = keyVal.split("#")[0].trim()
|
499 |
+
const key = keyVal.split("=")[0].trim()
|
500 |
+
let val = keyVal.split("=")[1].trim()
|
501 |
+
if (val=="false") val = false
|
502 |
+
if (val=="true") val = true
|
503 |
+
IniSettings[key.toLowerCase()] = val
|
504 |
+
|
505 |
+
const labelText = key[0].toUpperCase() + key.substring(1)
|
506 |
+
let label, input
|
507 |
+
const extraElems = []
|
508 |
+
|
509 |
+
if (comment && (comment.includes("$filepicker") || comment.includes("$folderpicker"))) {
|
510 |
+
|
511 |
+
input = createElem("input", {name: key, comment: comment})
|
512 |
+
input.style.width = "80%"
|
513 |
+
input.value = val
|
514 |
+
const button = createElem("button.svgButton")
|
515 |
+
button.innerHTML = `<svg class="openFolderSVG" width="400" height="350" viewBox="0, 0, 400,350"><g id="svgg" ><path id="path0" d="M39.960 53.003 C 36.442 53.516,35.992 53.635,30.800 55.422 C 15.784 60.591,3.913 74.835,0.636 91.617 C -0.372 96.776,-0.146 305.978,0.872 310.000 C 5.229 327.228,16.605 339.940,32.351 345.172 C 40.175 347.773,32.175 347.630,163.000 347.498 L 281.800 347.378 285.600 346.495 C 304.672 342.065,321.061 332.312,330.218 319.944 C 330.648 319.362,332.162 317.472,333.581 315.744 C 335.001 314.015,336.299 312.420,336.467 312.200 C 336.634 311.980,337.543 310.879,338.486 309.753 C 340.489 307.360,342.127 305.341,343.800 303.201 C 344.460 302.356,346.890 299.375,349.200 296.575 C 351.510 293.776,353.940 290.806,354.600 289.975 C 355.260 289.144,356.561 287.505,357.492 286.332 C 358.422 285.160,359.952 283.267,360.892 282.126 C 362.517 280.153,371.130 269.561,375.632 264.000 C 376.789 262.570,380.427 258.097,383.715 254.059 C 393.790 241.689,396.099 237.993,398.474 230.445 C 403.970 212.972,394.149 194.684,376.212 188.991 C 369.142 186.747,368.803 186.724,344.733 186.779 C 330.095 186.812,322.380 186.691,322.216 186.425 C 322.078 186.203,321.971 178.951,321.977 170.310 C 321.995 146.255,321.401 141.613,317.200 133.000 C 314.009 126.457,307.690 118.680,303.142 115.694 C 302.560 115.313,301.300 114.438,300.342 113.752 C 295.986 110.631,288.986 107.881,282.402 106.704 C 280.540 106.371,262.906 106.176,220.400 106.019 L 161.000 105.800 160.763 98.800 C 159.961 75.055,143.463 56.235,120.600 52.984 C 115.148 52.208,45.292 52.225,39.960 53.003 M120.348 80.330 C 130.472 83.988,133.993 90.369,133.998 105.071 C 134.003 120.968,137.334 127.726,147.110 131.675 L 149.400 132.600 213.800 132.807 C 272.726 132.996,278.392 133.071,280.453 133.690 C 286.872 135.615,292.306 141.010,294.261 147.400 C 294.928 149.578,294.996 151.483,294.998 168.000 L 295.000 186.200 292.800 186.449 C 291.590 186.585,254.330 186.725,210.000 186.759 C 163.866 186.795,128.374 186.977,127.000 187.186 C 115.800 188.887,104.936 192.929,96.705 198.458 C 95.442 199.306,94.302 200.000,94.171 200.000 C 93.815 200.000,89.287 203.526,87.000 205.583 C 84.269 208.039,80.083 212.649,76.488 217.159 C 72.902 221.657,72.598 222.031,70.800 224.169 C 70.030 225.084,68.770 226.620,68.000 227.582 C 67.230 228.544,66.054 229.977,65.387 230.766 C 64.720 231.554,62.727 234.000,60.957 236.200 C 59.188 238.400,56.346 241.910,54.642 244.000 C 52.938 246.090,50.163 249.510,48.476 251.600 C 44.000 257.146,36.689 266.126,36.212 266.665 C 35.985 266.921,34.900 268.252,33.800 269.623 C 32.700 270.994,30.947 273.125,29.904 274.358 C 28.861 275.591,28.006 276.735,28.004 276.900 C 28.002 277.065,27.728 277.200,27.395 277.200 C 26.428 277.200,26.700 96.271,27.670 93.553 C 30.020 86.972,35.122 81.823,40.800 80.300 C 44.238 79.378,47.793 79.296,81.800 79.351 L 117.800 79.410 120.348 80.330 M369.400 214.800 C 374.239 217.220,374.273 222.468,369.489 228.785 C 367.767 231.059,364.761 234.844,364.394 235.200 C 364.281 235.310,362.373 237.650,360.154 240.400 C 357.936 243.150,354.248 247.707,351.960 250.526 C 347.732 255.736,346.053 257.821,343.202 261.400 C 341.505 263.530,340.849 264.336,334.600 271.965 C 332.400 274.651,330.204 277.390,329.720 278.053 C 329.236 278.716,328.246 279.945,327.520 280.785 C 326.794 281.624,325.300 283.429,324.200 284.794 C 323.100 286.160,321.726 287.845,321.147 288.538 C 320.568 289.232,318.858 291.345,317.347 293.233 C 308.372 304.449,306.512 306.609,303.703 309.081 C 299.300 312.956,290.855 317.633,286.000 318.886 C 277.958 320.960,287.753 320.819,159.845 320.699 C 33.557 320.581,42.330 320.726,38.536 318.694 C 34.021 316.276,35.345 310.414,42.386 301.647 C 44.044 299.583,45.940 297.210,46.600 296.374 C 47.260 295.538,48.340 294.169,49.000 293.332 C 49.660 292.495,51.550 290.171,53.200 288.167 C 54.850 286.164,57.100 283.395,58.200 282.015 C 59.300 280.635,60.920 278.632,61.800 277.564 C 62.680 276.496,64.210 274.617,65.200 273.389 C 66.190 272.162,67.188 270.942,67.418 270.678 C 67.649 270.415,71.591 265.520,76.179 259.800 C 80.767 254.080,84.634 249.310,84.773 249.200 C 84.913 249.090,87.117 246.390,89.673 243.200 C 92.228 240.010,95.621 235.780,97.213 233.800 C 106.328 222.459,116.884 215.713,128.200 213.998 C 129.300 213.832,183.570 213.719,248.800 213.748 L 367.400 213.800 369.400 214.800 " stroke="none" fill="#fbfbfb" fill-rule="evenodd"></path><path id="path1" fill-opacity="0" d="M0.000 46.800 C 0.000 72.540,0.072 93.600,0.159 93.600 C 0.246 93.600,0.516 92.460,0.759 91.066 C 3.484 75.417,16.060 60.496,30.800 55.422 C 35.953 53.648,36.338 53.550,40.317 52.981 C 46.066 52.159,114.817 52.161,120.600 52.984 C 143.463 56.235,159.961 75.055,160.763 98.800 L 161.000 105.800 220.400 106.019 C 262.906 106.176,280.540 106.371,282.402 106.704 C 288.986 107.881,295.986 110.631,300.342 113.752 C 301.300 114.438,302.560 115.313,303.142 115.694 C 307.690 118.680,314.009 126.457,317.200 133.000 C 321.401 141.613,321.995 146.255,321.977 170.310 C 321.971 178.951,322.078 186.203,322.216 186.425 C 322.380 186.691,330.095 186.812,344.733 186.779 C 368.803 186.724,369.142 186.747,376.212 188.991 C 381.954 190.814,388.211 194.832,391.662 198.914 C 395.916 203.945,397.373 206.765,399.354 213.800 C 399.842 215.533,399.922 201.399,399.958 107.900 L 400.000 0.000 200.000 0.000 L 0.000 0.000 0.000 46.800 M44.000 79.609 C 35.903 81.030,30.492 85.651,27.670 93.553 C 26.700 96.271,26.428 277.200,27.395 277.200 C 27.728 277.200,28.002 277.065,28.004 276.900 C 28.006 276.735,28.861 275.591,29.904 274.358 C 30.947 273.125,32.700 270.994,33.800 269.623 C 34.900 268.252,35.985 266.921,36.212 266.665 C 36.689 266.126,44.000 257.146,48.476 251.600 C 50.163 249.510,52.938 246.090,54.642 244.000 C 56.346 241.910,59.188 238.400,60.957 236.200 C 62.727 234.000,64.720 231.554,65.387 230.766 C 66.054 229.977,67.230 228.544,68.000 227.582 C 68.770 226.620,70.030 225.084,70.800 224.169 C 72.598 222.031,72.902 221.657,76.488 217.159 C 80.083 212.649,84.269 208.039,87.000 205.583 C 89.287 203.526,93.815 200.000,94.171 200.000 C 94.302 200.000,95.442 199.306,96.705 198.458 C 104.936 192.929,115.800 188.887,127.000 187.186 C 128.374 186.977,163.866 186.795,210.000 186.759 C 254.330 186.725,291.590 186.585,292.800 186.449 L 295.000 186.200 294.998 168.000 C 294.996 151.483,294.928 149.578,294.261 147.400 C 292.306 141.010,286.872 135.615,280.453 133.690 C 278.392 133.071,272.726 132.996,213.800 132.807 L 149.400 132.600 147.110 131.675 C 137.334 127.726,134.003 120.968,133.998 105.071 C 133.993 90.369,130.472 83.988,120.348 80.330 L 117.800 79.410 81.800 79.351 C 62.000 79.319,44.990 79.435,44.000 79.609 M128.200 213.998 C 116.884 215.713,106.328 222.459,97.213 233.800 C 95.621 235.780,92.228 240.010,89.673 243.200 C 87.117 246.390,84.913 249.090,84.773 249.200 C 84.634 249.310,80.767 254.080,76.179 259.800 C 71.591 265.520,67.649 270.415,67.418 270.678 C 67.188 270.942,66.190 272.162,65.200 273.389 C 64.210 274.617,62.680 276.496,61.800 277.564 C 60.920 278.632,59.300 280.635,58.200 282.015 C 57.100 283.395,54.850 286.164,53.200 288.167 C 51.550 290.171,49.660 292.495,49.000 293.332 C 48.340 294.169,47.260 295.538,46.600 296.374 C 45.940 297.210,44.044 299.583,42.386 301.647 C 35.345 310.414,34.021 316.276,38.536 318.694 C 42.330 320.726,33.557 320.581,159.845 320.699 C 287.753 320.819,277.958 320.960,286.000 318.886 C 290.855 317.633,299.300 312.956,303.703 309.081 C 306.512 306.609,308.372 304.449,317.347 293.233 C 318.858 291.345,320.568 289.232,321.147 288.538 C 321.726 287.845,323.100 286.160,324.200 284.794 C 325.300 283.429,326.794 281.624,327.520 280.785 C 328.246 279.945,329.236 278.716,329.720 278.053 C 330.204 277.390,332.400 274.651,334.600 271.965 C 340.849 264.336,341.505 263.530,343.202 261.400 C 346.053 257.821,347.732 255.736,351.960 250.526 C 354.248 247.707,357.936 243.150,360.154 240.400 C 362.373 237.650,364.281 235.310,364.394 235.200 C 364.761 234.844,367.767 231.059,369.489 228.785 C 374.273 222.468,374.239 217.220,369.400 214.800 L 367.400 213.800 248.800 213.748 C 183.570 213.719,129.300 213.832,128.200 213.998 M399.600 225.751 C 399.600 231.796,394.623 240.665,383.715 254.059 C 380.427 258.097,376.789 262.570,375.632 264.000 C 371.130 269.561,362.517 280.153,360.892 282.126 C 359.952 283.267,358.422 285.160,357.492 286.332 C 356.561 287.505,355.260 289.144,354.600 289.975 C 353.940 290.806,351.510 293.776,349.200 296.575 C 346.890 299.375,344.460 302.356,343.800 303.201 C 342.127 305.341,340.489 307.360,338.486 309.753 C 337.543 310.879,336.634 311.980,336.467 312.200 C 336.299 312.420,335.001 314.015,333.581 315.744 C 332.162 317.472,330.648 319.362,330.218 319.944 C 321.061 332.312,304.672 342.065,285.600 346.495 L 281.800 347.378 163.000 347.498 C 32.175 347.630,40.175 347.773,32.351 345.172 C 16.471 339.895,3.810 325.502,0.820 309.326 C 0.591 308.085,0.312 306.979,0.202 306.868 C 0.091 306.757,-0.000 327.667,-0.000 353.333 L 0.000 400.000 200.000 400.000 L 400.000 400.000 400.000 312.400 C 400.000 264.220,399.910 224.800,399.800 224.800 C 399.690 224.800,399.600 225.228,399.600 225.751 " stroke="none" fill="#050505" fill-rule="evenodd"></path></g></svg>`
|
516 |
+
|
517 |
+
const openType = comment.includes("$filepicker") ? "openFile" : "openDirectory"
|
518 |
+
comment = comment.replace("$filepicker", "").replace("$folderpicker", "")
|
519 |
+
|
520 |
+
button.addEventListener("click", () => {
|
521 |
+
let filePathInput = er.dialog.showOpenDialog({ properties: [openType]})
|
522 |
+
if (filePathInput) {
|
523 |
+
filePathInput = filePathInput[0].replace(/\\/g, "/")
|
524 |
+
input.value = filePathInput.replace(/\\/g, "/")
|
525 |
+
|
526 |
+
this._saveINIFile(IniSettings, settingsKey, pluginId, filePath)
|
527 |
+
}
|
528 |
+
})
|
529 |
+
extraElems.push(button)
|
530 |
+
|
531 |
+
|
532 |
+
|
533 |
+
} else if (comment && comment.includes("{") && comment.includes(":")) {
|
534 |
+
|
535 |
+
const optionsList = comment.split("{")[1].split("}")[0].split(";").map(kv => {
|
536 |
+
return [kv.split(":")[0], kv.split(":")[1]]
|
537 |
+
})
|
538 |
+
const optionElems = optionsList.map(data => {
|
539 |
+
const opt = createElem("option", {value: data[1]})
|
540 |
+
opt.innerHTML = data[0]
|
541 |
+
return opt
|
542 |
+
})
|
543 |
+
|
544 |
+
comment = comment.split("}").reverse()[0].trim()
|
545 |
+
|
546 |
+
input = createElem("select", {name: key, comment: comment})
|
547 |
+
optionElems.forEach(option => {
|
548 |
+
input.appendChild(option)
|
549 |
+
})
|
550 |
+
input.value = val
|
551 |
+
|
552 |
+
} else {
|
553 |
+
const inputType = [true,false].includes(val) ? "checkbox" : "text"
|
554 |
+
input = createElem("input", {
|
555 |
+
type: inputType, name: key, comment: comment
|
556 |
+
})
|
557 |
+
if (inputType=="checkbox") {
|
558 |
+
input.checked = val
|
559 |
+
} else {
|
560 |
+
input.value = val
|
561 |
+
}
|
562 |
+
}
|
563 |
+
|
564 |
+
label = createElem("div", labelText.replace(/_/g, " ") + (comment ? `<br>(${comment})` : ""))
|
565 |
+
|
566 |
+
|
567 |
+
input.addEventListener("change", () => {
|
568 |
+
this._saveINIFile(IniSettings, settingsKey, pluginId, filePath)
|
569 |
+
})
|
570 |
+
|
571 |
+
const rhd_elem = createElem("div")
|
572 |
+
rhd_elem.appendChild(input)
|
573 |
+
extraElems.forEach(elem => rhd_elem.appendChild(elem))
|
574 |
+
if (extraElems.length) {
|
575 |
+
rhd_elem.style.flexDirection = "row"
|
576 |
+
}
|
577 |
+
|
578 |
+
settingsOptionsContainer.appendChild(createElem(`div.${pluginId}_plugin_setting`, [label, rhd_elem]))
|
579 |
+
})
|
580 |
+
|
581 |
+
window.pluginsContext[settingsKey] = IniSettings
|
582 |
+
|
583 |
+
|
584 |
+
|
585 |
+
} else {
|
586 |
+
window.appLogger.log(`Ini file does not exist here: ${filePath}`)
|
587 |
+
}
|
588 |
+
|
589 |
+
}
|
590 |
+
|
591 |
+
}
|
592 |
+
|
593 |
+
|
594 |
+
exports.PluginsManager = PluginsManager
|
javascript/script.js
ADDED
@@ -0,0 +1,1730 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
window.appVersion = "v3.0.3"
|
3 |
+
|
4 |
+
window.PRODUCTION = module.filename.includes("resources")
|
5 |
+
const path = window.PRODUCTION ? "./resources/app" : "."
|
6 |
+
window.path = path
|
7 |
+
|
8 |
+
const fs = require("fs")
|
9 |
+
const zipdir = require('zip-dir')
|
10 |
+
const {shell, ipcRenderer, clipboard} = require("electron")
|
11 |
+
const doFetch = require("node-fetch")
|
12 |
+
const {xVAAppLogger} = require("./javascript/appLogger.js")
|
13 |
+
window.appLogger = new xVAAppLogger(`./app.log`, window.appVersion)
|
14 |
+
process.on(`uncaughtException`, (data, origin) => {window.appLogger.log(`uncaughtException: ${data}`);window.appLogger.log(`uncaughtException: ${origin}`)})
|
15 |
+
window.onerror = (event, source, lineno, colno, error) => {window.appLogger.log(`onerror: ${error.stack}`)}
|
16 |
+
require("./javascript/i18n.js")
|
17 |
+
require("./javascript/util.js")
|
18 |
+
require("./javascript/nexus.js")
|
19 |
+
require("./javascript/dragdrop_model_install.js")
|
20 |
+
require("./javascript/embeddings.js")
|
21 |
+
require("./javascript/totd.js")
|
22 |
+
require("./javascript/arpabet.js")
|
23 |
+
require("./javascript/style_embeddings.js")
|
24 |
+
const {Editor} = require("./javascript/editor.js")
|
25 |
+
require("./javascript/textarea.js")
|
26 |
+
const {saveUserSettings, deleteFolderRecursive} = require("./javascript/settingsMenu.js")
|
27 |
+
const xVASpeech = require("./javascript/speech2speech.js")
|
28 |
+
require("./javascript/batch.js")
|
29 |
+
require("./javascript/outputFiles.js")
|
30 |
+
require("./javascript/workbench.js")
|
31 |
+
const er = require('@electron/remote')
|
32 |
+
window.electronBrowserWindow = er.getCurrentWindow()
|
33 |
+
const child = require("child_process").execFile
|
34 |
+
const spawn = require("child_process").spawn
|
35 |
+
|
36 |
+
// Newly introduced in v3. I will slowly start moving global context variables into this, and update code throughout to reference this
|
37 |
+
// instead of old variables such as window.games, window.currentModel, etc.
|
38 |
+
window.appState = {}
|
39 |
+
|
40 |
+
|
41 |
+
// Start the server
|
42 |
+
if (window.PRODUCTION) {
|
43 |
+
window.pythonProcess = spawn(`${path}/cpython_${window.userSettings.installation}/server.exe`, {stdio: "ignore"})
|
44 |
+
}
|
45 |
+
|
46 |
+
const {PluginsManager} = require("./javascript/plugins_manager.js")
|
47 |
+
window.pluginsContext = {}
|
48 |
+
window.pluginsManager = new PluginsManager(window.path, window.appLogger, window.appVersion)
|
49 |
+
window.pluginsManager.runPlugins(window.pluginsManager.pluginsModules["start"]["pre"], event="pre start")
|
50 |
+
|
51 |
+
|
52 |
+
let themeColour
|
53 |
+
let secondaryThemeColour
|
54 |
+
const oldCError = console.error
|
55 |
+
console.error = (...rest) => {
|
56 |
+
window.appLogger.log(`console.error: ${rest}`)
|
57 |
+
oldCError(rest)
|
58 |
+
}
|
59 |
+
|
60 |
+
window.addEventListener("error", function (e) {window.appLogger.log(`error: ${e.error.stack}`)})
|
61 |
+
window.addEventListener('unhandledrejection', function (e) {window.appLogger.log(`unhandledrejection: ${e.stack}`)})
|
62 |
+
|
63 |
+
|
64 |
+
setTimeout(() => {
|
65 |
+
window.electron = require("electron")
|
66 |
+
}, 1000)
|
67 |
+
|
68 |
+
|
69 |
+
window.games = {}
|
70 |
+
window.models = {}
|
71 |
+
window.sequenceEditor = new Editor()
|
72 |
+
window.currentModel = undefined
|
73 |
+
window.currentModelButton = undefined
|
74 |
+
window.watchedModelsDirs = []
|
75 |
+
|
76 |
+
window.appLogger.log(`Settings: ${JSON.stringify(window.userSettings)}`)
|
77 |
+
|
78 |
+
// Set up folders
|
79 |
+
try {fs.mkdirSync(`${path}/models`)} catch (e) {/*Do nothing*/}
|
80 |
+
try {fs.mkdirSync(`${path}/output`)} catch (e) {/*Do nothing*/}
|
81 |
+
try {fs.mkdirSync(`${path}/assets`)} catch (e) {/*Do nothing*/}
|
82 |
+
|
83 |
+
// Clean up temp files
|
84 |
+
const clearOldTempFiles = () => {
|
85 |
+
fs.readdir(`${__dirname.replace("/javascript", "")}/output`, (err, files) => {
|
86 |
+
if (err) {
|
87 |
+
window.appLogger.log(`Error cleaning up temp files: ${err}`)
|
88 |
+
}
|
89 |
+
if (files && files.length) {
|
90 |
+
files.filter(f => f.startsWith("temp-")).forEach(file => {
|
91 |
+
fs.unlink(`${__dirname.replace("/javascript", "")}/output/${file}`, err => err&&console.log(err))
|
92 |
+
})
|
93 |
+
}
|
94 |
+
})
|
95 |
+
}
|
96 |
+
clearOldTempFiles()
|
97 |
+
|
98 |
+
let fileRenameCounter = 0
|
99 |
+
let fileChangeCounter = 0
|
100 |
+
window.isGenerating = false
|
101 |
+
|
102 |
+
|
103 |
+
|
104 |
+
|
105 |
+
window.registerModel = (modelsPath, gameFolder, model, {gameId, voiceId, voiceName, voiceDescription, gender, variant, modelType, emb_i}) => {
|
106 |
+
// Add game, if the game hasn't been added yet
|
107 |
+
let audioPreviewPath
|
108 |
+
try {
|
109 |
+
audioPreviewPath = `${modelsPath}/${model.games.find(({gameId}) => gameId==gameFolder).voiceId}`
|
110 |
+
} catch (e) {}
|
111 |
+
if (!window.games.hasOwnProperty(gameId)) {
|
112 |
+
|
113 |
+
const gameAsset = fs.readdirSync(`${path}/assets`).find(f => f==gameId+".json")
|
114 |
+
if (gameAsset && gameAsset.length && gameAsset[0]) {
|
115 |
+
const gameTheme = JSON.parse(fs.readFileSync(`${path}/assets/${gameAsset}`))
|
116 |
+
|
117 |
+
window.games[gameId] = {
|
118 |
+
models: [],
|
119 |
+
gameTheme,
|
120 |
+
gameAsset
|
121 |
+
}
|
122 |
+
|
123 |
+
} else {
|
124 |
+
window.appLogger.log(`Something not right with loading model: ${voiceId} . The asset file for its game (${gameId}) could not be found here: ${path}/assets. You need a ${gameId}.json file. Loading a generic theme for this voice's game/category.`)
|
125 |
+
|
126 |
+
const dummyGameTheme = {
|
127 |
+
"gameName": gameId,
|
128 |
+
"assetFile": "other.jpg",
|
129 |
+
"themeColourPrimary": "aaaaaa",
|
130 |
+
"themeColourSecondary": null,
|
131 |
+
"gameCode": "x",
|
132 |
+
"nexusGamePageIDs": []
|
133 |
+
}
|
134 |
+
const dummyGameAsset = "other.json"
|
135 |
+
window.games[gameId] = {
|
136 |
+
models: [],
|
137 |
+
dummyGameTheme,
|
138 |
+
dummyGameAsset
|
139 |
+
}
|
140 |
+
audioPreviewPath = `${modelsPath}/${model.games[0].voiceId}`
|
141 |
+
}
|
142 |
+
}
|
143 |
+
|
144 |
+
// Catch duplicates, for when/if a model is registered for multiple games, but there's already the same model in that game, from another version
|
145 |
+
// const existingDuplicates = []
|
146 |
+
// window.games[gameId].models.forEach((item,i) => {
|
147 |
+
// if (item.voiceId==voiceId) {
|
148 |
+
// existingDuplicates.push([item, i])
|
149 |
+
// }
|
150 |
+
// })
|
151 |
+
|
152 |
+
// Check if a variant has already been added for this voice name, for this game
|
153 |
+
let foundVariantIndex = undefined
|
154 |
+
window.games[gameId].models.forEach((item,i) => {
|
155 |
+
if (foundVariantIndex!=undefined) return
|
156 |
+
|
157 |
+
if (item.voiceName.toLowerCase().trim()==voiceName.toLowerCase().trim()) {
|
158 |
+
foundVariantIndex = i
|
159 |
+
}
|
160 |
+
})
|
161 |
+
|
162 |
+
// Add the initial model metadata, if no existing variant has been added (will happen most of the time)
|
163 |
+
if (!foundVariantIndex) {
|
164 |
+
const modelData = {
|
165 |
+
gameId,
|
166 |
+
modelsPath,
|
167 |
+
voiceName,
|
168 |
+
lang_capabilities: model.lang_capabilities,
|
169 |
+
embOverABaseModel: model.embOverABaseModel,
|
170 |
+
variants: []
|
171 |
+
}
|
172 |
+
window.games[gameId].models.push(modelData)
|
173 |
+
foundVariantIndex = window.games[gameId].models.length-1
|
174 |
+
}
|
175 |
+
|
176 |
+
const variantData = {
|
177 |
+
author: model.author,
|
178 |
+
version: model.version,
|
179 |
+
modelVersion: model.modelVersion,
|
180 |
+
modelType: model.modelType,
|
181 |
+
base_speaker_emb: model.modelType=="xVAPitch" ? model.games[0].base_speaker_emb : undefined,
|
182 |
+
voiceId,
|
183 |
+
audioPreviewPath,
|
184 |
+
hifi: undefined,
|
185 |
+
num_speakers: model.emb_size,
|
186 |
+
emb_i,
|
187 |
+
variantName: variant ? variant.replace("Default :", "Default:").replace("Default:", "").trim() : "Default",
|
188 |
+
voiceDescription,
|
189 |
+
lang: model.lang,
|
190 |
+
gender,
|
191 |
+
modelType: modelType||model.modelType,
|
192 |
+
model,
|
193 |
+
}
|
194 |
+
const potentialHiFiPath = `${modelsPath}/${voiceId}.hg.pt`
|
195 |
+
if (fs.existsSync(potentialHiFiPath)) {
|
196 |
+
variantData.hifi = potentialHiFiPath
|
197 |
+
}
|
198 |
+
|
199 |
+
const isDefaultVariant = !variant || variant.toLowerCase().startsWith("default")
|
200 |
+
|
201 |
+
if (isDefaultVariant) {
|
202 |
+
// Place first in the list, if it's default
|
203 |
+
window.games[gameId].models[foundVariantIndex].audioPreviewPath = audioPreviewPath
|
204 |
+
window.games[gameId].models[foundVariantIndex].variants.splice(0,0,variantData)
|
205 |
+
} else {
|
206 |
+
window.games[gameId].models[foundVariantIndex].variants.push(variantData)
|
207 |
+
}
|
208 |
+
|
209 |
+
|
210 |
+
// // Using the detected duplicates, use only the latest version
|
211 |
+
// if (existingDuplicates.length) {
|
212 |
+
// if (existingDuplicates[0][0].modelVersion<model.modelVersion) {
|
213 |
+
// window.games[gameId].models.splice(existingDuplicates[0][1], 1)
|
214 |
+
// window.games[gameId].models.push(modelData)
|
215 |
+
// }
|
216 |
+
// } else {
|
217 |
+
// window.games[gameId].models.push(modelData)
|
218 |
+
// }
|
219 |
+
}
|
220 |
+
|
221 |
+
window.loadAllModels = (forceUpdate=false) => {
|
222 |
+
return new Promise(resolve => {
|
223 |
+
|
224 |
+
if (!forceUpdate && window.nexusState.installQueue.length) {
|
225 |
+
return
|
226 |
+
}
|
227 |
+
|
228 |
+
let gameFolder
|
229 |
+
let modelPathsKeys = Object.keys(window.userSettings).filter(key => key.includes("modelspath_"))
|
230 |
+
window.games = {}
|
231 |
+
|
232 |
+
// Do the current game first, and stop blocking the render process
|
233 |
+
if (window.currentGame) {
|
234 |
+
const currentGameFolder = window.userSettings[`modelspath_${window.currentGame.gameId}`]
|
235 |
+
gameFolder = currentGameFolder
|
236 |
+
try {
|
237 |
+
const files = fs.readdirSync(modelsPath).filter(f => f.endsWith(".json"))
|
238 |
+
files.forEach(fileName => {
|
239 |
+
try {
|
240 |
+
if (!models.hasOwnProperty(`${gameFolder}/${fileName}`)) {
|
241 |
+
models[`${gameFolder}/${fileName}`] = null
|
242 |
+
}
|
243 |
+
const model = JSON.parse(fs.readFileSync(`${modelsPath}/${fileName}`, "utf8"))
|
244 |
+
model.games.forEach(({gameId, voiceId, voiceName, voiceDescription, gender, variant, modelType, emb_i}) => {
|
245 |
+
window.registerModel(currentGameFolder, gameFolder, model, {gameId, voiceId, voiceName, voiceDescription, gender, variant, modelType, emb_i})
|
246 |
+
})
|
247 |
+
|
248 |
+
} catch (e) {
|
249 |
+
console.log(e)
|
250 |
+
// window.appLogger.log(`${window.i18n.ERR_LOADING_MODELS_FOR_GAME_WITH_FILENAME.replace("_1", gameFolder)} `+fileName)
|
251 |
+
// window.appLogger.log(e)
|
252 |
+
// window.appLogger.log(e.stack)
|
253 |
+
}
|
254 |
+
})
|
255 |
+
} catch (e) {
|
256 |
+
window.appLogger.log(`${window.i18n.ERR_LOADING_MODELS_FOR_GAME}: `+ gameFolder)
|
257 |
+
window.appLogger.log(e)
|
258 |
+
}
|
259 |
+
resolve() // Continue the rest but asynchronously
|
260 |
+
}
|
261 |
+
|
262 |
+
|
263 |
+
modelPathsKeys.forEach(modelsPathKey => {
|
264 |
+
const modelsPath = window.userSettings[modelsPathKey]
|
265 |
+
try {
|
266 |
+
const files = fs.readdirSync(modelsPath).filter(f => f.endsWith(".json"))
|
267 |
+
|
268 |
+
if (!files.length) {
|
269 |
+
return
|
270 |
+
}
|
271 |
+
|
272 |
+
files.forEach(fileName => {
|
273 |
+
|
274 |
+
gameFolder = modelsPathKey.split("_")[1]
|
275 |
+
|
276 |
+
try {
|
277 |
+
if (!models.hasOwnProperty(`${gameFolder}/${fileName}`)) {
|
278 |
+
models[`${gameFolder}/${fileName}`] = null
|
279 |
+
}
|
280 |
+
|
281 |
+
const model = JSON.parse(fs.readFileSync(`${modelsPath}/${fileName}`, "utf8"))
|
282 |
+
model.games.forEach(({gameId, voiceId, voiceName, voiceDescription, gender, variant, modelType, emb_i}) => {
|
283 |
+
window.registerModel(modelsPath, gameFolder, model, {gameId, voiceId, voiceName, voiceDescription, gender, variant, modelType, emb_i})
|
284 |
+
})
|
285 |
+
} catch (e) {
|
286 |
+
console.log(e)
|
287 |
+
setTimeout(() => {
|
288 |
+
window.errorModal(`${fileName}<br><br>${e.stack}`)
|
289 |
+
}, 1000)
|
290 |
+
window.appLogger.log(`${window.i18n.ERR_LOADING_MODELS_FOR_GAME_WITH_FILENAME.replace("_1", gameFolder)} `+fileName)
|
291 |
+
window.appLogger.log(e)
|
292 |
+
window.appLogger.log(e.stack)
|
293 |
+
}
|
294 |
+
})
|
295 |
+
} catch (e) {
|
296 |
+
window.appLogger.log(`${window.i18n.ERR_LOADING_MODELS_FOR_GAME}: `+ gameFolder)
|
297 |
+
window.appLogger.log(e)
|
298 |
+
}
|
299 |
+
})
|
300 |
+
window.updateGameList(false)
|
301 |
+
resolve()
|
302 |
+
})
|
303 |
+
}
|
304 |
+
|
305 |
+
|
306 |
+
// Change variant
|
307 |
+
let oldVariantSelection = undefined // For reverting, if versioning checks fail
|
308 |
+
variant_select.addEventListener("change", () => {
|
309 |
+
|
310 |
+
const model = window.games[window.currentGame.gameId].models.find(model => model.voiceName== window.currentModel.voiceName)
|
311 |
+
const variant = model.variants.find(variant => variant.variantName==variant_select.value)
|
312 |
+
|
313 |
+
const appVersionOk = window.checkVersionRequirements(variant.version, appVersion)
|
314 |
+
if (!appVersionOk) {
|
315 |
+
window.errorModal(`${window.i18n.MODEL_REQUIRES_VERSION} v${variant.version}<br><br>${window.i18n.THIS_APP_VERSION}: ${window.appVersion}`)
|
316 |
+
variant_select.value = oldVariantSelection
|
317 |
+
return
|
318 |
+
}
|
319 |
+
|
320 |
+
generateVoiceButton.dataset.modelQuery = JSON.stringify({
|
321 |
+
outputs: parseInt(model.outputs),
|
322 |
+
model: model.embOverABaseModel ? window.userSettings[`modelspath_${model.embOverABaseModel.split("/")[0]}`]+`/${model.embOverABaseModel.split("/")[1]}` : `${model.modelsPath}/${variant.voiceId}`,
|
323 |
+
modelType: variant.modelType,
|
324 |
+
version: variant.version,
|
325 |
+
model_speakers: model.num_speakers,
|
326 |
+
base_lang: model.lang || "en"
|
327 |
+
})
|
328 |
+
oldVariantSelection = variant_select.value
|
329 |
+
|
330 |
+
titleInfoVoiceID.innerHTML = variant.voiceId
|
331 |
+
titleInfoGender.innerHTML = variant.gender || "?"
|
332 |
+
titleInfoAppVersion.innerHTML = variant.version || "?"
|
333 |
+
titleInfoModelVersion.innerHTML = variant.modelVersion || "?"
|
334 |
+
titleInfoModelType.innerHTML = variant.modelType || "?"
|
335 |
+
titleInfoLanguage.innerHTML = variant.lang || window.currentModel.games[0].lang || "en"
|
336 |
+
titleInfoAuthor.innerHTML = variant.author || "?"
|
337 |
+
|
338 |
+
generateVoiceButton.click()
|
339 |
+
})
|
340 |
+
|
341 |
+
|
342 |
+
|
343 |
+
// Change game
|
344 |
+
window.changeGame = (meta) => {
|
345 |
+
|
346 |
+
titleInfo.style.display = "none"
|
347 |
+
window.currentGame = meta
|
348 |
+
themeColour = meta.themeColourPrimary
|
349 |
+
secondaryThemeColour = meta.themeColourSecondary
|
350 |
+
let titleID = meta.gameCode
|
351 |
+
|
352 |
+
generateVoiceButton.disabled = true
|
353 |
+
generateVoiceButton.innerHTML = window.i18n.GENERATE_VOICE
|
354 |
+
selectedGameDisplay.innerHTML = meta.gameName
|
355 |
+
|
356 |
+
// Change the app title
|
357 |
+
titleName.innerHTML = window.i18n.SELECT_VOICE_TYPE
|
358 |
+
if (window.games[window.currentGame.gameId] == undefined) {
|
359 |
+
titleName.innerHTML = `${window.i18n.NO_MODELS_IN}: ${window.userSettings[`modelspath_${window.currentGame.gameId}`]}`
|
360 |
+
}
|
361 |
+
|
362 |
+
const gameFolder = meta.gameId
|
363 |
+
const gameName = meta.gameName
|
364 |
+
|
365 |
+
setting_models_path_container.style.display = "flex"
|
366 |
+
setting_out_path_container.style.display = "flex"
|
367 |
+
setting_models_path_label.innerHTML = `<i style="display:inline">${gameName}</i><span>${window.i18n.SETTINGS_MODELS_PATH}</span>`
|
368 |
+
setting_models_path_input.value = window.userSettings[`modelspath_${gameFolder}`]
|
369 |
+
setting_out_path_label.innerHTML = `<i style="display:inline">${gameName}</i> ${window.i18n.SETTINGS_OUTPUT_PATH}`
|
370 |
+
setting_out_path_input.value = window.userSettings[`outpath_${gameFolder}`]
|
371 |
+
|
372 |
+
window.setTheme(window.currentGame)
|
373 |
+
try {
|
374 |
+
window.displayAllModels()
|
375 |
+
} catch (e) {console.log(e)}
|
376 |
+
|
377 |
+
try {fs.mkdirSync(`${path}/output/${meta.gameId}`)} catch (e) {/*Do nothing*/}
|
378 |
+
localStorage.setItem("lastGame", JSON.stringify(meta))
|
379 |
+
|
380 |
+
// Populate models
|
381 |
+
voiceTypeContainer.innerHTML = ""
|
382 |
+
voiceSamples.innerHTML = ""
|
383 |
+
|
384 |
+
const buttons = []
|
385 |
+
const totalNumVoices = (window.games[meta.gameId] ? window.games[meta.gameId].models : []).reduce((p,c)=>p+c.variants.length, 0)
|
386 |
+
voiceSearchInput.placeholder = window.i18n.SEARCH_N_VOICES.replace("_", window.games[meta.gameId] ? totalNumVoices : "0")
|
387 |
+
voiceSearchInput.value = ""
|
388 |
+
|
389 |
+
if (!window.games[meta.gameId]) {
|
390 |
+
return
|
391 |
+
}
|
392 |
+
|
393 |
+
(window.games[meta.gameId] ? window.games[meta.gameId].models : []).forEach(({modelsPath, audioPreviewPath, gameId, variants, voiceName, embOverABaseModel}) => {
|
394 |
+
|
395 |
+
const {voiceId, voiceDescription, hifi, model} = variants[0]
|
396 |
+
const modelVersion = variants[0].version
|
397 |
+
|
398 |
+
const button = createElem("div.voiceType", voiceName)
|
399 |
+
button.style.background = `#${themeColour}`
|
400 |
+
if (embOverABaseModel) {
|
401 |
+
button.style.fontStyle = "italic"
|
402 |
+
}
|
403 |
+
if (window.userSettings.do_model_version_highlight && parseFloat(modelVersion)<window.userSettings.model_version_highlight) {
|
404 |
+
button.style.border = `2px solid #${themeColour}`
|
405 |
+
button.style.padding = "0"
|
406 |
+
button.style.background = "none"
|
407 |
+
}
|
408 |
+
button.dataset.modelId = voiceId
|
409 |
+
if (secondaryThemeColour) {
|
410 |
+
button.style.color = `#${secondaryThemeColour}`
|
411 |
+
button.style.textShadow = `none`
|
412 |
+
}
|
413 |
+
|
414 |
+
// Quick voice set preview, if there is a preview file
|
415 |
+
button.addEventListener("contextmenu", () => {
|
416 |
+
window.appLogger.log(`${audioPreviewPath}.wav`)
|
417 |
+
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", {
|
418 |
+
src: `${audioPreviewPath}.wav`
|
419 |
+
}))
|
420 |
+
audioPreview.style.height = "25px"
|
421 |
+
audioPreview.setSinkId(window.userSettings.base_speaker)
|
422 |
+
})
|
423 |
+
|
424 |
+
if (embOverABaseModel) {
|
425 |
+
const gameOfBaseModel = embOverABaseModel.split("/")[0]
|
426 |
+
if (gameOfBaseModel=="<base>") {
|
427 |
+
// For included base v3 models
|
428 |
+
modelsPath = `${window.path}/python/xvapitch/${embOverABaseModel.split("/")[1]}`
|
429 |
+
} else {
|
430 |
+
// For any other model
|
431 |
+
const gameModelsPath = `${window.userSettings[`outpath_${gameOfBaseModel}`]}`
|
432 |
+
modelsPath = `${gameModelsPath}/${embOverABaseModel.split("/")[1]}`
|
433 |
+
}
|
434 |
+
}
|
435 |
+
|
436 |
+
button.addEventListener("click", event => window.selectVoice(event, variants, hifi, gameId, voiceId, model, button, audioPreviewPath, modelsPath, meta, embOverABaseModel))
|
437 |
+
buttons.push(button)
|
438 |
+
})
|
439 |
+
|
440 |
+
buttons.sort((a,b) => a.innerHTML.toLowerCase()<b.innerHTML.toLowerCase()?-1:1)
|
441 |
+
.forEach(button => voiceTypeContainer.appendChild(button))
|
442 |
+
|
443 |
+
}
|
444 |
+
|
445 |
+
|
446 |
+
window.selectVoice = (event, variants, hifi, gameId, voiceId, model, button, audioPreviewPath, modelsPath, meta, embOverABaseModel) => {
|
447 |
+
// Just for easier packaging of the voice models for publishing - yes, lazy
|
448 |
+
if (event.ctrlKey && event.shiftKey) {
|
449 |
+
window.packageVoice(event.altKey, variants, {modelsPath, gameId})
|
450 |
+
}
|
451 |
+
|
452 |
+
variant_select.innerHTML = ""
|
453 |
+
oldVariantSelection = undefined
|
454 |
+
if (variants.length==1) {
|
455 |
+
variantElements.style.display = "none"
|
456 |
+
} else {
|
457 |
+
variantElements.style.display = "flex"
|
458 |
+
variants.forEach(variant => {
|
459 |
+
const option = createElem("option", {value: variant.variantName})
|
460 |
+
option.innerHTML = variant.variantName
|
461 |
+
variant_select.appendChild(option)
|
462 |
+
if (!oldVariantSelection) {
|
463 |
+
oldVariantSelection = variant.variantName
|
464 |
+
}
|
465 |
+
})
|
466 |
+
}
|
467 |
+
|
468 |
+
|
469 |
+
if (hifi) {
|
470 |
+
// Remove the bespoke hifi option if there was one already there
|
471 |
+
Array.from(vocoder_select.children).forEach(opt => {
|
472 |
+
if (opt.innerHTML=="Bespoke HiFi GAN") {
|
473 |
+
vocoder_select.removeChild(opt)
|
474 |
+
}
|
475 |
+
})
|
476 |
+
bespoke_hifi_bolt.style.opacity = 1
|
477 |
+
const option = createElem("option", "Bespoke HiFi GAN")
|
478 |
+
option.value = `${gameId}/${voiceId}.hg.pt`
|
479 |
+
vocoder_select.appendChild(option)
|
480 |
+
} else {
|
481 |
+
bespoke_hifi_bolt.style.opacity = 0
|
482 |
+
// Set the vocoder select to quick-and-dirty if bespoke hifi-gan was selected
|
483 |
+
if (vocoder_select.value.includes(".hg.")) {
|
484 |
+
vocoder_select.value = "qnd"
|
485 |
+
window.changeVocoder("qnd")
|
486 |
+
}
|
487 |
+
// Remove the bespoke hifi option if there was one already there
|
488 |
+
Array.from(vocoder_select.children).forEach(opt => {
|
489 |
+
if (opt.innerHTML=="Bespoke HiFi GAN") {
|
490 |
+
vocoder_select.removeChild(opt)
|
491 |
+
}
|
492 |
+
})
|
493 |
+
}
|
494 |
+
|
495 |
+
window.currentModel = model
|
496 |
+
window.currentModel.voiceId = voiceId
|
497 |
+
window.currentModel.voiceName = button.innerHTML
|
498 |
+
window.currentModel.hifi = hifi
|
499 |
+
window.currentModel.audioPreviewPath = audioPreviewPath
|
500 |
+
window.currentModelButton = button
|
501 |
+
|
502 |
+
|
503 |
+
generateVoiceButton.dataset.modelQuery = null
|
504 |
+
|
505 |
+
// The model is already loaded. Don't re-load it.
|
506 |
+
if (generateVoiceButton.dataset.modelIDLoaded == voiceId) {
|
507 |
+
generateVoiceButton.innerHTML = window.i18n.GENERATE_VOICE
|
508 |
+
generateVoiceButton.dataset.modelQuery = "null"
|
509 |
+
|
510 |
+
} else {
|
511 |
+
generateVoiceButton.innerHTML = window.i18n.LOAD_MODEL
|
512 |
+
generateVoiceButton.dataset.modelQuery = JSON.stringify({
|
513 |
+
outputs: parseInt(model.outputs),
|
514 |
+
model: model.embOverABaseModel ? window.userSettings[`modelspath_${model.embOverABaseModel.split("/")[0]}`]+`/${model.embOverABaseModel.split("/")[1]}` : `${modelsPath}/${model.voiceId}`,
|
515 |
+
modelType: model.modelType,
|
516 |
+
version: model.version,
|
517 |
+
model_speakers: model.emb_size,
|
518 |
+
cmudict: model.cmudict,
|
519 |
+
base_lang: model.lang || "en"
|
520 |
+
})
|
521 |
+
generateVoiceButton.dataset.modelIDToLoad = voiceId
|
522 |
+
}
|
523 |
+
generateVoiceButton.disabled = false
|
524 |
+
|
525 |
+
titleName.innerHTML = button.innerHTML
|
526 |
+
titleInfo.style.display = "flex"
|
527 |
+
titleInfoName.innerHTML = window.currentModel.voiceName
|
528 |
+
titleInfoVoiceID.innerHTML = voiceId
|
529 |
+
titleInfoGender.innerHTML = window.currentModel.games[0].gender || "?"
|
530 |
+
titleInfoAppVersion.innerHTML = window.currentModel.version || "?"
|
531 |
+
titleInfoModelVersion.innerHTML = window.currentModel.modelVersion || "?"
|
532 |
+
titleInfoModelType.innerHTML = window.currentModel.modelType || "?"
|
533 |
+
titleInfoLanguage.innerHTML = window.currentModel.lang || window.currentModel.games[0].lang || "en"
|
534 |
+
titleInfoAuthor.innerHTML = window.currentModel.author || "?"
|
535 |
+
titleInfoLicense.innerHTML = window.currentModel.license || window.i18n.UNKNOWN
|
536 |
+
|
537 |
+
title.dataset.modelId = voiceId
|
538 |
+
keepSampleButton.style.display = "none"
|
539 |
+
samplePlayPause.style.display = "none"
|
540 |
+
|
541 |
+
// Voice samples
|
542 |
+
voiceSamples.innerHTML = ""
|
543 |
+
|
544 |
+
window.initMainPagePagination(`${window.userSettings[`outpath_${meta.gameId}`]}/${button.dataset.modelId}`)
|
545 |
+
window.refreshRecordsList()
|
546 |
+
}
|
547 |
+
|
548 |
+
titleInfo.addEventListener("click", () => titleDetails.style.display = titleDetails.style.display=="none" ? "block" : "none")
|
549 |
+
window.addEventListener("click", event => {
|
550 |
+
if (event.target!=titleInfo && event.target!=titleDetails && event.target.parentNode && event.target.parentNode!=titleDetails && event.target.parentNode.parentNode!=titleDetails) {
|
551 |
+
titleDetails.style.display = "none"
|
552 |
+
}
|
553 |
+
})
|
554 |
+
titleDetails.style.display = "none"
|
555 |
+
|
556 |
+
|
557 |
+
window.loadModel = () => {
|
558 |
+
return new Promise(resolve => {
|
559 |
+
if (window.batch_state.state) {
|
560 |
+
window.errorModal(window.i18n.BATCH_ERR_IN_PROGRESS)
|
561 |
+
return
|
562 |
+
}
|
563 |
+
|
564 |
+
const body = JSON.parse(generateVoiceButton.dataset.modelQuery)
|
565 |
+
|
566 |
+
const appVersionOk = window.checkVersionRequirements(body.version, appVersion)
|
567 |
+
if (!appVersionOk) {
|
568 |
+
window.errorModal(`${window.i18n.MODEL_REQUIRES_VERSION} v${body.version}<br><br>${window.i18n.THIS_APP_VERSION}: ${window.appVersion}`)
|
569 |
+
return
|
570 |
+
}
|
571 |
+
|
572 |
+
|
573 |
+
window.appLogger.log(`${window.i18n.LOADING_VOICE}: ${JSON.parse(generateVoiceButton.dataset.modelQuery).model}`)
|
574 |
+
window.batch_state.lastModel = JSON.parse(generateVoiceButton.dataset.modelQuery).model.split("/").reverse()[0]
|
575 |
+
|
576 |
+
body["pluginsContext"] = JSON.stringify(window.pluginsContext)
|
577 |
+
|
578 |
+
spinnerModal(`${window.i18n.LOADING_VOICE}`)
|
579 |
+
doFetch(`http://localhost:8008/loadModel`, {
|
580 |
+
method: "Post",
|
581 |
+
body: JSON.stringify(body)
|
582 |
+
}).then(r=>r.text()).then(res => {
|
583 |
+
|
584 |
+
window.currentModel.loaded = true
|
585 |
+
generateVoiceButton.dataset.modelQuery = null
|
586 |
+
generateVoiceButton.innerHTML = window.i18n.GENERATE_VOICE
|
587 |
+
generateVoiceButton.dataset.modelIDLoaded = generateVoiceButton.dataset.modelIDToLoad
|
588 |
+
|
589 |
+
// Set the editor pitch/energy dropdowns to pitch, and freeze them, if energy is not supported by the model
|
590 |
+
window.appState.currentModelEmbeddings = {}
|
591 |
+
if (window.currentModel.modelType.toLowerCase()=="xvapitch" && !window.currentModel.isBaseModel) {
|
592 |
+
vocoder_options_container.style.display = "none"
|
593 |
+
base_lang_select.disabled = false
|
594 |
+
style_emb_select.disabled = false
|
595 |
+
window.loadStyleEmbsForVoice(window.currentModel)
|
596 |
+
mic_SVG.children[0].style.fill = "white"
|
597 |
+
base_lang_select.value = window.currentModel.lang
|
598 |
+
} else {
|
599 |
+
vocoder_options_container.style.display = "inline-block"
|
600 |
+
base_lang_select.disabled = true
|
601 |
+
style_emb_select.disabled = true
|
602 |
+
mic_SVG.children[0].style.fill = "grey"
|
603 |
+
}
|
604 |
+
if (window.currentModel.modelType.toLowerCase()=="fastpitch") {
|
605 |
+
seq_edit_view_select.value = "pitch"
|
606 |
+
seq_edit_edit_select.value = "pitch"
|
607 |
+
seq_edit_view_select.disabled = true
|
608 |
+
seq_edit_edit_select.disabled = true
|
609 |
+
} else {
|
610 |
+
seq_edit_view_select.value = "pitch_energy"
|
611 |
+
seq_edit_view_select.disabled = false
|
612 |
+
seq_edit_edit_select.disabled = false
|
613 |
+
}
|
614 |
+
|
615 |
+
window.populateLanguagesDropdownsFromModel(base_lang_select, window.currentModel)
|
616 |
+
base_lang_select.value = window.currentModel.lang
|
617 |
+
|
618 |
+
if (window.userSettings.defaultToHiFi && window.currentModel.hifi) {
|
619 |
+
vocoder_select.value = Array.from(vocoder_select.children).find(opt => opt.innerHTML=="Bespoke HiFi GAN").value
|
620 |
+
window.changeVocoder(vocoder_select.value).then(() => dialogueInput.focus())
|
621 |
+
} else if (window.userSettings.vocoder.includes(".hg.pt")) {
|
622 |
+
window.changeVocoder("qnd").then(() => dialogueInput.focus())
|
623 |
+
} else {
|
624 |
+
closeModal(null, [workbenchContainer]).then(() => dialogueInput.focus())
|
625 |
+
}
|
626 |
+
resolve()
|
627 |
+
}).catch(e => {
|
628 |
+
console.log(e)
|
629 |
+
if (e.code =="ENOENT") {
|
630 |
+
closeModal(null, [modalContainer, workbenchContainer]).then(() => {
|
631 |
+
window.errorModal(window.i18n.ERR_SERVER)
|
632 |
+
resolve()
|
633 |
+
})
|
634 |
+
}
|
635 |
+
})
|
636 |
+
})
|
637 |
+
}
|
638 |
+
|
639 |
+
// Return true/false for if the prompt is the same - BUT: allow phoneme swaps
|
640 |
+
window.checkIfPromptIsTheSame = (sequence) => {
|
641 |
+
|
642 |
+
// False if there was no previous prompt
|
643 |
+
if (!window.sequenceEditor.historyState.length) {
|
644 |
+
return false
|
645 |
+
}
|
646 |
+
|
647 |
+
const lastPrompt = window.sequenceEditor.historyState.at(-1)
|
648 |
+
|
649 |
+
// False if they're different lengths
|
650 |
+
if (sequence.length != lastPrompt.length) {
|
651 |
+
return false
|
652 |
+
}
|
653 |
+
|
654 |
+
// Split into words (and phonemes)
|
655 |
+
const currentParts = sequence.split(" ")
|
656 |
+
const lastParts = lastPrompt.split(" ")
|
657 |
+
|
658 |
+
for (let si=0; si<currentParts.length; si++) {
|
659 |
+
// False if a word is different, but not if it's an ARPAbet symbol
|
660 |
+
const cleaned = currentParts[si].replace(/[^a-zA-Z]/g, "")
|
661 |
+
if (currentParts[si]!=lastParts[si] && !window.ARPAbetSymbols.includes(cleaned)) {
|
662 |
+
return false
|
663 |
+
}
|
664 |
+
}
|
665 |
+
|
666 |
+
return true
|
667 |
+
}
|
668 |
+
|
669 |
+
window.synthesizeSample = () => {
|
670 |
+
|
671 |
+
const game = window.currentGame.gameId
|
672 |
+
|
673 |
+
if (window.isGenerating) {
|
674 |
+
return
|
675 |
+
}
|
676 |
+
if (!window.speech2speechState.s2s_running) {
|
677 |
+
clearOldTempFiles()
|
678 |
+
}
|
679 |
+
|
680 |
+
let sequence = dialogueInput.value.replace("β¦", "...").replace("β", "'")
|
681 |
+
if (window.userSettings.spacePadding && !window.sequenceEditor.isEditingFromFile) { // Pad start and end of the input sequence with spaces
|
682 |
+
sequence = " "+sequence.trim()+" "
|
683 |
+
}
|
684 |
+
window.sequenceEditor.isEditingFromFile = false
|
685 |
+
|
686 |
+
if (sequence.length==0) {
|
687 |
+
return
|
688 |
+
}
|
689 |
+
window.isGenerating = true
|
690 |
+
|
691 |
+
window.pluginsManager.runPlugins(window.pluginsManager.pluginsModules["generate-voice"]["pre"], event="pre generate-voice")
|
692 |
+
|
693 |
+
if (window.wavesurfer) {
|
694 |
+
try {
|
695 |
+
window.wavesurfer.stop()
|
696 |
+
} catch (e) {
|
697 |
+
console.log(e)
|
698 |
+
}
|
699 |
+
wavesurferContainer.style.opacity = 0
|
700 |
+
}
|
701 |
+
toggleSpinnerButtons()
|
702 |
+
|
703 |
+
const voiceType = title.dataset.modelId
|
704 |
+
const outputFileName = dialogueInput.value.slice(0, 260).replace(/\n/g, " ").replace(/[\/\\:\*?<>"|]*/g, "").replace(/^[\.\s]+/, "")
|
705 |
+
|
706 |
+
try {fs.unlinkSync(localStorage.getItem("tempFileLocation"))} catch (e) {/*Do nothing*/}
|
707 |
+
|
708 |
+
// For some reason, the samplePlay audio element does not update the source when the file name is the same
|
709 |
+
const tempFileNum = `${Math.random().toString().split(".")[1]}`
|
710 |
+
let tempFileLocation = `${path}/output/temp-${tempFileNum}.wav`
|
711 |
+
let pitch = []
|
712 |
+
let duration = []
|
713 |
+
let energy = []
|
714 |
+
let emAngry = []
|
715 |
+
let emHappy = []
|
716 |
+
let emSad = []
|
717 |
+
let emSurprise = []
|
718 |
+
let editorStyles = {}
|
719 |
+
let isFreshRegen = true
|
720 |
+
let old_sequence = undefined
|
721 |
+
|
722 |
+
if (editorContainer.innerHTML && editorContainer.innerHTML.length && generateVoiceButton.dataset.modelIDLoaded==window.sequenceEditor.currentVoice) {
|
723 |
+
if (window.sequenceEditor.audioInput || window.sequenceEditor.sequence && sequence!=window.sequenceEditor.inputSequence) {
|
724 |
+
old_sequence = window.sequenceEditor.inputSequence
|
725 |
+
}
|
726 |
+
}
|
727 |
+
// Don't use the old_sequence if running speech-to-speech
|
728 |
+
if (window.speech2speechState.s2s_running) {
|
729 |
+
old_sequence = undefined
|
730 |
+
window.speech2speechState.s2s_running = false
|
731 |
+
}
|
732 |
+
|
733 |
+
// Check if editing an existing line (otherwise it's a fresh new line)
|
734 |
+
const languageHasChanged = window.sequenceEditor.base_lang && window.sequenceEditor.base_lang != base_lang_select.value
|
735 |
+
const promptHasChanged = !window.checkIfPromptIsTheSame(sequence)
|
736 |
+
if (!promptHasChanged && !languageHasChanged && !window.arpabetMenuState.hasChangedARPAbet && !window.styleEmbsMenuState.hasChangedEmb &&
|
737 |
+
(speech2speechState.s2s_autogenerate || (editorContainer.innerHTML && editorContainer.innerHTML.length && (window.userSettings.keepEditorOnVoiceChange || generateVoiceButton.dataset.modelIDLoaded==window.sequenceEditor.currentVoice)))) {
|
738 |
+
|
739 |
+
speech2speechState.s2s_autogenerate = false
|
740 |
+
pitch = window.sequenceEditor.pitchNew.map(v=> v==undefined?0:v)
|
741 |
+
duration = window.sequenceEditor.dursNew.map(v => v*pace_slid.value).map(v=> v==undefined?0:v)
|
742 |
+
energy = window.sequenceEditor.energyNew ? window.sequenceEditor.energyNew.map(v => v==undefined?0:v).filter(v => !isNaN(v)) : []
|
743 |
+
if (window.currentModel.modelType=="xVAPitch") {
|
744 |
+
emAngry = window.sequenceEditor.emAngryNew ? window.sequenceEditor.emAngryNew.map(v => v==undefined?0:v).filter(v => !isNaN(v)) : []
|
745 |
+
emHappy = window.sequenceEditor.emHappyNew ? window.sequenceEditor.emHappyNew.map(v => v==undefined?0:v).filter(v => !isNaN(v)) : []
|
746 |
+
emSad = window.sequenceEditor.emSadNew ? window.sequenceEditor.emSadNew.map(v => v==undefined?0:v).filter(v => !isNaN(v)) : []
|
747 |
+
emSurprise = window.sequenceEditor.emSurpriseNew ? window.sequenceEditor.emSurpriseNew.map(v => v==undefined?0:v).filter(v => !isNaN(v)) : []
|
748 |
+
|
749 |
+
if (window.sequenceEditor.registeredStyleKeys) {
|
750 |
+
window.sequenceEditor.registeredStyleKeys.forEach(styleKey => {
|
751 |
+
editorStyles[styleKey] = {
|
752 |
+
embedding: window.appState.currentModelEmbeddings[styleKey][1],
|
753 |
+
sliders: window.sequenceEditor.styleValuesNew[styleKey].map(v => v==undefined?0:v).filter(v => !isNaN(v))// : []
|
754 |
+
}
|
755 |
+
})
|
756 |
+
}
|
757 |
+
}
|
758 |
+
isFreshRegen = false
|
759 |
+
}
|
760 |
+
|
761 |
+
window.arpabetMenuState.hasChangedARPAbet = false
|
762 |
+
window.styleEmbsMenuState.hasChangedEmb = false
|
763 |
+
window.sequenceEditor.currentVoice = generateVoiceButton.dataset.modelIDLoaded
|
764 |
+
|
765 |
+
const speaker_i = window.currentModel.games[0].emb_i
|
766 |
+
const pace = (window.userSettings.keepPaceOnNew && isFreshRegen)?pace_slid.value:1
|
767 |
+
|
768 |
+
window.appLogger.log(`${window.i18n.SYNTHESIZING}: ${sequence}`)
|
769 |
+
|
770 |
+
doFetch(`http://localhost:8008/synthesize`, {
|
771 |
+
method: "Post",
|
772 |
+
body: JSON.stringify({
|
773 |
+
sequence, pitch, duration, energy, emAngry, emHappy, emSad, emSurprise, editorStyles, speaker_i, pace,
|
774 |
+
base_lang: base_lang_select.value,
|
775 |
+
base_emb: style_emb_select.value||"",
|
776 |
+
modelType: window.currentModel.modelType,
|
777 |
+
old_sequence, // For partial re-generation
|
778 |
+
device: window.userSettings.installation=="cpu"?"cpu":(window.userSettings.useGPU?"cuda:0":"cpu"),
|
779 |
+
// device: window.userSettings.useGPU?"gpu":"cpu", // Switch to this once DirectML is installed
|
780 |
+
useSR: useSRCkbx.checked,
|
781 |
+
useCleanup: useCleanupCkbx.checked,
|
782 |
+
outfile: tempFileLocation,
|
783 |
+
pluginsContext: JSON.stringify(window.pluginsContext),
|
784 |
+
vocoder: window.currentModel.modelType=="xVAPitch" ? "n/a" : window.userSettings.vocoder,
|
785 |
+
waveglowPath: vocoder_select.value=="256_waveglow" ? window.userSettings.waveglow_path : window.userSettings.bigwaveglow_path
|
786 |
+
})
|
787 |
+
}).then(r=>r.text()).then(res => {
|
788 |
+
window.isGenerating = false
|
789 |
+
|
790 |
+
if (res=="ENOENT" || res.startsWith("ERR:")) {
|
791 |
+
console.log(res)
|
792 |
+
if (res.startsWith("ERR:")) {
|
793 |
+
if (res.includes("ARPABET_NOT_IN_LIST")) {
|
794 |
+
const symbolNotInList = res.split(":").reverse()[0]
|
795 |
+
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}<br><br>${window.i18n.ERR_ARPABET_NOT_EXIST.replace("_1", symbolNotInList)}`)
|
796 |
+
} else {
|
797 |
+
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}<br><br>${res.replace("ERR:","").replaceAll(/\n/g, "<br>")}`)
|
798 |
+
}
|
799 |
+
} else {
|
800 |
+
window.appLogger.log(res)
|
801 |
+
window.errorModal(`${window.i18n.BATCH_MODEL_NOT_FOUND}.${vocoder_select.value.includes("waveglow")?" "+window.i18n.BATCH_DOWNLOAD_WAVEGLOW:""}`)
|
802 |
+
}
|
803 |
+
toggleSpinnerButtons()
|
804 |
+
return
|
805 |
+
}
|
806 |
+
|
807 |
+
dialogueInput.focus()
|
808 |
+
window.sequenceEditor.historyState.push(sequence)
|
809 |
+
|
810 |
+
if (window.userSettings.clear_text_after_synth) {
|
811 |
+
dialogueInput.value = ""
|
812 |
+
}
|
813 |
+
|
814 |
+
res = res.split("\n")
|
815 |
+
let pitchData = res[0]
|
816 |
+
let durationsData = res[1]
|
817 |
+
let energyData = res[2]
|
818 |
+
let em_angryData = res[3]
|
819 |
+
let em_happyData = res[4]
|
820 |
+
let em_sadData = res[5]
|
821 |
+
let em_surpriseData = res[6]
|
822 |
+
const editorStyles = res[7]&&res[7].length ? JSON.parse(res[7]) : undefined
|
823 |
+
let cleanedSequence = res[8].split("|").map(c=>c.replaceAll("{", "").replaceAll("}", "").replace(/\s/g, "_"))
|
824 |
+
const start_index = res[9]
|
825 |
+
const end_index = res[10]
|
826 |
+
pitchData = pitchData.split(",").map(v => parseFloat(v))
|
827 |
+
|
828 |
+
// For use in adjusting editor range
|
829 |
+
const maxPitchVal = pitchData.reduce((p,c)=>Math.max(p, Math.abs(c)), 0)
|
830 |
+
if (maxPitchVal>window.sequenceEditor.default_pitchSliderRange) {
|
831 |
+
window.sequenceEditor.pitchSliderRange = maxPitchVal
|
832 |
+
} else {
|
833 |
+
window.sequenceEditor.pitchSliderRange = window.sequenceEditor.default_pitchSliderRange
|
834 |
+
}
|
835 |
+
|
836 |
+
em_angryData = em_angryData.length ? em_angryData.split(",").map(v => parseFloat(v)).filter(v => !isNaN(v)) : []
|
837 |
+
em_happyData = em_happyData.length ? em_happyData.split(",").map(v => parseFloat(v)).filter(v => !isNaN(v)) : []
|
838 |
+
em_sadData = em_sadData.length ? em_sadData.split(",").map(v => parseFloat(v)).filter(v => !isNaN(v)) : []
|
839 |
+
em_surpriseData = em_surpriseData.length ? em_surpriseData.split(",").map(v => parseFloat(v)).filter(v => !isNaN(v)) : []
|
840 |
+
|
841 |
+
if (energyData.length) {
|
842 |
+
energyData = energyData.split(",").map(v => parseFloat(v)).filter(v => !isNaN(v))
|
843 |
+
|
844 |
+
// For use in adjusting editor range
|
845 |
+
const maxEnergyVal = energyData.reduce((p,c)=>Math.max(p, c), 0)
|
846 |
+
const minEnergyVal = energyData.reduce((p,c)=>Math.min(p, c), 100)
|
847 |
+
|
848 |
+
if (minEnergyVal<window.sequenceEditor.default_MIN_ENERGY) {
|
849 |
+
window.sequenceEditor.MIN_ENERGY = minEnergyVal
|
850 |
+
} else {
|
851 |
+
window.sequenceEditor.MIN_ENERGY = window.sequenceEditor.default_MIN_ENERGY
|
852 |
+
}
|
853 |
+
if (maxEnergyVal>window.sequenceEditor.default_MAX_ENERGY) {
|
854 |
+
window.sequenceEditor.MAX_ENERGY = maxEnergyVal
|
855 |
+
} else {
|
856 |
+
window.sequenceEditor.MAX_ENERGY = window.sequenceEditor.default_MAX_ENERGY
|
857 |
+
}
|
858 |
+
|
859 |
+
} else {
|
860 |
+
energyData = []
|
861 |
+
}
|
862 |
+
durationsData = durationsData.split(",").map(v => isFreshRegen ? parseFloat(v) : parseFloat(v)/pace_slid.value)
|
863 |
+
|
864 |
+
const doTheRest = () => {
|
865 |
+
|
866 |
+
window.sequenceEditor.base_lang = base_lang_select.value
|
867 |
+
window.sequenceEditor.inputSequence = sequence
|
868 |
+
window.sequenceEditor.sequence = cleanedSequence
|
869 |
+
|
870 |
+
if (pitch.length==0 || isFreshRegen) {
|
871 |
+
window.sequenceEditor.resetPitch = pitchData
|
872 |
+
window.sequenceEditor.resetDurs = durationsData
|
873 |
+
window.sequenceEditor.resetEnergy = energyData
|
874 |
+
window.sequenceEditor.resetEmAngry = em_angryData
|
875 |
+
window.sequenceEditor.resetEmHappy = em_happyData
|
876 |
+
window.sequenceEditor.resetEmSad = em_sadData
|
877 |
+
window.sequenceEditor.resetEmSurprise = em_surpriseData
|
878 |
+
}
|
879 |
+
|
880 |
+
window.sequenceEditor.letters = cleanedSequence
|
881 |
+
window.sequenceEditor.pitchNew = pitchData.map(p=>p)
|
882 |
+
window.sequenceEditor.dursNew = durationsData.map(v=>v)
|
883 |
+
window.sequenceEditor.energyNew = energyData.map(v=>v)
|
884 |
+
if (window.currentModel.modelType=="xVAPitch") {
|
885 |
+
window.sequenceEditor.emAngryNew = em_angryData.map(v=>v)
|
886 |
+
window.sequenceEditor.emHappyNew = em_happyData.map(v=>v)
|
887 |
+
window.sequenceEditor.emSadNew = em_sadData.map(v=>v)
|
888 |
+
window.sequenceEditor.emSurpriseNew = em_surpriseData.map(v=>v)
|
889 |
+
window.sequenceEditor.loadStylesData(editorStyles)
|
890 |
+
}
|
891 |
+
window.sequenceEditor.init()
|
892 |
+
const pitchRange = window.userSettings.pitchrangeoverride ? window.userSettings.pitchrangeoverride : window.sequenceEditor.pitchSliderRange
|
893 |
+
window.sequenceEditor.update(window.currentModel.modelType, pitchRange)
|
894 |
+
|
895 |
+
window.sequenceEditor.sliderBoxes.forEach((box, i) => {box.setValueFromValue(window.sequenceEditor.dursNew[i])})
|
896 |
+
window.sequenceEditor.autoInferTimer = null
|
897 |
+
window.sequenceEditor.hasChanged = false
|
898 |
+
|
899 |
+
|
900 |
+
toggleSpinnerButtons()
|
901 |
+
if (keepSampleButton.dataset.newFileLocation && keepSampleButton.dataset.newFileLocation.startsWith("BATCH_EDIT")) {
|
902 |
+
console.log("_debug_")
|
903 |
+
} else {
|
904 |
+
if (window.userSettings[`outpath_${game}`]) {
|
905 |
+
keepSampleButton.dataset.newFileLocation = `${window.userSettings[`outpath_${game}`]}/${voiceType}/${outputFileName}.wav`
|
906 |
+
} else {
|
907 |
+
keepSampleButton.dataset.newFileLocation = `${__dirname.replace(/\\/g,"/")}/output/${voiceType}/${outputFileName}.wav`
|
908 |
+
}
|
909 |
+
}
|
910 |
+
keepSampleButton.disabled = false
|
911 |
+
window.tempFileLocation = tempFileLocation
|
912 |
+
|
913 |
+
|
914 |
+
// Wavesurfer
|
915 |
+
if (!window.wavesurfer) {
|
916 |
+
window.initWaveSurfer(`${__dirname.replace("/javascript", "")}/output/${tempFileLocation.split("/").reverse()[0]}`)
|
917 |
+
} else {
|
918 |
+
window.wavesurfer.load(`${__dirname.replace("/javascript", "")}/output/${tempFileLocation.split("/").reverse()[0]}`)
|
919 |
+
}
|
920 |
+
|
921 |
+
window.wavesurfer.on("ready", () => {
|
922 |
+
|
923 |
+
wavesurferContainer.style.opacity = 1
|
924 |
+
|
925 |
+
if (window.userSettings.autoPlayGen) {
|
926 |
+
|
927 |
+
if (window.userSettings.playChangedAudio) {
|
928 |
+
const playbackStartEnd = window.sequenceEditor.getChangedTimeStamps(start_index, end_index, window.wavesurfer.getDuration())
|
929 |
+
if (playbackStartEnd) {
|
930 |
+
wavesurfer.play(playbackStartEnd[0], playbackStartEnd[1])
|
931 |
+
} else {
|
932 |
+
wavesurfer.play()
|
933 |
+
}
|
934 |
+
} else {
|
935 |
+
wavesurfer.play()
|
936 |
+
}
|
937 |
+
window.sequenceEditor.adjustedLetters = new Set()
|
938 |
+
samplePlayPause.innerHTML = window.i18n.PAUSE
|
939 |
+
}
|
940 |
+
})
|
941 |
+
|
942 |
+
// Persistance across sessions
|
943 |
+
localStorage.setItem("tempFileLocation", tempFileLocation)
|
944 |
+
}
|
945 |
+
|
946 |
+
|
947 |
+
if (window.userSettings.audio.ffmpeg) {
|
948 |
+
const options = {
|
949 |
+
hz: window.userSettings.audio.hz,
|
950 |
+
padStart: window.userSettings.audio.padStart,
|
951 |
+
padEnd: window.userSettings.audio.padEnd,
|
952 |
+
bit_depth: window.userSettings.audio.bitdepth,
|
953 |
+
amplitude: window.userSettings.audio.amplitude,
|
954 |
+
pitchMult: window.userSettings.audio.pitchMult,
|
955 |
+
tempo: window.userSettings.audio.tempo,
|
956 |
+
deessing: window.userSettings.audio.deessing,
|
957 |
+
nr: window.userSettings.audio.nr,
|
958 |
+
nf: window.userSettings.audio.nf,
|
959 |
+
useNR: window.userSettings.audio.useNR,
|
960 |
+
useSR: useSRCkbx.checked,
|
961 |
+
useCleanup: useCleanupCkbx.checked,
|
962 |
+
}
|
963 |
+
|
964 |
+
const extraInfo = {
|
965 |
+
game: window.currentGame.gameId,
|
966 |
+
voiceId: window.currentModel.voiceId,
|
967 |
+
voiceName: window.currentModel.voiceName,
|
968 |
+
inputSequence: sequence,
|
969 |
+
letters: cleanedSequence,
|
970 |
+
pitch: pitchData.map(p=>p),
|
971 |
+
energy: energyData.map(p=>p),
|
972 |
+
em_angry: em_angryData.map(p=>p),
|
973 |
+
em_happy: em_happyData.map(p=>p),
|
974 |
+
em_sad: em_sadData.map(p=>p),
|
975 |
+
em_surprise: em_surpriseData.map(p=>p),
|
976 |
+
durations: durationsData.map(v=>v)
|
977 |
+
}
|
978 |
+
|
979 |
+
doFetch(`http://localhost:8008/outputAudio`, {
|
980 |
+
method: "Post",
|
981 |
+
body: JSON.stringify({
|
982 |
+
input_path: tempFileLocation,
|
983 |
+
output_path: tempFileLocation.replace(".wav", `_ffmpeg.${window.userSettings.audio.format}`),
|
984 |
+
pluginsContext: JSON.stringify(window.pluginsContext),
|
985 |
+
extraInfo: JSON.stringify(extraInfo),
|
986 |
+
isBatchMode: false,
|
987 |
+
options: JSON.stringify(options)
|
988 |
+
})
|
989 |
+
}).then(r=>r.text()).then(res => {
|
990 |
+
if (res.length && res!="-") {
|
991 |
+
console.log("res", res)
|
992 |
+
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}<br><br>${res}`).then(() => toggleSpinnerButtons())
|
993 |
+
} else {
|
994 |
+
tempFileLocation = tempFileLocation.replace(".wav", `_ffmpeg.${window.userSettings.audio.format}`)
|
995 |
+
doTheRest()
|
996 |
+
}
|
997 |
+
}).catch(res => {
|
998 |
+
console.log(res)
|
999 |
+
window.appLogger.log(`outputAudio error: ${res}`)
|
1000 |
+
// closeModal().then(() => {
|
1001 |
+
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}<br><br>${res}`)
|
1002 |
+
// })
|
1003 |
+
})
|
1004 |
+
} else {
|
1005 |
+
doTheRest()
|
1006 |
+
}
|
1007 |
+
|
1008 |
+
|
1009 |
+
}).catch(res => {
|
1010 |
+
window.isGenerating = false
|
1011 |
+
console.log(res)
|
1012 |
+
window.appLogger.log(res)
|
1013 |
+
window.errorModal(window.i18n.SOMETHING_WENT_WRONG)
|
1014 |
+
toggleSpinnerButtons()
|
1015 |
+
})
|
1016 |
+
}
|
1017 |
+
|
1018 |
+
generateVoiceButton.addEventListener("click", () => {
|
1019 |
+
try {fs.mkdirSync(window.userSettings[`outpath_${game}`])} catch (e) {/*Do nothing*/}
|
1020 |
+
try {fs.mkdirSync(`${window.userSettings[`outpath_${game}`]}/${voiceId}`)} catch (e) {/*Do nothing*/}
|
1021 |
+
|
1022 |
+
if (generateVoiceButton.dataset.modelQuery && generateVoiceButton.dataset.modelQuery!="null") {
|
1023 |
+
window.loadModel()
|
1024 |
+
} else {
|
1025 |
+
window.synthesizeSample()
|
1026 |
+
}
|
1027 |
+
})
|
1028 |
+
|
1029 |
+
|
1030 |
+
|
1031 |
+
|
1032 |
+
window.saveFile = (from, to, skipUIRecord=false) => {
|
1033 |
+
to = to.split("%20").join(" ")
|
1034 |
+
to = to.replace(".wav", `.${window.userSettings.audio.format}`)
|
1035 |
+
|
1036 |
+
// Make the containing folder if it does not already exist
|
1037 |
+
let containerFolderPath = to.split("/")
|
1038 |
+
containerFolderPath = containerFolderPath.slice(0,containerFolderPath.length-1).join("/")
|
1039 |
+
|
1040 |
+
try {fs.mkdirSync(containerFolderPath)} catch (e) {/*Do nothing*/}
|
1041 |
+
|
1042 |
+
// For plugins
|
1043 |
+
const pluginData = {
|
1044 |
+
game: window.currentGame.gameId,
|
1045 |
+
voiceId: window.currentModel.voiceId,
|
1046 |
+
voiceName: window.currentModel.voiceName,
|
1047 |
+
inputSequence: window.sequenceEditor.inputSequence,
|
1048 |
+
letters: window.sequenceEditor.letters,
|
1049 |
+
pitch: window.sequenceEditor.pitchNew,
|
1050 |
+
durations: window.sequenceEditor.dursNew,
|
1051 |
+
vocoder: vocoder_select.value,
|
1052 |
+
from, to
|
1053 |
+
}
|
1054 |
+
const options = {
|
1055 |
+
hz: window.userSettings.audio.hz,
|
1056 |
+
padStart: window.userSettings.audio.padStart,
|
1057 |
+
padEnd: window.userSettings.audio.padEnd,
|
1058 |
+
bit_depth: window.userSettings.audio.bitdepth,
|
1059 |
+
amplitude: window.userSettings.audio.amplitude,
|
1060 |
+
pitchMult: window.userSettings.audio.pitchMult,
|
1061 |
+
tempo: window.userSettings.audio.tempo,
|
1062 |
+
deessing: window.userSettings.audio.deessing,
|
1063 |
+
nr: window.userSettings.audio.nr,
|
1064 |
+
nf: window.userSettings.audio.nf,
|
1065 |
+
useNR: window.userSettings.audio.useNR,
|
1066 |
+
useSR: useSRCkbx.checked
|
1067 |
+
}
|
1068 |
+
pluginData.audioOptions = options
|
1069 |
+
window.pluginsManager.runPlugins(window.pluginsManager.pluginsModules["keep-sample"]["pre"], event="pre keep-sample", pluginData)
|
1070 |
+
|
1071 |
+
const jsonDataOut = {
|
1072 |
+
modelType: window.currentModel.modelType,
|
1073 |
+
modelVersion: window.currentModel.modelVersion,
|
1074 |
+
version: window.currentModel.version,
|
1075 |
+
inputSequence: dialogueInput.value.trim(),
|
1076 |
+
pacing: parseFloat(pace_slid.value),
|
1077 |
+
letters: window.sequenceEditor.letters,
|
1078 |
+
currentVoice: window.sequenceEditor.currentVoice,
|
1079 |
+
resetEnergy: window.sequenceEditor.resetEnergy,
|
1080 |
+
resetPitch: window.sequenceEditor.resetPitch,
|
1081 |
+
resetDurs: window.sequenceEditor.resetDurs,
|
1082 |
+
resetEmAngry: window.sequenceEditor.resetEmAngry,
|
1083 |
+
resetEmHappy: window.sequenceEditor.resetEmHappy,
|
1084 |
+
resetEmSad: window.sequenceEditor.resetEmSad,
|
1085 |
+
resetEmSurprise: window.sequenceEditor.resetEmSurprise,
|
1086 |
+
styleValuesReset: window.sequenceEditor.styleValuesReset,
|
1087 |
+
ampFlatCounter: window.sequenceEditor.ampFlatCounter,
|
1088 |
+
inputSequence: window.sequenceEditor.inputSequence,
|
1089 |
+
sequence: window.sequenceEditor.sequence,
|
1090 |
+
pitchNew: window.sequenceEditor.pitchNew,
|
1091 |
+
energyNew: window.sequenceEditor.energyNew,
|
1092 |
+
dursNew: window.sequenceEditor.dursNew,
|
1093 |
+
emAngryNew: window.sequenceEditor.emAngryNew,
|
1094 |
+
emHappyNew: window.sequenceEditor.emHappyNew,
|
1095 |
+
emSadNew: window.sequenceEditor.emSadNew,
|
1096 |
+
emSurpriseNew: window.sequenceEditor.emSurpriseNew,
|
1097 |
+
styleValuesNew: window.sequenceEditor.styleValuesNew,
|
1098 |
+
}
|
1099 |
+
|
1100 |
+
let outputFileName = to.split("/").reverse()[0].split(".").reverse().slice(1, 1000)
|
1101 |
+
const toExt = to.split(".").reverse()[0]
|
1102 |
+
|
1103 |
+
if (window.userSettings.filenameNumericalSeq) {
|
1104 |
+
outputFileName = outputFileName[0]+"."+outputFileName.slice(1,1000).reverse().join(".")
|
1105 |
+
} else {
|
1106 |
+
outputFileName = outputFileName.reverse().join(".")
|
1107 |
+
}
|
1108 |
+
to = `${to.split("/").reverse().slice(1,10000).reverse().join("/")}/${outputFileName}`
|
1109 |
+
|
1110 |
+
|
1111 |
+
const allFiles = fs.readdirSync(`${path}/output`).filter(fname => fname.includes(from.split("/").reverse()[0].split(".")[0]))
|
1112 |
+
const toFolder = to.split("/").reverse().slice(1, 1000).reverse().join("/")
|
1113 |
+
|
1114 |
+
|
1115 |
+
allFiles.forEach(fname => {
|
1116 |
+
const ext = fname.split(".").reverse()[0]
|
1117 |
+
fs.copyFile(`${path}/output/${fname}`, `${toFolder}/${outputFileName}.${ext}`, err => {
|
1118 |
+
if (err) {
|
1119 |
+
console.log(err)
|
1120 |
+
window.appLogger.log(`Error in saveFile->outputAudio[no ffmpeg]: ${err}`)
|
1121 |
+
if (!fs.existsSync(from)) {
|
1122 |
+
window.appLogger.log(`${window.i18n.TEMP_FILE_NOT_EXIST}: ${from}`)
|
1123 |
+
}
|
1124 |
+
if (!fs.existsSync(toFolder)) {
|
1125 |
+
window.appLogger.log(`${window.i18n.OUT_DIR_NOT_EXIST}: ${toFolder}`)
|
1126 |
+
}
|
1127 |
+
} else {
|
1128 |
+
if (window.userSettings.outputJSON && window.sequenceEditor.letters.length) {
|
1129 |
+
fs.writeFileSync(`${to}.${toExt}.json`, JSON.stringify(jsonDataOut, null, 4))
|
1130 |
+
}
|
1131 |
+
if (!skipUIRecord) {
|
1132 |
+
window.initMainPagePagination(`${window.userSettings[`outpath_${window.currentGame.gameId}`]}/${window.currentModel.voiceId}`)
|
1133 |
+
window.refreshRecordsList()
|
1134 |
+
}
|
1135 |
+
window.pluginsManager.runPlugins(window.pluginsManager.pluginsModules["keep-sample"]["post"], event="post keep-sample", pluginData)
|
1136 |
+
}
|
1137 |
+
})
|
1138 |
+
})
|
1139 |
+
}
|
1140 |
+
|
1141 |
+
window.keepSampleFunction = shiftClick => {
|
1142 |
+
if (keepSampleButton.dataset.newFileLocation) {
|
1143 |
+
|
1144 |
+
const skipUIRecord = keepSampleButton.dataset.newFileLocation.includes("BATCH_EDIT")
|
1145 |
+
let fromLocation = window.tempFileLocation
|
1146 |
+
let toLocation = keepSampleButton.dataset.newFileLocation.replace("BATCH_EDIT", "")
|
1147 |
+
|
1148 |
+
if (!skipUIRecord) {
|
1149 |
+
toLocation = toLocation.split("/")
|
1150 |
+
toLocation[toLocation.length-1] = toLocation[toLocation.length-1].replace(/[\/\\:\*?<>"|]*/g, "")
|
1151 |
+
toLocation[toLocation.length-1] = toLocation[toLocation.length-1].replace(/\.wav$/, "").slice(0, window.userSettings.max_filename_chars).replace(/\.$/, "")
|
1152 |
+
}
|
1153 |
+
|
1154 |
+
|
1155 |
+
// Numerical file name counter
|
1156 |
+
if (!skipUIRecord && window.userSettings.filenameNumericalSeq) {
|
1157 |
+
let existingFiles = []
|
1158 |
+
try {
|
1159 |
+
existingFiles = fs.readdirSync(toLocation.slice(0, toLocation.length-1).join("/")).filter(fname => !fname.endsWith(".json"))
|
1160 |
+
} catch (e) {
|
1161 |
+
console.log(e)
|
1162 |
+
}
|
1163 |
+
existingFiles = existingFiles.filter(fname => fname.includes(toLocation[toLocation.length-1]))
|
1164 |
+
existingFiles = existingFiles.map(fname => {
|
1165 |
+
const parts = fname.split(".")
|
1166 |
+
parts.reverse()
|
1167 |
+
if (parts.length>2 && parts.reverse()[0].length) {
|
1168 |
+
if (parseInt(parts[0]) != NaN) {
|
1169 |
+
return parseInt(parts[0])
|
1170 |
+
}
|
1171 |
+
}
|
1172 |
+
return null
|
1173 |
+
})
|
1174 |
+
existingFiles = existingFiles.filter(val => !!val)
|
1175 |
+
if (existingFiles.length==0) {
|
1176 |
+
existingFiles.push(0)
|
1177 |
+
}
|
1178 |
+
|
1179 |
+
if (existingFiles.length) {
|
1180 |
+
existingFiles = existingFiles.sort((a,b) => {a<b?-1:1})
|
1181 |
+
toLocation[toLocation.length-1] = `${toLocation[toLocation.length-1]}.${String(existingFiles[existingFiles.length-1]+1).padStart(4, "0")}`
|
1182 |
+
}
|
1183 |
+
}
|
1184 |
+
|
1185 |
+
|
1186 |
+
if (!skipUIRecord) {
|
1187 |
+
toLocation[toLocation.length-1] += ".wav"
|
1188 |
+
toLocation = toLocation.join("/")
|
1189 |
+
}
|
1190 |
+
|
1191 |
+
|
1192 |
+
const outFolder = toLocation.split("/").reverse().slice(2, 100).reverse().join("/")
|
1193 |
+
if (!fs.existsSync(outFolder)) {
|
1194 |
+
return void window.errorModal(`${window.i18n.OUT_DIR_NOT_EXIST}:<br><br><i>${outFolder}</i><br><br>${window.i18n.YOU_CAN_CHANGE_IN_SETTINGS}`)
|
1195 |
+
}
|
1196 |
+
|
1197 |
+
// File name conflict
|
1198 |
+
const alreadyExists = fs.existsSync(toLocation)
|
1199 |
+
if (alreadyExists || shiftClick) {
|
1200 |
+
|
1201 |
+
const promptText = alreadyExists ? window.i18n.FILE_EXISTS_ADJUST : window.i18n.ENTER_FILE_NAME
|
1202 |
+
|
1203 |
+
createModal("prompt", {
|
1204 |
+
prompt: promptText,
|
1205 |
+
value: toLocation.split("/").reverse()[0].replace(".wav", `.${window.userSettings.audio.format}`)
|
1206 |
+
}).then(newFileName => {
|
1207 |
+
|
1208 |
+
let toLocationOut = toLocation.split("/").reverse()
|
1209 |
+
toLocationOut[0] = newFileName.replace(`.${window.userSettings.audio.format}`, "") + `.${window.userSettings.audio.format}`
|
1210 |
+
let outDir = toLocationOut
|
1211 |
+
outDir.shift()
|
1212 |
+
|
1213 |
+
newFileName = (newFileName.replace(`.${window.userSettings.audio.format}`, "") + `.${window.userSettings.audio.format}`).replace(/[\/\\:\*?<>"|]*/g, "")
|
1214 |
+
toLocationOut.reverse()
|
1215 |
+
toLocationOut.push(newFileName)
|
1216 |
+
|
1217 |
+
if (fs.existsSync(outDir.slice(0, outDir.length-1).join("/"))) {
|
1218 |
+
const existingFiles = fs.readdirSync(outDir.slice(0, outDir.length-1).join("/"))
|
1219 |
+
const existingFileConflict = existingFiles.filter(name => name==newFileName)
|
1220 |
+
|
1221 |
+
|
1222 |
+
const finalOutLocation = toLocationOut.join("/")
|
1223 |
+
|
1224 |
+
if (existingFileConflict.length) {
|
1225 |
+
// Remove the entry from the output files' preview
|
1226 |
+
Array.from(voiceSamples.querySelectorAll("div.sample")).forEach(sampleElem => {
|
1227 |
+
let sourceSrc = sampleElem.children[0].children[0].innerText
|
1228 |
+
sourceSrc = sourceSrc.split("/").reverse()
|
1229 |
+
const finalFileName = finalOutLocation.split("/").reverse()
|
1230 |
+
|
1231 |
+
if (sourceSrc[0] == finalFileName[0]) {
|
1232 |
+
sampleElem.parentNode.removeChild(sampleElem)
|
1233 |
+
}
|
1234 |
+
})
|
1235 |
+
|
1236 |
+
// Remove the old file and write the new one in
|
1237 |
+
fs.unlink(finalOutLocation, err => {
|
1238 |
+
if (err) {
|
1239 |
+
console.log(err)
|
1240 |
+
window.appLogger.log(`Error in keepSample: ${err}`)
|
1241 |
+
}
|
1242 |
+
window.saveFile(fromLocation, finalOutLocation, skipUIRecord)
|
1243 |
+
})
|
1244 |
+
return
|
1245 |
+
} else {
|
1246 |
+
window.saveFile(fromLocation, toLocationOut.join("/"), skipUIRecord)
|
1247 |
+
return
|
1248 |
+
}
|
1249 |
+
}
|
1250 |
+
window.saveFile(fromLocation, toLocationOut.join("/"), skipUIRecord)
|
1251 |
+
})
|
1252 |
+
|
1253 |
+
} else {
|
1254 |
+
window.saveFile(fromLocation, toLocation, skipUIRecord)
|
1255 |
+
}
|
1256 |
+
}
|
1257 |
+
}
|
1258 |
+
keepSampleButton.addEventListener("click", event => keepSampleFunction(event.shiftKey))
|
1259 |
+
|
1260 |
+
|
1261 |
+
|
1262 |
+
// Weird recursive intermittent promises to repeatedly check if the server is up yet - but it works!
|
1263 |
+
window.serverIsUp = false
|
1264 |
+
const serverStartingMessage = `${window.i18n.LOADING}...<br>${window.i18n.MAY_TAKE_A_MINUTE}<br><br>${window.i18n.STARTING_PYTHON}...`
|
1265 |
+
window.doWeirdServerStartupCheck = () => {
|
1266 |
+
const check = () => {
|
1267 |
+
return new Promise(topResolve => {
|
1268 |
+
if (window.serverIsUp) {
|
1269 |
+
topResolve()
|
1270 |
+
} else {
|
1271 |
+
(new Promise((resolve, reject) => {
|
1272 |
+
// Gather the model paths to send to the server
|
1273 |
+
const modelsPaths = {}
|
1274 |
+
Object.keys(window.userSettings).filter(key => key.includes("modelspath_")).forEach(key => {
|
1275 |
+
modelsPaths[key.split("_")[1]] = window.userSettings[key]
|
1276 |
+
})
|
1277 |
+
|
1278 |
+
doFetch(`http://localhost:8008/checkReady`, {
|
1279 |
+
method: "Post",
|
1280 |
+
body: JSON.stringify({
|
1281 |
+
device: (window.userSettings.useGPU&&window.userSettings.installation=="gpu")?"gpu":"cpu",
|
1282 |
+
modelsPaths: JSON.stringify(modelsPaths)
|
1283 |
+
})
|
1284 |
+
}).then(r => r.text()).then(r => {
|
1285 |
+
closeModal([document.querySelector("#activeModal"), modalContainer], [totdContainer, EULAContainer], true).then(() => {
|
1286 |
+
window.pluginsManager.updateUI()
|
1287 |
+
if (!window.pluginsManager.hasRunPostStartPlugins) {
|
1288 |
+
window.pluginsManager.hasRunPostStartPlugins = true
|
1289 |
+
window.pluginsManager.runPlugins(window.pluginsManager.pluginsModules["start"]["post"], event="post start")
|
1290 |
+
window.electronBrowserWindow.setProgressBar(-1)
|
1291 |
+
window.checkForWorkshopInstallations()
|
1292 |
+
}
|
1293 |
+
})
|
1294 |
+
window.serverIsUp = true
|
1295 |
+
if (window.userSettings.installation=="cpu") {
|
1296 |
+
|
1297 |
+
if (useGPUCbx.checked) {
|
1298 |
+
doFetch(`http://localhost:8008/setDevice`, {
|
1299 |
+
method: "Post",
|
1300 |
+
body: JSON.stringify({device: "cpu"})
|
1301 |
+
})
|
1302 |
+
}
|
1303 |
+
useGPUCbx.checked = false
|
1304 |
+
useGPUCbx.disabled = true
|
1305 |
+
window.userSettings.useGPU = false
|
1306 |
+
saveUserSettings()
|
1307 |
+
}
|
1308 |
+
|
1309 |
+
resolve()
|
1310 |
+
}).catch((err) => {
|
1311 |
+
reject()
|
1312 |
+
})
|
1313 |
+
})).catch(() => {
|
1314 |
+
setTimeout(async () => {
|
1315 |
+
await check()
|
1316 |
+
topResolve()
|
1317 |
+
}, 100)
|
1318 |
+
})
|
1319 |
+
}
|
1320 |
+
})
|
1321 |
+
}
|
1322 |
+
|
1323 |
+
check()
|
1324 |
+
}
|
1325 |
+
window.doWeirdServerStartupCheck()
|
1326 |
+
|
1327 |
+
modalContainer.addEventListener("click", event => {
|
1328 |
+
try {
|
1329 |
+
if (event.target==modalContainer && activeModal.dataset.type!="spinner") {
|
1330 |
+
closeModal()
|
1331 |
+
}
|
1332 |
+
} catch (e) {}
|
1333 |
+
})
|
1334 |
+
|
1335 |
+
|
1336 |
+
// Cached UI stuff
|
1337 |
+
// =========
|
1338 |
+
dialogueInput.addEventListener("keyup", (event) => {
|
1339 |
+
localStorage.setItem("dialogueInput", " "+dialogueInput.value.trim()+" ")
|
1340 |
+
window.sequenceEditor.hasChanged = true
|
1341 |
+
})
|
1342 |
+
|
1343 |
+
const dialogueInputCache = localStorage.getItem("dialogueInput")
|
1344 |
+
|
1345 |
+
if (dialogueInputCache) {
|
1346 |
+
dialogueInput.value = dialogueInputCache
|
1347 |
+
}
|
1348 |
+
|
1349 |
+
// =========
|
1350 |
+
|
1351 |
+
|
1352 |
+
|
1353 |
+
|
1354 |
+
|
1355 |
+
|
1356 |
+
|
1357 |
+
|
1358 |
+
vocoder_select.value = window.userSettings.vocoder.includes(".hg.") ? "qnd" : window.userSettings.vocoder
|
1359 |
+
window.changeVocoder = vocoder => {
|
1360 |
+
return new Promise(resolve => {
|
1361 |
+
spinnerModal(window.i18n.CHANGING_MODELS)
|
1362 |
+
doFetch(`http://localhost:8008/setVocoder`, {
|
1363 |
+
method: "Post",
|
1364 |
+
body: JSON.stringify({
|
1365 |
+
vocoder,
|
1366 |
+
modelPath: vocoder=="256_waveglow" ? window.userSettings.waveglow_path : window.userSettings.bigwaveglow_path
|
1367 |
+
})
|
1368 |
+
}).then(r=>r.text()).then((res) => {
|
1369 |
+
closeModal().then(() => {
|
1370 |
+
setTimeout(() => {
|
1371 |
+
if (res=="ENOENT") {
|
1372 |
+
vocoder_select.value = window.userSettings.vocoder
|
1373 |
+
window.errorModal(`${window.i18n.BATCH_MODEL_NOT_FOUND}.${vocoder.includes("waveglow")?" "+window.i18n.BATCH_DOWNLOAD_WAVEGLOW:""}`)
|
1374 |
+
resolve()
|
1375 |
+
} else {
|
1376 |
+
window.batch_state.lastVocoder = vocoder
|
1377 |
+
window.userSettings.vocoder = vocoder
|
1378 |
+
saveUserSettings()
|
1379 |
+
resolve()
|
1380 |
+
}
|
1381 |
+
}, 300)
|
1382 |
+
})
|
1383 |
+
})
|
1384 |
+
})
|
1385 |
+
}
|
1386 |
+
vocoder_select.addEventListener("change", () => window.changeVocoder(vocoder_select.value))
|
1387 |
+
|
1388 |
+
useSRCkbx.addEventListener("click", () => {
|
1389 |
+
let userHasSeenThisAlready = localStorage.getItem("useSRHintSeen")
|
1390 |
+
if (useSRCkbx.checked && !userHasSeenThisAlready) {
|
1391 |
+
window.confirmModal(window.i18n.USE_SR_HINT).then(resp => {
|
1392 |
+
if (resp) {
|
1393 |
+
localStorage.setItem("useSRHintSeen", "true")
|
1394 |
+
}
|
1395 |
+
})
|
1396 |
+
}
|
1397 |
+
|
1398 |
+
})
|
1399 |
+
|
1400 |
+
dialogueInput.addEventListener("contextmenu", event => {
|
1401 |
+
event.preventDefault()
|
1402 |
+
ipcRenderer.send('show-context-menu')
|
1403 |
+
})
|
1404 |
+
ipcRenderer.on('context-menu-command', (e, command) => {
|
1405 |
+
if (command=="context-copy") {
|
1406 |
+
if (dialogueInput.selectionStart != dialogueInput.selectionEnd) {
|
1407 |
+
clipboard.writeText(dialogueInput.value.slice(dialogueInput.selectionStart, dialogueInput.selectionEnd))
|
1408 |
+
}
|
1409 |
+
} else if (command=="context-paste") {
|
1410 |
+
if (clipboard.readText().length) {
|
1411 |
+
let newString = dialogueInput.value.slice(0, dialogueInput.selectionStart) + clipboard.readText() + dialogueInput.value.slice(dialogueInput.selectionEnd, dialogueInput.value.length)
|
1412 |
+
dialogueInput.value = newString
|
1413 |
+
}
|
1414 |
+
}
|
1415 |
+
})
|
1416 |
+
|
1417 |
+
|
1418 |
+
window.setupModal(workbenchIcon, workbenchContainer, () => window.initVoiceWorkbench())
|
1419 |
+
|
1420 |
+
|
1421 |
+
// Info
|
1422 |
+
// ====
|
1423 |
+
window.setupModal(infoIcon, infoContainer)
|
1424 |
+
|
1425 |
+
|
1426 |
+
// Patreon
|
1427 |
+
// =======
|
1428 |
+
window.setupModal(patreonIcon, patreonContainer, () => {
|
1429 |
+
const data = fs.readFileSync(`${path}/patreon.txt`, "utf8") + ", minermanb"
|
1430 |
+
creditsList.innerHTML = data
|
1431 |
+
})
|
1432 |
+
|
1433 |
+
|
1434 |
+
// Updates
|
1435 |
+
// =======
|
1436 |
+
app_version.innerHTML = window.appVersion
|
1437 |
+
updatesVersions.innerHTML = `${window.i18n.THIS_APP_VERSION}: ${window.appVersion}`
|
1438 |
+
|
1439 |
+
const checkForUpdates = () => {
|
1440 |
+
doFetch("http://danruta.co.uk/xvasynth_updates.txt").then(r=>r.json()).then(data => {
|
1441 |
+
fs.writeFileSync(`${path}/updates.json`, JSON.stringify(data), "utf8")
|
1442 |
+
checkUpdates.innerHTML = window.i18n.CHECK_FOR_UPDATES
|
1443 |
+
window.showUpdates()
|
1444 |
+
}).catch(() => {
|
1445 |
+
checkUpdates.innerHTML = window.i18n.CANT_REACH_SERVER
|
1446 |
+
})
|
1447 |
+
}
|
1448 |
+
window.showUpdates = () => {
|
1449 |
+
window.updatesLog = fs.readFileSync(`${path}/updates.json`, "utf8")
|
1450 |
+
window.updatesLog = JSON.parse(window.updatesLog)
|
1451 |
+
const sortedLogVersions = Object.keys(window.updatesLog).map( a => a.split('.').map( n => +n+100000 ).join('.') ).sort()
|
1452 |
+
.map( a => a.split('.').map( n => +n-100000 ).join('.') )
|
1453 |
+
|
1454 |
+
const appVersion = window.appVersion.replace("v", "")
|
1455 |
+
const appIsUpToDate = sortedLogVersions.indexOf(appVersion)==(sortedLogVersions.length-1) || sortedLogVersions.indexOf(appVersion)==-1
|
1456 |
+
|
1457 |
+
if (!appIsUpToDate) {
|
1458 |
+
update_nothing.style.display = "none"
|
1459 |
+
update_something.style.display = "block"
|
1460 |
+
updatesVersions.innerHTML = `${window.i18n.THIS_APP_VERSION}: ${appVersion}. ${window.i18n.AVAILABLE}: ${sortedLogVersions[sortedLogVersions.length-1]}`
|
1461 |
+
} else {
|
1462 |
+
updatesVersions.innerHTML = `${window.i18n.THIS_APP_VERSION}: ${appVersion}. ${window.i18n.UPTODATE}`
|
1463 |
+
}
|
1464 |
+
|
1465 |
+
updatesLogList.innerHTML = ""
|
1466 |
+
sortedLogVersions.reverse().forEach(version => {
|
1467 |
+
const versionLabel = createElem("h2", version)
|
1468 |
+
const logItem = createElem("div", versionLabel)
|
1469 |
+
window.updatesLog[version].split("\n").forEach(line => {
|
1470 |
+
logItem.appendChild(createElem("div", line))
|
1471 |
+
})
|
1472 |
+
updatesLogList.appendChild(logItem)
|
1473 |
+
})
|
1474 |
+
}
|
1475 |
+
checkForUpdates()
|
1476 |
+
window.setupModal(updatesIcon, updatesContainer)
|
1477 |
+
|
1478 |
+
checkUpdates.addEventListener("click", () => {
|
1479 |
+
checkUpdates.innerHTML = window.i18n.CHECKING_FOR_UPDATES
|
1480 |
+
checkForUpdates()
|
1481 |
+
})
|
1482 |
+
window.showUpdates()
|
1483 |
+
|
1484 |
+
|
1485 |
+
// Batch generation
|
1486 |
+
// ========
|
1487 |
+
window.setupModal(batchIcon, batchGenerationContainer)
|
1488 |
+
|
1489 |
+
// Settings
|
1490 |
+
// ========
|
1491 |
+
window.setupModal(settingsCog, settingsContainer)
|
1492 |
+
|
1493 |
+
// Change Game
|
1494 |
+
// ===========
|
1495 |
+
window.setupModal(changeGameButton, gameSelectionContainer)
|
1496 |
+
changeGameButton.addEventListener("click", () => searchGameInput.focus())
|
1497 |
+
|
1498 |
+
window.gameAssets = {}
|
1499 |
+
|
1500 |
+
window.updateGameList = (doLoadAllModels=true) => {
|
1501 |
+
gameSelectionListContainer.innerHTML = ""
|
1502 |
+
const fileNames = fs.readdirSync(`${window.path}/assets`)
|
1503 |
+
|
1504 |
+
let totalVoices = 0
|
1505 |
+
let totalGames = new Set()
|
1506 |
+
|
1507 |
+
const itemsToSort = []
|
1508 |
+
// const gameIDs = doLoadAllModels ? fileNames.filter(fn=>fn.endsWith(".json")) : Object.keys(window.games).map(gID => gID+".json")
|
1509 |
+
const gameIDs = fileNames.filter(fn=>fn.endsWith(".json"))
|
1510 |
+
|
1511 |
+
gameIDs.forEach(gameId => {
|
1512 |
+
|
1513 |
+
const metadata = fs.existsSync(`${window.path}/assets/${gameId}`) ? JSON.parse(fs.readFileSync(`${window.path}/assets/${gameId}`)) : window.games[gameId.replace(".json", "")].dummyGameTheme
|
1514 |
+
gameId = gameId.replace(".json", "")
|
1515 |
+
metadata.gameId = gameId
|
1516 |
+
const assetFile = metadata.assetFile
|
1517 |
+
|
1518 |
+
const gameSelection = createElem("div.gameSelection")
|
1519 |
+
gameSelection.style.background = `url("assets/${assetFile}")`
|
1520 |
+
|
1521 |
+
const gameName = metadata.gameName
|
1522 |
+
const gameSelectionContent = createElem("div.gameSelectionContent")
|
1523 |
+
|
1524 |
+
|
1525 |
+
let numVoices = 0
|
1526 |
+
const modelsPath = window.userSettings[`modelspath_${gameId}`]
|
1527 |
+
if (fs.existsSync(modelsPath)) {
|
1528 |
+
const files = fs.readdirSync(modelsPath)
|
1529 |
+
numVoices = files.filter(fn => fn.includes(".json")).length
|
1530 |
+
totalVoices += numVoices
|
1531 |
+
}
|
1532 |
+
if (numVoices==0) {
|
1533 |
+
gameSelectionContent.style.background = "rgba(150,150,150,0.7)"
|
1534 |
+
} else {
|
1535 |
+
gameSelectionContent.classList.add("gameSelectionContentToHover")
|
1536 |
+
totalGames.add(gameId)
|
1537 |
+
}
|
1538 |
+
|
1539 |
+
gameSelectionContent.appendChild(createElem("div", `${numVoices} ${(numVoices>1||numVoices==0)?window.i18n.VOICE_PLURAL:window.i18n.VOICE}`))
|
1540 |
+
gameSelectionContent.appendChild(createElem("div", gameName))
|
1541 |
+
|
1542 |
+
gameSelection.appendChild(gameSelectionContent)
|
1543 |
+
|
1544 |
+
window.gameAssets[gameId] = metadata
|
1545 |
+
gameSelectionContent.addEventListener("click", () => {
|
1546 |
+
voiceSearchInput.focus()
|
1547 |
+
searchGameInput.value = ""
|
1548 |
+
changeGame(metadata)
|
1549 |
+
closeModal(gameSelectionContainer)
|
1550 |
+
Array.from(gameSelectionListContainer.children).forEach(elem => elem.style.display="flex")
|
1551 |
+
})
|
1552 |
+
|
1553 |
+
itemsToSort.push([numVoices, gameSelection])
|
1554 |
+
|
1555 |
+
const modelsDir = window.userSettings[`modelspath_${gameId}`]
|
1556 |
+
if (!window.watchedModelsDirs.includes(modelsDir)) {
|
1557 |
+
window.watchedModelsDirs.push(modelsDir)
|
1558 |
+
|
1559 |
+
try {
|
1560 |
+
fs.watch(modelsDir, {recursive: false, persistent: true}, (eventType, filename) => {
|
1561 |
+
if (window.userSettings.autoReloadVoices) {
|
1562 |
+
if (doLoadAllModels) {
|
1563 |
+
loadAllModels().then(() => changeGame(metadata))
|
1564 |
+
}
|
1565 |
+
}
|
1566 |
+
})
|
1567 |
+
} catch (e) {
|
1568 |
+
// console.log(e)
|
1569 |
+
}
|
1570 |
+
}
|
1571 |
+
})
|
1572 |
+
|
1573 |
+
|
1574 |
+
itemsToSort.sort((a,b) => a[0]<b[0]?1:-1).forEach(([numVoices, elem]) => {
|
1575 |
+
gameSelectionListContainer.appendChild(elem)
|
1576 |
+
})
|
1577 |
+
|
1578 |
+
searchGameInput.addEventListener("keyup", (event) => {
|
1579 |
+
|
1580 |
+
if (event.key=="Enter") {
|
1581 |
+
const voiceElems = Array.from(gameSelectionListContainer.children).filter(elem => elem.style.display=="flex")
|
1582 |
+
if (voiceElems.length==1) {
|
1583 |
+
voiceElems[0].children[0].click()
|
1584 |
+
searchGameInput.value = ""
|
1585 |
+
}
|
1586 |
+
}
|
1587 |
+
|
1588 |
+
const voiceElems = Array.from(gameSelectionListContainer.children)
|
1589 |
+
if (searchGameInput.value.length) {
|
1590 |
+
voiceElems.forEach(elem => {
|
1591 |
+
if (elem.children[0].children[1].innerHTML.toLowerCase().includes(searchGameInput.value)) {
|
1592 |
+
elem.style.display="flex"
|
1593 |
+
} else {
|
1594 |
+
elem.style.display="none"
|
1595 |
+
}
|
1596 |
+
})
|
1597 |
+
|
1598 |
+
} else {
|
1599 |
+
voiceElems.forEach(elem => elem.style.display="block")
|
1600 |
+
}
|
1601 |
+
})
|
1602 |
+
|
1603 |
+
searchGameInput.placeholder = window.i18n.SEARCH_N_GAMES_WITH_N2_VOICES.replace("_1", Array.from(totalGames).length).replace("_2", totalVoices)
|
1604 |
+
|
1605 |
+
if (doLoadAllModels) {
|
1606 |
+
loadAllModels().then(() => {
|
1607 |
+
// Load the last selected game
|
1608 |
+
const lastGame = localStorage.getItem("lastGame")
|
1609 |
+
|
1610 |
+
if (lastGame) {
|
1611 |
+
changeGame(JSON.parse(lastGame))
|
1612 |
+
}
|
1613 |
+
})
|
1614 |
+
}
|
1615 |
+
}
|
1616 |
+
window.updateGameList()
|
1617 |
+
|
1618 |
+
|
1619 |
+
// Embeddings
|
1620 |
+
// ==========
|
1621 |
+
window.setupModal(embeddingsIcon, embeddingsContainer, () => {
|
1622 |
+
setTimeout(() => {
|
1623 |
+
if (window.embeddingsState.isReady) {
|
1624 |
+
window.embeddings_updateSize()
|
1625 |
+
}
|
1626 |
+
}, 100)
|
1627 |
+
window.embeddings_updateSize()
|
1628 |
+
window.embeddingsState.isOpen = true
|
1629 |
+
if (!window.embeddingsState.ready) {
|
1630 |
+
setTimeout(() => {
|
1631 |
+
window.embeddingsState.ready = true
|
1632 |
+
window.initEmbeddingsScene()
|
1633 |
+
setTimeout(() => {
|
1634 |
+
window.computeEmbsAndDimReduction(true)
|
1635 |
+
}, 300)
|
1636 |
+
}, 100)
|
1637 |
+
}
|
1638 |
+
}, () => {
|
1639 |
+
window.embeddingsState.isOpen = false
|
1640 |
+
})
|
1641 |
+
|
1642 |
+
// Arpabet
|
1643 |
+
// =======
|
1644 |
+
window.setupModal(arpabetIcon, arpabetContainer, () => setTimeout(()=> !window.arpabetMenuState.hasInitialised && window.refreshDictionariesList(), 100))
|
1645 |
+
|
1646 |
+
|
1647 |
+
// Plugins
|
1648 |
+
// =======
|
1649 |
+
window.setupModal(pluginsIcon, pluginsContainer)
|
1650 |
+
|
1651 |
+
|
1652 |
+
window.setupModal(style_emb_manage_btn, styleEmbeddingsContainer, window.styleEmbsModalOpenCallback)
|
1653 |
+
|
1654 |
+
|
1655 |
+
// Other
|
1656 |
+
// =====
|
1657 |
+
window.setupModal(reset_what_open_btn, resetContainer)
|
1658 |
+
window.setupModal(i18n_batch_metadata_open_btn, batchMetadataCSVContainer)
|
1659 |
+
|
1660 |
+
voiceSearchInput.addEventListener("keyup", () => {
|
1661 |
+
|
1662 |
+
if (event.key=="Enter") {
|
1663 |
+
const voiceElems = Array.from(voiceTypeContainer.children).filter(elem => elem.style.display=="block")
|
1664 |
+
if (voiceElems.length==1) {
|
1665 |
+
voiceElems[0].click()
|
1666 |
+
generateVoiceButton.click()
|
1667 |
+
voiceSearchInput.value = ""
|
1668 |
+
}
|
1669 |
+
}
|
1670 |
+
|
1671 |
+
const voiceElems = Array.from(voiceTypeContainer.children)
|
1672 |
+
|
1673 |
+
if (voiceSearchInput.value.length) {
|
1674 |
+
voiceElems.forEach(elem => {
|
1675 |
+
if (elem.innerHTML.toLowerCase().includes(voiceSearchInput.value)) {
|
1676 |
+
elem.style.display="block"
|
1677 |
+
} else {
|
1678 |
+
elem.style.display="none"
|
1679 |
+
}
|
1680 |
+
})
|
1681 |
+
|
1682 |
+
} else {
|
1683 |
+
voiceElems.forEach(elem => elem.style.display="block")
|
1684 |
+
}
|
1685 |
+
})
|
1686 |
+
|
1687 |
+
// Splash/EULA
|
1688 |
+
splashNextButton1.addEventListener("click", () => {
|
1689 |
+
splash_screen1.style.display = "none"
|
1690 |
+
splash_screen2.style.display = "flex"
|
1691 |
+
})
|
1692 |
+
EULA_closeButon.addEventListener("click", () => {
|
1693 |
+
if (EULA_accept_ckbx.checked) {
|
1694 |
+
closeModal(EULAContainer)
|
1695 |
+
window.userSettings.EULA_accepted_2023 = true
|
1696 |
+
saveUserSettings()
|
1697 |
+
|
1698 |
+
if (!window.totd_state.startupChecked) {
|
1699 |
+
window.showTipIfEnabledAndNewDay().then(() => {
|
1700 |
+
if (!serverIsUp) {
|
1701 |
+
spinnerModal(serverStartingMessage)
|
1702 |
+
}
|
1703 |
+
})
|
1704 |
+
}
|
1705 |
+
|
1706 |
+
if (!serverIsUp && totdContainer.style.display=="none") {
|
1707 |
+
window.spinnerModal(serverStartingMessage)
|
1708 |
+
}
|
1709 |
+
}
|
1710 |
+
})
|
1711 |
+
if (!Object.keys(window.userSettings).includes("EULA_accepted_2023") || !window.userSettings.EULA_accepted_2023) {
|
1712 |
+
EULAContainer.style.opacity = 0
|
1713 |
+
EULAContainer.style.display = "flex"
|
1714 |
+
requestAnimationFrame(() => requestAnimationFrame(() => EULAContainer.style.opacity = 1))
|
1715 |
+
requestAnimationFrame(() => requestAnimationFrame(() => chromeBar.style.opacity = 1))
|
1716 |
+
} else {
|
1717 |
+
window.showTipIfEnabledAndNewDay().then(() => {
|
1718 |
+
// If not, or the user closed the window quickly, show the server is starting message if still booting up
|
1719 |
+
if (!window.serverIsUp) {
|
1720 |
+
spinnerModal(serverStartingMessage)
|
1721 |
+
}
|
1722 |
+
})
|
1723 |
+
}
|
1724 |
+
|
1725 |
+
|
1726 |
+
// Links
|
1727 |
+
document.querySelectorAll('a[href^="http"]').forEach(a => a.addEventListener("click", e => {
|
1728 |
+
event.preventDefault()
|
1729 |
+
shell.openExternal(a.href)
|
1730 |
+
}))
|
javascript/settingsMenu.js
ADDED
@@ -0,0 +1,960 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
const er = require('@electron/remote')
|
4 |
+
const saveUserSettings = () => localStorage.setItem("userSettings", JSON.stringify(window.userSettings))
|
5 |
+
// const saveUserSettings = () => {}
|
6 |
+
|
7 |
+
const deleteFolderRecursive = function (directoryPath) {
|
8 |
+
if (fs.existsSync(directoryPath)) {
|
9 |
+
fs.readdirSync(directoryPath).forEach((file, index) => {
|
10 |
+
const curPath = `${directoryPath}/${file}`;
|
11 |
+
if (fs.lstatSync(curPath).isDirectory()) {
|
12 |
+
// recurse
|
13 |
+
deleteFolderRecursive(curPath);
|
14 |
+
} else {
|
15 |
+
// delete file
|
16 |
+
fs.unlinkSync(curPath);
|
17 |
+
}
|
18 |
+
});
|
19 |
+
fs.rmdirSync(directoryPath);
|
20 |
+
}
|
21 |
+
};
|
22 |
+
|
23 |
+
// Load user settings
|
24 |
+
window.userSettings = localStorage.getItem("userSettings") ||
|
25 |
+
// window.userSettings =
|
26 |
+
{
|
27 |
+
useGPU: false,
|
28 |
+
customWindowSize:`${window.innerHeight},${window.innerWidth}`,
|
29 |
+
base_speaker: "default",
|
30 |
+
autoplay: true,
|
31 |
+
autoPlayGen: false,
|
32 |
+
audio: {
|
33 |
+
format: "wav"
|
34 |
+
},
|
35 |
+
plugins: {
|
36 |
+
|
37 |
+
}
|
38 |
+
}
|
39 |
+
if ((typeof window.userSettings)=="string") {
|
40 |
+
window.userSettings = JSON.parse(window.userSettings)
|
41 |
+
}
|
42 |
+
if (!Object.keys(window.userSettings).includes("installation")) { // For backwards compatibility
|
43 |
+
window.userSettings.installation = "cpu"
|
44 |
+
}
|
45 |
+
if (!Object.keys(window.userSettings).includes("audio")) { // For backwards compatibility
|
46 |
+
window.userSettings.audio = {format: "wav", hz: 44100, padStart: 0, padEnd: 0, pitchMult: 1, tempo: 1, nr: 5, nf: -20, deessing: 0.1}
|
47 |
+
}
|
48 |
+
if (!Object.keys(window.userSettings).includes("sliderTooltip")) { // For backwards compatibility
|
49 |
+
window.userSettings.sliderTooltip = true
|
50 |
+
}
|
51 |
+
// if (!Object.keys(window.userSettings).includes("darkPrompt")) { // For backwards compatibility
|
52 |
+
// window.userSettings.darkPrompt = false
|
53 |
+
// }
|
54 |
+
if (!Object.keys(window.userSettings).includes("showDiscordStatus")) { // For backwards compatibility
|
55 |
+
window.userSettings.showDiscordStatus = true
|
56 |
+
}
|
57 |
+
if (!Object.keys(window.userSettings).includes("prompt_fontSize")) { // For backwards compatibility
|
58 |
+
window.userSettings.prompt_fontSize = 15
|
59 |
+
}
|
60 |
+
if (!Object.keys(window.userSettings).includes("bg_gradient_opacity")) { // For backwards compatibility
|
61 |
+
window.userSettings.bg_gradient_opacity = 13
|
62 |
+
}
|
63 |
+
if (!Object.keys(window.userSettings).includes("autoReloadVoices")) { // For backwards compatibility
|
64 |
+
window.userSettings.autoReloadVoices = false
|
65 |
+
}
|
66 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("hz")) { // For backwards compatibility
|
67 |
+
window.userSettings.audio.hz = 44100
|
68 |
+
}
|
69 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("padStart")) { // For backwards compatibility
|
70 |
+
window.userSettings.audio.padStart = 0
|
71 |
+
}
|
72 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("padEnd")) { // For backwards compatibility
|
73 |
+
window.userSettings.audio.padEnd = 0
|
74 |
+
}
|
75 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("pitchMult")) { // For backwards compatibility
|
76 |
+
window.userSettings.audio.pitchMult = 1
|
77 |
+
}
|
78 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("tempo")) { // For backwards compatibility
|
79 |
+
window.userSettings.audio.tempo = 1
|
80 |
+
}
|
81 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("deessing")) { // For backwards compatibility
|
82 |
+
window.userSettings.audio.deessing = 0.1
|
83 |
+
}
|
84 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("nr")) { // For backwards compatibility
|
85 |
+
window.userSettings.audio.nr = 5
|
86 |
+
}
|
87 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("nf")) { // For backwards compatibility
|
88 |
+
window.userSettings.audio.nf = -20
|
89 |
+
}
|
90 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("ffmpeg")) { // For backwards compatibility
|
91 |
+
window.userSettings.audio.ffmpeg = true
|
92 |
+
}
|
93 |
+
// if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("ffmpeg_preview")) { // For backwards compatibility
|
94 |
+
// window.userSettings.audio.ffmpeg_preview = true
|
95 |
+
// }
|
96 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("useNR")) { // For backwards compatibility
|
97 |
+
window.userSettings.audio.useNR = true
|
98 |
+
}
|
99 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("bitdepth")) { // For backwards compatibility
|
100 |
+
window.userSettings.audio.bitdepth = "pcm_s32le"
|
101 |
+
}
|
102 |
+
if (!Object.keys(window.userSettings).includes("showEditorFFMPEGAmplitude")) { // For backwards compatibility
|
103 |
+
window.userSettings.showEditorFFMPEGAmplitude = false
|
104 |
+
}
|
105 |
+
if (!Object.keys(window.userSettings).includes("vocoder")) { // For backwards compatibility
|
106 |
+
window.userSettings.vocoder = "256_waveglow"
|
107 |
+
}
|
108 |
+
if (!Object.keys(window.userSettings).includes("audio") || !Object.keys(window.userSettings.audio).includes("amplitude")) { // For backwards compatibility
|
109 |
+
window.userSettings.audio.amplitude = 1
|
110 |
+
}
|
111 |
+
if (!Object.keys(window.userSettings).includes("max_filename_chars")) { // For backwards compatibility
|
112 |
+
window.userSettings.max_filename_chars = 70
|
113 |
+
}
|
114 |
+
if (!Object.keys(window.userSettings).includes("clear_text_after_synth")) { // For backwards compatibility
|
115 |
+
window.userSettings.clear_text_after_synth = false
|
116 |
+
}
|
117 |
+
if (!Object.keys(window.userSettings).includes("do_model_version_highlight")) { // For backwards compatibility
|
118 |
+
window.userSettings.do_model_version_highlight = false
|
119 |
+
}
|
120 |
+
if (!Object.keys(window.userSettings).includes("model_version_highlight")) { // For backwards compatibility
|
121 |
+
window.userSettings.model_version_highlight = 3.0
|
122 |
+
}
|
123 |
+
if (!Object.keys(window.userSettings).includes("do_pitchrangeoverride")) { // For backwards compatibility
|
124 |
+
window.userSettings.do_pitchrangeoverride = false
|
125 |
+
}
|
126 |
+
if (!Object.keys(window.userSettings).includes("pitchrangeoverride")) { // For backwards compatibility
|
127 |
+
window.userSettings.pitchrangeoverride = 6.0
|
128 |
+
}
|
129 |
+
|
130 |
+
if (!Object.keys(window.userSettings).includes("keepPaceOnNew")) { // For backwards compatibility
|
131 |
+
window.userSettings.keepPaceOnNew = true
|
132 |
+
}
|
133 |
+
if (!Object.keys(window.userSettings).includes("batchOutFolder")) { // For backwards compatibility
|
134 |
+
window.userSettings.batchOutFolder = `${__dirname.replace(/\\/g,"/")}/batch`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
135 |
+
}
|
136 |
+
if (!Object.keys(window.userSettings).includes("batch_clearDirFirst")) { // For backwards compatibility
|
137 |
+
window.userSettings.batch_clearDirFirst = false
|
138 |
+
}
|
139 |
+
// if (!Object.keys(window.userSettings).includes("batch_fastMode")) { // For backwards compatibility
|
140 |
+
// window.userSettings.batch_fastMode = false
|
141 |
+
// }
|
142 |
+
// if (!Object.keys(window.userSettings).includes("batch_fastModeMaxParallelizations")) { // For backwards compatibility
|
143 |
+
// window.userSettings.batch_fastModeMaxParallelizations = 1000
|
144 |
+
// }
|
145 |
+
if (!Object.keys(window.userSettings).includes("batch_json")) { // For backwards compatibility
|
146 |
+
window.userSettings.batch_json = false
|
147 |
+
}
|
148 |
+
if (!Object.keys(window.userSettings).includes("batch_useMP")) { // For backwards compatibility
|
149 |
+
window.userSettings.batch_useMP = false
|
150 |
+
}
|
151 |
+
if (!Object.keys(window.userSettings).includes("batch_MPCount")) { // For backwards compatibility
|
152 |
+
window.userSettings.batch_MPCount = 0
|
153 |
+
}
|
154 |
+
if (!Object.keys(window.userSettings).includes("batch_skipExisting")) { // For backwards compatibility
|
155 |
+
window.userSettings.batch_skipExisting = true
|
156 |
+
}
|
157 |
+
if (!Object.keys(window.userSettings).includes("batch_doGrouping")) { // For backwards compatibility
|
158 |
+
window.userSettings.batch_doGrouping = true
|
159 |
+
}
|
160 |
+
if (!Object.keys(window.userSettings).includes("batch_doVocoderGrouping")) { // For backwards compatibility
|
161 |
+
window.userSettings.batch_doVocoderGrouping = false
|
162 |
+
}
|
163 |
+
if (!Object.keys(window.userSettings).includes("batch_delimiter")) { // For backwards compatibility
|
164 |
+
window.userSettings.batch_delimiter = ","
|
165 |
+
}
|
166 |
+
if (!Object.keys(window.userSettings).includes("batch_paginationSize")) { // For backwards compatibility
|
167 |
+
window.userSettings.batch_paginationSize = 100
|
168 |
+
}
|
169 |
+
if (!Object.keys(window.userSettings).includes("defaultToHiFi")) { // For backwards compatibility
|
170 |
+
window.userSettings.defaultToHiFi = true
|
171 |
+
}
|
172 |
+
if (!Object.keys(window.userSettings).includes("batch_batchSize")) { // For backwards compatibility
|
173 |
+
window.userSettings.batch_batchSize = 1
|
174 |
+
}
|
175 |
+
if (!Object.keys(window.userSettings).includes("autoPlayGen")) { // For backwards compatibility
|
176 |
+
window.userSettings.autoPlayGen = true
|
177 |
+
}
|
178 |
+
if (!Object.keys(window.userSettings).includes("outputJSON")) { // For backwards compatibility
|
179 |
+
window.userSettings.outputJSON = true
|
180 |
+
}
|
181 |
+
if (!Object.keys(window.userSettings).includes("keepEditorOnVoiceChange")) { // For backwards compatibility
|
182 |
+
window.userSettings.keepEditorOnVoiceChange = false
|
183 |
+
}
|
184 |
+
if (!Object.keys(window.userSettings).includes("filenameNumericalSeq")) { // For backwards compatibility
|
185 |
+
window.userSettings.filenameNumericalSeq = false
|
186 |
+
}
|
187 |
+
if (!Object.keys(window.userSettings).includes("spacePadding")) { // For backwards compatibility
|
188 |
+
window.userSettings.spacePadding = true
|
189 |
+
}
|
190 |
+
if (!Object.keys(window.userSettings).includes("useErrorSound")) { // For backwards compatibility
|
191 |
+
window.userSettings.useErrorSound = false
|
192 |
+
}
|
193 |
+
if (!Object.keys(window.userSettings).includes("showTipOfTheDay")) { // For backwards compatibility
|
194 |
+
window.userSettings.showTipOfTheDay = true
|
195 |
+
}
|
196 |
+
if (!Object.keys(window.userSettings).includes("showUnseenTipOfTheDay")) { // For backwards compatibility
|
197 |
+
window.userSettings.showUnseenTipOfTheDay = false
|
198 |
+
}
|
199 |
+
if (!Object.keys(window.userSettings).includes("playChangedAudio")) { // For backwards compatibility
|
200 |
+
window.userSettings.playChangedAudio = false
|
201 |
+
}
|
202 |
+
|
203 |
+
if (!Object.keys(window.userSettings).includes("errorSoundFile")) { // For backwards compatibility
|
204 |
+
window.userSettings.errorSoundFile = `${__dirname.replace(/\\/g,"/")}/lib/xp_error.mp3`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
205 |
+
}
|
206 |
+
if (!Object.keys(window.userSettings).includes("plugins")) { // For backwards compatibility
|
207 |
+
window.userSettings.plugins = {}
|
208 |
+
}
|
209 |
+
if (!Object.keys(window.userSettings.plugins).includes("loadOrder")) { // For backwards compatibility
|
210 |
+
window.userSettings.plugins.loadOrder = ""
|
211 |
+
}
|
212 |
+
if (!Object.keys(window.userSettings).includes("externalAudioEditor")) { // For backwards compatibility
|
213 |
+
window.userSettings.externalAudioEditor = ""
|
214 |
+
}
|
215 |
+
if (!Object.keys(window.userSettings).includes("s2s_autogenerate")) { // For backwards compatibility
|
216 |
+
window.userSettings.s2s_autogenerate = true
|
217 |
+
}
|
218 |
+
if (!Object.keys(window.userSettings).includes("s2s_prePitchShift")) { // For backwards compatibility
|
219 |
+
window.userSettings.s2s_prePitchShift = false
|
220 |
+
}
|
221 |
+
if (!Object.keys(window.userSettings).includes("s2s_removeNoise")) { // For backwards compatibility
|
222 |
+
window.userSettings.s2s_removeNoise = false
|
223 |
+
}
|
224 |
+
if (!Object.keys(window.userSettings).includes("s2s_noiseRemStrength")) { // For backwards compatibility
|
225 |
+
window.userSettings.s2s_noiseRemStrength = 0.25
|
226 |
+
}
|
227 |
+
if (!Object.keys(window.userSettings).includes("vc_strength")) { // For backwards compatibility
|
228 |
+
window.userSettings.vc_strength = 2
|
229 |
+
}
|
230 |
+
if (!Object.keys(window.userSettings).includes("waveglow_path")) { // For backwards compatibility
|
231 |
+
window.userSettings.waveglow_path = `${__dirname.replace(/\\/g,"/")}/models/waveglow_256channels_universal_v4.pt`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
232 |
+
}
|
233 |
+
if (!Object.keys(window.userSettings).includes("bigwaveglow_path")) { // For backwards compatibility
|
234 |
+
window.userSettings.bigwaveglow_path = `${__dirname.replace(/\\/g,"/")}/models/nvidia_waveglowpyt_fp32_20190427.pt`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
235 |
+
}
|
236 |
+
if (!Object.keys(window.userSettings).includes("arpabet_paginationSize")) { // For backwards compatibility
|
237 |
+
window.userSettings.arpabet_paginationSize = 200
|
238 |
+
}
|
239 |
+
if (!Object.keys(window.userSettings).includes("output_files_pagination_size")) { // For backwards compatibility
|
240 |
+
window.userSettings.output_files_pagination_size = 25
|
241 |
+
}
|
242 |
+
|
243 |
+
const updateUIWithSettings = () => {
|
244 |
+
useGPUCbx.checked = window.userSettings.useGPU
|
245 |
+
autoplay_ckbx.checked = window.userSettings.autoplay
|
246 |
+
// setting_slidersTooltip.checked = window.userSettings.sliderTooltip
|
247 |
+
setting_defaultToHiFi.checked = window.userSettings.defaultToHiFi
|
248 |
+
setting_keepPaceOnNew.checked = window.userSettings.keepPaceOnNew
|
249 |
+
setting_autoplaygenCbx.checked = window.userSettings.autoPlayGen
|
250 |
+
// setting_darkprompt.checked = window.userSettings.darkPrompt
|
251 |
+
setting_show_discord_status.checked = window.userSettings.showDiscordStatus
|
252 |
+
setting_prompt_fontSize.value = window.userSettings.prompt_fontSize
|
253 |
+
setting_bg_gradient_opacity.value = window.userSettings.bg_gradient_opacity
|
254 |
+
setting_areload_voices.checked = window.userSettings.autoReloadVoices
|
255 |
+
setting_output_json.checked = window.userSettings.outputJSON
|
256 |
+
setting_output_num_seq.checked = window.userSettings.filenameNumericalSeq
|
257 |
+
setting_space_padding.checked = window.userSettings.spacePadding
|
258 |
+
setting_keepEditorOnVoiceChange.checked = window.userSettings.keepEditorOnVoiceChange
|
259 |
+
setting_use_error_sound.checked = window.userSettings.useErrorSound
|
260 |
+
setting_error_sound_file.value = window.userSettings.errorSoundFile
|
261 |
+
|
262 |
+
setting_showTipOfTheDay.checked = window.userSettings.showTipOfTheDay
|
263 |
+
totdShowTips.checked = window.userSettings.showTipOfTheDay
|
264 |
+
setting_showUnseenTipOfTheDay.checked = window.userSettings.showUnseenTipOfTheDay
|
265 |
+
totdShowOnlyUnseenTips.checked = window.userSettings.showUnseenTipOfTheDay
|
266 |
+
|
267 |
+
setting_playChangedAudio.checked = window.userSettings.playChangedAudio
|
268 |
+
|
269 |
+
setting_external_audio_editor.value = window.userSettings.externalAudioEditor
|
270 |
+
setting_audio_ffmpeg.checked = window.userSettings.audio.ffmpeg
|
271 |
+
// setting_audio_ffmpeg_preview.checked = window.userSettings.audio.ffmpeg && window.userSettings.audio.ffmpeg_preview
|
272 |
+
setting_audio_useNR.checked = window.userSettings.audio.ffmpeg && window.userSettings.audio.useNR
|
273 |
+
setting_audio_format.value = window.userSettings.audio.format
|
274 |
+
setting_audio_hz.value = window.userSettings.audio.hz
|
275 |
+
setting_audio_pad_start.value = window.userSettings.audio.padStart
|
276 |
+
setting_audio_pad_end.value = window.userSettings.audio.padEnd
|
277 |
+
setting_audio_pitchMult.value = window.userSettings.audio.pitchMult
|
278 |
+
setting_audio_tempo.value = window.userSettings.audio.tempo
|
279 |
+
setting_audio_deessing.value = window.userSettings.audio.deessing
|
280 |
+
setting_audio_nr.value = window.userSettings.audio.nr
|
281 |
+
setting_audio_nf.value = window.userSettings.audio.nf
|
282 |
+
setting_audio_bitdepth.value = window.userSettings.audio.bitdepth
|
283 |
+
setting_audio_amplitude.value = window.userSettings.audio.amplitude
|
284 |
+
setting_editor_audio_amplitude.value = window.userSettings.audio.amplitude
|
285 |
+
setting_show_editor_ffmpegamplitude.checked = window.userSettings.showEditorFFMPEGAmplitude
|
286 |
+
editor_amplitude_options.style.display = window.userSettings.showEditorFFMPEGAmplitude ? "flex" : "none"
|
287 |
+
|
288 |
+
// setting_s2s_autogenerate.checked = window.userSettings.s2s_autogenerate
|
289 |
+
// setting_s2s_prePitchShift.checked = window.userSettings.s2s_prePitchShift
|
290 |
+
// setting_s2s_removeNoise.checked = window.userSettings.s2s_removeNoise
|
291 |
+
// setting_s2s_noiseRemStrength.value = window.userSettings.s2s_noiseRemStrength
|
292 |
+
setting_s2s_vcstrength.value = window.userSettings.vc_strength
|
293 |
+
|
294 |
+
setting_batch_json.checked = window.userSettings.batch_json
|
295 |
+
// setting_batch_fastmode.checked = window.userSettings.batch_fastMode // No more fast modde. TODO, remove completely
|
296 |
+
// setting_batch_maxFastModeParallelizations.value = window.userSettings.batch_fastModeMaxParallelizations // No more fast modde. TODO, remove completely
|
297 |
+
setting_batch_multip.checked = window.userSettings.batch_useMP
|
298 |
+
setting_batch_multip_count.value = window.userSettings.batch_MPCount
|
299 |
+
setting_batch_delimiter.value = window.userSettings.batch_delimiter
|
300 |
+
setting_batch_paginationSize.value = window.userSettings.batch_paginationSize
|
301 |
+
setting_batch_doGrouping.checked = window.userSettings.batch_doGrouping
|
302 |
+
setting_batch_doVocoderGrouping.checked = window.userSettings.batch_doVocoderGrouping
|
303 |
+
|
304 |
+
batch_batchSizeInput.value = parseInt(window.userSettings.batch_batchSize)
|
305 |
+
batch_skipExisting.checked = window.userSettings.batch_skipExisting
|
306 |
+
batch_clearDirFirstCkbx.checked = window.userSettings.batch_clearDirFirst
|
307 |
+
|
308 |
+
setting_256waveglow_path.value = window.userSettings.waveglow_path
|
309 |
+
setting_bigwaveglow_path.value = window.userSettings.bigwaveglow_path
|
310 |
+
|
311 |
+
setting_arpabet_paginationSize.value = window.userSettings.arpabet_paginationSize
|
312 |
+
setting_output_files_pagination_size.value = window.userSettings.output_files_pagination_size
|
313 |
+
setting_max_filename_chars.value = window.userSettings.max_filename_chars
|
314 |
+
setting_clear_text_after_synth.checked = window.userSettings.clear_text_after_synth
|
315 |
+
setting_do_model_version_highlight.checked = window.userSettings.do_model_version_highlight
|
316 |
+
setting_model_version_highlight.value = window.userSettings.model_version_highlight
|
317 |
+
setting_pitchrangeoverrideEnabledCkbx.checked = window.userSettings.do_pitchrangeoverride
|
318 |
+
setting_pitchrangeoverride.value = window.userSettings.pitchrangeoverride
|
319 |
+
|
320 |
+
const [height, width] = window.userSettings.customWindowSize.split(",").map(v => parseInt(v))
|
321 |
+
ipcRenderer.send("resize", {height, width})
|
322 |
+
}
|
323 |
+
updateUIWithSettings()
|
324 |
+
saveUserSettings()
|
325 |
+
|
326 |
+
|
327 |
+
// Add the SVG code this way, because otherwise the index.html file will be spammed with way too much svg code
|
328 |
+
Array.from(window.document.querySelectorAll(".svgButton")).forEach(svgButton => {
|
329 |
+
svgButton.innerHTML = `<svg class="openFolderSVG" width="400" height="350" viewBox="0, 0, 400,350"><g id="svgg" ><path id="path0" d="M39.960 53.003 C 36.442 53.516,35.992 53.635,30.800 55.422 C 15.784 60.591,3.913 74.835,0.636 91.617 C -0.372 96.776,-0.146 305.978,0.872 310.000 C 5.229 327.228,16.605 339.940,32.351 345.172 C 40.175 347.773,32.175 347.630,163.000 347.498 L 281.800 347.378 285.600 346.495 C 304.672 342.065,321.061 332.312,330.218 319.944 C 330.648 319.362,332.162 317.472,333.581 315.744 C 335.001 314.015,336.299 312.420,336.467 312.200 C 336.634 311.980,337.543 310.879,338.486 309.753 C 340.489 307.360,342.127 305.341,343.800 303.201 C 344.460 302.356,346.890 299.375,349.200 296.575 C 351.510 293.776,353.940 290.806,354.600 289.975 C 355.260 289.144,356.561 287.505,357.492 286.332 C 358.422 285.160,359.952 283.267,360.892 282.126 C 362.517 280.153,371.130 269.561,375.632 264.000 C 376.789 262.570,380.427 258.097,383.715 254.059 C 393.790 241.689,396.099 237.993,398.474 230.445 C 403.970 212.972,394.149 194.684,376.212 188.991 C 369.142 186.747,368.803 186.724,344.733 186.779 C 330.095 186.812,322.380 186.691,322.216 186.425 C 322.078 186.203,321.971 178.951,321.977 170.310 C 321.995 146.255,321.401 141.613,317.200 133.000 C 314.009 126.457,307.690 118.680,303.142 115.694 C 302.560 115.313,301.300 114.438,300.342 113.752 C 295.986 110.631,288.986 107.881,282.402 106.704 C 280.540 106.371,262.906 106.176,220.400 106.019 L 161.000 105.800 160.763 98.800 C 159.961 75.055,143.463 56.235,120.600 52.984 C 115.148 52.208,45.292 52.225,39.960 53.003 M120.348 80.330 C 130.472 83.988,133.993 90.369,133.998 105.071 C 134.003 120.968,137.334 127.726,147.110 131.675 L 149.400 132.600 213.800 132.807 C 272.726 132.996,278.392 133.071,280.453 133.690 C 286.872 135.615,292.306 141.010,294.261 147.400 C 294.928 149.578,294.996 151.483,294.998 168.000 L 295.000 186.200 292.800 186.449 C 291.590 186.585,254.330 186.725,210.000 186.759 C 163.866 186.795,128.374 186.977,127.000 187.186 C 115.800 188.887,104.936 192.929,96.705 198.458 C 95.442 199.306,94.302 200.000,94.171 200.000 C 93.815 200.000,89.287 203.526,87.000 205.583 C 84.269 208.039,80.083 212.649,76.488 217.159 C 72.902 221.657,72.598 222.031,70.800 224.169 C 70.030 225.084,68.770 226.620,68.000 227.582 C 67.230 228.544,66.054 229.977,65.387 230.766 C 64.720 231.554,62.727 234.000,60.957 236.200 C 59.188 238.400,56.346 241.910,54.642 244.000 C 52.938 246.090,50.163 249.510,48.476 251.600 C 44.000 257.146,36.689 266.126,36.212 266.665 C 35.985 266.921,34.900 268.252,33.800 269.623 C 32.700 270.994,30.947 273.125,29.904 274.358 C 28.861 275.591,28.006 276.735,28.004 276.900 C 28.002 277.065,27.728 277.200,27.395 277.200 C 26.428 277.200,26.700 96.271,27.670 93.553 C 30.020 86.972,35.122 81.823,40.800 80.300 C 44.238 79.378,47.793 79.296,81.800 79.351 L 117.800 79.410 120.348 80.330 M369.400 214.800 C 374.239 217.220,374.273 222.468,369.489 228.785 C 367.767 231.059,364.761 234.844,364.394 235.200 C 364.281 235.310,362.373 237.650,360.154 240.400 C 357.936 243.150,354.248 247.707,351.960 250.526 C 347.732 255.736,346.053 257.821,343.202 261.400 C 341.505 263.530,340.849 264.336,334.600 271.965 C 332.400 274.651,330.204 277.390,329.720 278.053 C 329.236 278.716,328.246 279.945,327.520 280.785 C 326.794 281.624,325.300 283.429,324.200 284.794 C 323.100 286.160,321.726 287.845,321.147 288.538 C 320.568 289.232,318.858 291.345,317.347 293.233 C 308.372 304.449,306.512 306.609,303.703 309.081 C 299.300 312.956,290.855 317.633,286.000 318.886 C 277.958 320.960,287.753 320.819,159.845 320.699 C 33.557 320.581,42.330 320.726,38.536 318.694 C 34.021 316.276,35.345 310.414,42.386 301.647 C 44.044 299.583,45.940 297.210,46.600 296.374 C 47.260 295.538,48.340 294.169,49.000 293.332 C 49.660 292.495,51.550 290.171,53.200 288.167 C 54.850 286.164,57.100 283.395,58.200 282.015 C 59.300 280.635,60.920 278.632,61.800 277.564 C 62.680 276.496,64.210 274.617,65.200 273.389 C 66.190 272.162,67.188 270.942,67.418 270.678 C 67.649 270.415,71.591 265.520,76.179 259.800 C 80.767 254.080,84.634 249.310,84.773 249.200 C 84.913 249.090,87.117 246.390,89.673 243.200 C 92.228 240.010,95.621 235.780,97.213 233.800 C 106.328 222.459,116.884 215.713,128.200 213.998 C 129.300 213.832,183.570 213.719,248.800 213.748 L 367.400 213.800 369.400 214.800 " stroke="none" fill="#fbfbfb" fill-rule="evenodd"></path><path id="path1" fill-opacity="0" d="M0.000 46.800 C 0.000 72.540,0.072 93.600,0.159 93.600 C 0.246 93.600,0.516 92.460,0.759 91.066 C 3.484 75.417,16.060 60.496,30.800 55.422 C 35.953 53.648,36.338 53.550,40.317 52.981 C 46.066 52.159,114.817 52.161,120.600 52.984 C 143.463 56.235,159.961 75.055,160.763 98.800 L 161.000 105.800 220.400 106.019 C 262.906 106.176,280.540 106.371,282.402 106.704 C 288.986 107.881,295.986 110.631,300.342 113.752 C 301.300 114.438,302.560 115.313,303.142 115.694 C 307.690 118.680,314.009 126.457,317.200 133.000 C 321.401 141.613,321.995 146.255,321.977 170.310 C 321.971 178.951,322.078 186.203,322.216 186.425 C 322.380 186.691,330.095 186.812,344.733 186.779 C 368.803 186.724,369.142 186.747,376.212 188.991 C 381.954 190.814,388.211 194.832,391.662 198.914 C 395.916 203.945,397.373 206.765,399.354 213.800 C 399.842 215.533,399.922 201.399,399.958 107.900 L 400.000 0.000 200.000 0.000 L 0.000 0.000 0.000 46.800 M44.000 79.609 C 35.903 81.030,30.492 85.651,27.670 93.553 C 26.700 96.271,26.428 277.200,27.395 277.200 C 27.728 277.200,28.002 277.065,28.004 276.900 C 28.006 276.735,28.861 275.591,29.904 274.358 C 30.947 273.125,32.700 270.994,33.800 269.623 C 34.900 268.252,35.985 266.921,36.212 266.665 C 36.689 266.126,44.000 257.146,48.476 251.600 C 50.163 249.510,52.938 246.090,54.642 244.000 C 56.346 241.910,59.188 238.400,60.957 236.200 C 62.727 234.000,64.720 231.554,65.387 230.766 C 66.054 229.977,67.230 228.544,68.000 227.582 C 68.770 226.620,70.030 225.084,70.800 224.169 C 72.598 222.031,72.902 221.657,76.488 217.159 C 80.083 212.649,84.269 208.039,87.000 205.583 C 89.287 203.526,93.815 200.000,94.171 200.000 C 94.302 200.000,95.442 199.306,96.705 198.458 C 104.936 192.929,115.800 188.887,127.000 187.186 C 128.374 186.977,163.866 186.795,210.000 186.759 C 254.330 186.725,291.590 186.585,292.800 186.449 L 295.000 186.200 294.998 168.000 C 294.996 151.483,294.928 149.578,294.261 147.400 C 292.306 141.010,286.872 135.615,280.453 133.690 C 278.392 133.071,272.726 132.996,213.800 132.807 L 149.400 132.600 147.110 131.675 C 137.334 127.726,134.003 120.968,133.998 105.071 C 133.993 90.369,130.472 83.988,120.348 80.330 L 117.800 79.410 81.800 79.351 C 62.000 79.319,44.990 79.435,44.000 79.609 M128.200 213.998 C 116.884 215.713,106.328 222.459,97.213 233.800 C 95.621 235.780,92.228 240.010,89.673 243.200 C 87.117 246.390,84.913 249.090,84.773 249.200 C 84.634 249.310,80.767 254.080,76.179 259.800 C 71.591 265.520,67.649 270.415,67.418 270.678 C 67.188 270.942,66.190 272.162,65.200 273.389 C 64.210 274.617,62.680 276.496,61.800 277.564 C 60.920 278.632,59.300 280.635,58.200 282.015 C 57.100 283.395,54.850 286.164,53.200 288.167 C 51.550 290.171,49.660 292.495,49.000 293.332 C 48.340 294.169,47.260 295.538,46.600 296.374 C 45.940 297.210,44.044 299.583,42.386 301.647 C 35.345 310.414,34.021 316.276,38.536 318.694 C 42.330 320.726,33.557 320.581,159.845 320.699 C 287.753 320.819,277.958 320.960,286.000 318.886 C 290.855 317.633,299.300 312.956,303.703 309.081 C 306.512 306.609,308.372 304.449,317.347 293.233 C 318.858 291.345,320.568 289.232,321.147 288.538 C 321.726 287.845,323.100 286.160,324.200 284.794 C 325.300 283.429,326.794 281.624,327.520 280.785 C 328.246 279.945,329.236 278.716,329.720 278.053 C 330.204 277.390,332.400 274.651,334.600 271.965 C 340.849 264.336,341.505 263.530,343.202 261.400 C 346.053 257.821,347.732 255.736,351.960 250.526 C 354.248 247.707,357.936 243.150,360.154 240.400 C 362.373 237.650,364.281 235.310,364.394 235.200 C 364.761 234.844,367.767 231.059,369.489 228.785 C 374.273 222.468,374.239 217.220,369.400 214.800 L 367.400 213.800 248.800 213.748 C 183.570 213.719,129.300 213.832,128.200 213.998 M399.600 225.751 C 399.600 231.796,394.623 240.665,383.715 254.059 C 380.427 258.097,376.789 262.570,375.632 264.000 C 371.130 269.561,362.517 280.153,360.892 282.126 C 359.952 283.267,358.422 285.160,357.492 286.332 C 356.561 287.505,355.260 289.144,354.600 289.975 C 353.940 290.806,351.510 293.776,349.200 296.575 C 346.890 299.375,344.460 302.356,343.800 303.201 C 342.127 305.341,340.489 307.360,338.486 309.753 C 337.543 310.879,336.634 311.980,336.467 312.200 C 336.299 312.420,335.001 314.015,333.581 315.744 C 332.162 317.472,330.648 319.362,330.218 319.944 C 321.061 332.312,304.672 342.065,285.600 346.495 L 281.800 347.378 163.000 347.498 C 32.175 347.630,40.175 347.773,32.351 345.172 C 16.471 339.895,3.810 325.502,0.820 309.326 C 0.591 308.085,0.312 306.979,0.202 306.868 C 0.091 306.757,-0.000 327.667,-0.000 353.333 L 0.000 400.000 200.000 400.000 L 400.000 400.000 400.000 312.400 C 400.000 264.220,399.910 224.800,399.800 224.800 C 399.690 224.800,399.600 225.228,399.600 225.751 " stroke="none" fill="#050505" fill-rule="evenodd"></path></g></svg>`
|
330 |
+
})
|
331 |
+
|
332 |
+
|
333 |
+
// Installation sever handling
|
334 |
+
// =========================
|
335 |
+
settings_installation.innerHTML = window.userSettings.installation=="cpu" ? `CPU` : "CPU+GPU"
|
336 |
+
setting_change_installation.innerHTML = window.userSettings.installation=="cpu" ? `Change to CPU+GPU` : `Change to CPU`
|
337 |
+
|
338 |
+
|
339 |
+
setting_change_installation.addEventListener("click", () => {
|
340 |
+
spinnerModal("Changing installation sever...")
|
341 |
+
doFetch(`http://localhost:8008/stopServer`, {
|
342 |
+
method: "Post",
|
343 |
+
body: JSON.stringify({})
|
344 |
+
}).then(r=>r.text()).then(console.log) // The server stopping should mean this never runs
|
345 |
+
.catch(() => {
|
346 |
+
|
347 |
+
if (window.userSettings.installation=="cpu") {
|
348 |
+
window.userSettings.installation = "gpu"
|
349 |
+
useGPUCbx.disabled = false
|
350 |
+
settings_installation.innerHTML = `GPU`
|
351 |
+
setting_change_installation.innerHTML = `Change to CPU`
|
352 |
+
} else {
|
353 |
+
doFetch(`http://localhost:8008/setDevice`, {
|
354 |
+
method: "Post",
|
355 |
+
body: JSON.stringify({device: "cpu"})
|
356 |
+
})
|
357 |
+
|
358 |
+
window.userSettings.installation = "cpu"
|
359 |
+
useGPUCbx.checked = false
|
360 |
+
useGPUCbx.disabled = true
|
361 |
+
window.userSettings.useGPU = false
|
362 |
+
settings_installation.innerHTML = `CPU`
|
363 |
+
setting_change_installation.innerHTML = `Change to CPU+GPU`
|
364 |
+
}
|
365 |
+
saveUserSettings()
|
366 |
+
|
367 |
+
// Start the new server
|
368 |
+
if (window.PRODUCTION) {
|
369 |
+
window.appLogger.log(window.userSettings.installation)
|
370 |
+
window.pythonProcess = spawn(`${path}/cpython_${window.userSettings.installation}/server.exe`, {stdio: "ignore"})
|
371 |
+
} else {
|
372 |
+
window.pythonProcess = spawn("python", [`${path}/server.py`], {stdio: "ignore"})
|
373 |
+
}
|
374 |
+
|
375 |
+
window.currentModel = undefined
|
376 |
+
titleName.innerHTML = window.i18n.SELECT_VOICE_TYPE
|
377 |
+
keepSampleButton.style.display = "none"
|
378 |
+
wavesurferContainer.innerHTML = ""
|
379 |
+
generateVoiceButton.dataset.modelQuery = "null"
|
380 |
+
generateVoiceButton.dataset.modelIDLoaded = undefined
|
381 |
+
generateVoiceButton.innerHTML = window.i18n.LOAD_MODEL
|
382 |
+
generateVoiceButton.disabled = true
|
383 |
+
window.serverIsUp = false
|
384 |
+
window.doWeirdServerStartupCheck(`${window.i18n.LOADING}...<br>${window.i18n.MAY_TAKE_A_MINUTE}<br><br>${window.i18n.STARTING_PYTHON}...`)
|
385 |
+
})
|
386 |
+
})
|
387 |
+
|
388 |
+
// =========================
|
389 |
+
|
390 |
+
|
391 |
+
|
392 |
+
|
393 |
+
// Audio hardware
|
394 |
+
// ==============
|
395 |
+
navigator.mediaDevices.enumerateDevices().then(devices => {
|
396 |
+
devices = devices.filter(device => device.kind=="audiooutput" && device.deviceId!="communications")
|
397 |
+
|
398 |
+
// Base device
|
399 |
+
devices.forEach(device => {
|
400 |
+
const option = createElem("option", device.label)
|
401 |
+
option.value = device.deviceId
|
402 |
+
setting_base_speaker.appendChild(option)
|
403 |
+
})
|
404 |
+
setting_base_speaker.addEventListener("change", () => {
|
405 |
+
window.userSettings.base_speaker = setting_base_speaker.value
|
406 |
+
window.saveUserSettings()
|
407 |
+
|
408 |
+
window.document.querySelectorAll("audio").forEach(audioElem => {
|
409 |
+
audioElem.setSinkId(window.userSettings.base_speaker)
|
410 |
+
})
|
411 |
+
})
|
412 |
+
if (Object.keys(window.userSettings).includes("base_speaker")) {
|
413 |
+
setting_base_speaker.value = window.userSettings.base_speaker
|
414 |
+
} else {
|
415 |
+
window.userSettings.base_speaker = setting_base_speaker.value
|
416 |
+
window.saveUserSettings()
|
417 |
+
}
|
418 |
+
|
419 |
+
// Alternate device
|
420 |
+
devices.forEach(device => {
|
421 |
+
const option = createElem("option", device.label)
|
422 |
+
option.value = device.deviceId
|
423 |
+
setting_alt_speaker.appendChild(option)
|
424 |
+
})
|
425 |
+
setting_alt_speaker.addEventListener("change", () => {
|
426 |
+
window.userSettings.alt_speaker = setting_alt_speaker.value
|
427 |
+
window.saveUserSettings()
|
428 |
+
})
|
429 |
+
if (Object.keys(window.userSettings).includes("alt_speaker")) {
|
430 |
+
setting_alt_speaker.value = window.userSettings.alt_speaker
|
431 |
+
} else {
|
432 |
+
window.userSettings.alt_speaker = setting_alt_speaker.value
|
433 |
+
window.saveUserSettings()
|
434 |
+
}
|
435 |
+
})
|
436 |
+
|
437 |
+
|
438 |
+
|
439 |
+
// Settings Menu
|
440 |
+
// =============
|
441 |
+
useGPUCbx.addEventListener("change", () => {
|
442 |
+
spinnerModal(window.i18n.CHANGING_DEVICE)
|
443 |
+
doFetch(`http://localhost:8008/setDevice`, {
|
444 |
+
method: "Post",
|
445 |
+
body: JSON.stringify({device: useGPUCbx.checked ? "gpu" : "cpu"})
|
446 |
+
}).then(r=>r.text()).then(res => {
|
447 |
+
window.closeModal(undefined, settingsContainer)
|
448 |
+
window.userSettings.useGPU = useGPUCbx.checked
|
449 |
+
saveUserSettings()
|
450 |
+
}).catch(e => {
|
451 |
+
console.log(e)
|
452 |
+
if (e.code =="ENOENT") {
|
453 |
+
window.closeModal(undefined, settingsContainer).then(() => {
|
454 |
+
window.errorModal(window.i18n.THERE_WAS_A_PROBLEM)
|
455 |
+
})
|
456 |
+
}
|
457 |
+
})
|
458 |
+
})
|
459 |
+
|
460 |
+
|
461 |
+
const initMenuSetting = (elem, setting, type, callback=undefined, valFn=undefined) => {
|
462 |
+
|
463 |
+
valFn = valFn ? valFn : x=>x
|
464 |
+
|
465 |
+
if (type=="checkbox") {
|
466 |
+
elem.addEventListener("click", () => {
|
467 |
+
if (setting.includes(".")) {
|
468 |
+
window.userSettings[setting.split(".")[0]][setting.split(".")[1]] = valFn(elem.checked)
|
469 |
+
} else {
|
470 |
+
window.userSettings[setting] = valFn(elem.checked)
|
471 |
+
}
|
472 |
+
saveUserSettings()
|
473 |
+
if (callback) callback()
|
474 |
+
})
|
475 |
+
} else {
|
476 |
+
elem.addEventListener("change", () => {
|
477 |
+
if (setting.includes(".")) {
|
478 |
+
window.userSettings[setting.split(".")[0]][setting.split(".")[1]] = valFn(elem.value)
|
479 |
+
} else {
|
480 |
+
window.userSettings[setting] = valFn(elem.value)
|
481 |
+
}
|
482 |
+
saveUserSettings()
|
483 |
+
if (callback) callback()
|
484 |
+
})
|
485 |
+
}
|
486 |
+
}
|
487 |
+
window.initFilePickerButton = (button, input, setting, properties, filters=undefined, defaultPath=undefined, callback=undefined) => {
|
488 |
+
button.addEventListener("click", () => {
|
489 |
+
const defaultPath = input.value.replace(/\//g, "\\")
|
490 |
+
er.dialog.showOpenDialog({ properties, filters, defaultPath}).then(filePath => {
|
491 |
+
if (filePath) {
|
492 |
+
filePath = filePath.filePaths[0].replace(/\\/g, "/")
|
493 |
+
input.value = filePath.replace(/\\/g, "/")
|
494 |
+
setting = typeof(setting)=="function" ? setting() : setting
|
495 |
+
window.userSettings[setting] = filePath
|
496 |
+
saveUserSettings()
|
497 |
+
if (callback) {
|
498 |
+
callback()
|
499 |
+
}
|
500 |
+
}
|
501 |
+
})
|
502 |
+
})
|
503 |
+
}
|
504 |
+
|
505 |
+
const setPromptTheme = () => {
|
506 |
+
// if (window.userSettings.darkPrompt) {
|
507 |
+
// dialogueInput.style.backgroundColor = "rgba(25,25,25,0.9)"
|
508 |
+
// dialogueInput.style.color = "white"
|
509 |
+
// } else {
|
510 |
+
// dialogueInput.style.backgroundColor = "rgba(255,255,255,0.9)"
|
511 |
+
// dialogueInput.style.color = "black"
|
512 |
+
// }
|
513 |
+
}
|
514 |
+
const updateDiscord = () => {
|
515 |
+
let gameName = undefined
|
516 |
+
if (window.userSettings.showDiscordStatus && window.currentGame) {
|
517 |
+
gameName = window.currentGame.gameName
|
518 |
+
}
|
519 |
+
ipcRenderer.send('updateDiscord', {details: gameName})
|
520 |
+
}
|
521 |
+
const setPromptFontSize = () => {
|
522 |
+
dialogueInput.style.fontSize = `${window.userSettings.prompt_fontSize}pt`
|
523 |
+
}
|
524 |
+
const updateBackground = () => {
|
525 |
+
const background = `linear-gradient(0deg, rgba(128,128,128,${window.userSettings.bg_gradient_opacity}) 0px, rgba(0,0,0,0)), url("assets/${window.currentGame.assetFile}")`
|
526 |
+
// Fade the background image transition
|
527 |
+
rightBG1.style.background = background
|
528 |
+
rightBG2.style.opacity = 0
|
529 |
+
setTimeout(() => {
|
530 |
+
rightBG2.style.background = rightBG1.style.background
|
531 |
+
rightBG2.style.opacity = 1
|
532 |
+
}, 1000)
|
533 |
+
}
|
534 |
+
|
535 |
+
initMenuSetting(setting_autoplaygenCbx, "autoPlayGen", "checkbox")
|
536 |
+
// initMenuSetting(setting_slidersTooltip, "sliderTooltip", "checkbox")
|
537 |
+
initMenuSetting(setting_defaultToHiFi, "defaultToHiFi", "checkbox")
|
538 |
+
initMenuSetting(setting_keepPaceOnNew, "keepPaceOnNew", "checkbox")
|
539 |
+
initMenuSetting(setting_areload_voices, "autoReloadVoices", "checkbox")
|
540 |
+
initMenuSetting(setting_output_json, "outputJSON", "checkbox")
|
541 |
+
initMenuSetting(setting_keepEditorOnVoiceChange, "keepEditorOnVoiceChange", "checkbox")
|
542 |
+
initMenuSetting(setting_output_num_seq, "filenameNumericalSeq", "checkbox")
|
543 |
+
initMenuSetting(setting_space_padding, "spacePadding", "checkbox")
|
544 |
+
// initMenuSetting(setting_darkprompt, "darkPrompt", "checkbox", setPromptTheme)
|
545 |
+
initMenuSetting(setting_show_discord_status, "showDiscordStatus", "checkbox", updateDiscord)
|
546 |
+
initMenuSetting(setting_prompt_fontSize, "prompt_fontSize", "number", setPromptFontSize)
|
547 |
+
initMenuSetting(setting_bg_gradient_opacity, "bg_gradient_opacity", "number", updateBackground)
|
548 |
+
initMenuSetting(setting_use_error_sound, "useErrorSound", "checkbox")
|
549 |
+
initMenuSetting(setting_error_sound_file, "errorSoundFile", "text")
|
550 |
+
initFilePickerButton(setting_errorSoundFileBtn, setting_error_sound_file, "errorSoundFile", ["openFile"], [{name: "Audio", extensions: ["wav", "mp3", "ogg"]}])
|
551 |
+
|
552 |
+
initMenuSetting(setting_showTipOfTheDay, "showTipOfTheDay", "checkbox", () => {
|
553 |
+
totdShowTips.checked = setting_showTipOfTheDay.checked
|
554 |
+
})
|
555 |
+
initMenuSetting(totdShowTips, "showTipOfTheDay", "checkbox", () => {
|
556 |
+
setting_showTipOfTheDay.checked = totdShowTips.checked
|
557 |
+
})
|
558 |
+
initMenuSetting(setting_showUnseenTipOfTheDay, "showUnseenTipOfTheDay", "checkbox", () => {
|
559 |
+
totdShowOnlyUnseenTips.checked = setting_showUnseenTipOfTheDay.checked
|
560 |
+
})
|
561 |
+
initMenuSetting(totdShowOnlyUnseenTips, "showUnseenTipOfTheDay", "checkbox", () => {
|
562 |
+
setting_showUnseenTipOfTheDay.checked = totdShowOnlyUnseenTips.checked
|
563 |
+
})
|
564 |
+
|
565 |
+
initMenuSetting(setting_playChangedAudio, "playChangedAudio", "checkbox")
|
566 |
+
|
567 |
+
|
568 |
+
initMenuSetting(setting_external_audio_editor, "externalAudioEditor", "text")
|
569 |
+
initFilePickerButton(setting_externalEditorButton, setting_external_audio_editor, "externalAudioEditor", ["openFile"])
|
570 |
+
|
571 |
+
initMenuSetting(setting_audio_ffmpeg, "audio.ffmpeg", "checkbox", () => {
|
572 |
+
// setting_audio_ffmpeg_preview.checked = window.userSettings.audio.ffmpeg && window.userSettings.audio.ffmpeg_preview
|
573 |
+
// setting_audio_ffmpeg_preview.disabled = !window.userSettings.audio.ffmpeg
|
574 |
+
setting_audio_useNR.checked = window.userSettings.audio.ffmpeg && window.userSettings.audio.useNR
|
575 |
+
setting_audio_useNR.disabled = !window.userSettings.audio.ffmpeg
|
576 |
+
setting_audio_format.disabled = !window.userSettings.audio.ffmpeg
|
577 |
+
setting_audio_hz.disabled = !window.userSettings.audio.ffmpeg
|
578 |
+
setting_audio_pad_start.disabled = !window.userSettings.audio.ffmpeg
|
579 |
+
setting_audio_pad_end.disabled = !window.userSettings.audio.ffmpeg
|
580 |
+
setting_audio_pitchMult.disabled = !window.userSettings.audio.ffmpeg
|
581 |
+
setting_audio_tempo.disabled = !window.userSettings.audio.ffmpeg
|
582 |
+
setting_audio_deessing.disabled = !window.userSettings.audio.ffmpeg
|
583 |
+
setting_audio_nr.disabled = !window.userSettings.audio.ffmpeg
|
584 |
+
setting_audio_nf.disabled = !window.userSettings.audio.ffmpeg
|
585 |
+
setting_audio_bitdepth.disabled = !window.userSettings.audio.ffmpeg
|
586 |
+
setting_audio_amplitude.disabled = !window.userSettings.audio.ffmpeg
|
587 |
+
setting_editor_audio_amplitude.disabled = !window.userSettings.audio.ffmpeg
|
588 |
+
})
|
589 |
+
// initMenuSetting(setting_audio_ffmpeg_preview, "audio.ffmpeg_preview", "checkbox")
|
590 |
+
initMenuSetting(setting_audio_useNR, "audio.useNR", "checkbox")
|
591 |
+
initMenuSetting(setting_audio_format, "audio.format", "text")
|
592 |
+
initMenuSetting(setting_audio_hz, "audio.hz", "text", undefined, parseInt)
|
593 |
+
initMenuSetting(setting_audio_pad_start, "audio.padStart", "text", undefined, parseInt)
|
594 |
+
initMenuSetting(setting_audio_pad_end, "audio.padEnd", "text", undefined, parseInt)
|
595 |
+
initMenuSetting(setting_audio_pitchMult, "audio.pitchMult", "number", undefined, parseFloat)
|
596 |
+
initMenuSetting(setting_audio_tempo, "audio.tempo", "number", undefined, parseFloat)
|
597 |
+
initMenuSetting(setting_audio_deessing, "audio.deessing", "number", undefined, parseFloat)
|
598 |
+
initMenuSetting(setting_audio_nr, "audio.nr", "number", undefined, parseFloat)
|
599 |
+
initMenuSetting(setting_audio_nf, "audio.nf", "number", undefined, parseFloat)
|
600 |
+
initMenuSetting(setting_audio_bitdepth, "audio.bitdepth", "select")
|
601 |
+
initMenuSetting(setting_audio_amplitude, "audio.amplitude", "number", () => {
|
602 |
+
setting_editor_audio_amplitude.value = setting_audio_amplitude.value
|
603 |
+
}, parseFloat)
|
604 |
+
initMenuSetting(setting_editor_audio_amplitude, "audio.amplitude", "number", () => {
|
605 |
+
setting_audio_amplitude.value = setting_editor_audio_amplitude.value
|
606 |
+
}, parseFloat)
|
607 |
+
initMenuSetting(setting_show_editor_ffmpegamplitude, "showEditorFFMPEGAmplitude", "checkbox", () => {
|
608 |
+
editor_amplitude_options.style.display = window.userSettings.showEditorFFMPEGAmplitude ? "flex" : "none"
|
609 |
+
})
|
610 |
+
|
611 |
+
initMenuSetting(setting_batch_json, "batch_json", "checkbox")
|
612 |
+
// initMenuSetting(setting_batch_fastmode, "batch_fastMode", "checkbox") // No more fast modde. TODO, remove completely
|
613 |
+
// initMenuSetting(setting_batch_maxFastModeParallelizations, "batch_fastModeMaxParallelizations", "number") // No more fast modde. TODO, remove completely
|
614 |
+
initMenuSetting(setting_batch_multip, "batch_useMP", "checkbox")
|
615 |
+
initMenuSetting(setting_batch_multip_count, "batch_MPCount", "number", undefined, parseInt)
|
616 |
+
initMenuSetting(setting_batch_doGrouping, "batch_doGrouping", "checkbox")
|
617 |
+
initMenuSetting(setting_batch_doVocoderGrouping, "batch_doVocoderGrouping", "checkbox")
|
618 |
+
initMenuSetting(batch_clearDirFirstCkbx, "batch_clearDirFirst", "checkbox")
|
619 |
+
initMenuSetting(batch_skipExisting, "batch_skipExisting", "checkbox")
|
620 |
+
initMenuSetting(batch_batchSizeInput, "batch_batchSize", "text", undefined, parseInt)
|
621 |
+
initMenuSetting(setting_batch_delimiter, "batch_delimiter")
|
622 |
+
initMenuSetting(setting_batch_paginationSize, "batch_paginationSize", "number", undefined, parseInt)
|
623 |
+
|
624 |
+
// initMenuSetting(setting_s2s_autogenerate, "s2s_autogenerate", "checkbox")
|
625 |
+
// initMenuSetting(setting_s2s_prePitchShift, "s2s_prePitchShift", "checkbox")
|
626 |
+
// initMenuSetting(setting_s2s_removeNoise, "s2s_removeNoise", "checkbox")
|
627 |
+
// initMenuSetting(setting_s2s_noiseRemStrength, "s2s_noiseRemStrength", "number", undefined, parseFloat)
|
628 |
+
initMenuSetting(setting_s2s_vcstrength, "vc_strength", "number", undefined, parseFloat)
|
629 |
+
|
630 |
+
initMenuSetting(setting_256waveglow_path, "waveglow_path", "text")
|
631 |
+
initFilePickerButton(setting_waveglowPathButton, setting_256waveglow_path, "waveglow_path", ["openFile"], [{name: "Pytorch checkpoint", extensions: ["pt"]}])
|
632 |
+
initMenuSetting(setting_bigwaveglow_path, "bigwaveglow_path", "text")
|
633 |
+
initFilePickerButton(setting_bigwaveglowPathButton, setting_bigwaveglow_path, "bigwaveglow_path", ["openFile"], [{name: "Pytorch checkpoint", extensions: ["pt"]}])
|
634 |
+
|
635 |
+
initFilePickerButton(setting_modelsPathButton, setting_models_path_input, ()=>`modelspath_${window.currentGame.gameId}`, ["openDirectory"], undefined, undefined, ()=>window.updateGameList())
|
636 |
+
initFilePickerButton(setting_outPathButton, setting_out_path_input, ()=>`outpath_${window.currentGame.gameId}`, ["openDirectory"], undefined, undefined, ()=>{
|
637 |
+
if (window.currentModelButton) {
|
638 |
+
window.currentModelButton.click()
|
639 |
+
}
|
640 |
+
})
|
641 |
+
initMenuSetting(setting_arpabet_paginationSize, "arpabet_paginationSize", "number", undefined, parseInt)
|
642 |
+
initMenuSetting(setting_output_files_pagination_size, "output_files_pagination_size", "number", () => {
|
643 |
+
window.resetPagination()
|
644 |
+
window.refreshRecordsList()
|
645 |
+
}, parseInt)
|
646 |
+
initMenuSetting(setting_max_filename_chars, "max_filename_chars", "number", undefined, parseInt)
|
647 |
+
initMenuSetting(setting_clear_text_after_synth, "clear_text_after_synth", "checkbox")
|
648 |
+
initMenuSetting(setting_do_model_version_highlight, "do_model_version_highlight", "checkbox", ()=>window.changeGame(window.currentGame))
|
649 |
+
initMenuSetting(setting_model_version_highlight, "model_version_highlight", "number", ()=>window.changeGame(window.currentGame), parseFloat)
|
650 |
+
const updateSequenceEditorRange = () => {
|
651 |
+
if (window.sequenceEditor.isCreated && window.currentModel) {
|
652 |
+
const pitchRange = window.userSettings.pitchrangeoverride ? window.userSettings.pitchrangeoverride : window.sequenceEditor.pitchSliderRange
|
653 |
+
|
654 |
+
// Make sure to cap the existing values if upding the range to be smaller than the current values
|
655 |
+
if (window.userSettings.pitchrangeoverride) {
|
656 |
+
window.sequenceEditor.pitchNew.forEach((val,vi) => {
|
657 |
+
if (Math.abs(val)>window.userSettings.pitchrangeoverride) {
|
658 |
+
val = Math.max(-window.userSettings.pitchrangeoverride, Math.min(window.userSettings.pitchrangeoverride, val))
|
659 |
+
window.sequenceEditor.pitchNew[vi] = val
|
660 |
+
}
|
661 |
+
})
|
662 |
+
}
|
663 |
+
|
664 |
+
window.sequenceEditor.update(window.currentModel.modelType, pitchRange)
|
665 |
+
}
|
666 |
+
}
|
667 |
+
initMenuSetting(setting_pitchrangeoverrideEnabledCkbx, "do_pitchrangeoverride", "checkbox", updateSequenceEditorRange)
|
668 |
+
initMenuSetting(setting_pitchrangeoverride, "pitchrangeoverride", "number", updateSequenceEditorRange, parseFloat)
|
669 |
+
|
670 |
+
|
671 |
+
setPromptTheme()
|
672 |
+
setPromptFontSize()
|
673 |
+
|
674 |
+
setting_audio_format.disabled = !window.userSettings.audio.ffmpeg
|
675 |
+
setting_audio_hz.disabled = !window.userSettings.audio.ffmpeg
|
676 |
+
setting_audio_pad_start.disabled = !window.userSettings.audio.ffmpeg
|
677 |
+
setting_audio_pad_end.disabled = !window.userSettings.audio.ffmpeg
|
678 |
+
setting_audio_pitchMult.disabled = !window.userSettings.audio.ffmpeg
|
679 |
+
setting_audio_tempo.disabled = !window.userSettings.audio.ffmpeg
|
680 |
+
setting_audio_deessing.disabled = !window.userSettings.audio.ffmpeg
|
681 |
+
setting_audio_nr.disabled = !window.userSettings.audio.ffmpeg
|
682 |
+
setting_audio_nf.disabled = !window.userSettings.audio.ffmpeg
|
683 |
+
setting_audio_bitdepth.disabled = !window.userSettings.audio.ffmpeg
|
684 |
+
setting_audio_amplitude.disabled = !window.userSettings.audio.ffmpeg
|
685 |
+
setting_editor_audio_amplitude.disabled = !window.userSettings.audio.ffmpeg
|
686 |
+
|
687 |
+
|
688 |
+
openDiscord.addEventListener("click", () => {
|
689 |
+
shell.openExternal("https://discord.gg/nv7c6E2TzV")
|
690 |
+
})
|
691 |
+
|
692 |
+
|
693 |
+
setting_models_path_input.addEventListener("change", () => {
|
694 |
+
const gameFolder = window.currentGame.gameId
|
695 |
+
|
696 |
+
setting_models_path_input.value = setting_models_path_input.value.replace(/\/\//g, "/").replace(/\\/g,"/")
|
697 |
+
window.userSettings[`modelspath_${gameFolder}`] = setting_models_path_input.value
|
698 |
+
window.saveUserSettings()
|
699 |
+
window.loadAllModels().then(() => {
|
700 |
+
window.changeGame(window.currentGame)
|
701 |
+
})
|
702 |
+
|
703 |
+
if (!window.watchedModelsDirs.includes(setting_models_path_input.value)) {
|
704 |
+
window.watchedModelsDirs.push(setting_models_path_input.value)
|
705 |
+
fs.watch(setting_models_path_input.value, {recursive: false, persistent: true}, (eventType, filename) => {
|
706 |
+
window.changeGame(window.currentGame)
|
707 |
+
})
|
708 |
+
}
|
709 |
+
window.updateGameList()
|
710 |
+
|
711 |
+
// Gather the model paths to send to the server
|
712 |
+
const modelsPaths = {}
|
713 |
+
Object.keys(window.userSettings).filter(key => key.includes("modelspath_")).forEach(key => {
|
714 |
+
modelsPaths[key.split("_")[1]] = window.userSettings[key]
|
715 |
+
})
|
716 |
+
doFetch(`http://localhost:8008/setAvailableVoices`, {
|
717 |
+
method: "Post",
|
718 |
+
body: JSON.stringify({
|
719 |
+
modelsPaths: JSON.stringify(modelsPaths)
|
720 |
+
})
|
721 |
+
})
|
722 |
+
})
|
723 |
+
|
724 |
+
|
725 |
+
// Output path
|
726 |
+
fs.readdir(`${window.path}/models`, (err, gameDirs) => {
|
727 |
+
gameDirs.filter(name => !name.includes(".")).forEach(gameFolder => {
|
728 |
+
// Initialize the default output directory setting for this game
|
729 |
+
if (!Object.keys(window.userSettings).includes(`outpath_${gameFolder}`)) {
|
730 |
+
window.userSettings[`outpath_${gameFolder}`] = `${__dirname.replace(/\\/g,"/")}/output/${gameFolder}`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
731 |
+
window.saveUserSettings()
|
732 |
+
}
|
733 |
+
})
|
734 |
+
})
|
735 |
+
setting_out_path_input.addEventListener("change", () => {
|
736 |
+
const gameFolder = window.currentGame.gameId
|
737 |
+
|
738 |
+
setting_out_path_input.value = setting_out_path_input.value.replace(/\/\//g, "/").replace(/\\/g,"/")
|
739 |
+
window.userSettings[`outpath_${gameFolder}`] = setting_out_path_input.value
|
740 |
+
saveUserSettings()
|
741 |
+
if (window.currentModelButton) {
|
742 |
+
window.currentModelButton.click()
|
743 |
+
}
|
744 |
+
})
|
745 |
+
// Models path
|
746 |
+
const assetFiles = fs.readdirSync(`${window.path}/assets`)
|
747 |
+
|
748 |
+
assetFiles.filter(fn=>fn.endsWith(".json")).forEach(assetFileName => {
|
749 |
+
const gameId = assetFileName.split(".json")[0]
|
750 |
+
|
751 |
+
// Initialize the default models directory setting for this game
|
752 |
+
if (!Object.keys(window.userSettings).includes(`modelspath_${gameId}`)) {
|
753 |
+
window.userSettings[`modelspath_${gameId}`] = `${__dirname.replace(/\\/g,"/")}/models/${gameId}`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
754 |
+
window.userSettings[`outpath_${gameId}`] = `${__dirname.replace(/\\/g,"/")}/output/${gameId}`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
755 |
+
saveUserSettings()
|
756 |
+
}
|
757 |
+
})
|
758 |
+
|
759 |
+
|
760 |
+
// Batch stuff
|
761 |
+
// Output folder
|
762 |
+
batch_outputFolderInput.addEventListener("change", () => {
|
763 |
+
if (batch_outputFolderInput.value.length==0) {
|
764 |
+
window.errorModal(window.i18n.ENTER_DIR_PATH)
|
765 |
+
batch_outputFolderInput.value = window.userSettings.batchOutFolder
|
766 |
+
} else {
|
767 |
+
window.userSettings.batchOutFolder = batch_outputFolderInput.value
|
768 |
+
saveUserSettings()
|
769 |
+
}
|
770 |
+
})
|
771 |
+
batch_outputFolderInput.value = window.userSettings.batchOutFolder
|
772 |
+
// ======
|
773 |
+
|
774 |
+
|
775 |
+
|
776 |
+
reset_settings_btn.addEventListener("click", () => {
|
777 |
+
window.confirmModal(window.i18n.SURE_RESET_SETTINGS).then(confirmation => {
|
778 |
+
if (confirmation) {
|
779 |
+
window.userSettings.audio.format = "wav"
|
780 |
+
window.userSettings.audio.hz = 44100
|
781 |
+
window.userSettings.audio.padStart = 0
|
782 |
+
window.userSettings.audio.padEnd = 0
|
783 |
+
window.userSettings.audio.pitchMult = 1
|
784 |
+
window.userSettings.audio.tempo = 1
|
785 |
+
window.userSettings.audio.deessing = 0.1
|
786 |
+
window.userSettings.audio.nr = 5
|
787 |
+
window.userSettings.audio.nf = -20
|
788 |
+
window.userSettings.audio.ffmpeg = true
|
789 |
+
// window.userSettings.audio.ffmpeg_preview = true
|
790 |
+
window.userSettings.audio.useNR = true
|
791 |
+
window.userSettings.audio.amplitude = 1
|
792 |
+
window.userSettings.autoPlayGen = true
|
793 |
+
window.userSettings.autoReloadVoices = false
|
794 |
+
window.userSettings.autoplay = true
|
795 |
+
// window.userSettings.darkPrompt = false
|
796 |
+
window.userSettings.showDiscordStatus = true
|
797 |
+
window.userSettings.prompt_fontSize = 15
|
798 |
+
window.userSettings.bg_gradient_opacity = 13
|
799 |
+
window.userSettings.outputJSON = true
|
800 |
+
window.userSettings.keepEditorOnVoiceChange = false
|
801 |
+
window.userSettings.filenameNumericalSeq = false
|
802 |
+
window.userSettings.spacePadding = true
|
803 |
+
window.userSettings.useErrorSound = false
|
804 |
+
window.userSettings.showTipOfTheDay = true
|
805 |
+
window.userSettings.showUnseenTipOfTheDay = false
|
806 |
+
window.userSettings.playChangedAudio = false
|
807 |
+
|
808 |
+
|
809 |
+
window.userSettings.plugins = {}
|
810 |
+
window.userSettings.plugins.loadOrder = ""
|
811 |
+
window.userSettings.externalAudioEditor = ""
|
812 |
+
window.userSettings.s2s_autogenerate = true // TODO, remove
|
813 |
+
window.userSettings.s2s_prePitchShift = false // TODO, remove
|
814 |
+
window.userSettings.s2s_removeNoise = false // TODO, remove
|
815 |
+
window.userSettings.s2s_noiseRemStrength = 0.25 // TODO, remove
|
816 |
+
window.userSettings.vc_strength = 2
|
817 |
+
|
818 |
+
window.userSettings.defaultToHiFi = true
|
819 |
+
window.userSettings.keepPaceOnNew = true
|
820 |
+
window.userSettings.sliderTooltip = true
|
821 |
+
window.userSettings.audio.bitdepth = "pcm_s32le"
|
822 |
+
window.userSettings.showEditorFFMPEGAmplitude = false
|
823 |
+
window.userSettings.vocoder = "256_waveglow"
|
824 |
+
window.userSettings.max_filename_chars = 70
|
825 |
+
window.userSettings.clear_text_after_synth = false
|
826 |
+
window.userSettings.do_model_version_highlight = false
|
827 |
+
window.userSettings.model_version_highlight = 3.0
|
828 |
+
window.userSettings.do_pitchrangeoverride = false
|
829 |
+
window.userSettings.pitchrangeoverride = 6.0
|
830 |
+
window.userSettings.keepPaceOnNew = true
|
831 |
+
window.userSettings.arpabet_paginationSize = 200
|
832 |
+
window.userSettings.output_files_pagination_size = 25
|
833 |
+
|
834 |
+
window.userSettings.batch_clearDirFirst = false
|
835 |
+
window.userSettings.batch_fastMode = false
|
836 |
+
window.userSettings.batch_batchSize = 1
|
837 |
+
window.userSettings.batch_skipExisting = true
|
838 |
+
window.userSettings.batch_fastModeMaxParallelizations = 1000
|
839 |
+
window.userSettings.batch_json = false
|
840 |
+
window.userSettings.batch_useMP = false
|
841 |
+
window.userSettings.batch_MPCount = 0
|
842 |
+
window.userSettings.batch_doGrouping = true
|
843 |
+
window.userSettings.batch_doVocoderGrouping = false
|
844 |
+
window.userSettings.batch_delimiter = ","
|
845 |
+
window.userSettings.batch_paginationSize = 100
|
846 |
+
|
847 |
+
updateUIWithSettings()
|
848 |
+
saveUserSettings()
|
849 |
+
}
|
850 |
+
})
|
851 |
+
})
|
852 |
+
reset_paths_btn.addEventListener("click", () => {
|
853 |
+
window.confirmModal(window.i18n.SURE_RESET_PATHS).then(confirmation => {
|
854 |
+
if (confirmation) {
|
855 |
+
|
856 |
+
const pathKeys = Object.keys(window.userSettings).filter(key => key.includes("modelspath_"))
|
857 |
+
pathKeys.forEach(key => {
|
858 |
+
delete window.userSettings[key]
|
859 |
+
})
|
860 |
+
|
861 |
+
const currGame = window.currentGame ? window.currentGame.gameId : undefined
|
862 |
+
|
863 |
+
// Models and output paths
|
864 |
+
const assetFiles = fs.readdirSync(`${path}/assets`)
|
865 |
+
assetFiles.filter(fn=>fn.endsWith(".json")).forEach(jsonFileName => {
|
866 |
+
|
867 |
+
const gameId = jsonFileName.split(".")[0]
|
868 |
+
window.userSettings[`modelspath_${gameId}`] = `${__dirname.replace(/\\/g,"/")}/models/${gameId}`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
869 |
+
window.userSettings[`outpath_${gameId}`] = `${__dirname.replace(/\\/g,"/")}/output/${gameId}`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
870 |
+
if (gameId==currGame) {
|
871 |
+
setting_models_path_input.value = window.userSettings[`modelspath_${gameId}`]
|
872 |
+
setting_out_path_input.value = window.userSettings[`outpath_${gameId}`]
|
873 |
+
}
|
874 |
+
})
|
875 |
+
|
876 |
+
if (window.currentModelButton) {
|
877 |
+
window.currentModelButton.click()
|
878 |
+
}
|
879 |
+
|
880 |
+
window.userSettings.errorSoundFile = `${__dirname.replace(/\\/g,"/")}/lib/xp_error.mp3`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
881 |
+
window.userSettings.batchOutFolder = `${__dirname.replace(/\\/g,"/")}/batch`.replace(/\/\//g, "/").replace("resources/app/resources/app", "resources/app").replace("/javascript", "")
|
882 |
+
batch_outputFolderInput.value = window.userSettings.batchOutFolder
|
883 |
+
|
884 |
+
window.loadAllModels().then(() => {
|
885 |
+
if (currGame) {
|
886 |
+
window.changeGame(window.currentGame)
|
887 |
+
}
|
888 |
+
})
|
889 |
+
saveUserSettings()
|
890 |
+
|
891 |
+
// Gather the model paths to send to the server
|
892 |
+
const modelsPaths = {}
|
893 |
+
Object.keys(window.userSettings).filter(key => key.includes("modelspath_")).forEach(key => {
|
894 |
+
modelsPaths[key.split("_")[1]] = window.userSettings[key]
|
895 |
+
})
|
896 |
+
doFetch(`http://localhost:8008/setAvailableVoices`, {
|
897 |
+
method: "Post",
|
898 |
+
body: JSON.stringify({
|
899 |
+
modelsPaths: JSON.stringify(modelsPaths)
|
900 |
+
})
|
901 |
+
})
|
902 |
+
}
|
903 |
+
})
|
904 |
+
})
|
905 |
+
|
906 |
+
// Search settings
|
907 |
+
const settingItems = Array.from(settingsOptionsContainer.children)
|
908 |
+
searchSettingsInput.addEventListener("keyup", () => {
|
909 |
+
|
910 |
+
const query = searchSettingsInput.value.trim().toLowerCase()
|
911 |
+
|
912 |
+
const filteredItems = settingItems.map(el => {
|
913 |
+
if (el.tagName=="HR") {return [el, true]}
|
914 |
+
if (el.tagName=="DIV") {
|
915 |
+
if (!query.length || el.children[0].innerHTML.toLowerCase().includes(query)) {
|
916 |
+
return [el, true]
|
917 |
+
}
|
918 |
+
}
|
919 |
+
return [el, false]
|
920 |
+
})
|
921 |
+
|
922 |
+
let lastIsHR = false
|
923 |
+
filteredItems.forEach(elem => {
|
924 |
+
const [el, showIt] = elem
|
925 |
+
if (el.tagName=="HR") {
|
926 |
+
if (lastIsHR) {
|
927 |
+
el.style.display = "none"
|
928 |
+
return
|
929 |
+
}
|
930 |
+
lastIsHR = true
|
931 |
+
el.style.display = "flex"
|
932 |
+
} else {
|
933 |
+
if (showIt) {
|
934 |
+
el.style.display = "flex"
|
935 |
+
lastIsHR = false
|
936 |
+
} else {
|
937 |
+
el.style.display = "none"
|
938 |
+
}
|
939 |
+
}
|
940 |
+
})
|
941 |
+
})
|
942 |
+
|
943 |
+
|
944 |
+
const currentWindow = er.getCurrentWindow()
|
945 |
+
currentWindow.on("move", () => {
|
946 |
+
const bounds = er.getCurrentWindow().webContents.getOwnerBrowserWindow().getBounds()
|
947 |
+
window.userSettings.customWindowPosition = `${bounds.x},${bounds.y}`
|
948 |
+
saveUserSettings()
|
949 |
+
})
|
950 |
+
|
951 |
+
if (window.userSettings.customWindowPosition) {
|
952 |
+
ipcRenderer.send('updatePosition', {details: window.userSettings.customWindowPosition.split(",")})
|
953 |
+
}
|
954 |
+
|
955 |
+
|
956 |
+
|
957 |
+
|
958 |
+
window.saveUserSettings = saveUserSettings
|
959 |
+
exports.saveUserSettings = saveUserSettings
|
960 |
+
exports.deleteFolderRecursive = deleteFolderRecursive
|
javascript/speech2speech.js
ADDED
@@ -0,0 +1,379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
window.speech2speechState = {
|
4 |
+
isReadingMic: false,
|
5 |
+
elapsedRecording: 0,
|
6 |
+
s2s_running: false
|
7 |
+
}
|
8 |
+
|
9 |
+
// Populate the microphone dropdown with the available options
|
10 |
+
navigator.mediaDevices.enumerateDevices().then(devices => {
|
11 |
+
devices = devices.filter(device => device.kind=="audioinput" && device.deviceId!="default" && device.deviceId!="communications")
|
12 |
+
devices.forEach(device => {
|
13 |
+
const option = createElem("option", device.label)
|
14 |
+
option.value = device.deviceId
|
15 |
+
setting_mic_selection.appendChild(option)
|
16 |
+
})
|
17 |
+
|
18 |
+
setting_mic_selection.addEventListener("change", () => {
|
19 |
+
window.userSettings.microphone = setting_mic_selection.value
|
20 |
+
window.saveUserSettings()
|
21 |
+
window.initMic()
|
22 |
+
})
|
23 |
+
|
24 |
+
if (Object.keys(window.userSettings).includes("microphone")) {
|
25 |
+
setting_mic_selection.value = window.userSettings.microphone
|
26 |
+
} else {
|
27 |
+
window.userSettings.microphone = setting_mic_selection.value
|
28 |
+
window.saveUserSettings()
|
29 |
+
}
|
30 |
+
})
|
31 |
+
|
32 |
+
|
33 |
+
|
34 |
+
|
35 |
+
window.initMic = () => {
|
36 |
+
return new Promise(resolve => {
|
37 |
+
const deviceId = window.userSettings.microphone
|
38 |
+
|
39 |
+
navigator.mediaDevices.getUserMedia({audio: {deviceId: deviceId}}).then(stream => {
|
40 |
+
const audio_context = new AudioContext
|
41 |
+
const input = audio_context.createMediaStreamSource(stream)
|
42 |
+
window.speech2speechState.stream = stream
|
43 |
+
resolve()
|
44 |
+
|
45 |
+
}).catch(err => {
|
46 |
+
console.log(err)
|
47 |
+
resolve()
|
48 |
+
})
|
49 |
+
})
|
50 |
+
}
|
51 |
+
window.initMic()
|
52 |
+
|
53 |
+
|
54 |
+
const animateRecordingProgress = () => {
|
55 |
+
const percentDone = (Date.now() - window.speech2speechState.elapsedRecording) / 10000
|
56 |
+
|
57 |
+
if (percentDone >= 1 && percentDone!=Infinity) {
|
58 |
+
if (window.speech2speechState.isReadingMic) {
|
59 |
+
window.stopRecord()
|
60 |
+
}
|
61 |
+
} else {
|
62 |
+
const circle = mic_progress_SVG_circle
|
63 |
+
const radius = circle.r.baseVal.value
|
64 |
+
const circumference = radius * 2 * Math.PI
|
65 |
+
const offset = circumference - percentDone * circumference
|
66 |
+
|
67 |
+
circle.style.strokeDasharray = `${circumference} ${circumference}`
|
68 |
+
circle.style.strokeDashoffset = circumference
|
69 |
+
circle.style.strokeDashoffset = Math.round(offset)
|
70 |
+
|
71 |
+
requestAnimationFrame(animateRecordingProgress)
|
72 |
+
}
|
73 |
+
}
|
74 |
+
|
75 |
+
|
76 |
+
window.clearVCMicSpinnerProgress = (percent=0) => {
|
77 |
+
const circle = mic_progress_SVG_circle
|
78 |
+
circle.style.stroke = "transparent"
|
79 |
+
const radius = circle.r.baseVal.value
|
80 |
+
const circumference = radius * 2 * Math.PI
|
81 |
+
const offset = circumference - percent * circumference
|
82 |
+
|
83 |
+
circle.style.strokeDasharray = `${circumference} ${circumference}`
|
84 |
+
circle.style.strokeDashoffset = circumference
|
85 |
+
circle.style.strokeDashoffset = Math.floor(offset)
|
86 |
+
}
|
87 |
+
window.clearVCMicSpinnerProgress = clearVCMicSpinnerProgress
|
88 |
+
|
89 |
+
window.startRecord = async () => {
|
90 |
+
|
91 |
+
doFetch(`http://localhost:8008/start_microphone_recording`, {
|
92 |
+
method: "Post"
|
93 |
+
})
|
94 |
+
|
95 |
+
window.speech2speechState.isReadingMic = true
|
96 |
+
window.speech2speechState.elapsedRecording = Date.now()
|
97 |
+
window.clearVCMicSpinnerProgress()
|
98 |
+
mic_progress_SVG_circle.style.stroke = "red"
|
99 |
+
requestAnimationFrame(animateRecordingProgress)
|
100 |
+
}
|
101 |
+
|
102 |
+
window.outputS2SRecording = (outPath, callback) => {
|
103 |
+
toggleSpinnerButtons()
|
104 |
+
doFetch(`http://localhost:8008/move_recorded_file`, {
|
105 |
+
method: "Post",
|
106 |
+
body: JSON.stringify({
|
107 |
+
file_path: outPath
|
108 |
+
})
|
109 |
+
}).then(r=>r.text()).then(res => {
|
110 |
+
callback()
|
111 |
+
})
|
112 |
+
}
|
113 |
+
|
114 |
+
window.useWavFileForspeech2speech = (fileName) => {
|
115 |
+
// let sequence = dialogueInput.value.trim().replace("β¦", "...")
|
116 |
+
|
117 |
+
// For some reason, the samplePlay audio element does not update the source when the file name is the same
|
118 |
+
const tempFileNum = `${Math.random().toString().split(".")[1]}`
|
119 |
+
let tempFileLocation = `${path}/output/temp-${tempFileNum}.wav`
|
120 |
+
|
121 |
+
let style_emb = window.currentModel.audioPreviewPath // Default to the preview audio file, if an embedding can't be found in the json - This shouldn't happen
|
122 |
+
try {
|
123 |
+
style_emb = window.currentModel.games[0].base_speaker_emb // If this fails, the json isn't complete
|
124 |
+
} catch (e) {
|
125 |
+
console.log(e)
|
126 |
+
}
|
127 |
+
|
128 |
+
if (window.wavesurfer) {
|
129 |
+
window.wavesurfer.stop()
|
130 |
+
wavesurferContainer.style.opacity = 0
|
131 |
+
}
|
132 |
+
window.tempFileLocation = `${__dirname.replace("/javascript", "").replace("\\javascript", "")}/output/temp-${tempFileNum}.wav`
|
133 |
+
|
134 |
+
|
135 |
+
const options = {
|
136 |
+
hz: window.userSettings.audio.hz,
|
137 |
+
padStart: window.userSettings.audio.padStart,
|
138 |
+
padEnd: window.userSettings.audio.padEnd,
|
139 |
+
bit_depth: window.userSettings.audio.bitdepth,
|
140 |
+
amplitude: window.userSettings.audio.amplitude,
|
141 |
+
pitchMult: window.userSettings.audio.pitchMult,
|
142 |
+
tempo: window.userSettings.audio.tempo,
|
143 |
+
deessing: window.userSettings.audio.deessing,
|
144 |
+
nr: window.userSettings.audio.nr,
|
145 |
+
nf: window.userSettings.audio.nf,
|
146 |
+
useNR: window.userSettings.audio.useNR,
|
147 |
+
useSR: useSRCkbx.checked,
|
148 |
+
useCleanup: useCleanupCkbx.checked
|
149 |
+
}
|
150 |
+
|
151 |
+
|
152 |
+
doFetch(`http://localhost:8008/runSpeechToSpeech`, {
|
153 |
+
method: "Post",
|
154 |
+
body: JSON.stringify({
|
155 |
+
input_path: fileName,
|
156 |
+
useSR: useSRCkbx.checked,
|
157 |
+
useCleanup: useCleanupCkbx.checked,
|
158 |
+
isBatchMode: false,
|
159 |
+
|
160 |
+
style_emb: style_emb_select.value=="default" ? window.currentModel.games[0].base_speaker_emb : style_emb_select.value.split(",").map(v=>parseFloat(v)),
|
161 |
+
audio_out_path: tempFileLocation,
|
162 |
+
|
163 |
+
doPitchShift: window.userSettings.s2s_prePitchShift,
|
164 |
+
removeNoise: window.userSettings.s2s_removeNoise, // Removed from UI
|
165 |
+
removeNoiseStrength: window.userSettings.s2s_noiseRemStrength, // Removed from UI
|
166 |
+
vc_strength: window.userSettings.vc_strength,
|
167 |
+
n_speakers: undefined,
|
168 |
+
modelPath: undefined,
|
169 |
+
voiceId: undefined,
|
170 |
+
|
171 |
+
options: JSON.stringify(options)
|
172 |
+
})
|
173 |
+
}).then(r=>r.text()).then(res => {
|
174 |
+
// This block of code sometimes gets called before the audio file has actually finished flushing to file
|
175 |
+
// I need a better way to make sure that this doesn't get called until it IS finished, but "for now",
|
176 |
+
// I've set up some recursive re-attempts, below doTheRest
|
177 |
+
window.clearVCMicSpinnerProgress()
|
178 |
+
mic_progress_SVG.style.animation = "none"
|
179 |
+
|
180 |
+
if (res=="TOO_SHORT") {
|
181 |
+
window.toggleSpinnerButtons(true)
|
182 |
+
window.errorModal(`<h3>${window.i18n.VC_TOO_SHORT}</h3>`)
|
183 |
+
return
|
184 |
+
}
|
185 |
+
|
186 |
+
generateVoiceButton.disabled = true
|
187 |
+
let hasLoaded = false
|
188 |
+
let numRetries = 0
|
189 |
+
window.toggleSpinnerButtons(true)
|
190 |
+
|
191 |
+
const doTheRest = () => {
|
192 |
+
if (hasLoaded) {
|
193 |
+
return
|
194 |
+
}
|
195 |
+
// window.wavesurfer = undefined
|
196 |
+
tempFileLocation = tempFileLocation.replaceAll(/\\/g, "/")
|
197 |
+
tempFileLocation = tempFileLocation.replaceAll('/resources/app/resources/app', "/resources/app")
|
198 |
+
tempFileLocation = tempFileLocation.replaceAll('/resources/app', "")
|
199 |
+
|
200 |
+
dialogueInput.value = ""
|
201 |
+
textEditorElem.innerHTML = ""
|
202 |
+
window.isGenerating = false
|
203 |
+
|
204 |
+
window.speech2speechState.s2s_running = true
|
205 |
+
|
206 |
+
|
207 |
+
|
208 |
+
if (res.includes("Traceback")) {
|
209 |
+
window.errorModal(`<h3>${window.i18n.SOMETHING_WENT_WRONG}</h3>${res.replaceAll("\n", "<br>")}`)
|
210 |
+
|
211 |
+
} else if (res.includes("ERROR:APP_VERSION")) {
|
212 |
+
const speech2speechModelVersion = "v"+res.split(",")[1]
|
213 |
+
window.errorModal(`${window.i18n.ERR_XVASPEECH_MODEL_VERSION.replace("_1", speech2speechModelVersion)} ${window.appVersion}`)
|
214 |
+
} else {
|
215 |
+
|
216 |
+
keepSampleButton.disabled = false
|
217 |
+
window.tempFileLocation = tempFileLocation
|
218 |
+
|
219 |
+
// Wavesurfer
|
220 |
+
if (!window.wavesurfer) {
|
221 |
+
window.initWaveSurfer(window.tempFileLocation)
|
222 |
+
} else {
|
223 |
+
window.wavesurfer.load(window.tempFileLocation)
|
224 |
+
}
|
225 |
+
window.wavesurfer.on("ready", () => {
|
226 |
+
|
227 |
+
hasLoaded = true
|
228 |
+
wavesurferContainer.style.opacity = 1
|
229 |
+
|
230 |
+
if (window.userSettings.autoPlayGen) {
|
231 |
+
|
232 |
+
if (window.userSettings.playChangedAudio) {
|
233 |
+
const playbackStartEnd = window.sequenceEditor.getChangedTimeStamps(start_index, end_index, window.wavesurfer.getDuration())
|
234 |
+
if (playbackStartEnd) {
|
235 |
+
wavesurfer.play(playbackStartEnd[0], playbackStartEnd[1])
|
236 |
+
} else {
|
237 |
+
wavesurfer.play()
|
238 |
+
}
|
239 |
+
} else {
|
240 |
+
wavesurfer.play()
|
241 |
+
}
|
242 |
+
window.sequenceEditor.adjustedLetters = new Set()
|
243 |
+
samplePlayPause.innerHTML = window.i18n.PAUSE
|
244 |
+
}
|
245 |
+
})
|
246 |
+
|
247 |
+
// Persistance across sessions
|
248 |
+
localStorage.setItem("tempFileLocation", tempFileLocation)
|
249 |
+
generateVoiceButton.innerHTML = window.i18n.GENERATE_VOICE
|
250 |
+
|
251 |
+
if (window.userSettings.s2s_autogenerate) {
|
252 |
+
speech2speechState.s2s_autogenerate = true
|
253 |
+
generateVoiceButton.click()
|
254 |
+
}
|
255 |
+
|
256 |
+
keepSampleButton.dataset.newFileLocation = `${window.userSettings[`outpath_${window.currentGame.gameId}`]}/${title.dataset.modelId}/vc_${tempFileNum}.wav`
|
257 |
+
keepSampleButton.disabled = false
|
258 |
+
keepSampleButton.style.display = "block"
|
259 |
+
samplePlayPause.style.display = "block"
|
260 |
+
|
261 |
+
setTimeout(doTheRest, 100)
|
262 |
+
}
|
263 |
+
|
264 |
+
}
|
265 |
+
|
266 |
+
doTheRest()
|
267 |
+
}).catch(e => {
|
268 |
+
console.log(e)
|
269 |
+
window.toggleSpinnerButtons(true)
|
270 |
+
window.errorModal(`<h3>${window.i18n.SOMETHING_WENT_WRONG}</h3>`)
|
271 |
+
mic_progress_SVG.style.animation = "none"
|
272 |
+
})
|
273 |
+
}
|
274 |
+
|
275 |
+
window.stopRecord = (cancelled) => {
|
276 |
+
fs.writeFileSync(`${window.path}/python/temp_stop_recording`, "")
|
277 |
+
|
278 |
+
if (!cancelled) {
|
279 |
+
window.clearVCMicSpinnerProgress(0.35)
|
280 |
+
mic_progress_SVG.style.animation = "spin 1.5s linear infinite"
|
281 |
+
mic_progress_SVG_circle.style.stroke = "white"
|
282 |
+
const fileName = `${__dirname.replace("\\javascript", "").replace("/javascript", "").replace(/\\/g,"/")}/output/recorded_file.wav`
|
283 |
+
|
284 |
+
window.sequenceEditor.clear()
|
285 |
+
|
286 |
+
window.outputS2SRecording(fileName, () => {
|
287 |
+
window.useWavFileForspeech2speech(fileName)
|
288 |
+
})
|
289 |
+
}
|
290 |
+
|
291 |
+
window.speech2speechState.isReadingMic = false
|
292 |
+
window.speech2speechState.elapsedRecording = 0
|
293 |
+
window.clearVCMicSpinnerProgress()
|
294 |
+
}
|
295 |
+
|
296 |
+
window.micClickHandler = (ctrlKey) => {
|
297 |
+
if (window.speech2speechState.isReadingMic) {
|
298 |
+
window.stopRecord()
|
299 |
+
} else {
|
300 |
+
if (window.currentModel && generateVoiceButton.innerHTML == window.i18n.GENERATE_VOICE) {
|
301 |
+
if (window.currentModel.modelType.toLowerCase()=="xvapitch") {
|
302 |
+
window.startRecord()
|
303 |
+
}
|
304 |
+
} else {
|
305 |
+
window.errorModal(window.i18n.LOAD_TARGET_MODEL)
|
306 |
+
}
|
307 |
+
}
|
308 |
+
}
|
309 |
+
mic_SVG.addEventListener("mouseenter", () => {
|
310 |
+
if (!window.currentModel || window.currentModel.modelType.toLowerCase()!="xvapitch") {
|
311 |
+
s2s_voiceId_selected_label.style.display = "inline-block"
|
312 |
+
}
|
313 |
+
})
|
314 |
+
mic_SVG.addEventListener("mouseleave", () => {
|
315 |
+
s2s_voiceId_selected_label.style.display = "none"
|
316 |
+
})
|
317 |
+
mic_SVG.addEventListener("click", event => window.micClickHandler(event.ctrlKey))
|
318 |
+
mic_SVG.addEventListener("contextmenu", () => {
|
319 |
+
if (window.speech2speechState.isReadingMic) {
|
320 |
+
window.stopRecord(true)
|
321 |
+
} else {
|
322 |
+
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", {
|
323 |
+
src: `${__dirname.replace("\\javascript", "").replace("/javascript", "").replace(/\\/g,"/")}/output/recorded_file_post${window.userSettings.s2s_prePitchShift?"_praat":""}.wav`
|
324 |
+
}))
|
325 |
+
audioPreview.setSinkId(window.userSettings.base_speaker)
|
326 |
+
}
|
327 |
+
})
|
328 |
+
window.clearVCMicSpinnerProgress()
|
329 |
+
|
330 |
+
|
331 |
+
|
332 |
+
// File dragging
|
333 |
+
window.uploadS2SFile = (eType, event) => {
|
334 |
+
|
335 |
+
if (["dragenter", "dragover"].includes(eType)) {
|
336 |
+
clearVCMicSpinnerProgress(1)
|
337 |
+
mic_progress_SVG_circle.style.stroke = "white"
|
338 |
+
}
|
339 |
+
if (["dragleave", "drop"].includes(eType)) {
|
340 |
+
window.clearVCMicSpinnerProgress()
|
341 |
+
}
|
342 |
+
|
343 |
+
event.preventDefault()
|
344 |
+
event.stopPropagation()
|
345 |
+
|
346 |
+
if (eType=="drop") {
|
347 |
+
if (window.currentModel && generateVoiceButton.innerHTML == window.i18n.GENERATE_VOICE) {
|
348 |
+
const dataTransfer = event.dataTransfer
|
349 |
+
const files = Array.from(dataTransfer.files)
|
350 |
+
const file = files[0]
|
351 |
+
|
352 |
+
if (!file.name.endsWith(".wav")) {
|
353 |
+
window.errorModal(window.i18n.ONLY_WAV_S2S)
|
354 |
+
return
|
355 |
+
}
|
356 |
+
|
357 |
+
clearVCMicSpinnerProgress(0.35)
|
358 |
+
// mic_progress_SVG.style.animation = "spin 1.5s linear infinite"
|
359 |
+
// mic_progress_SVG_circle.style.stroke = "white"
|
360 |
+
|
361 |
+
const fileName = `${__dirname.replace("\\javascript", "").replace("/javascript", "").replace(/\\/g,"/")}/output/recorded_file.wav`
|
362 |
+
fs.copyFileSync(file.path, fileName)
|
363 |
+
window.sequenceEditor.clear()
|
364 |
+
toggleSpinnerButtons()
|
365 |
+
window.useWavFileForspeech2speech(fileName)
|
366 |
+
} else {
|
367 |
+
window.errorModal(window.i18n.LOAD_TARGET_MODEL)
|
368 |
+
}
|
369 |
+
}
|
370 |
+
}
|
371 |
+
|
372 |
+
micContainer.addEventListener("dragenter", event => window.uploadS2SFile("dragenter", event), false)
|
373 |
+
micContainer.addEventListener("dragleave", event => window.uploadS2SFile("dragleave", event), false)
|
374 |
+
micContainer.addEventListener("dragover", event => window.uploadS2SFile("dragover", event), false)
|
375 |
+
micContainer.addEventListener("drop", event => window.uploadS2SFile("drop", event), false)
|
376 |
+
|
377 |
+
// Disable page navigation on badly dropped file
|
378 |
+
window.document.addEventListener("dragover", event => event.preventDefault(), false)
|
379 |
+
window.document.addEventListener("drop", event => event.preventDefault(), false)
|
javascript/style_embeddings.js
ADDED
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
window.allStyleEmbs = {}
|
4 |
+
window.styleEmbsMenuState = {
|
5 |
+
embeddingsDir: `${window.path}/embeddings`,
|
6 |
+
hasChangedEmb: false, // For clearing the use of the editor state when re-generating a line with a different embedding
|
7 |
+
selectedEmb: undefined,
|
8 |
+
activatedEmbeddings: {}
|
9 |
+
}
|
10 |
+
|
11 |
+
|
12 |
+
window.loadStyleEmbsFromDisk = () => {
|
13 |
+
window.allStyleEmbs = {}
|
14 |
+
|
15 |
+
// Read the activated embeddings file
|
16 |
+
window.styleEmbsMenuState.activatedEmbeddings = {}
|
17 |
+
if (fs.existsSync(`./embeddings.txt`)) {
|
18 |
+
const embeddingsEnabled = fs.readFileSync(`./embeddings.txt`, "utf8").split("\n")
|
19 |
+
embeddingsEnabled.forEach(emb => {
|
20 |
+
window.styleEmbsMenuState.activatedEmbeddings[emb.replace("*","")] = emb.includes("*")
|
21 |
+
})
|
22 |
+
}
|
23 |
+
|
24 |
+
|
25 |
+
// Read all the embedding files
|
26 |
+
fs.mkdirSync(window.styleEmbsMenuState.embeddingsDir, {recursive: true})
|
27 |
+
const embFiles = fs.readdirSync(window.styleEmbsMenuState.embeddingsDir)
|
28 |
+
embFiles.forEach(jsonFName => {
|
29 |
+
const jsonData = JSON.parse(fs.readFileSync(`${window.styleEmbsMenuState.embeddingsDir}/${jsonFName}`))
|
30 |
+
jsonData.fileName = `${window.styleEmbsMenuState.embeddingsDir}/${jsonFName}`
|
31 |
+
if (!Object.keys(window.styleEmbsMenuState.activatedEmbeddings).includes(jsonData.emb_id)) {
|
32 |
+
window.styleEmbsMenuState.activatedEmbeddings[jsonData.emb_id] = true
|
33 |
+
}
|
34 |
+
jsonData.enabled = window.styleEmbsMenuState.activatedEmbeddings[jsonData.emb_id]
|
35 |
+
window.allStyleEmbs[jsonData.voiceId] = window.allStyleEmbs[jsonData.voiceId] || []
|
36 |
+
|
37 |
+
window.allStyleEmbs[jsonData.voiceId].push(jsonData)
|
38 |
+
})
|
39 |
+
window.saveEnabledStyleEmbs()
|
40 |
+
}
|
41 |
+
window.saveEnabledStyleEmbs = () => {
|
42 |
+
fs.writeFileSync(`./embeddings.txt`, Object.keys(window.styleEmbsMenuState.activatedEmbeddings).map(key => {
|
43 |
+
return `${window.styleEmbsMenuState.activatedEmbeddings[key]?"*":""}${key}`
|
44 |
+
}).join("\n"), "utf8")
|
45 |
+
}
|
46 |
+
window.loadStyleEmbsFromDisk()
|
47 |
+
|
48 |
+
|
49 |
+
window.resetStyleEmbFields = () => {
|
50 |
+
styleEmbAuthorInput.value = ""
|
51 |
+
styleEmbGameIdInput.value = ""
|
52 |
+
styleEmbVoiceIdInput.value = ""
|
53 |
+
styleEmbNameInput.value = ""
|
54 |
+
styleEmbDescriptionInput.value = ""
|
55 |
+
styleEmbIdInput.value = ""
|
56 |
+
wavFilepathForEmbComputeInput.value = ""
|
57 |
+
styleEmbValuesInput.value = ""
|
58 |
+
|
59 |
+
styleEmbGameIdInput.value = window.currentGame.gameId
|
60 |
+
if (window.currentModel) {
|
61 |
+
styleEmbVoiceIdInput.value = window.currentModel.voiceId
|
62 |
+
}
|
63 |
+
}
|
64 |
+
|
65 |
+
window.refreshStyleEmbsTable = () => {
|
66 |
+
styleembsRecordsContainer.innerHTML = ""
|
67 |
+
window.styleEmbsMenuState.selectedEmb = undefined
|
68 |
+
styleEmbDelete.disabled = true
|
69 |
+
window.resetStyleEmbFields()
|
70 |
+
|
71 |
+
|
72 |
+
Object.keys(window.allStyleEmbs).sort().forEach(key => {
|
73 |
+
window.allStyleEmbs[key].forEach((emb,ei) => {
|
74 |
+
const record = createElem("div")
|
75 |
+
const enabledCkbx = createElem("input", {type: "checkbox"})
|
76 |
+
enabledCkbx.checked = emb.enabled
|
77 |
+
record.appendChild(createElem("div", enabledCkbx))
|
78 |
+
|
79 |
+
const embName = createElem("div", emb["embeddingName"])
|
80 |
+
embName.title = emb["embeddingName"]
|
81 |
+
record.appendChild(embName)
|
82 |
+
|
83 |
+
const embGameID = createElem("div", emb["gameId"])
|
84 |
+
embGameID.title = emb["gameId"]
|
85 |
+
record.appendChild(embGameID)
|
86 |
+
|
87 |
+
const embVoiceID = createElem("div", emb["voiceId"])
|
88 |
+
embVoiceID.title = emb["voiceId"]
|
89 |
+
record.appendChild(embVoiceID)
|
90 |
+
|
91 |
+
const embDescription = createElem("div", emb["description"]||"")
|
92 |
+
embDescription.title = emb["description"]||""
|
93 |
+
record.appendChild(embDescription)
|
94 |
+
|
95 |
+
const embID = createElem("div", emb["emb_id"])
|
96 |
+
embID.title = emb["emb_id"]
|
97 |
+
record.appendChild(embID)
|
98 |
+
|
99 |
+
const embVersion = createElem("div", emb["version"]||"1.0")
|
100 |
+
embVersion.title = emb["version"]||"1.0"
|
101 |
+
record.appendChild(embVersion)
|
102 |
+
|
103 |
+
enabledCkbx.addEventListener("click", () => {
|
104 |
+
window.allStyleEmbs[key][ei].enabled = !window.allStyleEmbs[key][ei].enabled
|
105 |
+
window.styleEmbsMenuState.activatedEmbeddings[emb["emb_id"]] = window.allStyleEmbs[key][ei].enabled
|
106 |
+
window.saveEnabledStyleEmbs()
|
107 |
+
|
108 |
+
if (window.currentModel) {
|
109 |
+
window.loadStyleEmbsForVoice(window.currentModel)
|
110 |
+
}
|
111 |
+
})
|
112 |
+
|
113 |
+
|
114 |
+
record.addEventListener("click", (e) => {
|
115 |
+
if (e.target==enabledCkbx || e.target.nodeName=="BUTTON") {
|
116 |
+
return
|
117 |
+
}
|
118 |
+
// Clear visual selection of the old selected item, if there was already an item selected before
|
119 |
+
if (window.styleEmbsMenuState.selectedEmb) {
|
120 |
+
window.styleEmbsMenuState.selectedEmb[0].style.background = "none"
|
121 |
+
Array.from(window.styleEmbsMenuState.selectedEmb[0].children).forEach(child => child.style.color = "white")
|
122 |
+
}
|
123 |
+
|
124 |
+
window.styleEmbsMenuState.selectedEmb = [record, emb]
|
125 |
+
|
126 |
+
// Visually show that this row is selected
|
127 |
+
window.styleEmbsMenuState.selectedEmb[0].style.background = "white"
|
128 |
+
Array.from(window.styleEmbsMenuState.selectedEmb[0].children).forEach(child => child.style.color = "black")
|
129 |
+
styleEmbDelete.disabled = false
|
130 |
+
|
131 |
+
|
132 |
+
// Populate the edit fields
|
133 |
+
styleEmbAuthorInput.value = emb.author||""
|
134 |
+
styleEmbGameIdInput.value = emb.gameId||""
|
135 |
+
styleEmbVoiceIdInput.value = emb.voiceId||""
|
136 |
+
styleEmbNameInput.value = emb.embeddingName||""
|
137 |
+
styleEmbDescriptionInput.value = emb.description||""
|
138 |
+
styleEmbIdInput.value = emb.emb_id||""
|
139 |
+
wavFilepathForEmbComputeInput.value = ""
|
140 |
+
styleEmbValuesInput.value = emb.emb||""
|
141 |
+
})
|
142 |
+
|
143 |
+
styleembsRecordsContainer.appendChild(record)
|
144 |
+
})
|
145 |
+
})
|
146 |
+
}
|
147 |
+
styleembs_main.addEventListener("click", (e) => {
|
148 |
+
if (e.target == styleembs_main) {
|
149 |
+
window.refreshStyleEmbsTable()
|
150 |
+
}
|
151 |
+
})
|
152 |
+
|
153 |
+
styleEmbSave.addEventListener("click", () => {
|
154 |
+
|
155 |
+
const missingFieldsValues = []
|
156 |
+
|
157 |
+
if (!styleEmbAuthorInput.value.trim().length) {
|
158 |
+
missingFieldsValues.push(window.i18n.AUTHOR)
|
159 |
+
}
|
160 |
+
if (!styleEmbGameIdInput.value.trim().length) {
|
161 |
+
missingFieldsValues.push(window.i18n.GAME_ID)
|
162 |
+
}
|
163 |
+
if (!styleEmbVoiceIdInput.value.trim().length) {
|
164 |
+
missingFieldsValues.push(window.i18n.VOICE_ID)
|
165 |
+
}
|
166 |
+
if (!styleEmbNameInput.value.trim().length) {
|
167 |
+
missingFieldsValues.push(window.i18n.EMB_NAME)
|
168 |
+
}
|
169 |
+
if (!styleEmbIdInput.value.trim().length) {
|
170 |
+
missingFieldsValues.push(window.i18n.EMB_ID)
|
171 |
+
}
|
172 |
+
if (!styleEmbValuesInput.value.trim().length) {
|
173 |
+
missingFieldsValues.push(window.i18n.STYLE_EMB_VALUES)
|
174 |
+
}
|
175 |
+
|
176 |
+
if (missingFieldsValues.length) {
|
177 |
+
window.errorModal(window.i18n.ERROR_MISSING_FIELDS.replace("_1", missingFieldsValues.join(", ")))
|
178 |
+
} else {
|
179 |
+
let outputFilename
|
180 |
+
if (window.styleEmbsMenuState.selectedEmb) {
|
181 |
+
outputFilename = window.styleEmbsMenuState.selectedEmb[1].fileName
|
182 |
+
} else {
|
183 |
+
outputFilename = `${window.styleEmbsMenuState.embeddingsDir}/${styleEmbVoiceIdInput.value.trim().toLowerCase()}.${styleEmbGameIdInput.value.trim().toLowerCase()}.${styleEmbIdInput.value.trim().toLowerCase()}.${styleEmbAuthorInput.value.trim().toLowerCase()}.json`
|
184 |
+
}
|
185 |
+
|
186 |
+
const jsonData = {
|
187 |
+
"author": styleEmbAuthorInput.value.trim(),
|
188 |
+
"version": "1.0", // Should I make this editable in the UI?
|
189 |
+
"gameId": styleEmbGameIdInput.value.trim().toLowerCase(),
|
190 |
+
"voiceId": styleEmbVoiceIdInput.value.trim().toLowerCase(),
|
191 |
+
"description": styleEmbDescriptionInput.value.trim()||"",
|
192 |
+
"embeddingName": styleEmbNameInput.value.trim(),
|
193 |
+
"emb": styleEmbValuesInput.value.trim().split(",").map(v=>parseFloat(v)),
|
194 |
+
"emb_id": styleEmbIdInput.value.trim()
|
195 |
+
}
|
196 |
+
|
197 |
+
fs.writeFileSync(outputFilename, JSON.stringify(jsonData, null, 4), "utf8")
|
198 |
+
window.loadStyleEmbsFromDisk()
|
199 |
+
window.refreshStyleEmbsTable()
|
200 |
+
}
|
201 |
+
if (window.currentModel) {
|
202 |
+
window.loadStyleEmbsForVoice(window.currentModel)
|
203 |
+
}
|
204 |
+
})
|
205 |
+
|
206 |
+
|
207 |
+
styleEmbDelete.addEventListener("click", () => {
|
208 |
+
window.confirmModal(window.i18n.CONFIRM_DELETE_STYLE_EMB).then(response => {
|
209 |
+
if (response) {
|
210 |
+
fs.unlinkSync(window.styleEmbsMenuState.selectedEmb[1].fileName)
|
211 |
+
window.loadStyleEmbsFromDisk()
|
212 |
+
window.refreshStyleEmbsTable()
|
213 |
+
if (window.currentModel) {
|
214 |
+
window.loadStyleEmbsForVoice(window.currentModel)
|
215 |
+
}
|
216 |
+
}
|
217 |
+
})
|
218 |
+
})
|
219 |
+
|
220 |
+
|
221 |
+
// Return the default embedding, plus any other ones
|
222 |
+
window.loadStyleEmbsForVoice = (currentModel) => {
|
223 |
+
|
224 |
+
const embeddings = {}
|
225 |
+
|
226 |
+
// Add the default option from the model json
|
227 |
+
embeddings["default"] = [window.i18n.DEFAULT, currentModel.games[0].base_speaker_emb] // TODO, specialize to specific game?
|
228 |
+
|
229 |
+
// Load any other style embeddings available
|
230 |
+
if (Object.keys(window.allStyleEmbs).includes(currentModel.voiceId)) {
|
231 |
+
window.allStyleEmbs[currentModel.voiceId].forEach(loadedStyleEmb => {
|
232 |
+
if (loadedStyleEmb.enabled) {
|
233 |
+
embeddings[loadedStyleEmb.emb_id] = [loadedStyleEmb.embeddingName, loadedStyleEmb.emb]
|
234 |
+
}
|
235 |
+
})
|
236 |
+
}
|
237 |
+
|
238 |
+
|
239 |
+
// Add every option to the embeddings selection dropdown
|
240 |
+
style_emb_select.innerHTML = ""
|
241 |
+
Array.from(seq_edit_edit_select.children).forEach(option => {
|
242 |
+
if (option.value.startsWith("style_")) {
|
243 |
+
seq_edit_edit_select.removeChild(option)
|
244 |
+
}
|
245 |
+
})
|
246 |
+
// Add Default first
|
247 |
+
const opt = createElem("option", embeddings["default"][0])
|
248 |
+
opt.value = embeddings["default"][1].join(",")
|
249 |
+
style_emb_select.appendChild(opt)
|
250 |
+
|
251 |
+
Object.keys(embeddings).forEach(key => {
|
252 |
+
if (key=="default") {
|
253 |
+
return
|
254 |
+
}
|
255 |
+
const opt = createElem("option", embeddings[key][0])
|
256 |
+
opt.value = embeddings[key][1].join(",")
|
257 |
+
style_emb_select.appendChild(opt)
|
258 |
+
})
|
259 |
+
|
260 |
+
|
261 |
+
// First remove all existing styles from the dropdown
|
262 |
+
Array.from(seq_edit_view_select.children).forEach(elem => {
|
263 |
+
if (elem.value.startsWith("style")) {
|
264 |
+
seq_edit_view_select.removeChild(elem)
|
265 |
+
}
|
266 |
+
})
|
267 |
+
|
268 |
+
// Add every option (except Default) to the sliders viewing/editing dropdowns
|
269 |
+
Object.keys(embeddings).forEach(key => {
|
270 |
+
if (key=="default") {
|
271 |
+
return
|
272 |
+
}
|
273 |
+
const opt = createElem("option", `${window.i18n.STYLE_EMB_IS} ${embeddings[key][0]}`)
|
274 |
+
opt.value = `style_${key}`
|
275 |
+
seq_edit_view_select.appendChild(opt)
|
276 |
+
|
277 |
+
const opt2 = createElem("option", `${window.i18n.STYLE_EMB_IS} ${embeddings[key][0]}`)
|
278 |
+
opt2.value = `style_${key}`
|
279 |
+
seq_edit_edit_select.appendChild(opt2)
|
280 |
+
})
|
281 |
+
|
282 |
+
window.appState.currentModelEmbeddings = embeddings
|
283 |
+
}
|
284 |
+
style_emb_select.addEventListener("change", () => window.styleEmbsMenuState.hasChangedEmb)
|
285 |
+
|
286 |
+
|
287 |
+
|
288 |
+
window.styleEmbsModalOpenCallback = () => {
|
289 |
+
styleEmbGameIdInput.value = window.currentGame.gameId
|
290 |
+
if (window.currentModel) {
|
291 |
+
styleEmbVoiceIdInput.value = window.currentModel.voiceId
|
292 |
+
}
|
293 |
+
window.refreshStyleEmbsTable()
|
294 |
+
}
|
295 |
+
window.dragDropWavForEmbComputeFilepathInput = (eType, event) => {
|
296 |
+
if (["dragenter", "dragover"].includes(eType)) {
|
297 |
+
wavFileDragDropArea.style.background = "#5b5b5b"
|
298 |
+
wavFileDragDropArea.style.color = "white"
|
299 |
+
}
|
300 |
+
if (["dragleave", "drop"].includes(eType)) {
|
301 |
+
wavFileDragDropArea.style.background = "rgba(0,0,0,0)"
|
302 |
+
wavFileDragDropArea.style.color = "white"
|
303 |
+
}
|
304 |
+
|
305 |
+
event.preventDefault()
|
306 |
+
event.stopPropagation()
|
307 |
+
|
308 |
+
const dataLines = []
|
309 |
+
|
310 |
+
if (eType=="drop") {
|
311 |
+
const dataTransfer = event.dataTransfer
|
312 |
+
const files = Array.from(dataTransfer.files)
|
313 |
+
|
314 |
+
if (files[0].path.endsWith(".wav")) {
|
315 |
+
wavFilepathForEmbComputeInput.value = String(files[0].path).replaceAll(/\\/g, "/")
|
316 |
+
} else {
|
317 |
+
window.errorModal(window.i18n.ERROR_FILE_MUST_BE_WAV)
|
318 |
+
}
|
319 |
+
}
|
320 |
+
}
|
321 |
+
|
322 |
+
wavFileDragDropArea.addEventListener("dragenter", event => window.dragDropWavForEmbComputeFilepathInput("dragenter", event), false)
|
323 |
+
wavFileDragDropArea.addEventListener("dragleave", event => window.dragDropWavForEmbComputeFilepathInput("dragleave", event), false)
|
324 |
+
wavFileDragDropArea.addEventListener("dragover", event => window.dragDropWavForEmbComputeFilepathInput("dragover", event), false)
|
325 |
+
wavFileDragDropArea.addEventListener("drop", event => window.dragDropWavForEmbComputeFilepathInput("drop", event), false)
|
326 |
+
|
327 |
+
|
328 |
+
getStyleEmbeddingBtn.addEventListener("click", async () => {
|
329 |
+
if (!wavFilepathForEmbComputeInput.value.trim().length) {
|
330 |
+
window.errorModal(window.i18n.ERROR_NEED_WAV_FILE)
|
331 |
+
} else {
|
332 |
+
const embedding = await window.getSpeakerEmbeddingFromFilePath(wavFilepathForEmbComputeInput.value)
|
333 |
+
styleEmbValuesInput.value = embedding
|
334 |
+
}
|
335 |
+
})
|
javascript/textarea.js
ADDED
@@ -0,0 +1,580 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
|
4 |
+
// This doesn't seem to work anymore. TODO, make it work again
|
5 |
+
function getCaretPosition() {
|
6 |
+
var sel = document.selection, range, rect;
|
7 |
+
var x = 0, y = 0;
|
8 |
+
if (sel) {
|
9 |
+
if (sel.type != "Control") {
|
10 |
+
range = sel.createRange();
|
11 |
+
range.collapse(true);
|
12 |
+
x = range.boundingLeft;
|
13 |
+
y = range.boundingTop;
|
14 |
+
}
|
15 |
+
} else if (window.getSelection) {
|
16 |
+
sel = window.getSelection();
|
17 |
+
if (sel.rangeCount) {
|
18 |
+
range = sel.getRangeAt(0).cloneRange();
|
19 |
+
if (range.getClientRects) {
|
20 |
+
range.collapse(true);
|
21 |
+
if (range.getClientRects().length>0){
|
22 |
+
rect = range.getClientRects()[0];
|
23 |
+
x = rect.left;
|
24 |
+
y = rect.top;
|
25 |
+
}
|
26 |
+
}
|
27 |
+
// Fall back to inserting a temporary element
|
28 |
+
if (x == 0 && y == 0) {
|
29 |
+
var span = document.createElement("span");
|
30 |
+
if (span.getClientRects) {
|
31 |
+
// Ensure span has dimensions and position by
|
32 |
+
// adding a zero-width space character
|
33 |
+
span.appendChild( document.createTextNode("\u200b") );
|
34 |
+
range.insertNode(span);
|
35 |
+
rect = span.getClientRects()[0];
|
36 |
+
x = rect.left;
|
37 |
+
y = rect.top;
|
38 |
+
var spanParent = span.parentNode;
|
39 |
+
spanParent.removeChild(span);
|
40 |
+
|
41 |
+
// Glue any broken text nodes back together
|
42 |
+
spanParent.normalize();
|
43 |
+
}
|
44 |
+
}
|
45 |
+
}
|
46 |
+
}
|
47 |
+
return { x: x, y: y };
|
48 |
+
}
|
49 |
+
|
50 |
+
|
51 |
+
window.ARPABET_SYMBOLS_v2 = [
|
52 |
+
"AA0", "AA1", "AA2", "AE0", "AE1", "AE2", "AH0", "AH1", "AH2", "AO0", "AO1","AO2", "AW0", "AW1", "AW2", "AY0", "AY1", "AY2", "B", "CH", "D", "DH",
|
53 |
+
"EH0", "EH1", "EH2", "ER0", "ER1", "ER2", "EY0", "EY1", "EY2", "F", "G", "HH", "IH0", "IH1", "IH2", "IY0", "IY1", "IY2", "JH", "K", "L", "M",
|
54 |
+
"N", "NG", "OW0", "OW1", "OW2", "OY0", "OY1", "OY2", "P", "R", "S", "SH", "T", "TH", "UH0", "UH1", "UH2", "UW0", "UW1", "UW2", "V", "W", "Y", "Z", "ZH"
|
55 |
+
]
|
56 |
+
|
57 |
+
let ARPABET_SYMBOLS = [
|
58 |
+
'AA0', 'AA1', 'AA2', 'AA', 'AE0', 'AE1', 'AE2', 'AE', 'AH0', 'AH1', 'AH2', 'AH',
|
59 |
+
'AO0', 'AO1', 'AO2', 'AO', 'AW0', 'AW1', 'AW2', 'AW', 'AY0', 'AY1', 'AY2', 'AY',
|
60 |
+
'B', 'CH', 'D', 'DH', 'EH0', 'EH1', 'EH2', 'EH', 'ER0', 'ER1', 'ER2', 'ER',
|
61 |
+
'EY0', 'EY1', 'EY2', 'EY', 'F', 'G', 'HH', 'IH0', 'IH1', 'IH2', 'IH', 'IY0', 'IY1',
|
62 |
+
'IY2', 'IY', 'JH', 'K', 'L', 'M', 'N', 'NG', 'OW0', 'OW1', 'OW2', 'OW', 'OY0',
|
63 |
+
'OY1', 'OY2', 'OY', 'P', 'R', 'S', 'SH', 'T', 'TH', 'UH0', 'UH1', 'UH2', 'UH',
|
64 |
+
'UW0', 'UW1', 'UW2', 'UW', 'V', 'W', 'Y', 'Z', 'ZH'
|
65 |
+
]
|
66 |
+
|
67 |
+
let extra_arpabet_symbols = [
|
68 |
+
"AX",
|
69 |
+
"AXR",
|
70 |
+
"IX",
|
71 |
+
"UX",
|
72 |
+
"DX",
|
73 |
+
"EL",
|
74 |
+
"EM",
|
75 |
+
"EN0",
|
76 |
+
"EN1",
|
77 |
+
"EN2",
|
78 |
+
"EN",
|
79 |
+
"NX",
|
80 |
+
"Q",
|
81 |
+
"WH",
|
82 |
+
]
|
83 |
+
let new_arpabet_symbols = [
|
84 |
+
"RRR",
|
85 |
+
"HR",
|
86 |
+
"OE",
|
87 |
+
"RH",
|
88 |
+
"TS",
|
89 |
+
|
90 |
+
"RR",
|
91 |
+
"UU",
|
92 |
+
"OO",
|
93 |
+
"KH",
|
94 |
+
"SJ",
|
95 |
+
"HJ",
|
96 |
+
"BR",
|
97 |
+
]
|
98 |
+
window.ARPABET_SYMBOLS_v3 = ARPABET_SYMBOLS.concat(extra_arpabet_symbols).concat(new_arpabet_symbols).sort((a,b)=>a<b?-1:1)
|
99 |
+
|
100 |
+
const preprocess = (caretStart) => {
|
101 |
+
|
102 |
+
const defaultLang = "en"
|
103 |
+
let text = dialogueInput.value
|
104 |
+
|
105 |
+
const finishedParts = []
|
106 |
+
// const parts = text.split(/\\lang\{[a-z]{0,2}\}\{/gi)
|
107 |
+
const parts = text.split(/\\lang\[[a-z]{0,2}\]\[/gi)
|
108 |
+
// const matchIterator = text.matchAll(/\\lang\{[a-z]{0,2}\}\{/gi)
|
109 |
+
const matchIterator = text.matchAll(/\\lang\[[a-z]{0,2}\]\[/gi)
|
110 |
+
finishedParts.push([defaultLang, parts[0]])
|
111 |
+
|
112 |
+
const langStack = []
|
113 |
+
|
114 |
+
// console.log("parts", parts)
|
115 |
+
let textCounter = parts[0].length
|
116 |
+
let caretInARPAbet = false
|
117 |
+
|
118 |
+
parts.forEach((part,pi) => {
|
119 |
+
if (pi) {
|
120 |
+
const match = matchIterator.next().value[0]
|
121 |
+
const langCode = match.split("lang[")[1].split("]")[0]
|
122 |
+
langStack.push(langCode)
|
123 |
+
// console.log("add langStack", langStack)
|
124 |
+
|
125 |
+
textCounter += match.length
|
126 |
+
|
127 |
+
let unescaped_part = ""
|
128 |
+
|
129 |
+
part.split("]").forEach(sub_part => {
|
130 |
+
// console.log("sub_part", sub_part, textCounter, textCounter+sub_part.length)
|
131 |
+
|
132 |
+
if (caretStart > textCounter && caretStart <textCounter+sub_part.length) {
|
133 |
+
caretInARPAbet = true
|
134 |
+
|
135 |
+
// If backspace-ing a "{", and the next non-space character is "}", then delete that also
|
136 |
+
|
137 |
+
// Add ctrl+<space> to open the auto-complete
|
138 |
+
|
139 |
+
}
|
140 |
+
|
141 |
+
// if (sub_part.includes("[")) {
|
142 |
+
// unescaped_part += sub_part+"]"
|
143 |
+
// return
|
144 |
+
// }
|
145 |
+
|
146 |
+
sub_part = unescaped_part+sub_part
|
147 |
+
|
148 |
+
unescaped_part = ""
|
149 |
+
|
150 |
+
if (part.includes("]")) {
|
151 |
+
finishedParts.push([langStack.pop()||defaultLang, sub_part])
|
152 |
+
} else {
|
153 |
+
finishedParts.push([langStack[langStack.length-1], sub_part])
|
154 |
+
}
|
155 |
+
// finishedParts.push([langStack.pop()||defaultLang, sub_part])
|
156 |
+
})
|
157 |
+
}
|
158 |
+
})
|
159 |
+
return caretInARPAbet
|
160 |
+
}
|
161 |
+
|
162 |
+
window.hideAutocomplete = () => {
|
163 |
+
textEditorTooltip.style.display = "none"
|
164 |
+
textEditorTooltip.innerHTML = ""
|
165 |
+
autocomplete_callback = undefined
|
166 |
+
}
|
167 |
+
let textWrittenSinceAutocompleteWasShown = ""
|
168 |
+
const filterOrHideAutocomplete = () => {
|
169 |
+
if (textEditorTooltip.style.display=="flex") {
|
170 |
+
highlightedAutocompleteIndex = 0
|
171 |
+
let childrenShown = 0
|
172 |
+
Array.from(textEditorTooltip.children).forEach(child => {
|
173 |
+
if (child.classList.contains("autocomplete_option_active")) {
|
174 |
+
child.classList.toggle("autocomplete_option_active")
|
175 |
+
}
|
176 |
+
|
177 |
+
if (child.innerText.toLowerCase().startsWith(textWrittenSinceAutocompleteWasShown) || textWrittenSinceAutocompleteWasShown.length==0) {
|
178 |
+
child.style.display = "flex"
|
179 |
+
childrenShown += 1
|
180 |
+
} else {
|
181 |
+
child.style.display = "none"
|
182 |
+
}
|
183 |
+
})
|
184 |
+
if (childrenShown==0) {
|
185 |
+
hideAutocomplete()
|
186 |
+
return
|
187 |
+
}
|
188 |
+
setHighlightedAutocomplete(0, true)
|
189 |
+
}
|
190 |
+
}
|
191 |
+
let autocomplete_callback = undefined
|
192 |
+
const showAutocomplete = (options, callback) => {
|
193 |
+
const position = getCaretPosition(dialogueInput)
|
194 |
+
|
195 |
+
// The getCaretPosition function doesn't work anymore. At least center it
|
196 |
+
position.x = window.visualViewport.width/2
|
197 |
+
|
198 |
+
textEditorTooltip.style.left = position.x + "px"
|
199 |
+
textEditorTooltip.style.top = position.y + "px"
|
200 |
+
|
201 |
+
highlightedAutocompleteIndex = 0
|
202 |
+
textWrittenSinceAutocompleteWasShown = ""
|
203 |
+
autocomplete_callback = callback
|
204 |
+
|
205 |
+
options.forEach(option => {
|
206 |
+
const optElem = createElem("div.autocomplete_option", option[0])
|
207 |
+
optElem.dataset.autocomplete_return = option.length>1 ? option[1] : option[0]
|
208 |
+
optElem.addEventListener("click", () => {
|
209 |
+
callback(optElem.dataset.autocomplete_return)
|
210 |
+
hideAutocomplete()
|
211 |
+
refreshText()
|
212 |
+
})
|
213 |
+
textEditorTooltip.appendChild(optElem)
|
214 |
+
})
|
215 |
+
textEditorTooltip.style.display = "flex"
|
216 |
+
setHighlightedAutocomplete(0, true)
|
217 |
+
return
|
218 |
+
}
|
219 |
+
|
220 |
+
let highlightedAutocompleteIndex = 0
|
221 |
+
const setHighlightedAutocomplete = (delta, override=false) => {
|
222 |
+
|
223 |
+
let NEW_highlightedAutocompleteIndex = Math.min(textEditorTooltip.children.length-1, Math.max(0, highlightedAutocompleteIndex+delta))
|
224 |
+
if (override || NEW_highlightedAutocompleteIndex != highlightedAutocompleteIndex) {
|
225 |
+
if (!override) {
|
226 |
+
textEditorTooltip.children[highlightedAutocompleteIndex].classList.toggle("autocomplete_option_active")
|
227 |
+
}
|
228 |
+
textEditorTooltip.children[override ? delta : NEW_highlightedAutocompleteIndex].classList.toggle("autocomplete_option_active")
|
229 |
+
highlightedAutocompleteIndex = NEW_highlightedAutocompleteIndex
|
230 |
+
// textEditorTooltip.children[highlightedAutocompleteIndex].scrollIntoView()
|
231 |
+
}
|
232 |
+
}
|
233 |
+
|
234 |
+
|
235 |
+
dialogueInput.addEventListener("keydown", event => {
|
236 |
+
if (event.key=="Tab" || event.key=="Enter") {
|
237 |
+
event.preventDefault()
|
238 |
+
|
239 |
+
if (autocomplete_callback!==undefined) {
|
240 |
+
autocomplete_callback(textEditorTooltip.children[highlightedAutocompleteIndex].dataset.autocomplete_return)
|
241 |
+
hideAutocomplete()
|
242 |
+
refreshText()
|
243 |
+
return
|
244 |
+
}
|
245 |
+
|
246 |
+
let cursorIndex = dialogueInput.selectionStart
|
247 |
+
if (cursorIndex) {
|
248 |
+
// Move into the next [] bracket if already wrote down language
|
249 |
+
let textPreCursor = dialogueInput.value.slice(0, cursorIndex)
|
250 |
+
let textPostCursor = dialogueInput.value.slice(cursorIndex, dialogueInput.value.length)
|
251 |
+
|
252 |
+
if (textPreCursor.slice(textPreCursor.length-8, 7)=="\\lang[") {
|
253 |
+
dialogueInput.setSelectionRange(cursorIndex+2,cursorIndex+2)
|
254 |
+
|
255 |
+
// Move out of [] if at the end
|
256 |
+
} else if (textEditorTooltip.style.display=="none" && (textPostCursor.startsWith("]") || textPostCursor.startsWith("}"))) {
|
257 |
+
console.log("moving one")
|
258 |
+
dialogueInput.setSelectionRange(cursorIndex+1,cursorIndex+1)
|
259 |
+
}
|
260 |
+
|
261 |
+
}
|
262 |
+
|
263 |
+
}
|
264 |
+
})
|
265 |
+
|
266 |
+
const splitWords = (sequence, addSpace) => {
|
267 |
+
const words = []
|
268 |
+
|
269 |
+
// const sequenceProcessed = sequence
|
270 |
+
const sequenceProcessed = [] // Do further processing to also split on { } symbols, not just spaces
|
271 |
+
sequence.forEach(word => {
|
272 |
+
if (word.includes("{")) {
|
273 |
+
word.split("{").forEach((w, wi) => {
|
274 |
+
sequenceProcessed.push(wi ? ["{"+w, addSpace] : [w, false])
|
275 |
+
})
|
276 |
+
} else if (word.includes("}")) {
|
277 |
+
word.split("}").forEach((w, wi) => {
|
278 |
+
sequenceProcessed.push(wi ? [w, addSpace] : [w+"}", false])
|
279 |
+
})
|
280 |
+
} else {
|
281 |
+
sequenceProcessed.push([word, addSpace])
|
282 |
+
}
|
283 |
+
})
|
284 |
+
|
285 |
+
sequenceProcessed.forEach(([word, addSpace]) => {
|
286 |
+
|
287 |
+
if (word.startsWith("\\lang[")) {
|
288 |
+
words.push(word.split("][")[0]+"][")
|
289 |
+
word = word.split("][")[1]
|
290 |
+
}
|
291 |
+
|
292 |
+
["}","]","[","{"].forEach(char => {
|
293 |
+
if (word.startsWith(char)) {
|
294 |
+
words.push(char)
|
295 |
+
word = word.slice(1,word.length)
|
296 |
+
}
|
297 |
+
})
|
298 |
+
|
299 |
+
const endExtras = [];
|
300 |
+
|
301 |
+
["}","]","[","{"].forEach(char => {
|
302 |
+
if (word.endsWith(char)) {
|
303 |
+
endExtras.push(char)
|
304 |
+
word = word.slice(0,word.length-1)
|
305 |
+
}
|
306 |
+
})
|
307 |
+
|
308 |
+
words.push(word)
|
309 |
+
endExtras.reverse().forEach(extra => words.push(extra))
|
310 |
+
|
311 |
+
// if (word.startsWith("{")) {
|
312 |
+
// split_words.push("{")
|
313 |
+
// word = word.slice(1,word.length)
|
314 |
+
// }
|
315 |
+
// if (word.endsWith("}")) {
|
316 |
+
// split_words.push("{")
|
317 |
+
// word = word.slice(1,word.length)
|
318 |
+
// }
|
319 |
+
|
320 |
+
if (addSpace) {
|
321 |
+
words.push(" ")
|
322 |
+
}
|
323 |
+
})
|
324 |
+
|
325 |
+
return words
|
326 |
+
}
|
327 |
+
|
328 |
+
window.refreshText = () => {
|
329 |
+
let all_text = dialogueInput.value
|
330 |
+
textEditorElem.innerHTML = ""
|
331 |
+
|
332 |
+
let openedCurlys = 0
|
333 |
+
let openedLangs = 0
|
334 |
+
|
335 |
+
let split_words = splitWords(all_text.split(" "), true)
|
336 |
+
split_words = splitWords(split_words)
|
337 |
+
split_words = splitWords(split_words)
|
338 |
+
split_words = splitWords(split_words)
|
339 |
+
|
340 |
+
// console.log("split_words", split_words)
|
341 |
+
// dfgd()
|
342 |
+
|
343 |
+
let caretCounter = 0
|
344 |
+
let caretInARPAbet = false
|
345 |
+
|
346 |
+
let firstOpenCurly = undefined
|
347 |
+
let lastOpenCurly = undefined
|
348 |
+
|
349 |
+
split_words.forEach(word => {
|
350 |
+
|
351 |
+
if (caretCounter<=dialogueInput.selectionStart && (caretCounter+word.length)>dialogueInput.selectionStart) {
|
352 |
+
// console.log(`caret (${dialogueInput.selectionStart}) in counter (${caretCounter}): `, word, openedCurlys, openedLangs)
|
353 |
+
caretInARPAbet = openedCurlys > 0
|
354 |
+
}
|
355 |
+
caretCounter += word.length
|
356 |
+
const spanElem = createElem("span.manyWhitespace", word)
|
357 |
+
|
358 |
+
if (word.startsWith("\\lang[")) {
|
359 |
+
openedLangs += 1
|
360 |
+
}
|
361 |
+
if (word.startsWith("{")) {
|
362 |
+
openedCurlys += 1
|
363 |
+
if (!caretInARPAbet) {
|
364 |
+
firstOpenCurly = spanElem
|
365 |
+
}
|
366 |
+
}
|
367 |
+
|
368 |
+
if (openedCurlys) {
|
369 |
+
spanElem.style.fontWeight = "bold"
|
370 |
+
spanElem.style.fontStyle = "italic"
|
371 |
+
}
|
372 |
+
if (openedLangs) {
|
373 |
+
spanElem.style.background = "rgba(50, 150, 250, 0.2)"
|
374 |
+
}
|
375 |
+
|
376 |
+
///====
|
377 |
+
if (word.includes("part-highlighted")) {
|
378 |
+
spanElem.style.textDecoration = "underline dotted red"
|
379 |
+
} else if (word.includes("highlighted")) {
|
380 |
+
spanElem.style.textDecoration = "underline solid red"
|
381 |
+
}
|
382 |
+
///====
|
383 |
+
|
384 |
+
if (word.endsWith("]")) {
|
385 |
+
openedLangs -= 1
|
386 |
+
}
|
387 |
+
if (word.endsWith("}")) {
|
388 |
+
openedCurlys -= 1
|
389 |
+
if (caretInARPAbet && lastOpenCurly===undefined) {
|
390 |
+
lastOpenCurly = spanElem
|
391 |
+
}
|
392 |
+
}
|
393 |
+
|
394 |
+
textEditorElem.appendChild(spanElem)
|
395 |
+
|
396 |
+
})
|
397 |
+
|
398 |
+
preprocess(dialogueInput.selectionStart)
|
399 |
+
return [caretInARPAbet, firstOpenCurly, lastOpenCurly]
|
400 |
+
}
|
401 |
+
|
402 |
+
const languagesList = Object.keys(window.supportedLanguages)
|
403 |
+
|
404 |
+
const insertText = (inputTextArea, textToInsert, caretOffset=0) => {
|
405 |
+
let cursorIndex = inputTextArea.selectionStart
|
406 |
+
inputTextArea.value = inputTextArea.value.slice(0, cursorIndex) + textToInsert + inputTextArea.value.slice(cursorIndex, inputTextArea.value.length)
|
407 |
+
caretOffset += textToInsert.length
|
408 |
+
inputTextArea.setSelectionRange(cursorIndex+caretOffset,cursorIndex+caretOffset)
|
409 |
+
refreshText()
|
410 |
+
}
|
411 |
+
|
412 |
+
dialogueInput.addEventListener("keydown", event => {
|
413 |
+
generateVoiceButton.disabled = window.currentModel==undefined || !dialogueInput.value.length
|
414 |
+
|
415 |
+
if (event.key=="Enter") {
|
416 |
+
event.stopPropagation()
|
417 |
+
event.preventDefault()
|
418 |
+
return
|
419 |
+
}
|
420 |
+
|
421 |
+
if (textEditorTooltip.style.display=="flex" && (event.key=="ArrowDown" || event.key=="ArrowUp" || (!window.shiftKeyIsPressed && event.key=="ArrowLeft") || (!window.shiftKeyIsPressed && event.key=="ArrowRight"))) {
|
422 |
+
// if (textEditorTooltip.style.display=="flex" && (event.key=="ArrowDown" || event.key=="ArrowUp")) {
|
423 |
+
event.stopPropagation()
|
424 |
+
event.preventDefault()
|
425 |
+
return
|
426 |
+
}
|
427 |
+
if (event.key=="}") {
|
428 |
+
if (dialogueInput.value.slice(dialogueInput.selectionStart, dialogueInput.value.length-1).startsWith("}")) {
|
429 |
+
dialogueInput.setSelectionRange(dialogueInput.selectionStart+1, dialogueInput.selectionStart+1)
|
430 |
+
event.stopPropagation()
|
431 |
+
event.preventDefault()
|
432 |
+
return
|
433 |
+
}
|
434 |
+
}
|
435 |
+
if (event.key=="]") {
|
436 |
+
if (dialogueInput.value.slice(dialogueInput.selectionStart, dialogueInput.value.length-1).startsWith("]")) {
|
437 |
+
dialogueInput.setSelectionRange(dialogueInput.selectionStart+1, dialogueInput.selectionStart+1)
|
438 |
+
event.stopPropagation()
|
439 |
+
event.preventDefault()
|
440 |
+
return
|
441 |
+
}
|
442 |
+
}
|
443 |
+
})
|
444 |
+
|
445 |
+
let is_doing_gp2 = false
|
446 |
+
window.get_g2p = (text_to_g2p) => {
|
447 |
+
return new Promise(resolve => {
|
448 |
+
doFetch("http://localhost:8008/getG2P", {method: "Post", body: JSON.stringify({base_lang: base_lang_select.value, text: text_to_g2p})})
|
449 |
+
.then(r=>r.text()).then(res => {
|
450 |
+
is_doing_gp2 = false
|
451 |
+
resolve(res)
|
452 |
+
})
|
453 |
+
})
|
454 |
+
}
|
455 |
+
|
456 |
+
const handleTextUpdate = (event) => {
|
457 |
+
generateVoiceButton.disabled = window.currentModel==undefined || !dialogueInput.value.length
|
458 |
+
|
459 |
+
window.shiftKeyIsPressed = event.shiftKey
|
460 |
+
|
461 |
+
if (textEditorTooltip.style.display=="flex" && (event.type=="click" || (!window.shiftKeyIsPressed && event.key=="ArrowDown") || (!window.shiftKeyIsPressed && event.key=="ArrowRight"))) {
|
462 |
+
event.stopPropagation()
|
463 |
+
event.preventDefault()
|
464 |
+
setHighlightedAutocomplete(1)
|
465 |
+
return
|
466 |
+
}
|
467 |
+
if (textEditorTooltip.style.display=="flex" && (event.type=="click" || (!window.shiftKeyIsPressed && event.key=="ArrowLeft") || (!window.shiftKeyIsPressed && event.key=="ArrowUp"))) {
|
468 |
+
event.stopPropagation()
|
469 |
+
event.preventDefault()
|
470 |
+
setHighlightedAutocomplete(-1)
|
471 |
+
return
|
472 |
+
}
|
473 |
+
|
474 |
+
|
475 |
+
if (event.type!="click" && (event.key=="Shift" || event.key=="Control")) {
|
476 |
+
event.stopPropagation()
|
477 |
+
event.preventDefault()
|
478 |
+
return
|
479 |
+
}
|
480 |
+
|
481 |
+
const [caretInARPAbet, firstOpenCurly, lastOpenCurly] = refreshText()
|
482 |
+
if (caretInARPAbet) {
|
483 |
+
firstOpenCurly && (firstOpenCurly.style.color = "red")
|
484 |
+
lastOpenCurly && (lastOpenCurly.style.color = "red")
|
485 |
+
}
|
486 |
+
|
487 |
+
|
488 |
+
textEditorElem.scrollTop = dialogueInput.scrollTop
|
489 |
+
|
490 |
+
|
491 |
+
if (dialogueInput.selectionStart!=dialogueInput.selectionEnd && !is_doing_gp2) {
|
492 |
+
|
493 |
+
hideAutocomplete()
|
494 |
+
|
495 |
+
showAutocomplete([["<Convert to phonemes>"]], () => {
|
496 |
+
|
497 |
+
const text_to_g2p = dialogueInput.value.slice(dialogueInput.selectionStart, dialogueInput.selectionEnd)
|
498 |
+
is_doing_gp2 = true
|
499 |
+
|
500 |
+
get_g2p(text_to_g2p).then(phonemes => {
|
501 |
+
const initialStart = dialogueInput.selectionStart
|
502 |
+
dialogueInput.value = dialogueInput.value.slice(0, dialogueInput.selectionStart) + dialogueInput.value.slice(dialogueInput.selectionEnd, dialogueInput.value.length)
|
503 |
+
|
504 |
+
dialogueInput.selectionStart = initialStart
|
505 |
+
|
506 |
+
insertText(dialogueInput, phonemes, 0)
|
507 |
+
})
|
508 |
+
})
|
509 |
+
|
510 |
+
} else
|
511 |
+
// } else {
|
512 |
+
if (event.type!="click" && event.key.length==1 && event.key.match(/[a-z]/i)) {
|
513 |
+
textWrittenSinceAutocompleteWasShown += event.key.toLowerCase()
|
514 |
+
filterOrHideAutocomplete()
|
515 |
+
} else if (event.type!="click" && event.key=="Backspace") {
|
516 |
+
if (textWrittenSinceAutocompleteWasShown.length==0) {
|
517 |
+
hideAutocomplete()
|
518 |
+
} else {
|
519 |
+
textWrittenSinceAutocompleteWasShown = textWrittenSinceAutocompleteWasShown.slice(0,textWrittenSinceAutocompleteWasShown.length-1)
|
520 |
+
filterOrHideAutocomplete()
|
521 |
+
}
|
522 |
+
} else {
|
523 |
+
hideAutocomplete()
|
524 |
+
}
|
525 |
+
|
526 |
+
const ctrlSpace = event.ctrlKey && event.code=="Space"
|
527 |
+
|
528 |
+
if (event.type!="click" && (event.key=="{" || event.key=="") || ctrlSpace) {
|
529 |
+
if (!ctrlSpace && event.key!="") {
|
530 |
+
insertText(dialogueInput, "}", -1)
|
531 |
+
}
|
532 |
+
|
533 |
+
if (caretInARPAbet) {
|
534 |
+
let symbols = window.ARPABET_SYMBOLS_v3
|
535 |
+
if (window.currentModel&&window.currentModel.modelType=="FastPitch1.1") {
|
536 |
+
symbols = window.ARPABET_SYMBOLS_v2
|
537 |
+
} else if (window.currentModel&&window.currentModel.modelType=="FastPitch") {
|
538 |
+
symbols = ["<ARPAbet only available for v2+ models>"]
|
539 |
+
}
|
540 |
+
showAutocomplete(symbols.map(v=>{return [v]}), option => {
|
541 |
+
if (symbols.length>1) {
|
542 |
+
insertText(dialogueInput, option.slice(textWrittenSinceAutocompleteWasShown.length, option.length)+" ", 0)
|
543 |
+
}
|
544 |
+
})
|
545 |
+
}
|
546 |
+
if (event.key!="") {
|
547 |
+
handleTextUpdate({type: "keydown", key: ""})
|
548 |
+
}
|
549 |
+
}
|
550 |
+
|
551 |
+
if (event.type!="click" && event.key=="\\") {
|
552 |
+
// showAutocomplete([["\\lang[language][text]", "\\lang[][]"], ["\\sil[milliseconds]", "\\sil[]"]], (option) => {
|
553 |
+
showAutocomplete([["\\lang[language][text]", "\\lang[][]"]], (option) => {
|
554 |
+
|
555 |
+
if (option.includes("lang")) {
|
556 |
+
insertText(dialogueInput, option.slice(1, option.length), -3)
|
557 |
+
} else {
|
558 |
+
insertText(dialogueInput, option.slice(1, option.length), -1)
|
559 |
+
}
|
560 |
+
|
561 |
+
setTimeout(() => {
|
562 |
+
showAutocomplete(languagesList.map(v=>{return [v]}), option => {
|
563 |
+
insertText(dialogueInput, option.slice(textWrittenSinceAutocompleteWasShown.length, option.length), 2)
|
564 |
+
})
|
565 |
+
}, 100)
|
566 |
+
})
|
567 |
+
}
|
568 |
+
// }
|
569 |
+
// })
|
570 |
+
}
|
571 |
+
dialogueInput.addEventListener("click", event => handleTextUpdate(event))
|
572 |
+
dialogueInput.addEventListener("keyup", event => handleTextUpdate(event))
|
573 |
+
refreshText()
|
574 |
+
setTimeout(window.refreshText, 500)
|
575 |
+
|
576 |
+
window.addEventListener("click", event => {
|
577 |
+
if (event.target && event.target!=textEditorTooltip && event.target.className && event.target.className.includes && !event.target.className.includes("autocomplete_option")) {
|
578 |
+
hideAutocomplete()
|
579 |
+
}
|
580 |
+
})
|
javascript/totd.js
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
// tip of the day
|
3 |
+
|
4 |
+
const tips = {
|
5 |
+
"1": window.i18n.TOTD_1,
|
6 |
+
"2": window.i18n.TOTD_2,
|
7 |
+
"3": window.i18n.TOTD_3,
|
8 |
+
"4": window.i18n.TOTD_4,
|
9 |
+
"5": window.i18n.TOTD_5,
|
10 |
+
"6": window.i18n.TOTD_6,
|
11 |
+
"7": window.i18n.TOTD_7,
|
12 |
+
"8": window.i18n.TOTD_8,
|
13 |
+
"9": window.i18n.TOTD_9,
|
14 |
+
"10": window.i18n.TOTD_10,
|
15 |
+
"11": window.i18n.TOTD_11,
|
16 |
+
"12": window.i18n.TOTD_12,
|
17 |
+
"13": window.i18n.TOTD_13,
|
18 |
+
"14": window.i18n.TOTD_14,
|
19 |
+
"15": window.i18n.TOTD_15,
|
20 |
+
"16": window.i18n.TOTD_16,
|
21 |
+
"17": window.i18n.TOTD_17,
|
22 |
+
"18": window.i18n.TOTD_18,
|
23 |
+
"19": window.i18n.TOTD_19,
|
24 |
+
"20": window.i18n.TOTD_20,
|
25 |
+
"21": window.i18n.TOTD_21,
|
26 |
+
"22": window.i18n.TOTD_22,
|
27 |
+
"23": window.i18n.TOTD_23,
|
28 |
+
"24": window.i18n.TOTD_24,
|
29 |
+
"25": window.i18n.TOTD_25,
|
30 |
+
"26": window.i18n.TOTD_26,
|
31 |
+
"27": window.i18n.TOTD_27,
|
32 |
+
"28": window.i18n.TOTD_28,
|
33 |
+
"29": window.i18n.TOTD_29,
|
34 |
+
"30": window.i18n.TOTD_30,
|
35 |
+
"31": window.i18n.TOTD_31,
|
36 |
+
"32": window.i18n.TOTD_32,
|
37 |
+
}
|
38 |
+
|
39 |
+
window.totd_state = {
|
40 |
+
startupChecked: false,
|
41 |
+
filteredIDs: [],
|
42 |
+
tipPageIndex: 0
|
43 |
+
}
|
44 |
+
|
45 |
+
const initTipOfTheDayMenu = (now, tipIDs) => {
|
46 |
+
|
47 |
+
window.totd_state.filteredIDs = tipIDs
|
48 |
+
|
49 |
+
totdContainer.style.opacity = 1
|
50 |
+
totdContainer.style.display = "flex"
|
51 |
+
chromeBar.style.opacity = 1
|
52 |
+
requestAnimationFrame(() => requestAnimationFrame(() => totdContainer.style.opacity = 1))
|
53 |
+
|
54 |
+
return new Promise(resolve => {
|
55 |
+
// Close button
|
56 |
+
totd_close.addEventListener("click", () => {
|
57 |
+
closeModal(totdContainer)
|
58 |
+
resolve()
|
59 |
+
})
|
60 |
+
|
61 |
+
localStorage.setItem("totd_lastDate", now.toJSON(now))
|
62 |
+
tipMessage.innerHTML = tips[window.totd_state.filteredIDs[0]]
|
63 |
+
|
64 |
+
totd_counter.innerHTML = `1/${window.totd_state.filteredIDs.length}`
|
65 |
+
|
66 |
+
saveSeenTip(window.totd_state.filteredIDs[0])
|
67 |
+
})
|
68 |
+
}
|
69 |
+
|
70 |
+
|
71 |
+
const saveSeenTip = ID => {
|
72 |
+
let seenTipIDs = localStorage.getItem("totd_seenIDs")
|
73 |
+
seenTipIDs = seenTipIDs ? seenTipIDs.split(",") : []
|
74 |
+
seenTipIDs = new Set(seenTipIDs)
|
75 |
+
seenTipIDs.add(ID)
|
76 |
+
localStorage.setItem("totd_seenIDs", Array.from(seenTipIDs).join(","))
|
77 |
+
}
|
78 |
+
|
79 |
+
setting_btnShowTOTD.addEventListener("click", () => {
|
80 |
+
window.showTipIfEnabledAndNewDay(true)
|
81 |
+
})
|
82 |
+
|
83 |
+
|
84 |
+
totdPrevTipBtn.addEventListener("click", () => {
|
85 |
+
const newIndex = Math.max(0, window.totd_state.tipPageIndex-1)
|
86 |
+
if (newIndex!=window.totd_state.tipPageIndex) {
|
87 |
+
window.totd_state.tipPageIndex = newIndex
|
88 |
+
tipMessage.innerHTML = tips[window.totd_state.filteredIDs[window.totd_state.tipPageIndex]]
|
89 |
+
saveSeenTip(window.totd_state.filteredIDs[window.totd_state.tipPageIndex])
|
90 |
+
totd_counter.innerHTML = `${window.totd_state.tipPageIndex+1}/${window.totd_state.filteredIDs.length}`
|
91 |
+
}
|
92 |
+
})
|
93 |
+
|
94 |
+
totdNextTipBtn.addEventListener("click", () => {
|
95 |
+
const newIndex = Math.min(window.totd_state.filteredIDs.length-1, window.totd_state.tipPageIndex+1)
|
96 |
+
if (newIndex!=window.totd_state.tipPageIndex) {
|
97 |
+
window.totd_state.tipPageIndex = newIndex
|
98 |
+
tipMessage.innerHTML = tips[window.totd_state.filteredIDs[window.totd_state.tipPageIndex]]
|
99 |
+
saveSeenTip(window.totd_state.filteredIDs[window.totd_state.tipPageIndex])
|
100 |
+
totd_counter.innerHTML = `${window.totd_state.tipPageIndex+1}/${window.totd_state.filteredIDs.length}`
|
101 |
+
}
|
102 |
+
})
|
103 |
+
|
104 |
+
|
105 |
+
window.showTipIfEnabledAndNewDay = (justShowIt) => {
|
106 |
+
|
107 |
+
window.totd_state.startupChecked = true
|
108 |
+
|
109 |
+
return new Promise(async resolve => {
|
110 |
+
const lastDate = localStorage.getItem("totd_lastDate")
|
111 |
+
const now = new Date()
|
112 |
+
|
113 |
+
// If this has never happened before, or the last date is not today, then show the tip menu
|
114 |
+
if (justShowIt || !lastDate || lastDate.split("T")[0]!=now.toJSON().split("T")[0]) {
|
115 |
+
|
116 |
+
// If the tips of the day are enabled
|
117 |
+
if (justShowIt || window.userSettings.showTipOfTheDay) {
|
118 |
+
|
119 |
+
let shuffledTipIDs = window.shuffle(Object.keys(tips))
|
120 |
+
|
121 |
+
// If only new/unseen tips are to be shown, get the seen list, and filter out the seen tips
|
122 |
+
if (window.userSettings.showUnseenTipOfTheDay) {
|
123 |
+
let seenTipIDs = localStorage.getItem("totd_seenIDs")
|
124 |
+
if (seenTipIDs) {
|
125 |
+
seenTipIDs = seenTipIDs.split(",")
|
126 |
+
shuffledTipIDs = shuffledTipIDs.filter(id => !seenTipIDs.includes(id))
|
127 |
+
}
|
128 |
+
}
|
129 |
+
|
130 |
+
// If there are any tips remaining, after any filtering, then show the menu
|
131 |
+
if (shuffledTipIDs && shuffledTipIDs.length) {
|
132 |
+
await initTipOfTheDayMenu(now, shuffledTipIDs)
|
133 |
+
resolve()
|
134 |
+
} else if (justShowIt) {
|
135 |
+
window.errorModal(window.i18n.TOTD_NO_UNSEEN)
|
136 |
+
}
|
137 |
+
} else {
|
138 |
+
resolve()
|
139 |
+
}
|
140 |
+
} else {
|
141 |
+
resolve()
|
142 |
+
}
|
143 |
+
})
|
144 |
+
}
|
145 |
+
|
javascript/util.js
ADDED
@@ -0,0 +1,740 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
const unzipper = require('unzipper')
|
4 |
+
const er = require('@electron/remote')
|
5 |
+
|
6 |
+
/**
|
7 |
+
* String.prototype.replaceAll() polyfill
|
8 |
+
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
|
9 |
+
* @author Chris Ferdinandi
|
10 |
+
* @license MIT
|
11 |
+
*/
|
12 |
+
if (!String.prototype.replaceAll) {
|
13 |
+
String.prototype.replaceAll = function(str, newStr){
|
14 |
+
|
15 |
+
// If a regex pattern
|
16 |
+
if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
|
17 |
+
return this.replace(str, newStr);
|
18 |
+
}
|
19 |
+
|
20 |
+
// If a string
|
21 |
+
return this.replace(new RegExp(str, 'g'), newStr);
|
22 |
+
|
23 |
+
};
|
24 |
+
}
|
25 |
+
|
26 |
+
|
27 |
+
window.toggleSpinnerButtons = (spinnerVisible=undefined) => {
|
28 |
+
|
29 |
+
if (spinnerVisible===undefined) {
|
30 |
+
spinnerVisible = window.getComputedStyle(spinner).display == "block"
|
31 |
+
}
|
32 |
+
|
33 |
+
spinner.style.display = spinnerVisible ? "none" : "block"
|
34 |
+
keepSampleButton.style.display = spinnerVisible ? "block" : "none"
|
35 |
+
generateVoiceButton.style.display = spinnerVisible ? "block" : "none"
|
36 |
+
samplePlayPause.style.display = spinnerVisible ? "flex" : "none"
|
37 |
+
}
|
38 |
+
|
39 |
+
window.infoModal = message => new Promise(resolve => resolve(createModal("info", message)))
|
40 |
+
window.confirmModal = message => new Promise(resolve => resolve(createModal("confirm", message)))
|
41 |
+
window.spinnerModal = message => new Promise(resolve => resolve(createModal("spinner", message)))
|
42 |
+
window.errorModal = message => {
|
43 |
+
window.errorModalHasOpened = true
|
44 |
+
if (window.userSettings.useErrorSound) {
|
45 |
+
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", {
|
46 |
+
src: window.userSettings.errorSoundFile
|
47 |
+
}))
|
48 |
+
audioPreview.setSinkId(window.userSettings.base_speaker)
|
49 |
+
}
|
50 |
+
window.electronBrowserWindow.setProgressBar(window.batch_state.taskBarPercent?window.batch_state.taskBarPercent:1, {mode: "error"})
|
51 |
+
return new Promise(topResolve => {
|
52 |
+
createModal("error", message).then(() => {
|
53 |
+
window.electronBrowserWindow.setProgressBar(window.batch_state.taskBarPercent?window.batch_state.taskBarPercent:-1, {mode: window.batch_state.state?"normal":"paused"})
|
54 |
+
topResolve()
|
55 |
+
})
|
56 |
+
})
|
57 |
+
}
|
58 |
+
window.createModal = (type, message) => {
|
59 |
+
dialogueInput.blur()
|
60 |
+
return new Promise(resolve => {
|
61 |
+
modalContainer.innerHTML = ""
|
62 |
+
const displayMessage = message.prompt ? message.prompt : message
|
63 |
+
const modal = createElem("div.modal#activeModal", {style: {opacity: 0}}, createElem("span.createModalContents", displayMessage))
|
64 |
+
modal.dataset.type = type
|
65 |
+
|
66 |
+
if (type=="confirm") {
|
67 |
+
const yesButton = createElem("button", {style: {background: `#${themeColour}`}})
|
68 |
+
yesButton.innerHTML = window.i18n.YES
|
69 |
+
const noButton = createElem("button", {style: {background: `#${themeColour}`}})
|
70 |
+
noButton.innerHTML = window.i18n.NO
|
71 |
+
modal.appendChild(createElem("div", yesButton, noButton))
|
72 |
+
|
73 |
+
yesButton.addEventListener("click", () => {
|
74 |
+
closeModal(modalContainer).then(() => {
|
75 |
+
resolve(true)
|
76 |
+
})
|
77 |
+
})
|
78 |
+
noButton.addEventListener("click", () => {
|
79 |
+
closeModal(modalContainer).then(() => {
|
80 |
+
resolve(false)
|
81 |
+
})
|
82 |
+
})
|
83 |
+
} else if (type=="error" || type=="info") {
|
84 |
+
const closeButton = createElem("button", {style: {background: `#${themeColour}`}})
|
85 |
+
closeButton.innerHTML = window.i18n.CLOSE
|
86 |
+
modal.appendChild(createElem("div", closeButton))
|
87 |
+
|
88 |
+
closeButton.addEventListener("click", () => {
|
89 |
+
closeModal(modalContainer).then(() => {
|
90 |
+
resolve(false)
|
91 |
+
})
|
92 |
+
})
|
93 |
+
} else if (type=="prompt") {
|
94 |
+
const closeButton = createElem("button", {style: {background: `#${themeColour}`}})
|
95 |
+
closeButton.innerHTML = window.i18n.SUBMIT
|
96 |
+
const inputElem = createElem("input", {type: "text", value: message.value})
|
97 |
+
modal.appendChild(createElem("div", inputElem))
|
98 |
+
modal.appendChild(createElem("div", closeButton))
|
99 |
+
|
100 |
+
closeButton.addEventListener("click", () => {
|
101 |
+
closeModal(modalContainer).then(() => {
|
102 |
+
resolve(inputElem.value)
|
103 |
+
})
|
104 |
+
})
|
105 |
+
} else {
|
106 |
+
modal.appendChild(createElem("div.spinner", {style: {borderLeftColor: document.querySelector("button").style.background}}))
|
107 |
+
}
|
108 |
+
|
109 |
+
modalContainer.appendChild(modal)
|
110 |
+
modalContainer.style.opacity = 0
|
111 |
+
modalContainer.style.display = "flex"
|
112 |
+
|
113 |
+
requestAnimationFrame(() => requestAnimationFrame(() => modalContainer.style.opacity = 1))
|
114 |
+
requestAnimationFrame(() => requestAnimationFrame(() => chromeBar.style.opacity = 1))
|
115 |
+
})
|
116 |
+
}
|
117 |
+
window.closeModal = (container=undefined, notThisOne=undefined, skipIfErrorOpen=false) => {
|
118 |
+
return new Promise(resolve => {
|
119 |
+
if (window.errorModalHasOpened && skipIfErrorOpen) {
|
120 |
+
return resolve()
|
121 |
+
}
|
122 |
+
window.errorModalHasOpened = false
|
123 |
+
const allContainers = [batchGenerationContainer, gameSelectionContainer, updatesContainer, infoContainer, settingsContainer, patreonContainer, pluginsContainer, modalContainer, nexusContainer, embeddingsContainer, totdContainer, nexusReposContainer, EULAContainer, arpabetContainer, styleEmbeddingsContainer, workbenchContainer]
|
124 |
+
const containers = container==undefined ? allContainers : (Array.isArray(container) ? container.filter(c=>c!=undefined) : [container])
|
125 |
+
|
126 |
+
notThisOne = Array.isArray(notThisOne) ? notThisOne : (notThisOne==undefined ? [] : [notThisOne])
|
127 |
+
|
128 |
+
containers.forEach(cont => {
|
129 |
+
// Fade out the containers except the exceptions
|
130 |
+
if (cont!=undefined && !notThisOne.includes(cont)) {
|
131 |
+
cont.style.opacity = 0
|
132 |
+
}
|
133 |
+
})
|
134 |
+
|
135 |
+
const someOpenContainer = allContainers.filter(c=>c!=undefined).find(cont => cont.style.opacity==1 && cont.style.display!="none" && cont!=modalContainer)
|
136 |
+
if (!someOpenContainer || someOpenContainer==container) {
|
137 |
+
chromeBar.style.opacity = 0.88
|
138 |
+
}
|
139 |
+
|
140 |
+
setTimeout(() => {
|
141 |
+
if (window.errorModalHasOpened && skipIfErrorOpen) {
|
142 |
+
} else {
|
143 |
+
containers.forEach(cont => {
|
144 |
+
// Hide the containers except the exceptions
|
145 |
+
if (cont!=undefined && !notThisOne.includes(cont)) {
|
146 |
+
cont.style.display = "none"
|
147 |
+
const someOpenContainer2 = allContainers.filter(c=>c!=undefined).find(cont => cont.style.opacity==1 && cont.style.display!="none" && cont!=modalContainer)
|
148 |
+
if (!someOpenContainer2 || someOpenContainer2==container) {
|
149 |
+
chromeBar.style.opacity = 0.88
|
150 |
+
}
|
151 |
+
}
|
152 |
+
})
|
153 |
+
window.errorModalHasOpened = false
|
154 |
+
}
|
155 |
+
// resolve()
|
156 |
+
}, 200)
|
157 |
+
try {
|
158 |
+
activeModal.remove()
|
159 |
+
} catch (e) {}
|
160 |
+
resolve()
|
161 |
+
})
|
162 |
+
}
|
163 |
+
|
164 |
+
|
165 |
+
window.setTheme = (meta) => {
|
166 |
+
|
167 |
+
const primaryColour = meta.themeColourPrimary
|
168 |
+
const secondaryColour = meta.themeColourSecondary
|
169 |
+
const gameName = meta.gameName
|
170 |
+
|
171 |
+
if (window.userSettings.showDiscordStatus) {
|
172 |
+
ipcRenderer.send('updateDiscord', {details: gameName})
|
173 |
+
}
|
174 |
+
|
175 |
+
// Change batch panel colours, if it is initialized
|
176 |
+
try {
|
177 |
+
Array.from(batchRecordsHeader.children).forEach(item => item.style.backgroundColor = `#${primaryColour}`)
|
178 |
+
} catch (e) {}
|
179 |
+
try {
|
180 |
+
Array.from(pluginsRecordsHeader.children).forEach(item => item.style.backgroundColor = `#${primaryColour}`)
|
181 |
+
} catch (e) {}
|
182 |
+
try {
|
183 |
+
Array.from(styleembsRecordsHeader.children).forEach(item => item.style.backgroundColor = `#${primaryColour}`)
|
184 |
+
} catch (e) {}
|
185 |
+
try {
|
186 |
+
Array.from(nexusRecordsHeader.children).forEach(item => item.style.backgroundColor = `#${primaryColour}`)
|
187 |
+
} catch (e) {}
|
188 |
+
try {
|
189 |
+
Array.from(nexusSearchHeader.children).forEach(item => item.style.backgroundColor = `#${primaryColour}`)
|
190 |
+
} catch (e) {}
|
191 |
+
try {
|
192 |
+
Array.from(nexusReposUsedHeader.children).forEach(item => item.style.backgroundColor = `#${primaryColour}`)
|
193 |
+
} catch (e) {}
|
194 |
+
try {
|
195 |
+
Array.from(embeddingsRecordsHeader.children).forEach(item => item.style.backgroundColor = `#${primaryColour}`)
|
196 |
+
} catch (e) {}
|
197 |
+
try {
|
198 |
+
Array.from(arpabetWordsListHeader.children).forEach(item => item.style.backgroundColor = `#${primaryColour}`)
|
199 |
+
} catch (e) {}
|
200 |
+
try {
|
201 |
+
window.sequenceEditor.grabbers.forEach(grabber => grabber.fillStyle = `#${primaryColour}`)
|
202 |
+
window.sequenceEditor.energyGrabbers.forEach(grabber => grabber.fillStyle = `#${primaryColour}`)
|
203 |
+
} catch (e) {}
|
204 |
+
|
205 |
+
const background = `linear-gradient(0deg, rgba(128,128,128,${window.userSettings.bg_gradient_opacity}) 0px, rgba(0,0,0,0)), url("assets/${meta.assetFile}")`
|
206 |
+
Array.from(document.querySelectorAll("button:not(.fixedColour)")).forEach(e => e.style.background = `#${primaryColour}`)
|
207 |
+
Array.from(document.querySelectorAll(".voiceType")).forEach(e => e.style.background = `#${primaryColour}`)
|
208 |
+
Array.from(document.querySelectorAll(".spinner")).forEach(e => e.style.borderLeftColor = `#${primaryColour}`)
|
209 |
+
Array.from(document.querySelectorAll(".checkbox")).forEach(e => e.style.accentColor = `#${primaryColour}`)
|
210 |
+
Array.from(document.querySelectorAll(".input[type=range]")).forEach(e => e.style.accentColor = `#${primaryColour}`)
|
211 |
+
|
212 |
+
if (secondaryColour) {
|
213 |
+
Array.from(document.querySelectorAll("button:not(.fixedColour)")).forEach(e => e.style.color = `#${secondaryColour}`)
|
214 |
+
Array.from(document.querySelectorAll(".voiceType")).forEach(e => e.style.color = `#${secondaryColour}`)
|
215 |
+
Array.from(document.querySelectorAll("button")).forEach(e => e.style.textShadow = `none`)
|
216 |
+
Array.from(document.querySelectorAll(".voiceType")).forEach(e => e.style.textShadow = `none`)
|
217 |
+
} else {
|
218 |
+
Array.from(document.querySelectorAll("button:not(.fixedColour)")).forEach(e => e.style.color = `white`)
|
219 |
+
Array.from(document.querySelectorAll(".voiceType")).forEach(e => e.style.color = `white`)
|
220 |
+
Array.from(document.querySelectorAll("button")).forEach(e => e.style.textShadow = `0 0 2px black`)
|
221 |
+
Array.from(document.querySelectorAll(".voiceType")).forEach(e => e.style.textShadow = `0 0 2px black`)
|
222 |
+
}
|
223 |
+
|
224 |
+
if (window.wavesurfer) {
|
225 |
+
window.wavesurfer.setWaveColor(`#${window.currentGame.themeColourPrimary}`)
|
226 |
+
}
|
227 |
+
|
228 |
+
// Fade the background image transition
|
229 |
+
rightBG1.style.background = background
|
230 |
+
rightBG2.style.opacity = 0
|
231 |
+
setTimeout(() => {
|
232 |
+
rightBG2.style.background = rightBG1.style.background
|
233 |
+
rightBG2.style.opacity = 1
|
234 |
+
}, 1000)
|
235 |
+
|
236 |
+
cssHack.innerHTML = `::selection {
|
237 |
+
background: #${primaryColour};
|
238 |
+
}
|
239 |
+
::-webkit-scrollbar-thumb {
|
240 |
+
background-color: #${primaryColour} !important;
|
241 |
+
}
|
242 |
+
.slider::-webkit-slider-thumb {
|
243 |
+
background-color: #${primaryColour} !important;
|
244 |
+
}
|
245 |
+
input[type=checkbox], input[type=range] {accent-color: #${primaryColour} !important;}
|
246 |
+
a {color: #${primaryColour}};
|
247 |
+
#batchRecordsHeader > div {background-color: #${primaryColour} !important;}
|
248 |
+
#pluginsRecordsHeader > div {background-color: #${primaryColour} !important;}
|
249 |
+
|
250 |
+
.invertedButton {
|
251 |
+
background: none !important;
|
252 |
+
border: 2px solid #${primaryColour} !important;
|
253 |
+
}
|
254 |
+
|
255 |
+
`
|
256 |
+
if (secondaryColour) {
|
257 |
+
cssHack.innerHTML += `
|
258 |
+
#batchRecordsHeader > div {color: #${secondaryColour} !important;text-shadow: none}
|
259 |
+
#pluginsRecordsHeader > div {color: #${secondaryColour} !important;text-shadow: none}
|
260 |
+
`
|
261 |
+
} else {
|
262 |
+
cssHack.innerHTML += `
|
263 |
+
#batchRecordsHeader > div {color: white !important;text-shadow: 0 0 2px black;}
|
264 |
+
#pluginsRecordsHeader > div {color: white !important;text-shadow: 0 0 2px black;}
|
265 |
+
`
|
266 |
+
}
|
267 |
+
}
|
268 |
+
|
269 |
+
window.getAudioPlayTriangleSVG = () => {
|
270 |
+
const div = createElem("div", `<svg class="renameSVG" version="1.0" xmlns="http://www.w3.org/2000/svg" width="770.000000pt" height="980.000000pt" viewBox="0 0 770.000000 980.000000"
|
271 |
+
preserveAspectRatio="xMidYMid meet"><g transform="translate(0.000000,980.000000) scale(0.100000,-0.100000)"fill="#555555" stroke="none">
|
272 |
+
<path d="M26 9718 c-3 -46 -9 -2249 -12 -4897 -5 -4085 -4 -4813 8 -4809 8 3
|
273 |
+
389 244 848 535 459 291 1598 1013 2530 1603 2872 1819 3648 2311 4182 2656
|
274 |
+
60 38 108 75 108 81 0 55 -595 448 -2855 1886 -1259 801 -4552 2877 -4792
|
275 |
+
3020 -8 5 -13 -16 -17 -75z"/>
|
276 |
+
</g>
|
277 |
+
</svg>`)
|
278 |
+
return div
|
279 |
+
}
|
280 |
+
|
281 |
+
|
282 |
+
window.addEventListener("resize", e => {
|
283 |
+
window.userSettings.customWindowSize = `${window.innerHeight},${window.innerWidth}`
|
284 |
+
saveUserSettings()
|
285 |
+
})
|
286 |
+
|
287 |
+
// Keyboard actions
|
288 |
+
// ================
|
289 |
+
window.addEventListener("keyup", event => {
|
290 |
+
if (!event.ctrlKey) {
|
291 |
+
window.ctrlKeyIsPressed = false
|
292 |
+
}
|
293 |
+
if (!event.shiftKey) {
|
294 |
+
window.shiftKeyIsPressed = false
|
295 |
+
}
|
296 |
+
})
|
297 |
+
|
298 |
+
dialogueInput.addEventListener("keydown", event => {
|
299 |
+
if (event.target==dialogueInput || event.target==letterPitchNumb || event.target==letterLengthNumb) {
|
300 |
+
// Enter: Generate sample
|
301 |
+
if (event.key=="Enter") {
|
302 |
+
generateVoiceButton.click()
|
303 |
+
event.preventDefault()
|
304 |
+
}
|
305 |
+
return
|
306 |
+
}
|
307 |
+
})
|
308 |
+
|
309 |
+
window.addEventListener("click", event => {
|
310 |
+
if (event.target.id!="dialogueInput") {
|
311 |
+
window.hideAutocomplete()
|
312 |
+
}
|
313 |
+
})
|
314 |
+
|
315 |
+
window.addEventListener("keydown", event => {
|
316 |
+
|
317 |
+
if (event.ctrlKey) {
|
318 |
+
window.ctrlKeyIsPressed = true
|
319 |
+
}
|
320 |
+
if (event.shiftKey) {
|
321 |
+
window.shiftKeyIsPressed = true
|
322 |
+
}
|
323 |
+
|
324 |
+
if (event.ctrlKey && event.key.toLowerCase()=="r") {
|
325 |
+
location.reload()
|
326 |
+
}
|
327 |
+
|
328 |
+
if (event.ctrlKey && event.shiftKey && event.key.toLowerCase()=="i") {
|
329 |
+
window.electron = require("electron")
|
330 |
+
er.BrowserWindow.getFocusedWindow().webContents.openDevTools()
|
331 |
+
return
|
332 |
+
}
|
333 |
+
|
334 |
+
if (event.ctrlKey) {
|
335 |
+
window.ctrlKeyIsPressed = true
|
336 |
+
}
|
337 |
+
|
338 |
+
// Re-gen the line if the user presses CTRL-ENTER, evne outside the prompt box
|
339 |
+
if (event.key=="Enter" && window.ctrlKeyIsPressed) {
|
340 |
+
generateVoiceButton.click()
|
341 |
+
}
|
342 |
+
// The Enter key to submit text input prompts in modals
|
343 |
+
if (event.key=="Enter" && modalContainer.style.display!="none" && event.target.tagName=="INPUT") {
|
344 |
+
activeModal.querySelector("button").click()
|
345 |
+
}
|
346 |
+
const key = event.key.toLowerCase()
|
347 |
+
|
348 |
+
// CTRL-S: Keep sample
|
349 |
+
if (key=="s" && event.ctrlKey && !event.shiftKey) {
|
350 |
+
keepSampleFunction(false)
|
351 |
+
}
|
352 |
+
// CTRL-SHIFT-S: Keep sample (but with rename prompt)
|
353 |
+
if (key=="s" && event.ctrlKey && event.shiftKey) {
|
354 |
+
keepSampleFunction(true)
|
355 |
+
}
|
356 |
+
|
357 |
+
// Disable keyboard controls while in a text input
|
358 |
+
if (event.target.tagName=="INPUT" && event.target.tagName!=dialogueInput || event.target.tagName=="TEXTAREA") {
|
359 |
+
return
|
360 |
+
}
|
361 |
+
|
362 |
+
if (event.target==dialogueInput || event.target==letterPitchNumb || event.target==letterLengthNumb) {
|
363 |
+
// Enter: Generate sample
|
364 |
+
if (event.key=="Enter") {
|
365 |
+
generateVoiceButton.click()
|
366 |
+
event.preventDefault()
|
367 |
+
}
|
368 |
+
return
|
369 |
+
}
|
370 |
+
|
371 |
+
// Escape: close modals
|
372 |
+
if (key=="escape") {
|
373 |
+
closeModal()
|
374 |
+
}
|
375 |
+
// Space: bring focus to the input textarea
|
376 |
+
if (key==" ") {
|
377 |
+
setTimeout(() => dialogueInput.focus(), 0)
|
378 |
+
}
|
379 |
+
// Create selection for all of the editor letters
|
380 |
+
if (key=="a" && event.ctrlKey && !event.shiftKey) {
|
381 |
+
window.sequenceEditor.letterFocus = []
|
382 |
+
window.sequenceEditor.sliderBoxes.forEach((_,i) => {
|
383 |
+
window.sequenceEditor.letterFocus.push(i)
|
384 |
+
window.sequenceEditor.setLetterFocus(i, true)
|
385 |
+
})
|
386 |
+
event.preventDefault()
|
387 |
+
return
|
388 |
+
}
|
389 |
+
// Y/N for prompt modals
|
390 |
+
if (key=="y" || key=="n" || key==" ") {
|
391 |
+
if (document.querySelector("#activeModal")) {
|
392 |
+
const buttons = Array.from(document.querySelector("#activeModal").querySelectorAll("button"))
|
393 |
+
const yesBtn = buttons.find(btn => btn.innerHTML.toLowerCase()=="yes")
|
394 |
+
const noBtn = buttons.find(btn => btn.innerHTML.toLowerCase()=="no")
|
395 |
+
if (key=="y") yesBtn.click()
|
396 |
+
if (key==" ") yesBtn.click()
|
397 |
+
if (key=="n") noBtn.click()
|
398 |
+
}
|
399 |
+
}
|
400 |
+
// Left/Right arrows: Move between letter focused (clears multi-select, picks the min(0,first-1) if left, max($L, last+1) if right)
|
401 |
+
// SHIFT-Left/Right: multi-letter create selection range
|
402 |
+
if ((key=="arrowleft" || key=="arrowright") && !event.ctrlKey) {
|
403 |
+
event.preventDefault()
|
404 |
+
if (window.sequenceEditor.letterFocus.length==0) {
|
405 |
+
window.sequenceEditor.setLetterFocus(0)
|
406 |
+
} else if (window.sequenceEditor.letterFocus.length==1) {
|
407 |
+
const curr_l = window.sequenceEditor.letterFocus[0]
|
408 |
+
const newIndex = key=="arrowleft" ? Math.max(0, curr_l-1) : Math.min(curr_l+1, window.sequenceEditor.letters.length-1)
|
409 |
+
window.sequenceEditor.setLetterFocus(newIndex, event.shiftKey)
|
410 |
+
} else {
|
411 |
+
if (key=="arrowleft") {
|
412 |
+
window.sequenceEditor.setLetterFocus(Math.max(0, Math.min(...window.sequenceEditor.letterFocus)-1), event.shiftKey)
|
413 |
+
} else {
|
414 |
+
window.sequenceEditor.setLetterFocus(Math.min(Math.max(...window.sequenceEditor.letterFocus)+1, window.sequenceEditor.letters.length-1), event.shiftKey)
|
415 |
+
}
|
416 |
+
}
|
417 |
+
}
|
418 |
+
|
419 |
+
// Up/Down arrows: Move pitch up/down for the letter(s) selected
|
420 |
+
if ((key=="arrowup" || key=="arrowdown") && !event.ctrlKey) {
|
421 |
+
event.preventDefault()
|
422 |
+
if (window.sequenceEditor.letterFocus.length) {
|
423 |
+
window.sequenceEditor.letterFocus.forEach(li => {
|
424 |
+
window.sequenceEditor.pitchNew[li] += (key=="arrowup" ? 0.1 : -0.1)
|
425 |
+
window.sequenceEditor.grabbers[li].setValueFromValue(window.sequenceEditor.pitchNew[li])
|
426 |
+
window.sequenceEditor.hasChanged = true
|
427 |
+
})
|
428 |
+
if (window.sequenceEditor.autoInferTimer != null) {
|
429 |
+
clearTimeout(window.sequenceEditor.autoInferTimer)
|
430 |
+
window.sequenceEditor.autoInferTimer = null
|
431 |
+
}
|
432 |
+
if (autoplay_ckbx.checked) {
|
433 |
+
window.sequenceEditor.autoInferTimer = setTimeout(infer, 500)
|
434 |
+
}
|
435 |
+
|
436 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
437 |
+
letterPitchNumb.value = window.sequenceEditor.pitchNew[window.sequenceEditor.letterFocus[0]]
|
438 |
+
}
|
439 |
+
}
|
440 |
+
}
|
441 |
+
|
442 |
+
// CTRL+Left/Right arrows: change the sequence-wide pacing
|
443 |
+
if ((key=="arrowleft" || key=="arrowright") && event.ctrlKey) {
|
444 |
+
if (event.altKey) {
|
445 |
+
window.sequenceEditor.letterFocus.forEach(li => {
|
446 |
+
window.sequenceEditor.dursNew[li] = window.sequenceEditor.dursNew[li] + (key=="arrowleft"? -0.1 : 0.1)
|
447 |
+
window.sequenceEditor.hasChanged = true
|
448 |
+
})
|
449 |
+
if (window.sequenceEditor.autoInferTimer != null) {
|
450 |
+
clearTimeout(window.sequenceEditor.autoInferTimer)
|
451 |
+
window.sequenceEditor.autoInferTimer = null
|
452 |
+
}
|
453 |
+
if (autoplay_ckbx.checked) {
|
454 |
+
window.sequenceEditor.autoInferTimer = setTimeout(infer, 500)
|
455 |
+
}
|
456 |
+
window.sequenceEditor.sliderBoxes.forEach((box, i) => box.setValueFromValue(window.sequenceEditor.dursNew[i]))
|
457 |
+
|
458 |
+
if (window.sequenceEditor.letterFocus.length==1) {
|
459 |
+
letterLengthNumb.value = parseInt(window.sequenceEditor.dursNew[window.sequenceEditor.letterFocus[0]]*100)/100
|
460 |
+
}
|
461 |
+
} else {
|
462 |
+
pace_slid.value = parseFloat(pace_slid.value) + (key=="arrowleft"? -0.01 : 0.01)
|
463 |
+
paceNumbInput.value = pace_slid.value
|
464 |
+
const new_lengths = window.sequenceEditor.dursNew.map((v,l) => v * pace_slid.value)
|
465 |
+
window.sequenceEditor.sliderBoxes.forEach((box, i) => box.setValueFromValue(window.sequenceEditor.dursNew[i]))
|
466 |
+
window.sequenceEditor.pacing = parseFloat(pace_slid.value)
|
467 |
+
window.sequenceEditor.init()
|
468 |
+
|
469 |
+
}
|
470 |
+
}
|
471 |
+
|
472 |
+
// CTRL+Up/Down arrows: increase/decrease buttons
|
473 |
+
if (key=="arrowup" && event.ctrlKey && !event.shiftKey) {
|
474 |
+
increase_btn.click()
|
475 |
+
}
|
476 |
+
if (key=="arrowdown" && event.ctrlKey && !event.shiftKey) {
|
477 |
+
decrease_btn.click()
|
478 |
+
}
|
479 |
+
// CTRL+SHIFT+Up/Down arrows: amplify/flatten buttons
|
480 |
+
if (key=="arrowup" && event.ctrlKey && event.shiftKey) {
|
481 |
+
amplify_btn.click()
|
482 |
+
}
|
483 |
+
if (key=="arrowdown" && event.ctrlKey && event.shiftKey) {
|
484 |
+
flatten_btn.click()
|
485 |
+
}
|
486 |
+
})
|
487 |
+
|
488 |
+
|
489 |
+
|
490 |
+
window.setupModal = (openingButton, modalContainerElem, callback, exitCallback) => {
|
491 |
+
if (openingButton) {
|
492 |
+
openingButton.addEventListener("click", () => {
|
493 |
+
closeModal(undefined, modalContainerElem).then(() => {
|
494 |
+
modalContainerElem.style.opacity = 0
|
495 |
+
modalContainerElem.style.display = "flex"
|
496 |
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
497 |
+
modalContainerElem.style.opacity = 1
|
498 |
+
chromeBar.style.opacity = 1
|
499 |
+
requestAnimationFrame(() => {
|
500 |
+
setTimeout(() => {
|
501 |
+
if (callback) {
|
502 |
+
callback()
|
503 |
+
}
|
504 |
+
}, 250)
|
505 |
+
})
|
506 |
+
}))
|
507 |
+
})
|
508 |
+
})
|
509 |
+
}
|
510 |
+
modalContainerElem.addEventListener("click", event => {
|
511 |
+
if (event.target==modalContainerElem) {
|
512 |
+
if (exitCallback) {
|
513 |
+
exitCallback()
|
514 |
+
}
|
515 |
+
window.closeModal(modalContainerElem)
|
516 |
+
}
|
517 |
+
})
|
518 |
+
}
|
519 |
+
|
520 |
+
window.checkVersionRequirements = (requirements, appVersion, checkMax=false) => {
|
521 |
+
|
522 |
+
if (!requirements) {
|
523 |
+
return true
|
524 |
+
}
|
525 |
+
|
526 |
+
const appVersionRequirement = requirements.toString().split(".").map(v=>parseInt(v))
|
527 |
+
const appVersionInts = appVersion.replace("v", "").split(".").map(v=>parseInt(v))
|
528 |
+
let appVersionOk = true
|
529 |
+
|
530 |
+
if (checkMax) {
|
531 |
+
|
532 |
+
if (appVersionRequirement[0] >= appVersionInts[0] ) {
|
533 |
+
if (appVersionRequirement.length>1 && parseInt(appVersionRequirement[0]) == appVersionInts[0]) {
|
534 |
+
if (appVersionRequirement[1] >= appVersionInts[1] ) {
|
535 |
+
if (appVersionRequirement.length>2 && parseInt(appVersionRequirement[1]) == appVersionInts[1]) {
|
536 |
+
if (appVersionRequirement[2] >= appVersionInts[2] ) {
|
537 |
+
} else {
|
538 |
+
appVersionOk = false
|
539 |
+
}
|
540 |
+
}
|
541 |
+
} else {
|
542 |
+
appVersionOk = false
|
543 |
+
}
|
544 |
+
}
|
545 |
+
} else {
|
546 |
+
appVersionOk = false
|
547 |
+
}
|
548 |
+
|
549 |
+
|
550 |
+
} else {
|
551 |
+
if (appVersionRequirement[0] <= appVersionInts[0] ) {
|
552 |
+
if (appVersionRequirement.length>1 && parseInt(appVersionRequirement[0]) == appVersionInts[0]) {
|
553 |
+
if (appVersionRequirement[1] <= appVersionInts[1] ) {
|
554 |
+
if (appVersionRequirement.length>2 && parseInt(appVersionRequirement[1]) == appVersionInts[1]) {
|
555 |
+
if (appVersionRequirement[2] <= appVersionInts[2] ) {
|
556 |
+
} else {
|
557 |
+
appVersionOk = false
|
558 |
+
}
|
559 |
+
}
|
560 |
+
} else {
|
561 |
+
appVersionOk = false
|
562 |
+
}
|
563 |
+
}
|
564 |
+
} else {
|
565 |
+
appVersionOk = false
|
566 |
+
}
|
567 |
+
}
|
568 |
+
return appVersionOk
|
569 |
+
}
|
570 |
+
|
571 |
+
|
572 |
+
// https://stackoverflow.com/questions/18052762/remove-directory-which-is-not-empty
|
573 |
+
const path = require('path')
|
574 |
+
window.deleteFolderRecursive = function (directoryPath, keepRoot=false) {
|
575 |
+
if (fs.existsSync(directoryPath)) {
|
576 |
+
fs.readdirSync(directoryPath).forEach((file, index) => {
|
577 |
+
const curPath = path.join(directoryPath, file);
|
578 |
+
if (fs.lstatSync(curPath).isDirectory()) {
|
579 |
+
// recurse
|
580 |
+
window.deleteFolderRecursive(curPath);
|
581 |
+
} else {
|
582 |
+
// delete file
|
583 |
+
fs.unlinkSync(curPath);
|
584 |
+
}
|
585 |
+
});
|
586 |
+
if (!keepRoot) {
|
587 |
+
fs.rmdirSync(directoryPath);
|
588 |
+
}
|
589 |
+
}
|
590 |
+
};
|
591 |
+
|
592 |
+
window.createFolderRecursive = (pathToMake) => {
|
593 |
+
console.log("createFolderRecursive", pathToMake)
|
594 |
+
pathToMake.split('/').reduce((directories, directory) => {
|
595 |
+
directories += `${directory}/`
|
596 |
+
|
597 |
+
if (!fs.existsSync(directories)) {
|
598 |
+
fs.mkdirSync(directories)
|
599 |
+
}
|
600 |
+
|
601 |
+
return directories
|
602 |
+
}, '')
|
603 |
+
}
|
604 |
+
|
605 |
+
window.uuidv4 = () => {
|
606 |
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
607 |
+
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
|
608 |
+
return v.toString(16)
|
609 |
+
})
|
610 |
+
}
|
611 |
+
|
612 |
+
// https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
|
613 |
+
window.shuffle = (array) => {
|
614 |
+
var currentIndex = array.length, randomIndex;
|
615 |
+
|
616 |
+
// While there remain elements to shuffle...
|
617 |
+
while (0 !== currentIndex) {
|
618 |
+
|
619 |
+
// Pick a remaining element...
|
620 |
+
randomIndex = Math.floor(Math.random() * currentIndex);
|
621 |
+
currentIndex--;
|
622 |
+
|
623 |
+
// And swap it with the current element.
|
624 |
+
[array[currentIndex], array[randomIndex]] = [
|
625 |
+
array[randomIndex], array[currentIndex]];
|
626 |
+
}
|
627 |
+
|
628 |
+
return array;
|
629 |
+
}
|
630 |
+
|
631 |
+
|
632 |
+
// Just for easier packaging of the voice models for publishing - yes, lazy
|
633 |
+
window.packageVoice_variantIndex = 0
|
634 |
+
window.packageVoice = (doVoicePreviewCreate, variants, {modelsPath, gameId}={}) => {
|
635 |
+
|
636 |
+
const {voiceId, hifi} = variants[window.packageVoice_variantIndex]
|
637 |
+
|
638 |
+
if (doVoicePreviewCreate) {
|
639 |
+
const files = fs.readdirSync(`./output`).filter(fname => fname.includes("temp-") && fname.includes(".wav"))
|
640 |
+
if (files.length) {
|
641 |
+
const options = {
|
642 |
+
hz: window.userSettings.audio.hz,
|
643 |
+
padStart: window.userSettings.audio.padStart,
|
644 |
+
padEnd: window.userSettings.audio.padEnd,
|
645 |
+
bit_depth: window.userSettings.audio.bitdepth,
|
646 |
+
amplitude: window.userSettings.audio.amplitude,
|
647 |
+
pitchMult: window.userSettings.audio.pitchMult,
|
648 |
+
tempo: window.userSettings.audio.tempo
|
649 |
+
}
|
650 |
+
|
651 |
+
doFetch(`http://localhost:8008/outputAudio`, {
|
652 |
+
method: "Post",
|
653 |
+
body: JSON.stringify({
|
654 |
+
input_path: `./output/${files[0]}`,
|
655 |
+
isBatchMode: false,
|
656 |
+
output_path: `${modelsPath}/${voiceId}_raw.wav`,
|
657 |
+
options: JSON.stringify(options)
|
658 |
+
})
|
659 |
+
}).then(r=>r.text()).then(() => {
|
660 |
+
try {
|
661 |
+
fs.unlinkSync(`${modelsPath}/${voiceId}.wav`)
|
662 |
+
} catch (e) {}
|
663 |
+
doFetch(`http://localhost:8008/normalizeAudio`, {
|
664 |
+
method: "Post",
|
665 |
+
body: JSON.stringify({
|
666 |
+
input_path: `${modelsPath}/${voiceId}_raw.wav`,
|
667 |
+
output_path: `${modelsPath}/${voiceId}.wav`
|
668 |
+
})
|
669 |
+
}).then(r=>r.text()).then((resp) => {
|
670 |
+
console.log(resp)
|
671 |
+
fs.unlinkSync(`${modelsPath}/${voiceId}_raw.wav`)
|
672 |
+
})
|
673 |
+
})
|
674 |
+
}
|
675 |
+
} else {
|
676 |
+
fs.mkdirSync(`./build/${voiceId}`)
|
677 |
+
fs.mkdirSync(`./build/${voiceId}/resources`)
|
678 |
+
fs.mkdirSync(`./build/${voiceId}/resources/app`)
|
679 |
+
fs.mkdirSync(`./build/${voiceId}/resources/app/models`)
|
680 |
+
fs.mkdirSync(`./build/${voiceId}/resources/app/models/${gameId}`)
|
681 |
+
fs.copyFileSync(`${modelsPath}/${voiceId}.json`, `./build/${voiceId}/resources/app/models/${gameId}/${voiceId}.json`)
|
682 |
+
fs.copyFileSync(`${modelsPath}/${voiceId}.wav`, `./build/${voiceId}/resources/app/models/${gameId}/${voiceId}.wav`)
|
683 |
+
fs.copyFileSync(`${modelsPath}/${voiceId}.pt`, `./build/${voiceId}/resources/app/models/${gameId}/${voiceId}.pt`)
|
684 |
+
// if (hifi) {
|
685 |
+
// fs.copyFileSync(`${modelsPath}/${voiceId}.hg.pt`, `./build/${voiceId}/resources/app/models/${gameId}/${voiceId}.hg.pt`)
|
686 |
+
// }
|
687 |
+
zipdir(`./build/${voiceId}`, {saveTo: `./build/${voiceId}.zip`}, (err, buffer) => deleteFolderRecursive(`./build/${voiceId}`))
|
688 |
+
}
|
689 |
+
}
|
690 |
+
|
691 |
+
|
692 |
+
const assetFiles = fs.readdirSync(`${window.path}/assets`)
|
693 |
+
const jsonFiles = fs.readdirSync(`${window.path}/assets`).filter(fn => fn.endsWith(".json"))
|
694 |
+
const missingAssetFiles = jsonFiles.filter(jsonFile => !(assetFiles.includes(jsonFile.replace(".json", ".png"))||assetFiles.includes(jsonFile.replace(".json", ".jpg"))) )
|
695 |
+
if (missingAssetFiles.length) {
|
696 |
+
noAssetFilesFoundMessage.style.display = "block"
|
697 |
+
assetDirLink.addEventListener("click", () => {
|
698 |
+
// shell.showItemInFolder((require("path")).resolve(`${window.path}/assets/other.jpg`))
|
699 |
+
er.shell.showItemInFolder((require("path")).resolve(`${window.path}/assets/other.jpg`))
|
700 |
+
spawn(`explorer`, [(require("path")).resolve(`${window.path}/assets`)], {stdio: "ignore"})
|
701 |
+
})
|
702 |
+
}
|
703 |
+
|
704 |
+
|
705 |
+
window.getSpeakerEmbeddingFromFilePath = (filePath) => {
|
706 |
+
spinnerModal(`${window.i18n.GETTING_SPEAKER_EMBEDDING}`)
|
707 |
+
|
708 |
+
return new Promise(resolve => {
|
709 |
+
doFetch(`http://localhost:8008/getWavV3StyleEmb`, {
|
710 |
+
method: "Post",
|
711 |
+
body: JSON.stringify({wav_path: filePath})
|
712 |
+
}).then(r=>r.text()).then(v => {
|
713 |
+
closeModal(undefined, [styleEmbeddingsContainer, workbenchContainer])
|
714 |
+
if (v=="ENOENT") {
|
715 |
+
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}<br><br>ENOENT`)
|
716 |
+
} else {
|
717 |
+
resolve(v)
|
718 |
+
}
|
719 |
+
}).catch(() => {
|
720 |
+
window.errorModal(`${window.i18n.SOMETHING_WENT_WRONG}`)
|
721 |
+
})
|
722 |
+
})
|
723 |
+
}
|
724 |
+
|
725 |
+
window.unzipFileTo = (zipPath, outputFolder) => {
|
726 |
+
return fs.createReadStream(zipPath).pipe(unzipper.Parse()).on("entry", entry => {
|
727 |
+
const fileName = entry.path
|
728 |
+
const dirOrFile = entry.type
|
729 |
+
|
730 |
+
if (/\/$/.test(fileName)) { // It's a directory
|
731 |
+
return
|
732 |
+
}
|
733 |
+
|
734 |
+
let fileContainerFolderPath = fileName.split("/").reverse()
|
735 |
+
const justFileName = fileContainerFolderPath[0]
|
736 |
+
|
737 |
+
entry.pipe(fs.createWriteStream(`${outputFolder}/${justFileName}`))
|
738 |
+
})
|
739 |
+
.promise()
|
740 |
+
}
|
javascript/workbench.js
ADDED
@@ -0,0 +1,497 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
const er = require('@electron/remote')
|
3 |
+
const dialog = er.dialog
|
4 |
+
|
5 |
+
|
6 |
+
window.voiceWorkbenchState = {
|
7 |
+
isInit: false,
|
8 |
+
isStarted: false,
|
9 |
+
currentAudioFilePath: undefined,
|
10 |
+
newAudioFilePath: undefined,
|
11 |
+
currentEmb: undefined,
|
12 |
+
refAEmb: undefined,
|
13 |
+
refBEmb: undefined,
|
14 |
+
}
|
15 |
+
|
16 |
+
window.initVoiceWorkbench = () => {
|
17 |
+
if (!window.voiceWorkbenchState.isInit) {
|
18 |
+
window.voiceWorkbenchState.isInit = true
|
19 |
+
window.refreshExistingCraftedVoices()
|
20 |
+
window.initDropdowns()
|
21 |
+
voiceWorkbenchLanguageDropdown.value = "en"
|
22 |
+
}
|
23 |
+
window.refreshExistingCraftedVoices()
|
24 |
+
}
|
25 |
+
|
26 |
+
window.refreshExistingCraftedVoices = () => {
|
27 |
+
voiceWorkbenchVoicesList.innerHTML = ""
|
28 |
+
Object.keys(window.games).sort((a,b)=>a>b?1:-1).forEach(gameId => {
|
29 |
+
if (Object.keys(window.gameAssets).includes(gameId)) {
|
30 |
+
const themeColour = window.gameAssets[gameId].themeColourPrimary
|
31 |
+
window.games[gameId].models.forEach(model => {
|
32 |
+
if (model.embOverABaseModel) {
|
33 |
+
const button = createElem("div.voiceType", model.voiceName)
|
34 |
+
button.style.background = `#${themeColour}`
|
35 |
+
button.addEventListener("click", () => window.voiceWorkbenchLoadOrResetCraftedVoice(model))
|
36 |
+
voiceWorkbenchVoicesList.appendChild(button)
|
37 |
+
}
|
38 |
+
})
|
39 |
+
}
|
40 |
+
})
|
41 |
+
}
|
42 |
+
|
43 |
+
window.voiceWorkbenchLoadOrResetCraftedVoice = (model) => {
|
44 |
+
|
45 |
+
voiceWorkbenchModelDropdown.value = model ? model.embOverABaseModel : "<base>/base_v1.0"
|
46 |
+
voiceWorkbenchVoiceNameInput.value = model ? model.voiceName : ""
|
47 |
+
voiceWorkbenchVoiceIDInput.value = model ? model.variants[0].voiceId : ""
|
48 |
+
voiceWorkbenchGenderDropdown.value = model ? model.variants[0].gender : "male"
|
49 |
+
voiceWorkbenchAuthorInput.value = model ? (model.variants[0].author || "Anonymous") : ""
|
50 |
+
voiceWorkbenchLanguageDropdown.value = model ? model.variants[0].lang : "en"
|
51 |
+
voiceWorkbenchGamesDropdown.value = model ? model.gameId : "other"
|
52 |
+
voiceWorkbenchCurrentEmbeddingInput.value = model ? model.variants[0].base_speaker_emb : ""
|
53 |
+
window.voiceWorkbenchState.currentEmb = model ? model.variants[0].base_speaker_emb : undefined
|
54 |
+
|
55 |
+
window.voiceWorkbenchState.currentAudioFilePath = undefined
|
56 |
+
window.voiceWorkbenchState.newAudioFilePath = undefined
|
57 |
+
window.voiceWorkbenchState.refAEmb = undefined
|
58 |
+
window.voiceWorkbenchState.refBEmb = undefined
|
59 |
+
|
60 |
+
voiceWorkbenchRefAInput.value = ""
|
61 |
+
voiceWorkbenchRefBInput.value = ""
|
62 |
+
|
63 |
+
if (model) {
|
64 |
+
voiceWorkbenchDeleteButton.disabled = false
|
65 |
+
voiceWorkbenchStartButton.click()
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
window.voiceWorkbenchGenerateVoice = async () => {
|
70 |
+
|
71 |
+
if (!voiceWorkbenchCurrentEmbeddingInput.value.length) {
|
72 |
+
return window.errorModal(window.i18n.ENTER_VOICE_CRAFTING_STARTING_EMB)
|
73 |
+
}
|
74 |
+
|
75 |
+
// Load the model if it hasn't been loaded already
|
76 |
+
let voiceId = voiceWorkbenchModelDropdown.value.split("/").at(-1)
|
77 |
+
if (!window.currentModel || window.currentModel.voiceId!=voiceId) {
|
78 |
+
let modelPath
|
79 |
+
if (voiceId.includes("Base xVAPitch Model")) {
|
80 |
+
modelPath = `${window.path}/python/xvapitch/base_v1.0.pt`
|
81 |
+
} else {
|
82 |
+
const gameId = voiceWorkbenchModelDropdown.value.split("/").at(0)
|
83 |
+
modelPath = window.userSettings[`modelspath_${gameId}`]+"/"+voiceId
|
84 |
+
}
|
85 |
+
await window.voiceWorkbenchChangeModel(modelPath, voiceId)
|
86 |
+
}
|
87 |
+
|
88 |
+
const base_lang = voiceWorkbenchLanguageDropdown.value//voiceId.includes("Base xVAPitch Model") ? "en" : window.currentModel.lang
|
89 |
+
let newEmb = undefined
|
90 |
+
|
91 |
+
// const currentEmbedding = voiceWorkbenchCurrentEmbeddingInput.value.split(",").map(v=>parseFloat(v))
|
92 |
+
const currentEmbedding = window.voiceWorkbenchState.currentEmb
|
93 |
+
// const currentDelta = voiceWorkbenchCurrentDeltaInput.value.split(",").map(v=>parseFloat(v))
|
94 |
+
const newEmbedding = window.getVoiceWorkbenchNewEmbedding()
|
95 |
+
|
96 |
+
const tempFileNum = `${Math.random().toString().split(".")[1]}`
|
97 |
+
const currentTempFileLocation = `${path}/output/temp-${tempFileNum}_current.wav`
|
98 |
+
const newTempFileLocation = `${path}/output/temp-${tempFileNum}_new.wav`
|
99 |
+
|
100 |
+
// Do the current embedding first
|
101 |
+
const synthRequests = []
|
102 |
+
synthRequests.push(doSynth(JSON.stringify({
|
103 |
+
sequence: voiceWorkbenchInputTextArea.value.trim(),
|
104 |
+
useCleanup: true, // TODO, user setting?
|
105 |
+
base_lang, base_emb: currentEmbedding.join(","), outfile: currentTempFileLocation
|
106 |
+
})))
|
107 |
+
let doingNewAudioFile = false
|
108 |
+
if (voiceWorkbenchCurrentDeltaInput.value.length) {
|
109 |
+
doingNewAudioFile = true
|
110 |
+
synthRequests.push(doSynth(JSON.stringify({
|
111 |
+
sequence: voiceWorkbenchInputTextArea.value.trim(),
|
112 |
+
useCleanup: true, // TODO, user setting?
|
113 |
+
base_lang, base_emb: newEmbedding.join(","), outfile: newTempFileLocation
|
114 |
+
})))
|
115 |
+
}
|
116 |
+
|
117 |
+
// toggleSpinnerButtons()
|
118 |
+
spinnerModal(`${window.i18n.SYNTHESIZING}`)
|
119 |
+
Promise.all(synthRequests).then(res => {
|
120 |
+
closeModal(undefined, [workbenchContainer])
|
121 |
+
window.voiceWorkbenchState.currentAudioFilePath = currentTempFileLocation
|
122 |
+
voiceWorkbenchAudioCurrentPlayPauseBtn.disabled = false
|
123 |
+
voiceWorkbenchAudioCurrentSaveBtn.disabled = false
|
124 |
+
|
125 |
+
if (doingNewAudioFile) {
|
126 |
+
window.voiceWorkbenchState.newAudioFilePath = newTempFileLocation
|
127 |
+
voiceWorkbenchAudioNewPlayBtn.disabled = false
|
128 |
+
voiceWorkbenchAudioNewSaveBtn.disabled = false
|
129 |
+
}
|
130 |
+
})
|
131 |
+
}
|
132 |
+
const doSynth = (body) => {
|
133 |
+
return new Promise(resolve => {
|
134 |
+
doFetch("http://localhost:8008/synthesizeSimple", {
|
135 |
+
method: "Post",
|
136 |
+
body
|
137 |
+
}).then(r=>r.text()).then(resolve)
|
138 |
+
})
|
139 |
+
}
|
140 |
+
|
141 |
+
window.getVoiceWorkbenchNewEmbedding = () => {
|
142 |
+
const currentDelta = voiceWorkbenchCurrentDeltaInput.value.split(",").map(v=>parseFloat(v))
|
143 |
+
const newEmb = window.voiceWorkbenchState.currentEmb.map((v,vi) => {
|
144 |
+
return v + currentDelta[vi]//*strength
|
145 |
+
})
|
146 |
+
return newEmb
|
147 |
+
}
|
148 |
+
|
149 |
+
window.voiceWorkbenchChangeModel = (modelPath, voiceId) => {
|
150 |
+
window.currentModel = {
|
151 |
+
outputs: undefined,
|
152 |
+
model: modelPath.replace(".pt", ""),
|
153 |
+
modelType: "xVAPitch",
|
154 |
+
base_lang: voiceWorkbenchLanguageDropdown.value,
|
155 |
+
isBaseModel: true,
|
156 |
+
voiceId: voiceId
|
157 |
+
}
|
158 |
+
generateVoiceButton.dataset.modelQuery = JSON.stringify(window.currentModel)
|
159 |
+
return window.loadModel()
|
160 |
+
}
|
161 |
+
voiceWorkbenchGenerateSampleButton.addEventListener("click", window.voiceWorkbenchGenerateVoice)
|
162 |
+
|
163 |
+
|
164 |
+
window.initDropdowns = () => {
|
165 |
+
// Games dropdown
|
166 |
+
Object.keys(window.games).sort((a,b)=>a>b?1:-1).forEach(gameId => {
|
167 |
+
if (gameId!="other") {
|
168 |
+
if (Object.keys(window.gameAssets).includes(gameId)) {
|
169 |
+
const gameName = window.games[gameId].gameTheme.gameName
|
170 |
+
const option = createElem("option", gameName)
|
171 |
+
option.value = gameId
|
172 |
+
voiceWorkbenchGamesDropdown.appendChild(option)
|
173 |
+
}
|
174 |
+
}
|
175 |
+
})
|
176 |
+
|
177 |
+
// Models dropdown
|
178 |
+
Object.keys(window.games).forEach(gameId => {
|
179 |
+
if (window.games[gameId].gameTheme) {
|
180 |
+
const gameName = window.games[gameId].gameTheme.gameName
|
181 |
+
window.games[gameId].models.forEach(modelMeta => {
|
182 |
+
const voiceName = modelMeta.voiceName
|
183 |
+
const voiceId = modelMeta.variants[0].voiceId
|
184 |
+
|
185 |
+
// Variants are not supported by v3 models, so pick the first one only. Also, filter out crafted voices
|
186 |
+
if (modelMeta.variants[0].modelType=="xVAPitch" && !modelMeta.embOverABaseModel) {
|
187 |
+
|
188 |
+
const option = createElem("option", `[${gameName}] ${voiceName}`)
|
189 |
+
option.value = `${gameId}/${voiceId}`
|
190 |
+
voiceWorkbenchModelDropdown.appendChild(option)
|
191 |
+
}
|
192 |
+
})
|
193 |
+
}
|
194 |
+
})
|
195 |
+
}
|
196 |
+
|
197 |
+
// Change the available languages when the model is changed
|
198 |
+
voiceWorkbenchModelDropdown.addEventListener("change", () => {
|
199 |
+
let voiceId = voiceWorkbenchModelDropdown.value.split("/").at(-1)
|
200 |
+
if (voiceId.includes("base_v1.0")) {
|
201 |
+
window.populateLanguagesDropdownsFromModel(voiceWorkbenchLanguageDropdown)
|
202 |
+
voiceWorkbenchLanguageDropdown.value = "en"
|
203 |
+
} else {
|
204 |
+
const gameId = voiceWorkbenchModelDropdown.value.split("/")[0]
|
205 |
+
if (Object.keys(window.games).includes(gameId)) {
|
206 |
+
const baseModelData = window.games[gameId].models.filter(model => {
|
207 |
+
return model.variants[0].voiceId == voiceWorkbenchModelDropdown.value.split("/").at(-1)
|
208 |
+
})[0]
|
209 |
+
window.populateLanguagesDropdownsFromModel(voiceWorkbenchLanguageDropdown, baseModelData)
|
210 |
+
voiceWorkbenchLanguageDropdown.value = baseModelData.variants[0].lang
|
211 |
+
}
|
212 |
+
}
|
213 |
+
})
|
214 |
+
|
215 |
+
voiceWorkbenchStartButton.addEventListener("click", () => {
|
216 |
+
window.voiceWorkbenchState.isStarted = true
|
217 |
+
|
218 |
+
voiceWorkbenchLoadedContent.style.display = "flex"
|
219 |
+
voiceWorkbenchLoadedContent2.style.display = "flex"
|
220 |
+
voiceWorkbenchStartButton.style.display = "none"
|
221 |
+
|
222 |
+
|
223 |
+
// Load the base model's embedding as a starting point, if it's not the built-in base model
|
224 |
+
let voiceId = voiceWorkbenchModelDropdown.value.split("/").at(-1)
|
225 |
+
if (voiceId.includes("base_v1.0")) {
|
226 |
+
} else {
|
227 |
+
const gameId = voiceWorkbenchModelDropdown.value.split("/")[0]
|
228 |
+
if (Object.keys(window.games).includes(gameId)) {
|
229 |
+
const baseModelData = window.games[gameId].models.filter(model => {
|
230 |
+
return model.variants[0].voiceId == voiceWorkbenchModelDropdown.value.split("/").at(-1)
|
231 |
+
})[0]
|
232 |
+
voiceWorkbenchCurrentEmbeddingInput.value = baseModelData.variants[0].base_speaker_emb.join(",")
|
233 |
+
window.voiceWorkbenchState.currentEmb = baseModelData.variants[0].base_speaker_emb
|
234 |
+
}
|
235 |
+
}
|
236 |
+
})
|
237 |
+
|
238 |
+
window.setupVoiceWorkbenchDropArea = (container, inputField, callback=undefined) => {
|
239 |
+
const dropFn = (eType, event) => {
|
240 |
+
if (["dragenter", "dragover"].includes(eType)) {
|
241 |
+
container.style.background = "#5b5b5b"
|
242 |
+
container.style.color = "white"
|
243 |
+
}
|
244 |
+
if (["dragleave", "drop"].includes(eType)) {
|
245 |
+
container.style.background = "rgba(0,0,0,0)"
|
246 |
+
container.style.color = "white"
|
247 |
+
}
|
248 |
+
|
249 |
+
event.preventDefault()
|
250 |
+
event.stopPropagation()
|
251 |
+
|
252 |
+
const dataLines = []
|
253 |
+
|
254 |
+
if (eType=="drop") {
|
255 |
+
const dataTransfer = event.dataTransfer
|
256 |
+
const files = Array.from(dataTransfer.files)
|
257 |
+
|
258 |
+
if (files[0].path.endsWith(".wav")) {
|
259 |
+
const filePath = String(files[0].path).replaceAll(/\\/g, "/")
|
260 |
+
console.log("filePath", filePath)
|
261 |
+
window.getSpeakerEmbeddingFromFilePath(filePath).then(embedding => {
|
262 |
+
inputField.value = embedding
|
263 |
+
if (callback) {
|
264 |
+
callback(filePath)
|
265 |
+
}
|
266 |
+
})
|
267 |
+
} else {
|
268 |
+
window.errorModal(window.i18n.ERROR_FILE_MUST_BE_WAV)
|
269 |
+
}
|
270 |
+
}
|
271 |
+
}
|
272 |
+
|
273 |
+
container.addEventListener("dragenter", event => dropFn("dragenter", event), false)
|
274 |
+
container.addEventListener("dragleave", event => dropFn("dragleave", event), false)
|
275 |
+
container.addEventListener("dragover", event => dropFn("dragover", event), false)
|
276 |
+
container.addEventListener("drop", event => dropFn("drop", event), false)
|
277 |
+
}
|
278 |
+
|
279 |
+
window.setupVoiceWorkbenchDropArea(voiceWorkbenchCurrentEmbeddingDropzone, voiceWorkbenchCurrentEmbeddingInput, () => {
|
280 |
+
window.voiceWorkbenchState.currentEmb = voiceWorkbenchCurrentEmbeddingInput.value.split(",").map(v=>parseFloat(v))
|
281 |
+
})
|
282 |
+
voiceWorkbenchCurrentEmbeddingInput.addEventListener("change", ()=>{
|
283 |
+
window.voiceWorkbenchState.currentEmb = voiceWorkbenchCurrentEmbeddingInput.value.split(",").map(v=>parseFloat(v))
|
284 |
+
})
|
285 |
+
window.setupVoiceWorkbenchDropArea(voiceWorkbenchRefADropzone, voiceWorkbenchRefAInput, (filePath) => {
|
286 |
+
voiceWorkbenchRefAFilePath.innerHTML = window.i18n.FROM_FILE_IS_FILEPATH.replace("_1", filePath)
|
287 |
+
voiceWorkshopApplyDeltaButton.disabled = false
|
288 |
+
window.voiceWorkbenchState.refAEmb = voiceWorkbenchRefAInput.value.split(",").map(v=>parseFloat(v))
|
289 |
+
window.voiceWorkbenchUpdateDelta()
|
290 |
+
})
|
291 |
+
window.setupVoiceWorkbenchDropArea(voiceWorkbenchRefBDropzone, voiceWorkbenchRefBInput, (filePath) => {
|
292 |
+
voiceWorkbenchRefBFilePath.innerHTML = window.i18n.FROM_FILE_IS_FILEPATH.replace("_1", filePath)
|
293 |
+
window.voiceWorkbenchState.refBEmb = voiceWorkbenchRefBInput.value.split(",").map(v=>parseFloat(v))
|
294 |
+
window.voiceWorkbenchUpdateDelta()
|
295 |
+
})
|
296 |
+
|
297 |
+
voiceWorkbenchInputTextArea.addEventListener("keyup", () => {
|
298 |
+
voiceWorkbenchGenerateSampleButton.disabled = voiceWorkbenchInputTextArea.value.trim().length==0
|
299 |
+
})
|
300 |
+
voiceWorkbenchAudioCurrentPlayPauseBtn.addEventListener("click", () => {
|
301 |
+
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", {
|
302 |
+
src: window.voiceWorkbenchState.currentAudioFilePath
|
303 |
+
}))
|
304 |
+
audioPreview.setSinkId(window.userSettings.base_speaker)
|
305 |
+
})
|
306 |
+
voiceWorkbenchAudioCurrentSaveBtn.addEventListener("click", async () => {
|
307 |
+
const userChosenPath = await dialog.showSaveDialog({ defaultPath: window.voiceWorkbenchState.currentAudioFilePath })
|
308 |
+
if (userChosenPath && userChosenPath.filePath) {
|
309 |
+
const outFilePath = userChosenPath.filePath.split(".").at(-1)=="wav" ? userChosenPath.filePath : userChosenPath.filePath+".wav"
|
310 |
+
fs.copyFileSync(window.voiceWorkbenchState.currentAudioFilePath, outFilePath)
|
311 |
+
}
|
312 |
+
})
|
313 |
+
voiceWorkbenchAudioNewPlayBtn.addEventListener("click", () => {
|
314 |
+
const audioPreview = createElem("audio", {autoplay: false}, createElem("source", {
|
315 |
+
src: window.voiceWorkbenchState.newAudioFilePath
|
316 |
+
}))
|
317 |
+
audioPreview.setSinkId(window.userSettings.base_speaker)
|
318 |
+
})
|
319 |
+
voiceWorkbenchAudioNewSaveBtn.addEventListener("click", async () => {
|
320 |
+
const userChosenPath = await dialog.showSaveDialog({ defaultPath: window.voiceWorkbenchState.newAudioFilePath })
|
321 |
+
if (userChosenPath && userChosenPath.filePath) {
|
322 |
+
const outFilePath = userChosenPath.filePath.split(".").at(-1)=="wav" ? userChosenPath.filePath : userChosenPath.filePath+".wav"
|
323 |
+
fs.copyFileSync(window.voiceWorkbenchState.newAudioFilePath, outFilePath)
|
324 |
+
}
|
325 |
+
})
|
326 |
+
|
327 |
+
window.voiceWorkbenchUpdateDelta = () => {
|
328 |
+
// Don't do anything if reference file A isn't given
|
329 |
+
if (!window.voiceWorkbenchState.refAEmb) {
|
330 |
+
return
|
331 |
+
}
|
332 |
+
|
333 |
+
const strengthValue = parseFloat(voiceWorkbenchStrengthInput.value)
|
334 |
+
|
335 |
+
let delta
|
336 |
+
|
337 |
+
// When only Ref A is used, the delta is from <current> towards the first reference file A
|
338 |
+
if (window.voiceWorkbenchState.refBEmb == undefined) {
|
339 |
+
delta = window.voiceWorkbenchState.currentEmb.map((v,vi) => {
|
340 |
+
return (window.voiceWorkbenchState.refAEmb[vi] - v) * strengthValue
|
341 |
+
})
|
342 |
+
} else {
|
343 |
+
// When Ref B is also used, the delta is from ref A to ref B
|
344 |
+
delta = window.voiceWorkbenchState.refAEmb.map((v,vi) => {
|
345 |
+
return (window.voiceWorkbenchState.refBEmb[vi] - v) * strengthValue
|
346 |
+
})
|
347 |
+
}
|
348 |
+
|
349 |
+
voiceWorkbenchCurrentDeltaInput.value = delta.join(",")
|
350 |
+
}
|
351 |
+
|
352 |
+
voiceWorkbenchStrengthSlider.addEventListener("change", () => {
|
353 |
+
voiceWorkbenchStrengthInput.value = voiceWorkbenchStrengthSlider.value
|
354 |
+
window.voiceWorkbenchUpdateDelta()
|
355 |
+
})
|
356 |
+
voiceWorkbenchStrengthInput.addEventListener("change", () => {
|
357 |
+
voiceWorkbenchStrengthSlider.value = voiceWorkbenchStrengthInput.value
|
358 |
+
window.voiceWorkbenchUpdateDelta()
|
359 |
+
})
|
360 |
+
voiceWorkshopApplyDeltaButton.addEventListener("click", () => {
|
361 |
+
if (voiceWorkbenchCurrentDeltaInput.value.length) {
|
362 |
+
const newEmb = window.getVoiceWorkbenchNewEmbedding()
|
363 |
+
window.voiceWorkbenchState.currentEmb = newEmb
|
364 |
+
voiceWorkbenchCurrentEmbeddingInput.value = newEmb.join(",")
|
365 |
+
voiceWorkbenchCurrentDeltaInput.value = ""
|
366 |
+
voiceWorkshopApplyDeltaButton.disabled = true
|
367 |
+
voiceWorkbenchRefAInput.value = ""
|
368 |
+
window.voiceWorkbenchState.refAEmb = undefined
|
369 |
+
voiceWorkbenchRefBInput.value = ""
|
370 |
+
window.voiceWorkbenchState.refBEmb = undefined
|
371 |
+
}
|
372 |
+
})
|
373 |
+
|
374 |
+
/*
|
375 |
+
Drop file A over the reference audio file A area, to get its embedding
|
376 |
+
When only the reference A file is used, the current delta is this embedding multiplied by the strength
|
377 |
+
|
378 |
+
Drop file B over the B area, to get a second embedding
|
379 |
+
When both this and A are active, the current delta is the direction from A to B, multiplied by the strength
|
380 |
+
direction meaning B minus A, instead of <current> minus A
|
381 |
+
*/
|
382 |
+
|
383 |
+
voiceWorkbenchSaveButton.addEventListener("click", () => {
|
384 |
+
|
385 |
+
const voiceName = voiceWorkbenchVoiceNameInput.value
|
386 |
+
const voiceId = voiceWorkbenchVoiceIDInput.value
|
387 |
+
const gender = voiceWorkbenchGenderDropdown.value
|
388 |
+
const author = voiceWorkbenchAuthorInput.value || "Anonymous"
|
389 |
+
const lang = voiceWorkbenchLanguageDropdown.value
|
390 |
+
|
391 |
+
|
392 |
+
if (!voiceName.trim().length) {
|
393 |
+
return window.errorModal(window.i18n.ENTER_VOICE_NAME)
|
394 |
+
}
|
395 |
+
if (!voiceId.trim().length) {
|
396 |
+
return window.errorModal(window.i18n.ENTER_VOICE_ID)
|
397 |
+
}
|
398 |
+
|
399 |
+
const modelJson = {
|
400 |
+
"version": "3.0",
|
401 |
+
"modelVersion": "3.0",
|
402 |
+
"modelType": "xVAPitch",
|
403 |
+
"author": author,
|
404 |
+
"lang": lang,
|
405 |
+
"embOverABaseModel": voiceWorkbenchModelDropdown.value,
|
406 |
+
"games": [
|
407 |
+
{
|
408 |
+
"gameId": voiceWorkbenchGamesDropdown.value,
|
409 |
+
"voiceId": voiceId,
|
410 |
+
"variant": "Default",
|
411 |
+
"voiceName": voiceName,
|
412 |
+
"base_speaker_emb": window.voiceWorkbenchState.currentEmb,
|
413 |
+
"gender": gender
|
414 |
+
}
|
415 |
+
]
|
416 |
+
}
|
417 |
+
const gameModelsPath = `${window.userSettings[`modelspath_${voiceWorkbenchGamesDropdown.value}`]}`
|
418 |
+
|
419 |
+
const jsonDestination = `${gameModelsPath}/${voiceId}.json`
|
420 |
+
fs.writeFileSync(jsonDestination, JSON.stringify(modelJson, null, 4))
|
421 |
+
|
422 |
+
doSynth(JSON.stringify({
|
423 |
+
sequence: " This is what my voice sounds like. ",
|
424 |
+
useCleanup: true, // TODO, user setting?
|
425 |
+
base_lang: lang,
|
426 |
+
base_emb: window.voiceWorkbenchState.currentEmb.join(","), outfile: jsonDestination.replace(".json", ".wav")
|
427 |
+
})).then(() => {
|
428 |
+
voiceWorkbenchDeleteButton.disabled = false
|
429 |
+
window.currentModel = undefined
|
430 |
+
generateVoiceButton.dataset.modelQuery = null
|
431 |
+
window.infoModal(window.i18n.VOICE_CREATED_AT.replace("_1", jsonDestination))
|
432 |
+
|
433 |
+
// Clean up the temp file from the clean-up post-processing, if it exists
|
434 |
+
if (fs.existsSync(jsonDestination.replace(".json", "_preCleanup.wav"))) {
|
435 |
+
fs.unlinkSync(jsonDestination.replace(".json", "_preCleanup.wav"))
|
436 |
+
}
|
437 |
+
|
438 |
+
window.loadAllModels().then(() => {
|
439 |
+
window.refreshExistingCraftedVoices()
|
440 |
+
|
441 |
+
// Refresh the main page voice models if the same game is loaded as the target game models directory as saved into
|
442 |
+
if (window.currentGame.gameId==voiceWorkbenchGamesDropdown.value) {
|
443 |
+
window.changeGame(window.currentGame)
|
444 |
+
window.refreshExistingCraftedVoices()
|
445 |
+
}
|
446 |
+
})
|
447 |
+
})
|
448 |
+
})
|
449 |
+
|
450 |
+
voiceWorkbenchGamesDropdown.addEventListener("change", () => {
|
451 |
+
const gameModelsPath = `${window.userSettings[`modelspath_${voiceWorkbenchGamesDropdown.value}`]}`
|
452 |
+
const voiceId = voiceWorkbenchVoiceIDInput.value
|
453 |
+
const jsonLocation = `${gameModelsPath}/${voiceId}.json`
|
454 |
+
voiceWorkbenchDeleteButton.disabled = !fs.existsSync(jsonLocation)
|
455 |
+
})
|
456 |
+
voiceWorkbenchVoiceIDInput.addEventListener("change", () => {
|
457 |
+
const gameModelsPath = `${window.userSettings[`modelspath_${voiceWorkbenchGamesDropdown.value}`]}`
|
458 |
+
const voiceId = voiceWorkbenchVoiceIDInput.value
|
459 |
+
const jsonLocation = `${gameModelsPath}/${voiceId}.json`
|
460 |
+
voiceWorkbenchDeleteButton.disabled = !fs.existsSync(jsonLocation)
|
461 |
+
})
|
462 |
+
voiceWorkbenchDeleteButton.addEventListener("click", () => {
|
463 |
+
const gameModelsPath = `${window.userSettings[`modelspath_${voiceWorkbenchGamesDropdown.value}`]}`
|
464 |
+
const voiceId = voiceWorkbenchVoiceIDInput.value
|
465 |
+
const jsonLocation = `${gameModelsPath}/${voiceId}.json`
|
466 |
+
window.confirmModal(window.i18n.CONFIRM_DELETE_CRAFTED_VOICE.replace("_1", voiceWorkbenchVoiceNameInput.value).replace("_2", jsonLocation)).then(resp => {
|
467 |
+
if (resp) {
|
468 |
+
if (fs.existsSync(jsonLocation.replace(".json", ".wav"))) {
|
469 |
+
fs.unlinkSync(jsonLocation.replace(".json", ".wav"))
|
470 |
+
}
|
471 |
+
fs.unlinkSync(jsonLocation)
|
472 |
+
}
|
473 |
+
window.infoModal(window.i18n.SUCCESSFULLY_DELETED_CRAFTED_VOICE)
|
474 |
+
window.loadAllModels().then(() => {
|
475 |
+
|
476 |
+
// Refresh the main page voice models if the same game is loaded as the target game models directory deleted from
|
477 |
+
if (window.currentGame.gameId==voiceWorkbenchGamesDropdown.value) {
|
478 |
+
|
479 |
+
window.changeGame(window.currentGame)
|
480 |
+
window.refreshExistingCraftedVoices()
|
481 |
+
}
|
482 |
+
voiceWorkbenchCancelButton.click()
|
483 |
+
})
|
484 |
+
})
|
485 |
+
})
|
486 |
+
|
487 |
+
|
488 |
+
|
489 |
+
voiceWorkbenchCancelButton.addEventListener("click", () => {
|
490 |
+
window.voiceWorkbenchState.isStarted = false
|
491 |
+
|
492 |
+
voiceWorkbenchLoadedContent.style.display = "none"
|
493 |
+
voiceWorkbenchLoadedContent2.style.display = "none"
|
494 |
+
voiceWorkbenchStartButton.style.display = "flex"
|
495 |
+
|
496 |
+
window.voiceWorkbenchLoadOrResetCraftedVoice()
|
497 |
+
})
|
lib/AbortControllerPolyfill.js
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Polyfill service v3.105.0
|
2 |
+
* Disable minification (remove `.min` from URL path) for more info */
|
3 |
+
|
4 |
+
(function(self, undefined) {!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.WHATWGFetch={})}(this,function(t){"use strict";function e(t){return t&&DataView.prototype.isPrototypeOf(t)}function r(t){if("string"!=typeof t&&(t=String(t)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(t))throw new TypeError("Invalid character in header field name");return t.toLowerCase()}function o(t){return"string"!=typeof t&&(t=String(t)),t}function n(t){var e={next:function(){var e=t.shift();return{done:e===undefined,value:e}}};return v.iterable&&(e[Symbol.iterator]=function(){return e}),e}function i(t){this.map={},t instanceof i?t.forEach(function(t,e){this.append(e,t)},this):Array.isArray(t)?t.forEach(function(t){this.append(t[0],t[1])},this):t&&Object.getOwnPropertyNames(t).forEach(function(e){this.append(e,t[e])},this)}function s(t){if(t.bodyUsed)return Promise.reject(new TypeError("Already read"));t.bodyUsed=!0}function a(t){return new Promise(function(e,r){t.onload=function(){e(t.result)},t.onerror=function(){r(t.error)}})}function f(t){var e=new FileReader,r=a(e);return e.readAsArrayBuffer(t),r}function u(t){var e=new FileReader,r=a(e);return e.readAsText(t),r}function h(t){for(var e=new Uint8Array(t),r=new Array(e.length),o=0;o<e.length;o++)r[o]=String.fromCharCode(e[o]);return r.join("")}function d(t){if(t.slice)return t.slice(0);var e=new Uint8Array(t.byteLength);return e.set(new Uint8Array(t)),e.buffer}function c(){return this.bodyUsed=!1,this._initBody=function(t){this._bodyInit=t,t?"string"==typeof t?this._bodyText=t:v.blob&&Blob.prototype.isPrototypeOf(t)?this._bodyBlob=t:v.formData&&FormData.prototype.isPrototypeOf(t)?this._bodyFormData=t:v.searchParams&&URLSearchParams.prototype.isPrototypeOf(t)?this._bodyText=t.toString():v.arrayBuffer&&v.blob&&e(t)?(this._bodyArrayBuffer=d(t.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):v.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(t)||A(t))?this._bodyArrayBuffer=d(t):this._bodyText=t=Object.prototype.toString.call(t):this._bodyText="",this.headers.get("content-type")||("string"==typeof t?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):v.searchParams&&URLSearchParams.prototype.isPrototypeOf(t)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},v.blob&&(this.blob=function(){var t=s(this);if(t)return t;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?s(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(f)}),this.text=function(){var t=s(this);if(t)return t;if(this._bodyBlob)return u(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(h(this._bodyArrayBuffer));if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)},v.formData&&(this.formData=function(){return this.text().then(l)}),this.json=function(){return this.text().then(JSON.parse)},this}function y(t){var e=t.toUpperCase();return _.indexOf(e)>-1?e:t}function p(t,e){e=e||{};var r=e.body;if(t instanceof p){if(t.bodyUsed)throw new TypeError("Already read");this.url=t.url,this.credentials=t.credentials,e.headers||(this.headers=new i(t.headers)),this.method=t.method,this.mode=t.mode,this.signal=t.signal,r||null==t._bodyInit||(r=t._bodyInit,t.bodyUsed=!0)}else this.url=String(t);if(this.credentials=e.credentials||this.credentials||"same-origin",!e.headers&&this.headers||(this.headers=new i(e.headers)),this.method=y(e.method||this.method||"GET"),this.mode=e.mode||this.mode||null,this.signal=e.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&r)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(r)}function l(t){var e=new FormData;return t.trim().split("&").forEach(function(t){if(t){var r=t.split("="),o=r.shift().replace(/\+/g," "),n=r.join("=").replace(/\+/g," ");e.append(decodeURIComponent(o),decodeURIComponent(n))}}),e}function b(t){var e=new i;return t.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach(function(t){var r=t.split(":"),o=r.shift().trim();if(o){var n=r.join(":").trim();e.append(o,n)}}),e}function m(t,e){e||(e={}),this.type="default",this.status=e.status===undefined?200:e.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in e?e.statusText:"OK",this.headers=new i(e.headers),this.url=e.url||"",this._initBody(t)}function w(e,r){return new Promise(function(o,n){function i(){a.abort()}var s=new p(e,r);if(s.signal&&s.signal.aborted)return n(new t.DOMException("Aborted","AbortError"));var a=new XMLHttpRequest;a.onload=function(){var t={status:a.status,statusText:a.statusText,headers:b(a.getAllResponseHeaders()||"")};t.url="responseURL"in a?a.responseURL:t.headers.get("X-Request-URL");var e="response"in a?a.response:a.responseText;o(new m(e,t))},a.onerror=function(){n(new TypeError("Network request failed"))},a.ontimeout=function(){n(new TypeError("Network request failed"))},a.onabort=function(){n(new t.DOMException("Aborted","AbortError"))},a.open(s.method,s.url,!0),"include"===s.credentials?a.withCredentials=!0:"omit"===s.credentials&&(a.withCredentials=!1),"responseType"in a&&v.blob&&(a.responseType="blob"),s.headers.forEach(function(t,e){a.setRequestHeader(e,t)}),s.signal&&(s.signal.addEventListener("abort",i),a.onreadystatechange=function(){4===a.readyState&&s.signal.removeEventListener("abort",i)}),a.send("undefined"==typeof s._bodyInit?null:s._bodyInit)})}var v={searchParams:"URLSearchParams"in self,iterable:"Symbol"in self&&"iterator"in Symbol,blob:"FileReader"in self&&"Blob"in self&&function(){try{return new Blob,!0}catch(t){return!1}}(),formData:"FormData"in self,arrayBuffer:"ArrayBuffer"in self};if(v.arrayBuffer)var E=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],A=ArrayBuffer.isView||function(t){return t&&E.indexOf(Object.prototype.toString.call(t))>-1};i.prototype.append=function(t,e){t=r(t),e=o(e);var n=this.map[t];this.map[t]=n?n+", "+e:e},i.prototype["delete"]=function(t){delete this.map[r(t)]},i.prototype.get=function(t){return t=r(t),this.has(t)?this.map[t]:null},i.prototype.has=function(t){return this.map.hasOwnProperty(r(t))},i.prototype.set=function(t,e){this.map[r(t)]=o(e)},i.prototype.forEach=function(t,e){for(var r in this.map)this.map.hasOwnProperty(r)&&t.call(e,this.map[r],r,this)},i.prototype.keys=function(){var t=[];return this.forEach(function(e,r){t.push(r)}),n(t)},i.prototype.values=function(){var t=[];return this.forEach(function(e){t.push(e)}),n(t)},i.prototype.entries=function(){var t=[];return this.forEach(function(e,r){t.push([r,e])}),n(t)},v.iterable&&(i.prototype[Symbol.iterator]=i.prototype.entries);var _=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];p.prototype.clone=function(){return new p(this,{body:this._bodyInit})},c.call(p.prototype),c.call(m.prototype),m.prototype.clone=function(){return new m(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new i(this.headers),url:this.url})},m.error=function(){var t=new m(null,{status:0,statusText:""});return t.type="error",t};var x=[301,302,303,307,308];m.redirect=function(t,e){if(-1===x.indexOf(e))throw new RangeError("Invalid status code");return new m(null,{status:e,headers:{location:t}})},t.DOMException=self.DOMException;try{new t.DOMException}catch(g){t.DOMException=function(t,e){this.message=t,this.name=e;var r=Error(t);this.stack=r.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}w.polyfill=!0,self.fetch=w,self.Headers=i,self.Request=p,self.Response=m,t.Headers=i,t.Request=p,t.Response=m,t.fetch=w,Object.defineProperty(t,"__esModule",{value:!0})});!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):(e=e||self,t(e.AbortControllerShim={}))}(this,function(e){"use strict";function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){for(var n,o=0;o<t.length;o++)n=t[o],n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}function r(e,t,n){return t&&o(e.prototype,t),n&&o(e,n),e}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&c(e,t)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function c(e,t){return(c=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function l(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function f(e,t){return!t||"object"!=typeof t&&"function"!=typeof t?l(e):t}function a(e){var t=C.get(e);return console.assert(null!=t,"'this' is expected an Event object, but got",e),t}function p(e){return null==e.passiveListener?void(!e.event.cancelable||(e.canceled=!0,"function"==typeof e.event.preventDefault&&e.event.preventDefault())):void("undefined"!=typeof console&&"function"==typeof console.error&&console.error("Unable to preventDefault inside passive event listener invocation.",e.passiveListener))}function s(e,t){C.set(this,{eventTarget:e,event:t,eventPhase:2,currentTarget:e,canceled:!1,stopped:!1,immediateStopped:!1,passiveListener:null,timeStamp:t.timeStamp||Date.now()}),Object.defineProperty(this,"isTrusted",{value:!1,enumerable:!0});for(var n,o=Object.keys(t),r=0;r<o.length;++r)(n=o[r])in this||Object.defineProperty(this,n,b(n))}function b(e){return{get:function(){return a(this).event[e]},set:function(t){a(this).event[e]=t},configurable:!0,enumerable:!0}}function y(e){return{value:function(){var t=a(this).event;return t[e].apply(t,arguments)},configurable:!0,enumerable:!0}}function v(e,t){function n(t,n){e.call(this,t,n)}var o=Object.keys(t);if(0===o.length)return e;n.prototype=Object.create(e.prototype,{constructor:{value:n,configurable:!0,writable:!0}});for(var r,i=0;i<o.length;++i)if(!((r=o[i])in e.prototype)){var u=Object.getOwnPropertyDescriptor(t,r),c="function"==typeof u.value;Object.defineProperty(n.prototype,r,c?y(r):b(r))}return n}function d(e){if(null==e||e===Object.prototype)return s;var t=M.get(e);return null==t&&(t=v(d(Object.getPrototypeOf(e)),e),M.set(e,t)),t}function g(e,t){return new(d(Object.getPrototypeOf(t)))(e,t)}function h(e){return a(e).immediateStopped}function j(e,t){a(e).eventPhase=t}function O(e,t){a(e).currentTarget=t}function m(e,t){a(e).passiveListener=t}function w(e){return null!==e&&"object"===t(e)}function P(e){var t=D.get(e);if(null==t)throw new TypeError("'this' is expected an EventTarget object, but got another value.");return t}function T(e){return{get:function(){for(var t=P(this),n=t.get(e);null!=n;){if(3===n.listenerType)return n.listener;n=n.next}return null},set:function(t){"function"==typeof t||w(t)||(t=null);for(var n=P(this),o=null,r=n.get(e);null!=r;)3===r.listenerType?null===o?null===r.next?n["delete"](e):n.set(e,r.next):o.next=r.next:o=r,r=r.next;if(null!==t){var i={listener:t,listenerType:3,passive:!1,once:!1,next:null};null===o?n.set(e,i):o.next=i}},configurable:!0,enumerable:!0}}function x(e,t){Object.defineProperty(e,"on".concat(t),T(t))}function S(e){function t(){E.call(this)}t.prototype=Object.create(E.prototype,{constructor:{value:t,configurable:!0,writable:!0}});for(var n=0;n<e.length;++n)x(t.prototype,e[n]);return t}function E(){if(this instanceof E)return void D.set(this,new Map);if(1===arguments.length&&Array.isArray(arguments[0]))return S(arguments[0]);if(0<arguments.length){for(var e=Array(arguments.length),t=0;t<arguments.length;++t)e[t]=arguments[t];return S(e)}throw new TypeError("Cannot call a class as a function")}function A(){var e=Object.create(L.prototype);return E.call(e),W.set(e,!1),e}function k(e){!1!==W.get(e)||(W.set(e,!0),e.dispatchEvent({type:"abort"}))}function _(e){var n=B.get(e);if(null==n)throw new TypeError("Expected 'this' to be an 'AbortController' object, but got ".concat(null===e?"null":t(e)));return n}var C=new WeakMap,M=new WeakMap;s.prototype={get type(){return a(this).event.type},get target(){return a(this).eventTarget},get currentTarget(){return a(this).currentTarget},composedPath:function(){var e=a(this).currentTarget;return null==e?[]:[e]},get NONE(){return 0},get CAPTURING_PHASE(){return 1},get AT_TARGET(){return 2},get BUBBLING_PHASE(){return 3},get eventPhase(){return a(this).eventPhase},stopPropagation:function(){var e=a(this);e.stopped=!0,"function"==typeof e.event.stopPropagation&&e.event.stopPropagation()},stopImmediatePropagation:function(){var e=a(this);e.stopped=!0,e.immediateStopped=!0,"function"==typeof e.event.stopImmediatePropagation&&e.event.stopImmediatePropagation()},get bubbles(){return!!a(this).event.bubbles},get cancelable(){return!!a(this).event.cancelable},preventDefault:function(){p(a(this))},get defaultPrevented(){return a(this).canceled},get composed(){return!!a(this).event.composed},get timeStamp(){return a(this).timeStamp},get srcElement(){return a(this).eventTarget},get cancelBubble(){return a(this).stopped},set cancelBubble(e){if(e){var t=a(this);t.stopped=!0,"boolean"==typeof t.event.cancelBubble&&(t.event.cancelBubble=!0)}},get returnValue(){return!a(this).canceled},set returnValue(e){e||p(a(this))},initEvent:function(){}},Object.defineProperty(s.prototype,"constructor",{value:s,configurable:!0,writable:!0}),"undefined"!=typeof window&&"undefined"!=typeof window.Event&&(Object.setPrototypeOf(s.prototype,window.Event.prototype),M.set(window.Event.prototype,s));var D=new WeakMap;E.prototype={addEventListener:function(e,t,n){if(null!=t){if("function"!=typeof t&&!w(t))throw new TypeError("'listener' should be a function or an object.");var o=P(this),r=w(n),i=r?!!n.capture:!!n,u=i?1:2,c={listener:t,listenerType:u,passive:r&&!!n.passive,once:r&&!!n.once,next:null},l=o.get(e);if(void 0===l)return void o.set(e,c);for(var f=null;null!=l;){if(l.listener===t&&l.listenerType===u)return;f=l,l=l.next}f.next=c}},removeEventListener:function(e,t,n){if(null!=t)for(var o=P(this),r=w(n)?!!n.capture:!!n,i=r?1:2,u=null,c=o.get(e);null!=c;){if(c.listener===t&&c.listenerType===i)return void(null===u?null===c.next?o["delete"](e):o.set(e,c.next):u.next=c.next);u=c,c=c.next}},dispatchEvent:function(e){if(null==e||"string"!=typeof e.type)throw new TypeError('"event.type" should be a string.');var t=P(this),n=e.type,o=t.get(n);if(null==o)return!0;for(var r=g(this,e),i=null;null!=o;){if(o.once?null===i?null===o.next?t["delete"](n):t.set(n,o.next):i.next=o.next:i=o,m(r,o.passive?o.listener:null),"function"==typeof o.listener)try{o.listener.call(this,r)}catch(e){"undefined"!=typeof console&&"function"==typeof console.error&&console.error(e)}else 3!==o.listenerType&&"function"==typeof o.listener.handleEvent&&o.listener.handleEvent(r);if(h(r))break;o=o.next}return m(r,null),j(r,0),O(r,null),!r.defaultPrevented}},Object.defineProperty(E.prototype,"constructor",{value:E,configurable:!0,writable:!0}),"undefined"!=typeof window&&"undefined"!=typeof window.EventTarget&&Object.setPrototypeOf(E.prototype,window.EventTarget.prototype);var L=function(e){function o(){throw n(this,o),f(this,u(o).call(this)),new TypeError("AbortSignal cannot be constructed directly")}return i(o,e),r(o,[{key:"aborted",get:function(){var e=W.get(this);if("boolean"!=typeof e)throw new TypeError("Expected 'this' to be an 'AbortSignal' object, but got ".concat(null===this?"null":t(this)));return e}}]),o}(E);x(L.prototype,"abort");var W=new WeakMap;Object.defineProperties(L.prototype,{aborted:{enumerable:!0}}),"function"==typeof Symbol&&"symbol"===t(Symbol.toStringTag)&&Object.defineProperty(L.prototype,Symbol.toStringTag,{configurable:!0,value:"AbortSignal"});var I=function(){function e(){n(this,e),B.set(this,A())}return r(e,[{key:"abort",value:function(){k(_(this))}},{key:"signal",get:function(){return _(this)}}]),e}(),B=new WeakMap;if(Object.defineProperties(I.prototype,{signal:{enumerable:!0},abort:{enumerable:!0}}),"function"==typeof Symbol&&"symbol"===t(Symbol.toStringTag)&&Object.defineProperty(I.prototype,Symbol.toStringTag,{configurable:!0,value:"AbortController"}),e.AbortController=I,e.AbortSignal=L,e["default"]=I,Object.defineProperty(e,"__esModule",{value:!0}),"undefined"==typeof module&&"undefined"==typeof define){var F=Function("return this")();"undefined"==typeof F.AbortController&&(F.AbortController=I,F.AbortSignal=L)}});})('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});
|
lib/OrbitControls.js
ADDED
@@ -0,0 +1,1102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function () {
|
2 |
+
|
3 |
+
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
|
4 |
+
//
|
5 |
+
// Orbit - left mouse / touch: one-finger move
|
6 |
+
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
|
7 |
+
// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
|
8 |
+
|
9 |
+
const _changeEvent = {
|
10 |
+
type: 'change'
|
11 |
+
};
|
12 |
+
const _startEvent = {
|
13 |
+
type: 'start'
|
14 |
+
};
|
15 |
+
const _endEvent = {
|
16 |
+
type: 'end'
|
17 |
+
};
|
18 |
+
|
19 |
+
class OrbitControls extends THREE.EventDispatcher {
|
20 |
+
|
21 |
+
constructor( object, domElement ) {
|
22 |
+
|
23 |
+
super();
|
24 |
+
if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
|
25 |
+
if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
|
26 |
+
this.object = object;
|
27 |
+
this.domElement = domElement;
|
28 |
+
this.domElement.style.touchAction = 'none'; // disable touch scroll
|
29 |
+
// Set to false to disable this control
|
30 |
+
|
31 |
+
this.enabled = true; // "target" sets the location of focus, where the object orbits around
|
32 |
+
|
33 |
+
this.target = new THREE.Vector3(); // How far you can dolly in and out ( PerspectiveCamera only )
|
34 |
+
|
35 |
+
this.minDistance = 0;
|
36 |
+
this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only )
|
37 |
+
|
38 |
+
this.minZoom = 0;
|
39 |
+
this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits.
|
40 |
+
// Range is 0 to Math.PI radians.
|
41 |
+
|
42 |
+
this.minPolarAngle = 0; // radians
|
43 |
+
|
44 |
+
this.maxPolarAngle = Math.PI; // radians
|
45 |
+
// How far you can orbit horizontally, upper and lower limits.
|
46 |
+
// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
|
47 |
+
|
48 |
+
this.minAzimuthAngle = - Infinity; // radians
|
49 |
+
|
50 |
+
this.maxAzimuthAngle = Infinity; // radians
|
51 |
+
// Set to true to enable damping (inertia)
|
52 |
+
// If damping is enabled, you must call controls.update() in your animation loop
|
53 |
+
|
54 |
+
this.enableDamping = false;
|
55 |
+
this.dampingFactor = 0.05; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
|
56 |
+
// Set to false to disable zooming
|
57 |
+
|
58 |
+
this.enableZoom = true;
|
59 |
+
this.zoomSpeed = 1.0; // Set to false to disable rotating
|
60 |
+
|
61 |
+
this.enableRotate = true;
|
62 |
+
this.rotateSpeed = 1.0; // Set to false to disable panning
|
63 |
+
|
64 |
+
this.enablePan = true;
|
65 |
+
this.panSpeed = 1.0;
|
66 |
+
this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
|
67 |
+
|
68 |
+
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
|
69 |
+
// Set to true to automatically rotate around the target
|
70 |
+
// If auto-rotate is enabled, you must call controls.update() in your animation loop
|
71 |
+
|
72 |
+
this.autoRotate = false;
|
73 |
+
this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
|
74 |
+
// The four arrow keys
|
75 |
+
|
76 |
+
this.keys = {
|
77 |
+
LEFT: 'ArrowLeft',
|
78 |
+
UP: 'ArrowUp',
|
79 |
+
RIGHT: 'ArrowRight',
|
80 |
+
BOTTOM: 'ArrowDown'
|
81 |
+
}; // Mouse buttons
|
82 |
+
|
83 |
+
this.mouseButtons = {
|
84 |
+
LEFT: THREE.MOUSE.ROTATE,
|
85 |
+
MIDDLE: THREE.MOUSE.DOLLY,
|
86 |
+
RIGHT: THREE.MOUSE.PAN
|
87 |
+
}; // Touch fingers
|
88 |
+
|
89 |
+
this.touches = {
|
90 |
+
ONE: THREE.TOUCH.ROTATE,
|
91 |
+
TWO: THREE.TOUCH.DOLLY_PAN
|
92 |
+
}; // for reset
|
93 |
+
|
94 |
+
this.target0 = this.target.clone();
|
95 |
+
this.position0 = this.object.position.clone();
|
96 |
+
this.zoom0 = this.object.zoom; // the target DOM element for key events
|
97 |
+
|
98 |
+
this._domElementKeyEvents = null; //
|
99 |
+
// public methods
|
100 |
+
//
|
101 |
+
|
102 |
+
this.getPolarAngle = function () {
|
103 |
+
|
104 |
+
return spherical.phi;
|
105 |
+
|
106 |
+
};
|
107 |
+
|
108 |
+
this.getAzimuthalAngle = function () {
|
109 |
+
|
110 |
+
return spherical.theta;
|
111 |
+
|
112 |
+
};
|
113 |
+
|
114 |
+
this.listenToKeyEvents = function ( domElement ) {
|
115 |
+
|
116 |
+
domElement.addEventListener( 'keydown', onKeyDown );
|
117 |
+
this._domElementKeyEvents = domElement;
|
118 |
+
|
119 |
+
};
|
120 |
+
|
121 |
+
this.saveState = function () {
|
122 |
+
|
123 |
+
scope.target0.copy( scope.target );
|
124 |
+
scope.position0.copy( scope.object.position );
|
125 |
+
scope.zoom0 = scope.object.zoom;
|
126 |
+
|
127 |
+
};
|
128 |
+
|
129 |
+
this.reset = function () {
|
130 |
+
|
131 |
+
scope.target.copy( scope.target0 );
|
132 |
+
scope.object.position.copy( scope.position0 );
|
133 |
+
scope.object.zoom = scope.zoom0;
|
134 |
+
scope.object.updateProjectionMatrix();
|
135 |
+
scope.dispatchEvent( _changeEvent );
|
136 |
+
scope.update();
|
137 |
+
state = STATE.NONE;
|
138 |
+
|
139 |
+
}; // this method is exposed, but perhaps it would be better if we can make it private...
|
140 |
+
|
141 |
+
|
142 |
+
this.update = function () {
|
143 |
+
|
144 |
+
const offset = new THREE.Vector3(); // so camera.up is the orbit axis
|
145 |
+
|
146 |
+
const quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
|
147 |
+
const quatInverse = quat.clone().invert();
|
148 |
+
const lastPosition = new THREE.Vector3();
|
149 |
+
const lastQuaternion = new THREE.Quaternion();
|
150 |
+
const twoPI = 2 * Math.PI;
|
151 |
+
return function update() {
|
152 |
+
|
153 |
+
const position = scope.object.position;
|
154 |
+
offset.copy( position ).sub( scope.target ); // rotate offset to "y-axis-is-up" space
|
155 |
+
|
156 |
+
offset.applyQuaternion( quat ); // angle from z-axis around y-axis
|
157 |
+
|
158 |
+
spherical.setFromVector3( offset );
|
159 |
+
|
160 |
+
if ( scope.autoRotate && state === STATE.NONE ) {
|
161 |
+
|
162 |
+
rotateLeft( getAutoRotationAngle() );
|
163 |
+
|
164 |
+
}
|
165 |
+
|
166 |
+
if ( scope.enableDamping ) {
|
167 |
+
|
168 |
+
spherical.theta += sphericalDelta.theta * scope.dampingFactor;
|
169 |
+
spherical.phi += sphericalDelta.phi * scope.dampingFactor;
|
170 |
+
|
171 |
+
} else {
|
172 |
+
|
173 |
+
spherical.theta += sphericalDelta.theta;
|
174 |
+
spherical.phi += sphericalDelta.phi;
|
175 |
+
|
176 |
+
} // restrict theta to be between desired limits
|
177 |
+
|
178 |
+
|
179 |
+
let min = scope.minAzimuthAngle;
|
180 |
+
let max = scope.maxAzimuthAngle;
|
181 |
+
|
182 |
+
if ( isFinite( min ) && isFinite( max ) ) {
|
183 |
+
|
184 |
+
if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
|
185 |
+
if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
|
186 |
+
|
187 |
+
if ( min <= max ) {
|
188 |
+
|
189 |
+
spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
|
190 |
+
|
191 |
+
} else {
|
192 |
+
|
193 |
+
spherical.theta = spherical.theta > ( min + max ) / 2 ? Math.max( min, spherical.theta ) : Math.min( max, spherical.theta );
|
194 |
+
|
195 |
+
}
|
196 |
+
|
197 |
+
} // restrict phi to be between desired limits
|
198 |
+
|
199 |
+
|
200 |
+
spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
|
201 |
+
spherical.makeSafe();
|
202 |
+
spherical.radius *= scale; // restrict radius to be between desired limits
|
203 |
+
|
204 |
+
spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); // move target to panned location
|
205 |
+
|
206 |
+
if ( scope.enableDamping === true ) {
|
207 |
+
|
208 |
+
scope.target.addScaledVector( panOffset, scope.dampingFactor );
|
209 |
+
|
210 |
+
} else {
|
211 |
+
|
212 |
+
scope.target.add( panOffset );
|
213 |
+
|
214 |
+
}
|
215 |
+
|
216 |
+
offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space
|
217 |
+
|
218 |
+
offset.applyQuaternion( quatInverse );
|
219 |
+
position.copy( scope.target ).add( offset );
|
220 |
+
scope.object.lookAt( scope.target );
|
221 |
+
|
222 |
+
if ( scope.enableDamping === true ) {
|
223 |
+
|
224 |
+
sphericalDelta.theta *= 1 - scope.dampingFactor;
|
225 |
+
sphericalDelta.phi *= 1 - scope.dampingFactor;
|
226 |
+
panOffset.multiplyScalar( 1 - scope.dampingFactor );
|
227 |
+
|
228 |
+
} else {
|
229 |
+
|
230 |
+
sphericalDelta.set( 0, 0, 0 );
|
231 |
+
panOffset.set( 0, 0, 0 );
|
232 |
+
|
233 |
+
}
|
234 |
+
|
235 |
+
scale = 1; // update condition is:
|
236 |
+
// min(camera displacement, camera rotation in radians)^2 > EPS
|
237 |
+
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
|
238 |
+
|
239 |
+
if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
|
240 |
+
|
241 |
+
scope.dispatchEvent( _changeEvent );
|
242 |
+
lastPosition.copy( scope.object.position );
|
243 |
+
lastQuaternion.copy( scope.object.quaternion );
|
244 |
+
zoomChanged = false;
|
245 |
+
return true;
|
246 |
+
|
247 |
+
}
|
248 |
+
|
249 |
+
return false;
|
250 |
+
|
251 |
+
};
|
252 |
+
|
253 |
+
}();
|
254 |
+
|
255 |
+
this.dispose = function () {
|
256 |
+
|
257 |
+
scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
|
258 |
+
scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
|
259 |
+
scope.domElement.removeEventListener( 'pointercancel', onPointerCancel );
|
260 |
+
scope.domElement.removeEventListener( 'wheel', onMouseWheel );
|
261 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
|
262 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
|
263 |
+
|
264 |
+
if ( scope._domElementKeyEvents !== null ) {
|
265 |
+
|
266 |
+
scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
|
267 |
+
|
268 |
+
} //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
|
269 |
+
|
270 |
+
}; //
|
271 |
+
// internals
|
272 |
+
//
|
273 |
+
|
274 |
+
|
275 |
+
const scope = this;
|
276 |
+
const STATE = {
|
277 |
+
NONE: - 1,
|
278 |
+
ROTATE: 0,
|
279 |
+
DOLLY: 1,
|
280 |
+
PAN: 2,
|
281 |
+
TOUCH_ROTATE: 3,
|
282 |
+
TOUCH_PAN: 4,
|
283 |
+
TOUCH_DOLLY_PAN: 5,
|
284 |
+
TOUCH_DOLLY_ROTATE: 6
|
285 |
+
};
|
286 |
+
let state = STATE.NONE;
|
287 |
+
const EPS = 0.000001; // current position in spherical coordinates
|
288 |
+
|
289 |
+
const spherical = new THREE.Spherical();
|
290 |
+
const sphericalDelta = new THREE.Spherical();
|
291 |
+
let scale = 1;
|
292 |
+
const panOffset = new THREE.Vector3();
|
293 |
+
let zoomChanged = false;
|
294 |
+
const rotateStart = new THREE.Vector2();
|
295 |
+
const rotateEnd = new THREE.Vector2();
|
296 |
+
const rotateDelta = new THREE.Vector2();
|
297 |
+
const panStart = new THREE.Vector2();
|
298 |
+
const panEnd = new THREE.Vector2();
|
299 |
+
const panDelta = new THREE.Vector2();
|
300 |
+
const dollyStart = new THREE.Vector2();
|
301 |
+
const dollyEnd = new THREE.Vector2();
|
302 |
+
const dollyDelta = new THREE.Vector2();
|
303 |
+
const pointers = [];
|
304 |
+
const pointerPositions = {};
|
305 |
+
|
306 |
+
function getAutoRotationAngle() {
|
307 |
+
|
308 |
+
return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
|
309 |
+
|
310 |
+
}
|
311 |
+
|
312 |
+
function getZoomScale() {
|
313 |
+
|
314 |
+
return Math.pow( 0.95, scope.zoomSpeed );
|
315 |
+
|
316 |
+
}
|
317 |
+
|
318 |
+
function rotateLeft( angle ) {
|
319 |
+
|
320 |
+
sphericalDelta.theta -= angle;
|
321 |
+
|
322 |
+
}
|
323 |
+
|
324 |
+
function rotateUp( angle ) {
|
325 |
+
|
326 |
+
sphericalDelta.phi -= angle;
|
327 |
+
|
328 |
+
}
|
329 |
+
|
330 |
+
const panLeft = function () {
|
331 |
+
|
332 |
+
const v = new THREE.Vector3();
|
333 |
+
return function panLeft( distance, objectMatrix ) {
|
334 |
+
|
335 |
+
v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
|
336 |
+
|
337 |
+
v.multiplyScalar( - distance );
|
338 |
+
panOffset.add( v );
|
339 |
+
|
340 |
+
};
|
341 |
+
|
342 |
+
}();
|
343 |
+
|
344 |
+
const panUp = function () {
|
345 |
+
|
346 |
+
const v = new THREE.Vector3();
|
347 |
+
return function panUp( distance, objectMatrix ) {
|
348 |
+
|
349 |
+
if ( scope.screenSpacePanning === true ) {
|
350 |
+
|
351 |
+
v.setFromMatrixColumn( objectMatrix, 1 );
|
352 |
+
|
353 |
+
} else {
|
354 |
+
|
355 |
+
v.setFromMatrixColumn( objectMatrix, 0 );
|
356 |
+
v.crossVectors( scope.object.up, v );
|
357 |
+
|
358 |
+
}
|
359 |
+
|
360 |
+
v.multiplyScalar( distance );
|
361 |
+
panOffset.add( v );
|
362 |
+
|
363 |
+
};
|
364 |
+
|
365 |
+
}(); // deltaX and deltaY are in pixels; right and down are positive
|
366 |
+
|
367 |
+
|
368 |
+
const pan = function () {
|
369 |
+
|
370 |
+
const offset = new THREE.Vector3();
|
371 |
+
return function pan( deltaX, deltaY ) {
|
372 |
+
|
373 |
+
const element = scope.domElement;
|
374 |
+
|
375 |
+
if ( scope.object.isPerspectiveCamera ) {
|
376 |
+
|
377 |
+
// perspective
|
378 |
+
const position = scope.object.position;
|
379 |
+
offset.copy( position ).sub( scope.target );
|
380 |
+
let targetDistance = offset.length(); // half of the fov is center to top of screen
|
381 |
+
|
382 |
+
targetDistance *= Math.tan( scope.object.fov / 2 * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed
|
383 |
+
|
384 |
+
panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
|
385 |
+
panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
|
386 |
+
|
387 |
+
} else if ( scope.object.isOrthographicCamera ) {
|
388 |
+
|
389 |
+
// orthographic
|
390 |
+
panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
|
391 |
+
panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
|
392 |
+
|
393 |
+
} else {
|
394 |
+
|
395 |
+
// camera neither orthographic nor perspective
|
396 |
+
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
|
397 |
+
scope.enablePan = false;
|
398 |
+
|
399 |
+
}
|
400 |
+
|
401 |
+
};
|
402 |
+
|
403 |
+
}();
|
404 |
+
|
405 |
+
function dollyOut( dollyScale ) {
|
406 |
+
|
407 |
+
if ( scope.object.isPerspectiveCamera ) {
|
408 |
+
|
409 |
+
scale /= dollyScale;
|
410 |
+
|
411 |
+
} else if ( scope.object.isOrthographicCamera ) {
|
412 |
+
|
413 |
+
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
|
414 |
+
scope.object.updateProjectionMatrix();
|
415 |
+
zoomChanged = true;
|
416 |
+
|
417 |
+
} else {
|
418 |
+
|
419 |
+
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
|
420 |
+
scope.enableZoom = false;
|
421 |
+
|
422 |
+
}
|
423 |
+
|
424 |
+
}
|
425 |
+
|
426 |
+
function dollyIn( dollyScale ) {
|
427 |
+
|
428 |
+
if ( scope.object.isPerspectiveCamera ) {
|
429 |
+
|
430 |
+
scale *= dollyScale;
|
431 |
+
|
432 |
+
} else if ( scope.object.isOrthographicCamera ) {
|
433 |
+
|
434 |
+
scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
|
435 |
+
scope.object.updateProjectionMatrix();
|
436 |
+
zoomChanged = true;
|
437 |
+
|
438 |
+
} else {
|
439 |
+
|
440 |
+
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
|
441 |
+
scope.enableZoom = false;
|
442 |
+
|
443 |
+
}
|
444 |
+
|
445 |
+
} //
|
446 |
+
// event callbacks - update the object state
|
447 |
+
//
|
448 |
+
|
449 |
+
|
450 |
+
function handleMouseDownRotate( event ) {
|
451 |
+
|
452 |
+
rotateStart.set( event.clientX, event.clientY );
|
453 |
+
|
454 |
+
}
|
455 |
+
|
456 |
+
function handleMouseDownDolly( event ) {
|
457 |
+
|
458 |
+
dollyStart.set( event.clientX, event.clientY );
|
459 |
+
|
460 |
+
}
|
461 |
+
|
462 |
+
function handleMouseDownPan( event ) {
|
463 |
+
|
464 |
+
panStart.set( event.clientX, event.clientY );
|
465 |
+
|
466 |
+
}
|
467 |
+
|
468 |
+
function handleMouseMoveRotate( event ) {
|
469 |
+
|
470 |
+
rotateEnd.set( event.clientX, event.clientY );
|
471 |
+
rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
|
472 |
+
const element = scope.domElement;
|
473 |
+
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
|
474 |
+
|
475 |
+
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
|
476 |
+
rotateStart.copy( rotateEnd );
|
477 |
+
scope.update();
|
478 |
+
|
479 |
+
}
|
480 |
+
|
481 |
+
function handleMouseMoveDolly( event ) {
|
482 |
+
|
483 |
+
dollyEnd.set( event.clientX, event.clientY );
|
484 |
+
dollyDelta.subVectors( dollyEnd, dollyStart );
|
485 |
+
|
486 |
+
if ( dollyDelta.y > 0 ) {
|
487 |
+
|
488 |
+
dollyOut( getZoomScale() );
|
489 |
+
|
490 |
+
} else if ( dollyDelta.y < 0 ) {
|
491 |
+
|
492 |
+
dollyIn( getZoomScale() );
|
493 |
+
|
494 |
+
}
|
495 |
+
|
496 |
+
dollyStart.copy( dollyEnd );
|
497 |
+
scope.update();
|
498 |
+
|
499 |
+
}
|
500 |
+
|
501 |
+
function handleMouseMovePan( event ) {
|
502 |
+
|
503 |
+
panEnd.set( event.clientX, event.clientY );
|
504 |
+
panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
|
505 |
+
pan( panDelta.x, panDelta.y );
|
506 |
+
panStart.copy( panEnd );
|
507 |
+
scope.update();
|
508 |
+
|
509 |
+
}
|
510 |
+
|
511 |
+
function handleMouseUp( ) { // no-op
|
512 |
+
}
|
513 |
+
|
514 |
+
function handleMouseWheel( event ) {
|
515 |
+
|
516 |
+
if ( event.deltaY < 0 ) {
|
517 |
+
|
518 |
+
dollyIn( getZoomScale() );
|
519 |
+
|
520 |
+
} else if ( event.deltaY > 0 ) {
|
521 |
+
|
522 |
+
dollyOut( getZoomScale() );
|
523 |
+
|
524 |
+
}
|
525 |
+
|
526 |
+
scope.update();
|
527 |
+
|
528 |
+
}
|
529 |
+
|
530 |
+
function handleKeyDown( event ) {
|
531 |
+
|
532 |
+
let needsUpdate = false;
|
533 |
+
|
534 |
+
switch ( event.code ) {
|
535 |
+
|
536 |
+
case scope.keys.UP:
|
537 |
+
pan( 0, scope.keyPanSpeed );
|
538 |
+
needsUpdate = true;
|
539 |
+
break;
|
540 |
+
|
541 |
+
case scope.keys.BOTTOM:
|
542 |
+
pan( 0, - scope.keyPanSpeed );
|
543 |
+
needsUpdate = true;
|
544 |
+
break;
|
545 |
+
|
546 |
+
case scope.keys.LEFT:
|
547 |
+
pan( scope.keyPanSpeed, 0 );
|
548 |
+
needsUpdate = true;
|
549 |
+
break;
|
550 |
+
|
551 |
+
case scope.keys.RIGHT:
|
552 |
+
pan( - scope.keyPanSpeed, 0 );
|
553 |
+
needsUpdate = true;
|
554 |
+
break;
|
555 |
+
|
556 |
+
}
|
557 |
+
|
558 |
+
if ( needsUpdate ) {
|
559 |
+
|
560 |
+
// prevent the browser from scrolling on cursor keys
|
561 |
+
event.preventDefault();
|
562 |
+
scope.update();
|
563 |
+
|
564 |
+
}
|
565 |
+
|
566 |
+
}
|
567 |
+
|
568 |
+
function handleTouchStartRotate() {
|
569 |
+
|
570 |
+
if ( pointers.length === 1 ) {
|
571 |
+
|
572 |
+
rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
|
573 |
+
|
574 |
+
} else {
|
575 |
+
|
576 |
+
const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
|
577 |
+
const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
|
578 |
+
rotateStart.set( x, y );
|
579 |
+
|
580 |
+
}
|
581 |
+
|
582 |
+
}
|
583 |
+
|
584 |
+
function handleTouchStartPan() {
|
585 |
+
|
586 |
+
if ( pointers.length === 1 ) {
|
587 |
+
|
588 |
+
panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
|
589 |
+
|
590 |
+
} else {
|
591 |
+
|
592 |
+
const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
|
593 |
+
const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
|
594 |
+
panStart.set( x, y );
|
595 |
+
|
596 |
+
}
|
597 |
+
|
598 |
+
}
|
599 |
+
|
600 |
+
function handleTouchStartDolly() {
|
601 |
+
|
602 |
+
const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX;
|
603 |
+
const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY;
|
604 |
+
const distance = Math.sqrt( dx * dx + dy * dy );
|
605 |
+
dollyStart.set( 0, distance );
|
606 |
+
|
607 |
+
}
|
608 |
+
|
609 |
+
function handleTouchStartDollyPan() {
|
610 |
+
|
611 |
+
if ( scope.enableZoom ) handleTouchStartDolly();
|
612 |
+
if ( scope.enablePan ) handleTouchStartPan();
|
613 |
+
|
614 |
+
}
|
615 |
+
|
616 |
+
function handleTouchStartDollyRotate() {
|
617 |
+
|
618 |
+
if ( scope.enableZoom ) handleTouchStartDolly();
|
619 |
+
if ( scope.enableRotate ) handleTouchStartRotate();
|
620 |
+
|
621 |
+
}
|
622 |
+
|
623 |
+
function handleTouchMoveRotate( event ) {
|
624 |
+
|
625 |
+
if ( pointers.length == 1 ) {
|
626 |
+
|
627 |
+
rotateEnd.set( event.pageX, event.pageY );
|
628 |
+
|
629 |
+
} else {
|
630 |
+
|
631 |
+
const position = getSecondPointerPosition( event );
|
632 |
+
const x = 0.5 * ( event.pageX + position.x );
|
633 |
+
const y = 0.5 * ( event.pageY + position.y );
|
634 |
+
rotateEnd.set( x, y );
|
635 |
+
|
636 |
+
}
|
637 |
+
|
638 |
+
rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
|
639 |
+
const element = scope.domElement;
|
640 |
+
rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
|
641 |
+
|
642 |
+
rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
|
643 |
+
rotateStart.copy( rotateEnd );
|
644 |
+
|
645 |
+
}
|
646 |
+
|
647 |
+
function handleTouchMovePan( event ) {
|
648 |
+
|
649 |
+
if ( pointers.length === 1 ) {
|
650 |
+
|
651 |
+
panEnd.set( event.pageX, event.pageY );
|
652 |
+
|
653 |
+
} else {
|
654 |
+
|
655 |
+
const position = getSecondPointerPosition( event );
|
656 |
+
const x = 0.5 * ( event.pageX + position.x );
|
657 |
+
const y = 0.5 * ( event.pageY + position.y );
|
658 |
+
panEnd.set( x, y );
|
659 |
+
|
660 |
+
}
|
661 |
+
|
662 |
+
panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
|
663 |
+
pan( panDelta.x, panDelta.y );
|
664 |
+
panStart.copy( panEnd );
|
665 |
+
|
666 |
+
}
|
667 |
+
|
668 |
+
function handleTouchMoveDolly( event ) {
|
669 |
+
|
670 |
+
const position = getSecondPointerPosition( event );
|
671 |
+
const dx = event.pageX - position.x;
|
672 |
+
const dy = event.pageY - position.y;
|
673 |
+
const distance = Math.sqrt( dx * dx + dy * dy );
|
674 |
+
dollyEnd.set( 0, distance );
|
675 |
+
dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
|
676 |
+
dollyOut( dollyDelta.y );
|
677 |
+
dollyStart.copy( dollyEnd );
|
678 |
+
|
679 |
+
}
|
680 |
+
|
681 |
+
function handleTouchMoveDollyPan( event ) {
|
682 |
+
|
683 |
+
if ( scope.enableZoom ) handleTouchMoveDolly( event );
|
684 |
+
if ( scope.enablePan ) handleTouchMovePan( event );
|
685 |
+
|
686 |
+
}
|
687 |
+
|
688 |
+
function handleTouchMoveDollyRotate( event ) {
|
689 |
+
|
690 |
+
if ( scope.enableZoom ) handleTouchMoveDolly( event );
|
691 |
+
if ( scope.enableRotate ) handleTouchMoveRotate( event );
|
692 |
+
|
693 |
+
}
|
694 |
+
|
695 |
+
function handleTouchEnd( ) { // no-op
|
696 |
+
} //
|
697 |
+
// event handlers - FSM: listen for events and reset state
|
698 |
+
//
|
699 |
+
|
700 |
+
|
701 |
+
function onPointerDown( event ) {
|
702 |
+
|
703 |
+
if ( scope.enabled === false ) return;
|
704 |
+
|
705 |
+
if ( pointers.length === 0 ) {
|
706 |
+
|
707 |
+
scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
|
708 |
+
scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
|
709 |
+
|
710 |
+
} //
|
711 |
+
|
712 |
+
|
713 |
+
addPointer( event );
|
714 |
+
|
715 |
+
if ( event.pointerType === 'touch' ) {
|
716 |
+
|
717 |
+
onTouchStart( event );
|
718 |
+
|
719 |
+
} else {
|
720 |
+
|
721 |
+
onMouseDown( event );
|
722 |
+
|
723 |
+
}
|
724 |
+
|
725 |
+
}
|
726 |
+
|
727 |
+
function onPointerMove( event ) {
|
728 |
+
|
729 |
+
if ( scope.enabled === false ) return;
|
730 |
+
|
731 |
+
if ( event.pointerType === 'touch' ) {
|
732 |
+
|
733 |
+
onTouchMove( event );
|
734 |
+
|
735 |
+
} else {
|
736 |
+
|
737 |
+
onMouseMove( event );
|
738 |
+
|
739 |
+
}
|
740 |
+
|
741 |
+
}
|
742 |
+
|
743 |
+
function onPointerUp( event ) {
|
744 |
+
|
745 |
+
if ( scope.enabled === false ) return;
|
746 |
+
|
747 |
+
if ( event.pointerType === 'touch' ) {
|
748 |
+
|
749 |
+
onTouchEnd();
|
750 |
+
|
751 |
+
} else {
|
752 |
+
|
753 |
+
onMouseUp( event );
|
754 |
+
|
755 |
+
}
|
756 |
+
|
757 |
+
removePointer( event ); //
|
758 |
+
|
759 |
+
if ( pointers.length === 0 ) {
|
760 |
+
|
761 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
|
762 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
|
763 |
+
|
764 |
+
}
|
765 |
+
|
766 |
+
}
|
767 |
+
|
768 |
+
function onPointerCancel( event ) {
|
769 |
+
|
770 |
+
removePointer( event );
|
771 |
+
|
772 |
+
}
|
773 |
+
|
774 |
+
function onMouseDown( event ) {
|
775 |
+
|
776 |
+
let mouseAction;
|
777 |
+
|
778 |
+
switch ( event.button ) {
|
779 |
+
|
780 |
+
case 0:
|
781 |
+
mouseAction = scope.mouseButtons.LEFT;
|
782 |
+
break;
|
783 |
+
|
784 |
+
case 1:
|
785 |
+
mouseAction = scope.mouseButtons.MIDDLE;
|
786 |
+
break;
|
787 |
+
|
788 |
+
case 2:
|
789 |
+
mouseAction = scope.mouseButtons.RIGHT;
|
790 |
+
break;
|
791 |
+
|
792 |
+
default:
|
793 |
+
mouseAction = - 1;
|
794 |
+
|
795 |
+
}
|
796 |
+
|
797 |
+
switch ( mouseAction ) {
|
798 |
+
|
799 |
+
case THREE.MOUSE.DOLLY:
|
800 |
+
if ( scope.enableZoom === false ) return;
|
801 |
+
handleMouseDownDolly( event );
|
802 |
+
state = STATE.DOLLY;
|
803 |
+
break;
|
804 |
+
|
805 |
+
case THREE.MOUSE.ROTATE:
|
806 |
+
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
807 |
+
|
808 |
+
if ( scope.enablePan === false ) return;
|
809 |
+
handleMouseDownPan( event );
|
810 |
+
state = STATE.PAN;
|
811 |
+
|
812 |
+
} else {
|
813 |
+
|
814 |
+
if ( scope.enableRotate === false ) return;
|
815 |
+
handleMouseDownRotate( event );
|
816 |
+
state = STATE.ROTATE;
|
817 |
+
|
818 |
+
}
|
819 |
+
|
820 |
+
break;
|
821 |
+
|
822 |
+
case THREE.MOUSE.PAN:
|
823 |
+
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
824 |
+
|
825 |
+
if ( scope.enableRotate === false ) return;
|
826 |
+
handleMouseDownRotate( event );
|
827 |
+
state = STATE.ROTATE;
|
828 |
+
|
829 |
+
} else {
|
830 |
+
|
831 |
+
if ( scope.enablePan === false ) return;
|
832 |
+
handleMouseDownPan( event );
|
833 |
+
state = STATE.PAN;
|
834 |
+
|
835 |
+
}
|
836 |
+
|
837 |
+
break;
|
838 |
+
|
839 |
+
default:
|
840 |
+
state = STATE.NONE;
|
841 |
+
|
842 |
+
}
|
843 |
+
|
844 |
+
if ( state !== STATE.NONE ) {
|
845 |
+
|
846 |
+
scope.dispatchEvent( _startEvent );
|
847 |
+
|
848 |
+
}
|
849 |
+
|
850 |
+
}
|
851 |
+
|
852 |
+
function onMouseMove( event ) {
|
853 |
+
|
854 |
+
if ( scope.enabled === false ) return;
|
855 |
+
|
856 |
+
switch ( state ) {
|
857 |
+
|
858 |
+
case STATE.ROTATE:
|
859 |
+
if ( scope.enableRotate === false ) return;
|
860 |
+
handleMouseMoveRotate( event );
|
861 |
+
break;
|
862 |
+
|
863 |
+
case STATE.DOLLY:
|
864 |
+
if ( scope.enableZoom === false ) return;
|
865 |
+
handleMouseMoveDolly( event );
|
866 |
+
break;
|
867 |
+
|
868 |
+
case STATE.PAN:
|
869 |
+
if ( scope.enablePan === false ) return;
|
870 |
+
handleMouseMovePan( event );
|
871 |
+
break;
|
872 |
+
|
873 |
+
}
|
874 |
+
|
875 |
+
}
|
876 |
+
|
877 |
+
function onMouseUp( event ) {
|
878 |
+
|
879 |
+
handleMouseUp( event );
|
880 |
+
scope.dispatchEvent( _endEvent );
|
881 |
+
state = STATE.NONE;
|
882 |
+
|
883 |
+
}
|
884 |
+
|
885 |
+
function onMouseWheel( event ) {
|
886 |
+
|
887 |
+
if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE && state !== STATE.ROTATE ) return;
|
888 |
+
event.preventDefault();
|
889 |
+
scope.dispatchEvent( _startEvent );
|
890 |
+
handleMouseWheel( event );
|
891 |
+
scope.dispatchEvent( _endEvent );
|
892 |
+
|
893 |
+
}
|
894 |
+
|
895 |
+
function onKeyDown( event ) {
|
896 |
+
|
897 |
+
if ( scope.enabled === false || scope.enablePan === false ) return;
|
898 |
+
handleKeyDown( event );
|
899 |
+
|
900 |
+
}
|
901 |
+
|
902 |
+
function onTouchStart( event ) {
|
903 |
+
|
904 |
+
trackPointer( event );
|
905 |
+
|
906 |
+
switch ( pointers.length ) {
|
907 |
+
|
908 |
+
case 1:
|
909 |
+
switch ( scope.touches.ONE ) {
|
910 |
+
|
911 |
+
case THREE.TOUCH.ROTATE:
|
912 |
+
if ( scope.enableRotate === false ) return;
|
913 |
+
handleTouchStartRotate();
|
914 |
+
state = STATE.TOUCH_ROTATE;
|
915 |
+
break;
|
916 |
+
|
917 |
+
case THREE.TOUCH.PAN:
|
918 |
+
if ( scope.enablePan === false ) return;
|
919 |
+
handleTouchStartPan();
|
920 |
+
state = STATE.TOUCH_PAN;
|
921 |
+
break;
|
922 |
+
|
923 |
+
default:
|
924 |
+
state = STATE.NONE;
|
925 |
+
|
926 |
+
}
|
927 |
+
|
928 |
+
break;
|
929 |
+
|
930 |
+
case 2:
|
931 |
+
switch ( scope.touches.TWO ) {
|
932 |
+
|
933 |
+
case THREE.TOUCH.DOLLY_PAN:
|
934 |
+
if ( scope.enableZoom === false && scope.enablePan === false ) return;
|
935 |
+
handleTouchStartDollyPan();
|
936 |
+
state = STATE.TOUCH_DOLLY_PAN;
|
937 |
+
break;
|
938 |
+
|
939 |
+
case THREE.TOUCH.DOLLY_ROTATE:
|
940 |
+
if ( scope.enableZoom === false && scope.enableRotate === false ) return;
|
941 |
+
handleTouchStartDollyRotate();
|
942 |
+
state = STATE.TOUCH_DOLLY_ROTATE;
|
943 |
+
break;
|
944 |
+
|
945 |
+
default:
|
946 |
+
state = STATE.NONE;
|
947 |
+
|
948 |
+
}
|
949 |
+
|
950 |
+
break;
|
951 |
+
|
952 |
+
default:
|
953 |
+
state = STATE.NONE;
|
954 |
+
|
955 |
+
}
|
956 |
+
|
957 |
+
if ( state !== STATE.NONE ) {
|
958 |
+
|
959 |
+
scope.dispatchEvent( _startEvent );
|
960 |
+
|
961 |
+
}
|
962 |
+
|
963 |
+
}
|
964 |
+
|
965 |
+
function onTouchMove( event ) {
|
966 |
+
|
967 |
+
trackPointer( event );
|
968 |
+
|
969 |
+
switch ( state ) {
|
970 |
+
|
971 |
+
case STATE.TOUCH_ROTATE:
|
972 |
+
if ( scope.enableRotate === false ) return;
|
973 |
+
handleTouchMoveRotate( event );
|
974 |
+
scope.update();
|
975 |
+
break;
|
976 |
+
|
977 |
+
case STATE.TOUCH_PAN:
|
978 |
+
if ( scope.enablePan === false ) return;
|
979 |
+
handleTouchMovePan( event );
|
980 |
+
scope.update();
|
981 |
+
break;
|
982 |
+
|
983 |
+
case STATE.TOUCH_DOLLY_PAN:
|
984 |
+
if ( scope.enableZoom === false && scope.enablePan === false ) return;
|
985 |
+
handleTouchMoveDollyPan( event );
|
986 |
+
scope.update();
|
987 |
+
break;
|
988 |
+
|
989 |
+
case STATE.TOUCH_DOLLY_ROTATE:
|
990 |
+
if ( scope.enableZoom === false && scope.enableRotate === false ) return;
|
991 |
+
handleTouchMoveDollyRotate( event );
|
992 |
+
scope.update();
|
993 |
+
break;
|
994 |
+
|
995 |
+
default:
|
996 |
+
state = STATE.NONE;
|
997 |
+
|
998 |
+
}
|
999 |
+
|
1000 |
+
}
|
1001 |
+
|
1002 |
+
function onTouchEnd( event ) {
|
1003 |
+
|
1004 |
+
handleTouchEnd( event );
|
1005 |
+
scope.dispatchEvent( _endEvent );
|
1006 |
+
state = STATE.NONE;
|
1007 |
+
|
1008 |
+
}
|
1009 |
+
|
1010 |
+
function onContextMenu( event ) {
|
1011 |
+
|
1012 |
+
if ( scope.enabled === false ) return;
|
1013 |
+
event.preventDefault();
|
1014 |
+
|
1015 |
+
}
|
1016 |
+
|
1017 |
+
function addPointer( event ) {
|
1018 |
+
|
1019 |
+
pointers.push( event );
|
1020 |
+
|
1021 |
+
}
|
1022 |
+
|
1023 |
+
function removePointer( event ) {
|
1024 |
+
|
1025 |
+
delete pointerPositions[ event.pointerId ];
|
1026 |
+
|
1027 |
+
for ( let i = 0; i < pointers.length; i ++ ) {
|
1028 |
+
|
1029 |
+
if ( pointers[ i ].pointerId == event.pointerId ) {
|
1030 |
+
|
1031 |
+
pointers.splice( i, 1 );
|
1032 |
+
return;
|
1033 |
+
|
1034 |
+
}
|
1035 |
+
|
1036 |
+
}
|
1037 |
+
|
1038 |
+
}
|
1039 |
+
|
1040 |
+
function trackPointer( event ) {
|
1041 |
+
|
1042 |
+
let position = pointerPositions[ event.pointerId ];
|
1043 |
+
|
1044 |
+
if ( position === undefined ) {
|
1045 |
+
|
1046 |
+
position = new THREE.Vector2();
|
1047 |
+
pointerPositions[ event.pointerId ] = position;
|
1048 |
+
|
1049 |
+
}
|
1050 |
+
|
1051 |
+
position.set( event.pageX, event.pageY );
|
1052 |
+
|
1053 |
+
}
|
1054 |
+
|
1055 |
+
function getSecondPointerPosition( event ) {
|
1056 |
+
|
1057 |
+
const pointer = event.pointerId === pointers[ 0 ].pointerId ? pointers[ 1 ] : pointers[ 0 ];
|
1058 |
+
return pointerPositions[ pointer.pointerId ];
|
1059 |
+
|
1060 |
+
} //
|
1061 |
+
|
1062 |
+
|
1063 |
+
scope.domElement.addEventListener( 'contextmenu', onContextMenu );
|
1064 |
+
scope.domElement.addEventListener( 'pointerdown', onPointerDown );
|
1065 |
+
scope.domElement.addEventListener( 'pointercancel', onPointerCancel );
|
1066 |
+
scope.domElement.addEventListener( 'wheel', onMouseWheel, {
|
1067 |
+
passive: false
|
1068 |
+
} ); // force an update at start
|
1069 |
+
|
1070 |
+
this.update();
|
1071 |
+
|
1072 |
+
}
|
1073 |
+
|
1074 |
+
} // This set of controls performs orbiting, dollying (zooming), and panning.
|
1075 |
+
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
|
1076 |
+
// This is very similar to OrbitControls, another set of touch behavior
|
1077 |
+
//
|
1078 |
+
// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
|
1079 |
+
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
|
1080 |
+
// Pan - left mouse, or arrow keys / touch: one-finger move
|
1081 |
+
|
1082 |
+
|
1083 |
+
class MapControls extends OrbitControls {
|
1084 |
+
|
1085 |
+
constructor( object, domElement ) {
|
1086 |
+
|
1087 |
+
super( object, domElement );
|
1088 |
+
this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
|
1089 |
+
|
1090 |
+
this.mouseButtons.LEFT = THREE.MOUSE.PAN;
|
1091 |
+
this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
|
1092 |
+
this.touches.ONE = THREE.TOUCH.PAN;
|
1093 |
+
this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
|
1094 |
+
|
1095 |
+
}
|
1096 |
+
|
1097 |
+
}
|
1098 |
+
|
1099 |
+
THREE.MapControls = MapControls;
|
1100 |
+
THREE.OrbitControls = OrbitControls;
|
1101 |
+
|
1102 |
+
} )();
|
lib/Three.min.js
ADDED
The diff for this file is too large to render.
See raw diff
|
|
lib/Three.sprite.js
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("three"),require("@seregpie/three.text-texture")):"function"==typeof define&&define.amd?define(["three","@seregpie/three.text-texture"],t):((e="undefined"!=typeof globalThis?globalThis:e||self).THREE=e.THREE||{},e.THREE.TextSprite=t(e.THREE,e.THREE.TextTexture))}(this,(function(e,t){"use strict";function i(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=i(t);let o=class extends e.Sprite{constructor({fontSize:t=1,...i}={},o=new e.SpriteMaterial({depthWrite:!1})){super(o);let a=new r.default({fontSize:t,...i});this.material.map=a}onBeforeRender(e,t,i){let{material:r}=this,{map:o}=r;if(o.checkFontFace()){let{scale:t}=this,{height:r,width:a}=o;a&&r?(t.setX(a).setY(r),o.setOptimalPixelRatio(this,e,i),o.redraw()):t.setScalar(1)}else o.loadFontFace()}dispose(){let{material:e}=this,{map:t}=e;t.dispose(),e.dispose()}};return["alignment","backgroundColor","color","fontFamily","fontSize","fontStyle","fontVariant","fontWeight","lineGap","padding","strokeColor","strokeWidth","text"].forEach((e=>{Object.defineProperty(o.prototype,e,{get(){return this.material.map[e]},set(t){this.material.map[e]=t}})})),o.prototype.isTextSprite=!0,o}));
|
lib/Three.texture.js
ADDED
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
! function(t, e) {
|
2 |
+
"object" == typeof exports && "undefined" != typeof module ? module.exports = e(require("three")) : "function" == typeof define && define.amd ? define(["three"], e) : ((t = "undefined" != typeof globalThis ? globalThis : t || self).THREE = t.THREE || {}, t.THREE.TextTexture = e(t.THREE))
|
3 |
+
}(this, (function(t) {
|
4 |
+
"use strict";
|
5 |
+
let e = class extends t.Texture {
|
6 |
+
constructor() {
|
7 |
+
super(document.createElement("canvas"));
|
8 |
+
let e = null,
|
9 |
+
i = () => e || (e = this.createDrawable()),
|
10 |
+
n = () => i().width,
|
11 |
+
o = () => i().height,
|
12 |
+
r = !0,
|
13 |
+
l = 1,
|
14 |
+
a = () => t.MathUtils.ceilPowerOfTwo(n() * l),
|
15 |
+
s = () => t.MathUtils.ceilPowerOfTwo(o() * l),
|
16 |
+
h = t => {
|
17 |
+
if (l !== t) {
|
18 |
+
let e = a(),
|
19 |
+
i = s();
|
20 |
+
l = t;
|
21 |
+
let n = a(),
|
22 |
+
o = s();
|
23 |
+
n === e && o === i || (r = !0)
|
24 |
+
}
|
25 |
+
},
|
26 |
+
c = (() => {
|
27 |
+
let e = new t.Vector3,
|
28 |
+
i = new t.Vector2,
|
29 |
+
r = new t.Vector3,
|
30 |
+
l = new t.Vector3,
|
31 |
+
a = new t.Vector2;
|
32 |
+
return (s, h, c) => {
|
33 |
+
if (a.set(n(), o()), a.x && a.y) {
|
34 |
+
s.getWorldPosition(r), c.getWorldPosition(e);
|
35 |
+
let n = r.distanceTo(e);
|
36 |
+
if (c.isPerspectiveCamera && (n *= 2 * Math.tan(t.MathUtils.degToRad(c.fov) / 2)), (c.isPerspectiveCamera || c.isOrthographicCamera) && (n /= c.zoom), n) {
|
37 |
+
var f, d;
|
38 |
+
s.getWorldScale(l);
|
39 |
+
let t = null !== (f = null === (d = h.capabilities) || void 0 === d ? void 0 : d.maxTextureSize) && void 0 !== f ? f : 1 / 0;
|
40 |
+
return h.getDrawingBufferSize(i), Math.min(Math.max(l.x / n * (i.x / a.x), l.y / n * (i.y / a.y)), t / a.x, t / a.y)
|
41 |
+
}
|
42 |
+
}
|
43 |
+
return 0
|
44 |
+
}
|
45 |
+
})();
|
46 |
+
Object.defineProperties(this, {
|
47 |
+
width: {
|
48 |
+
get: n
|
49 |
+
},
|
50 |
+
height: {
|
51 |
+
get: o
|
52 |
+
},
|
53 |
+
pixelRatio: {
|
54 |
+
get: () => l,
|
55 |
+
set: h
|
56 |
+
},
|
57 |
+
needsRedraw: {
|
58 |
+
set(t) {
|
59 |
+
t && (r = !0, e = null)
|
60 |
+
}
|
61 |
+
}
|
62 |
+
}), Object.assign(this, {
|
63 |
+
redraw() {
|
64 |
+
if (r) {
|
65 |
+
let t = this.image,
|
66 |
+
e = t.getContext("2d");
|
67 |
+
e.clearRect(0, 0, t.width, t.height), t.width = a(), t.height = s(), t.width && t.height ? (e.save(), e.scale(t.width / n(), t.height / o()), ((...t) => {
|
68 |
+
i().draw(...t)
|
69 |
+
})(e), e.restore()) : t.width = t.height = 1, r = !1, this.needsUpdate = !0
|
70 |
+
}
|
71 |
+
},
|
72 |
+
setOptimalPixelRatio(...t) {
|
73 |
+
h(c(...t))
|
74 |
+
}
|
75 |
+
})
|
76 |
+
}
|
77 |
+
};
|
78 |
+
e.prototype.isDynamicTexture = !0;
|
79 |
+
let i = class extends e {
|
80 |
+
constructor({
|
81 |
+
alignment: t = "center",
|
82 |
+
backgroundColor: e = "rgba(0,0,0,0)",
|
83 |
+
color: i = "#fff",
|
84 |
+
fontFamily: n = "sans-serif",
|
85 |
+
fontSize: o = 16,
|
86 |
+
fontStyle: r = "normal",
|
87 |
+
fontVariant: l = "normal",
|
88 |
+
fontWeight: a = "normal",
|
89 |
+
lineGap: s = 1 / 4,
|
90 |
+
padding: h = .5,
|
91 |
+
strokeColor: c = "#fff",
|
92 |
+
strokeWidth: f = 0,
|
93 |
+
text: d = ""
|
94 |
+
} = {}) {
|
95 |
+
super(), Object.entries({
|
96 |
+
alignment: t,
|
97 |
+
backgroundColor: e,
|
98 |
+
color: i,
|
99 |
+
fontFamily: n,
|
100 |
+
fontSize: o,
|
101 |
+
fontStyle: r,
|
102 |
+
fontVariant: l,
|
103 |
+
fontWeight: a,
|
104 |
+
lineGap: s,
|
105 |
+
padding: h,
|
106 |
+
strokeColor: c,
|
107 |
+
strokeWidth: f,
|
108 |
+
text: d
|
109 |
+
}).forEach((([t, e]) => {
|
110 |
+
Object.defineProperty(this, t, {
|
111 |
+
get: () => e,
|
112 |
+
set(t) {
|
113 |
+
e !== t && (e = t, this.needsRedraw = !0)
|
114 |
+
}
|
115 |
+
})
|
116 |
+
}))
|
117 |
+
}
|
118 |
+
get lines() {
|
119 |
+
let {
|
120 |
+
text: t
|
121 |
+
} = this;
|
122 |
+
return t ? t.split("\n") : []
|
123 |
+
}
|
124 |
+
get font() {
|
125 |
+
return function(t, e, i, n, o) {
|
126 |
+
let r = document.createElement("span");
|
127 |
+
return r.style.font = "1px serif", r.style.fontFamily = t, r.style.fontSize = "".concat(e, "px"), r.style.fontStyle = i, r.style.fontVariant = n, r.style.fontWeight = o, r.style.font
|
128 |
+
}(this.fontFamily, this.fontSize, this.fontStyle, this.fontVariant, this.fontWeight)
|
129 |
+
}
|
130 |
+
checkFontFace() {
|
131 |
+
try {
|
132 |
+
let {
|
133 |
+
font: t
|
134 |
+
} = this;
|
135 |
+
return document.fonts.check(t)
|
136 |
+
} catch (e) {}
|
137 |
+
return !0
|
138 |
+
}
|
139 |
+
async loadFontFace() {
|
140 |
+
try {
|
141 |
+
let {
|
142 |
+
font: t
|
143 |
+
} = this;
|
144 |
+
await document.fonts.load(t)
|
145 |
+
} catch (e) {}
|
146 |
+
}
|
147 |
+
createDrawable() {
|
148 |
+
let {
|
149 |
+
alignment: t,
|
150 |
+
backgroundColor: e,
|
151 |
+
color: i,
|
152 |
+
font: n,
|
153 |
+
fontSize: o,
|
154 |
+
lineGap: r,
|
155 |
+
lines: l,
|
156 |
+
padding: a,
|
157 |
+
strokeColor: s,
|
158 |
+
strokeWidth: h
|
159 |
+
} = this;
|
160 |
+
a *= o, r *= o, h *= o;
|
161 |
+
let c = l.length,
|
162 |
+
f = o + r,
|
163 |
+
d = c ? (() => {
|
164 |
+
let t = document.createElement("canvas").getContext("2d");
|
165 |
+
return t.font = n, Math.max(...l.map((e => t.measureText(e).width)))
|
166 |
+
})() : 0,
|
167 |
+
g = a + h / 2,
|
168 |
+
u = d + 2 * g;
|
169 |
+
return {
|
170 |
+
width: u,
|
171 |
+
height: (c ? o + f * (c - 1) : 0) + 2 * g,
|
172 |
+
draw(r) {
|
173 |
+
let a;
|
174 |
+
r.fillStyle = e, r.fillRect(0, 0, r.canvas.width, r.canvas.height);
|
175 |
+
let c = g + o / 2;
|
176 |
+
Object.assign(r, {
|
177 |
+
fillStyle: i,
|
178 |
+
font: n,
|
179 |
+
lineWidth: h,
|
180 |
+
miterLimit: 1,
|
181 |
+
strokeStyle: s,
|
182 |
+
textAlign: (() => {
|
183 |
+
switch (t) {
|
184 |
+
case "left":
|
185 |
+
return a = g, "left";
|
186 |
+
case "right":
|
187 |
+
return a = u - g, "right"
|
188 |
+
}
|
189 |
+
return a = u / 2, "center"
|
190 |
+
})(),
|
191 |
+
textBaseline: "middle"
|
192 |
+
}), l.forEach((t => {
|
193 |
+
|
194 |
+
// r.lineWidth=60
|
195 |
+
// r.shadowColor="white"
|
196 |
+
// r.shadowBlur=2
|
197 |
+
// r.fillStyle = "white"
|
198 |
+
// r.fillText(t, a, c);
|
199 |
+
|
200 |
+
r.lineWidth=5
|
201 |
+
r.shadowColor="black"
|
202 |
+
r.shadowBlur=0
|
203 |
+
r.fillStyle = "black"
|
204 |
+
r.fillText(t, a, c);
|
205 |
+
|
206 |
+
h && r.strokeText(t, a, c), c += f
|
207 |
+
}))
|
208 |
+
}
|
209 |
+
}
|
210 |
+
}
|
211 |
+
};
|
212 |
+
return i.prototype.isTextTexture = !0, i
|
213 |
+
}));
|
lib/TrackballControls.js
ADDED
@@ -0,0 +1,778 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function () {
|
2 |
+
|
3 |
+
const _changeEvent = {
|
4 |
+
type: 'change'
|
5 |
+
};
|
6 |
+
const _startEvent = {
|
7 |
+
type: 'start'
|
8 |
+
};
|
9 |
+
const _endEvent = {
|
10 |
+
type: 'end'
|
11 |
+
};
|
12 |
+
|
13 |
+
class TrackballControls extends THREE.EventDispatcher {
|
14 |
+
|
15 |
+
constructor( object, domElement ) {
|
16 |
+
|
17 |
+
super();
|
18 |
+
if ( domElement === undefined ) console.warn( 'THREE.TrackballControls: The second parameter "domElement" is now mandatory.' );
|
19 |
+
if ( domElement === document ) console.error( 'THREE.TrackballControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
|
20 |
+
const scope = this;
|
21 |
+
const STATE = {
|
22 |
+
NONE: - 1,
|
23 |
+
ROTATE: 0,
|
24 |
+
ZOOM: 1,
|
25 |
+
PAN: 2,
|
26 |
+
TOUCH_ROTATE: 3,
|
27 |
+
TOUCH_ZOOM_PAN: 4
|
28 |
+
};
|
29 |
+
this.object = object;
|
30 |
+
this.domElement = domElement;
|
31 |
+
this.domElement.style.touchAction = 'none'; // disable touch scroll
|
32 |
+
// API
|
33 |
+
|
34 |
+
this.enabled = true;
|
35 |
+
this.screen = {
|
36 |
+
left: 0,
|
37 |
+
top: 0,
|
38 |
+
width: 0,
|
39 |
+
height: 0
|
40 |
+
};
|
41 |
+
this.rotateSpeed = 1.0;
|
42 |
+
this.zoomSpeed = 1.2;
|
43 |
+
this.panSpeed = 0.3;
|
44 |
+
this.noRotate = false;
|
45 |
+
this.noZoom = false;
|
46 |
+
this.noPan = false;
|
47 |
+
this.staticMoving = false;
|
48 |
+
this.dynamicDampingFactor = 0.2;
|
49 |
+
this.minDistance = 0;
|
50 |
+
this.maxDistance = Infinity;
|
51 |
+
this.keys = [ 'KeyA',
|
52 |
+
/*A*/
|
53 |
+
'KeyS',
|
54 |
+
/*S*/
|
55 |
+
'KeyD'
|
56 |
+
/*D*/
|
57 |
+
];
|
58 |
+
this.mouseButtons = {
|
59 |
+
LEFT: THREE.MOUSE.ROTATE,
|
60 |
+
MIDDLE: THREE.MOUSE.DOLLY,
|
61 |
+
RIGHT: THREE.MOUSE.PAN
|
62 |
+
}; // internals
|
63 |
+
|
64 |
+
this.target = new THREE.Vector3();
|
65 |
+
const EPS = 0.000001;
|
66 |
+
const lastPosition = new THREE.Vector3();
|
67 |
+
let lastZoom = 1;
|
68 |
+
let _state = STATE.NONE,
|
69 |
+
_keyState = STATE.NONE,
|
70 |
+
_touchZoomDistanceStart = 0,
|
71 |
+
_touchZoomDistanceEnd = 0,
|
72 |
+
_lastAngle = 0;
|
73 |
+
|
74 |
+
const _eye = new THREE.Vector3(),
|
75 |
+
_movePrev = new THREE.Vector2(),
|
76 |
+
_moveCurr = new THREE.Vector2(),
|
77 |
+
_lastAxis = new THREE.Vector3(),
|
78 |
+
_zoomStart = new THREE.Vector2(),
|
79 |
+
_zoomEnd = new THREE.Vector2(),
|
80 |
+
_panStart = new THREE.Vector2(),
|
81 |
+
_panEnd = new THREE.Vector2(),
|
82 |
+
_pointers = [],
|
83 |
+
_pointerPositions = {}; // for reset
|
84 |
+
|
85 |
+
|
86 |
+
this.target0 = this.target.clone();
|
87 |
+
this.position0 = this.object.position.clone();
|
88 |
+
this.up0 = this.object.up.clone();
|
89 |
+
this.zoom0 = this.object.zoom; // methods
|
90 |
+
|
91 |
+
this.handleResize = function () {
|
92 |
+
|
93 |
+
const box = scope.domElement.getBoundingClientRect(); // adjustments come from similar code in the jquery offset() function
|
94 |
+
|
95 |
+
const d = scope.domElement.ownerDocument.documentElement;
|
96 |
+
scope.screen.left = box.left + window.pageXOffset - d.clientLeft;
|
97 |
+
scope.screen.top = box.top + window.pageYOffset - d.clientTop;
|
98 |
+
scope.screen.width = box.width;
|
99 |
+
scope.screen.height = box.height;
|
100 |
+
|
101 |
+
};
|
102 |
+
|
103 |
+
const getMouseOnScreen = function () {
|
104 |
+
|
105 |
+
const vector = new THREE.Vector2();
|
106 |
+
return function getMouseOnScreen( pageX, pageY ) {
|
107 |
+
|
108 |
+
vector.set( ( pageX - scope.screen.left ) / scope.screen.width, ( pageY - scope.screen.top ) / scope.screen.height );
|
109 |
+
return vector;
|
110 |
+
|
111 |
+
};
|
112 |
+
|
113 |
+
}();
|
114 |
+
|
115 |
+
const getMouseOnCircle = function () {
|
116 |
+
|
117 |
+
const vector = new THREE.Vector2();
|
118 |
+
return function getMouseOnCircle( pageX, pageY ) {
|
119 |
+
|
120 |
+
vector.set( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ), ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width // screen.width intentional
|
121 |
+
);
|
122 |
+
return vector;
|
123 |
+
|
124 |
+
};
|
125 |
+
|
126 |
+
}();
|
127 |
+
|
128 |
+
this.rotateCamera = function () {
|
129 |
+
|
130 |
+
const axis = new THREE.Vector3(),
|
131 |
+
quaternion = new THREE.Quaternion(),
|
132 |
+
eyeDirection = new THREE.Vector3(),
|
133 |
+
objectUpDirection = new THREE.Vector3(),
|
134 |
+
objectSidewaysDirection = new THREE.Vector3(),
|
135 |
+
moveDirection = new THREE.Vector3();
|
136 |
+
return function rotateCamera() {
|
137 |
+
|
138 |
+
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
|
139 |
+
let angle = moveDirection.length();
|
140 |
+
|
141 |
+
if ( angle ) {
|
142 |
+
|
143 |
+
_eye.copy( scope.object.position ).sub( scope.target );
|
144 |
+
|
145 |
+
eyeDirection.copy( _eye ).normalize();
|
146 |
+
objectUpDirection.copy( scope.object.up ).normalize();
|
147 |
+
objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
|
148 |
+
objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
|
149 |
+
objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
|
150 |
+
moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
|
151 |
+
axis.crossVectors( moveDirection, _eye ).normalize();
|
152 |
+
angle *= scope.rotateSpeed;
|
153 |
+
quaternion.setFromAxisAngle( axis, angle );
|
154 |
+
|
155 |
+
_eye.applyQuaternion( quaternion );
|
156 |
+
|
157 |
+
scope.object.up.applyQuaternion( quaternion );
|
158 |
+
|
159 |
+
_lastAxis.copy( axis );
|
160 |
+
|
161 |
+
_lastAngle = angle;
|
162 |
+
|
163 |
+
} else if ( ! scope.staticMoving && _lastAngle ) {
|
164 |
+
|
165 |
+
_lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor );
|
166 |
+
|
167 |
+
_eye.copy( scope.object.position ).sub( scope.target );
|
168 |
+
|
169 |
+
quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
|
170 |
+
|
171 |
+
_eye.applyQuaternion( quaternion );
|
172 |
+
|
173 |
+
scope.object.up.applyQuaternion( quaternion );
|
174 |
+
|
175 |
+
}
|
176 |
+
|
177 |
+
_movePrev.copy( _moveCurr );
|
178 |
+
|
179 |
+
};
|
180 |
+
|
181 |
+
}();
|
182 |
+
|
183 |
+
this.zoomCamera = function () {
|
184 |
+
|
185 |
+
let factor;
|
186 |
+
|
187 |
+
if ( _state === STATE.TOUCH_ZOOM_PAN ) {
|
188 |
+
|
189 |
+
factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
|
190 |
+
_touchZoomDistanceStart = _touchZoomDistanceEnd;
|
191 |
+
|
192 |
+
if ( scope.object.isPerspectiveCamera ) {
|
193 |
+
|
194 |
+
_eye.multiplyScalar( factor );
|
195 |
+
|
196 |
+
} else if ( scope.object.isOrthographicCamera ) {
|
197 |
+
|
198 |
+
scope.object.zoom *= factor;
|
199 |
+
scope.object.updateProjectionMatrix();
|
200 |
+
|
201 |
+
} else {
|
202 |
+
|
203 |
+
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
204 |
+
|
205 |
+
}
|
206 |
+
|
207 |
+
} else {
|
208 |
+
|
209 |
+
factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed;
|
210 |
+
|
211 |
+
if ( factor !== 1.0 && factor > 0.0 ) {
|
212 |
+
|
213 |
+
if ( scope.object.isPerspectiveCamera ) {
|
214 |
+
|
215 |
+
_eye.multiplyScalar( factor );
|
216 |
+
|
217 |
+
} else if ( scope.object.isOrthographicCamera ) {
|
218 |
+
|
219 |
+
scope.object.zoom /= factor;
|
220 |
+
scope.object.updateProjectionMatrix();
|
221 |
+
|
222 |
+
} else {
|
223 |
+
|
224 |
+
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
225 |
+
|
226 |
+
}
|
227 |
+
|
228 |
+
}
|
229 |
+
|
230 |
+
if ( scope.staticMoving ) {
|
231 |
+
|
232 |
+
_zoomStart.copy( _zoomEnd );
|
233 |
+
|
234 |
+
} else {
|
235 |
+
|
236 |
+
_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
|
237 |
+
|
238 |
+
}
|
239 |
+
|
240 |
+
}
|
241 |
+
|
242 |
+
};
|
243 |
+
|
244 |
+
this.panCamera = function () {
|
245 |
+
|
246 |
+
const mouseChange = new THREE.Vector2(),
|
247 |
+
objectUp = new THREE.Vector3(),
|
248 |
+
pan = new THREE.Vector3();
|
249 |
+
return function panCamera() {
|
250 |
+
|
251 |
+
mouseChange.copy( _panEnd ).sub( _panStart );
|
252 |
+
|
253 |
+
if ( mouseChange.lengthSq() ) {
|
254 |
+
|
255 |
+
if ( scope.object.isOrthographicCamera ) {
|
256 |
+
|
257 |
+
const scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth;
|
258 |
+
const scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth;
|
259 |
+
mouseChange.x *= scale_x;
|
260 |
+
mouseChange.y *= scale_y;
|
261 |
+
|
262 |
+
}
|
263 |
+
|
264 |
+
mouseChange.multiplyScalar( _eye.length() * scope.panSpeed );
|
265 |
+
pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x );
|
266 |
+
pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) );
|
267 |
+
scope.object.position.add( pan );
|
268 |
+
scope.target.add( pan );
|
269 |
+
|
270 |
+
if ( scope.staticMoving ) {
|
271 |
+
|
272 |
+
_panStart.copy( _panEnd );
|
273 |
+
|
274 |
+
} else {
|
275 |
+
|
276 |
+
_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) );
|
277 |
+
|
278 |
+
}
|
279 |
+
|
280 |
+
}
|
281 |
+
|
282 |
+
};
|
283 |
+
|
284 |
+
}();
|
285 |
+
|
286 |
+
this.checkDistances = function () {
|
287 |
+
|
288 |
+
if ( ! scope.noZoom || ! scope.noPan ) {
|
289 |
+
|
290 |
+
if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) {
|
291 |
+
|
292 |
+
scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) );
|
293 |
+
|
294 |
+
_zoomStart.copy( _zoomEnd );
|
295 |
+
|
296 |
+
}
|
297 |
+
|
298 |
+
if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) {
|
299 |
+
|
300 |
+
scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) );
|
301 |
+
|
302 |
+
_zoomStart.copy( _zoomEnd );
|
303 |
+
|
304 |
+
}
|
305 |
+
|
306 |
+
}
|
307 |
+
|
308 |
+
};
|
309 |
+
|
310 |
+
this.update = function () {
|
311 |
+
|
312 |
+
_eye.subVectors( scope.object.position, scope.target );
|
313 |
+
|
314 |
+
if ( ! scope.noRotate ) {
|
315 |
+
|
316 |
+
scope.rotateCamera();
|
317 |
+
|
318 |
+
}
|
319 |
+
|
320 |
+
if ( ! scope.noZoom ) {
|
321 |
+
|
322 |
+
scope.zoomCamera();
|
323 |
+
|
324 |
+
}
|
325 |
+
|
326 |
+
if ( ! scope.noPan ) {
|
327 |
+
|
328 |
+
scope.panCamera();
|
329 |
+
|
330 |
+
}
|
331 |
+
|
332 |
+
scope.object.position.addVectors( scope.target, _eye );
|
333 |
+
|
334 |
+
if ( scope.object.isPerspectiveCamera ) {
|
335 |
+
|
336 |
+
scope.checkDistances();
|
337 |
+
scope.object.lookAt( scope.target );
|
338 |
+
|
339 |
+
if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) {
|
340 |
+
|
341 |
+
scope.dispatchEvent( _changeEvent );
|
342 |
+
lastPosition.copy( scope.object.position );
|
343 |
+
|
344 |
+
}
|
345 |
+
|
346 |
+
} else if ( scope.object.isOrthographicCamera ) {
|
347 |
+
|
348 |
+
scope.object.lookAt( scope.target );
|
349 |
+
|
350 |
+
if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) {
|
351 |
+
|
352 |
+
scope.dispatchEvent( _changeEvent );
|
353 |
+
lastPosition.copy( scope.object.position );
|
354 |
+
lastZoom = scope.object.zoom;
|
355 |
+
|
356 |
+
}
|
357 |
+
|
358 |
+
} else {
|
359 |
+
|
360 |
+
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
361 |
+
|
362 |
+
}
|
363 |
+
|
364 |
+
};
|
365 |
+
|
366 |
+
this.reset = function () {
|
367 |
+
|
368 |
+
_state = STATE.NONE;
|
369 |
+
_keyState = STATE.NONE;
|
370 |
+
scope.target.copy( scope.target0 );
|
371 |
+
scope.object.position.copy( scope.position0 );
|
372 |
+
scope.object.up.copy( scope.up0 );
|
373 |
+
scope.object.zoom = scope.zoom0;
|
374 |
+
scope.object.updateProjectionMatrix();
|
375 |
+
|
376 |
+
_eye.subVectors( scope.object.position, scope.target );
|
377 |
+
|
378 |
+
scope.object.lookAt( scope.target );
|
379 |
+
scope.dispatchEvent( _changeEvent );
|
380 |
+
lastPosition.copy( scope.object.position );
|
381 |
+
lastZoom = scope.object.zoom;
|
382 |
+
|
383 |
+
}; // listeners
|
384 |
+
|
385 |
+
|
386 |
+
function onPointerDown( event ) {
|
387 |
+
|
388 |
+
if ( scope.enabled === false ) return;
|
389 |
+
|
390 |
+
if ( _pointers.length === 0 ) {
|
391 |
+
|
392 |
+
scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
|
393 |
+
scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
|
394 |
+
|
395 |
+
} //
|
396 |
+
|
397 |
+
|
398 |
+
addPointer( event );
|
399 |
+
|
400 |
+
if ( event.pointerType === 'touch' ) {
|
401 |
+
|
402 |
+
onTouchStart( event );
|
403 |
+
|
404 |
+
} else {
|
405 |
+
|
406 |
+
onMouseDown( event );
|
407 |
+
|
408 |
+
}
|
409 |
+
|
410 |
+
}
|
411 |
+
|
412 |
+
function onPointerMove( event ) {
|
413 |
+
|
414 |
+
if ( scope.enabled === false ) return;
|
415 |
+
|
416 |
+
if ( event.pointerType === 'touch' ) {
|
417 |
+
|
418 |
+
onTouchMove( event );
|
419 |
+
|
420 |
+
} else {
|
421 |
+
|
422 |
+
onMouseMove( event );
|
423 |
+
|
424 |
+
}
|
425 |
+
|
426 |
+
}
|
427 |
+
|
428 |
+
function onPointerUp( event ) {
|
429 |
+
|
430 |
+
if ( scope.enabled === false ) return;
|
431 |
+
|
432 |
+
if ( event.pointerType === 'touch' ) {
|
433 |
+
|
434 |
+
onTouchEnd( event );
|
435 |
+
|
436 |
+
} else {
|
437 |
+
|
438 |
+
onMouseUp();
|
439 |
+
|
440 |
+
} //
|
441 |
+
|
442 |
+
|
443 |
+
removePointer( event );
|
444 |
+
|
445 |
+
if ( _pointers.length === 0 ) {
|
446 |
+
|
447 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
|
448 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
|
449 |
+
|
450 |
+
}
|
451 |
+
|
452 |
+
}
|
453 |
+
|
454 |
+
function onPointerCancel( event ) {
|
455 |
+
|
456 |
+
removePointer( event );
|
457 |
+
|
458 |
+
}
|
459 |
+
|
460 |
+
function keydown( event ) {
|
461 |
+
|
462 |
+
if ( scope.enabled === false ) return;
|
463 |
+
window.removeEventListener( 'keydown', keydown );
|
464 |
+
|
465 |
+
if ( _keyState !== STATE.NONE ) {
|
466 |
+
|
467 |
+
return;
|
468 |
+
|
469 |
+
} else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) {
|
470 |
+
|
471 |
+
_keyState = STATE.ROTATE;
|
472 |
+
|
473 |
+
} else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) {
|
474 |
+
|
475 |
+
_keyState = STATE.ZOOM;
|
476 |
+
|
477 |
+
} else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) {
|
478 |
+
|
479 |
+
_keyState = STATE.PAN;
|
480 |
+
|
481 |
+
}
|
482 |
+
|
483 |
+
}
|
484 |
+
|
485 |
+
function keyup() {
|
486 |
+
|
487 |
+
if ( scope.enabled === false ) return;
|
488 |
+
_keyState = STATE.NONE;
|
489 |
+
window.addEventListener( 'keydown', keydown );
|
490 |
+
|
491 |
+
}
|
492 |
+
|
493 |
+
function onMouseDown( event ) {
|
494 |
+
|
495 |
+
if ( _state === STATE.NONE ) {
|
496 |
+
|
497 |
+
switch ( event.button ) {
|
498 |
+
|
499 |
+
case scope.mouseButtons.LEFT:
|
500 |
+
_state = STATE.ROTATE;
|
501 |
+
break;
|
502 |
+
|
503 |
+
case scope.mouseButtons.MIDDLE:
|
504 |
+
_state = STATE.ZOOM;
|
505 |
+
break;
|
506 |
+
|
507 |
+
case scope.mouseButtons.RIGHT:
|
508 |
+
_state = STATE.PAN;
|
509 |
+
break;
|
510 |
+
|
511 |
+
default:
|
512 |
+
_state = STATE.NONE;
|
513 |
+
|
514 |
+
}
|
515 |
+
|
516 |
+
}
|
517 |
+
|
518 |
+
const state = _keyState !== STATE.NONE ? _keyState : _state;
|
519 |
+
|
520 |
+
if ( state === STATE.ROTATE && ! scope.noRotate ) {
|
521 |
+
|
522 |
+
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
523 |
+
|
524 |
+
_movePrev.copy( _moveCurr );
|
525 |
+
|
526 |
+
} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
|
527 |
+
|
528 |
+
_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
529 |
+
|
530 |
+
_zoomEnd.copy( _zoomStart );
|
531 |
+
|
532 |
+
} else if ( state === STATE.PAN && ! scope.noPan ) {
|
533 |
+
|
534 |
+
_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
535 |
+
|
536 |
+
_panEnd.copy( _panStart );
|
537 |
+
|
538 |
+
}
|
539 |
+
|
540 |
+
scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
|
541 |
+
scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
|
542 |
+
scope.dispatchEvent( _startEvent );
|
543 |
+
|
544 |
+
}
|
545 |
+
|
546 |
+
function onMouseMove( event ) {
|
547 |
+
|
548 |
+
const state = _keyState !== STATE.NONE ? _keyState : _state;
|
549 |
+
|
550 |
+
if ( state === STATE.ROTATE && ! scope.noRotate ) {
|
551 |
+
|
552 |
+
_movePrev.copy( _moveCurr );
|
553 |
+
|
554 |
+
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
555 |
+
|
556 |
+
} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
|
557 |
+
|
558 |
+
_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
559 |
+
|
560 |
+
} else if ( state === STATE.PAN && ! scope.noPan ) {
|
561 |
+
|
562 |
+
_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
|
563 |
+
|
564 |
+
}
|
565 |
+
|
566 |
+
}
|
567 |
+
|
568 |
+
function onMouseUp() {
|
569 |
+
|
570 |
+
_state = STATE.NONE;
|
571 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
|
572 |
+
scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
|
573 |
+
scope.dispatchEvent( _endEvent );
|
574 |
+
|
575 |
+
}
|
576 |
+
|
577 |
+
function onMouseWheel( event ) {
|
578 |
+
|
579 |
+
if ( scope.enabled === false ) return;
|
580 |
+
if ( scope.noZoom === true ) return;
|
581 |
+
event.preventDefault();
|
582 |
+
|
583 |
+
switch ( event.deltaMode ) {
|
584 |
+
|
585 |
+
case 2:
|
586 |
+
// Zoom in pages
|
587 |
+
_zoomStart.y -= event.deltaY * 0.025;
|
588 |
+
break;
|
589 |
+
|
590 |
+
case 1:
|
591 |
+
// Zoom in lines
|
592 |
+
_zoomStart.y -= event.deltaY * 0.01;
|
593 |
+
break;
|
594 |
+
|
595 |
+
default:
|
596 |
+
// undefined, 0, assume pixels
|
597 |
+
_zoomStart.y -= event.deltaY * 0.00025;
|
598 |
+
break;
|
599 |
+
|
600 |
+
}
|
601 |
+
|
602 |
+
scope.dispatchEvent( _startEvent );
|
603 |
+
scope.dispatchEvent( _endEvent );
|
604 |
+
|
605 |
+
}
|
606 |
+
|
607 |
+
function onTouchStart( event ) {
|
608 |
+
|
609 |
+
trackPointer( event );
|
610 |
+
|
611 |
+
switch ( _pointers.length ) {
|
612 |
+
|
613 |
+
case 1:
|
614 |
+
_state = STATE.TOUCH_ROTATE;
|
615 |
+
|
616 |
+
_moveCurr.copy( getMouseOnCircle( _pointers[ 0 ].pageX, _pointers[ 0 ].pageY ) );
|
617 |
+
|
618 |
+
_movePrev.copy( _moveCurr );
|
619 |
+
|
620 |
+
break;
|
621 |
+
|
622 |
+
default:
|
623 |
+
// 2 or more
|
624 |
+
_state = STATE.TOUCH_ZOOM_PAN;
|
625 |
+
const dx = _pointers[ 0 ].pageX - _pointers[ 1 ].pageX;
|
626 |
+
const dy = _pointers[ 0 ].pageY - _pointers[ 1 ].pageY;
|
627 |
+
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
|
628 |
+
const x = ( _pointers[ 0 ].pageX + _pointers[ 1 ].pageX ) / 2;
|
629 |
+
const y = ( _pointers[ 0 ].pageY + _pointers[ 1 ].pageY ) / 2;
|
630 |
+
|
631 |
+
_panStart.copy( getMouseOnScreen( x, y ) );
|
632 |
+
|
633 |
+
_panEnd.copy( _panStart );
|
634 |
+
|
635 |
+
break;
|
636 |
+
|
637 |
+
}
|
638 |
+
|
639 |
+
scope.dispatchEvent( _startEvent );
|
640 |
+
|
641 |
+
}
|
642 |
+
|
643 |
+
function onTouchMove( event ) {
|
644 |
+
|
645 |
+
trackPointer( event );
|
646 |
+
|
647 |
+
switch ( _pointers.length ) {
|
648 |
+
|
649 |
+
case 1:
|
650 |
+
_movePrev.copy( _moveCurr );
|
651 |
+
|
652 |
+
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
653 |
+
|
654 |
+
break;
|
655 |
+
|
656 |
+
default:
|
657 |
+
// 2 or more
|
658 |
+
const position = getSecondPointerPosition( event );
|
659 |
+
const dx = event.pageX - position.x;
|
660 |
+
const dy = event.pageY - position.y;
|
661 |
+
_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
|
662 |
+
const x = ( event.pageX + position.x ) / 2;
|
663 |
+
const y = ( event.pageY + position.y ) / 2;
|
664 |
+
|
665 |
+
_panEnd.copy( getMouseOnScreen( x, y ) );
|
666 |
+
|
667 |
+
break;
|
668 |
+
|
669 |
+
}
|
670 |
+
|
671 |
+
}
|
672 |
+
|
673 |
+
function onTouchEnd( event ) {
|
674 |
+
|
675 |
+
switch ( _pointers.length ) {
|
676 |
+
|
677 |
+
case 0:
|
678 |
+
_state = STATE.NONE;
|
679 |
+
break;
|
680 |
+
|
681 |
+
case 1:
|
682 |
+
_state = STATE.TOUCH_ROTATE;
|
683 |
+
|
684 |
+
_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
|
685 |
+
|
686 |
+
_movePrev.copy( _moveCurr );
|
687 |
+
|
688 |
+
break;
|
689 |
+
|
690 |
+
}
|
691 |
+
|
692 |
+
scope.dispatchEvent( _endEvent );
|
693 |
+
|
694 |
+
}
|
695 |
+
|
696 |
+
function contextmenu( event ) {
|
697 |
+
|
698 |
+
if ( scope.enabled === false ) return;
|
699 |
+
event.preventDefault();
|
700 |
+
|
701 |
+
}
|
702 |
+
|
703 |
+
function addPointer( event ) {
|
704 |
+
|
705 |
+
_pointers.push( event );
|
706 |
+
|
707 |
+
}
|
708 |
+
|
709 |
+
function removePointer( event ) {
|
710 |
+
|
711 |
+
delete _pointerPositions[ event.pointerId ];
|
712 |
+
|
713 |
+
for ( let i = 0; i < _pointers.length; i ++ ) {
|
714 |
+
|
715 |
+
if ( _pointers[ i ].pointerId == event.pointerId ) {
|
716 |
+
|
717 |
+
_pointers.splice( i, 1 );
|
718 |
+
|
719 |
+
return;
|
720 |
+
|
721 |
+
}
|
722 |
+
|
723 |
+
}
|
724 |
+
|
725 |
+
}
|
726 |
+
|
727 |
+
function trackPointer( event ) {
|
728 |
+
|
729 |
+
let position = _pointerPositions[ event.pointerId ];
|
730 |
+
|
731 |
+
if ( position === undefined ) {
|
732 |
+
|
733 |
+
position = new THREE.Vector2();
|
734 |
+
_pointerPositions[ event.pointerId ] = position;
|
735 |
+
|
736 |
+
}
|
737 |
+
|
738 |
+
position.set( event.pageX, event.pageY );
|
739 |
+
|
740 |
+
}
|
741 |
+
|
742 |
+
function getSecondPointerPosition( event ) {
|
743 |
+
|
744 |
+
const pointer = event.pointerId === _pointers[ 0 ].pointerId ? _pointers[ 1 ] : _pointers[ 0 ];
|
745 |
+
return _pointerPositions[ pointer.pointerId ];
|
746 |
+
|
747 |
+
}
|
748 |
+
|
749 |
+
this.dispose = function () {
|
750 |
+
|
751 |
+
scope.domElement.removeEventListener( 'contextmenu', contextmenu );
|
752 |
+
scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
|
753 |
+
scope.domElement.removeEventListener( 'pointercancel', onPointerCancel );
|
754 |
+
scope.domElement.removeEventListener( 'wheel', onMouseWheel );
|
755 |
+
window.removeEventListener( 'keydown', keydown );
|
756 |
+
window.removeEventListener( 'keyup', keyup );
|
757 |
+
|
758 |
+
};
|
759 |
+
|
760 |
+
this.domElement.addEventListener( 'contextmenu', contextmenu );
|
761 |
+
this.domElement.addEventListener( 'pointerdown', onPointerDown );
|
762 |
+
this.domElement.addEventListener( 'pointercancel', onPointerCancel );
|
763 |
+
this.domElement.addEventListener( 'wheel', onMouseWheel, {
|
764 |
+
passive: false
|
765 |
+
} );
|
766 |
+
window.addEventListener( 'keydown', keydown );
|
767 |
+
window.addEventListener( 'keyup', keyup );
|
768 |
+
this.handleResize(); // force an update at start
|
769 |
+
|
770 |
+
this.update();
|
771 |
+
|
772 |
+
}
|
773 |
+
|
774 |
+
}
|
775 |
+
|
776 |
+
THREE.TrackballControls = TrackballControls;
|
777 |
+
|
778 |
+
} )();
|
lib/ffmpeg_normalize/__init__.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from ._ffmpeg_normalize import FFmpegNormalize
|
2 |
+
from ._media_file import MediaFile
|
3 |
+
from ._version import __version__
|
4 |
+
|
5 |
+
__all__ = ["FFmpegNormalize", "MediaFile", "__version__"]
|
lib/ffmpeg_normalize/__main__.py
ADDED
@@ -0,0 +1,548 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import argparse
|
2 |
+
import textwrap
|
3 |
+
import logging
|
4 |
+
import os
|
5 |
+
import shlex
|
6 |
+
import json
|
7 |
+
|
8 |
+
try:
|
9 |
+
from json.decoder import JSONDecodeError
|
10 |
+
except ImportError:
|
11 |
+
JSONDecodeError = ValueError
|
12 |
+
|
13 |
+
from ._version import __version__
|
14 |
+
from ._ffmpeg_normalize import FFmpegNormalize, NORMALIZATION_TYPES
|
15 |
+
from ._errors import FFmpegNormalizeError
|
16 |
+
from ._logger import setup_custom_logger
|
17 |
+
|
18 |
+
logger = setup_custom_logger("ffmpeg_normalize")
|
19 |
+
|
20 |
+
|
21 |
+
def create_parser():
|
22 |
+
parser = argparse.ArgumentParser(
|
23 |
+
prog="ffmpeg-normalize",
|
24 |
+
description=textwrap.dedent(
|
25 |
+
"""\
|
26 |
+
ffmpeg-normalize v{} -- command line tool for normalizing audio files
|
27 |
+
""".format(
|
28 |
+
__version__
|
29 |
+
)
|
30 |
+
),
|
31 |
+
usage=textwrap.dedent(
|
32 |
+
"""\
|
33 |
+
ffmpeg-normalize input [input ...]
|
34 |
+
[-h]
|
35 |
+
[-o OUTPUT [OUTPUT ...]] [-of OUTPUT_FOLDER]
|
36 |
+
[-f] [-d] [-v] [-q] [-n] [-pr]
|
37 |
+
[--version]
|
38 |
+
[-nt {ebu,rms,peak}] [-t TARGET_LEVEL] [-p]
|
39 |
+
[-lrt LOUDNESS_RANGE_TARGET] [-tp TRUE_PEAK] [--offset OFFSET] [--dual-mono]
|
40 |
+
[-c:a AUDIO_CODEC] [-b:a AUDIO_BITRATE] [-ar SAMPLE_RATE] [-koa]
|
41 |
+
[-prf PRE_FILTER] [-pof POST_FILTER]
|
42 |
+
[-vn] [-c:v VIDEO_CODEC]
|
43 |
+
[-sn] [-mn] [-cn]
|
44 |
+
[-ei EXTRA_INPUT_OPTIONS] [-e EXTRA_OUTPUT_OPTIONS]
|
45 |
+
[-ofmt OUTPUT_FORMAT]
|
46 |
+
[-ext EXTENSION]
|
47 |
+
"""
|
48 |
+
),
|
49 |
+
formatter_class=argparse.RawTextHelpFormatter,
|
50 |
+
epilog=textwrap.dedent(
|
51 |
+
"""\
|
52 |
+
The program additionally respects environment variables:
|
53 |
+
|
54 |
+
- `TMP` / `TEMP` / `TMPDIR`
|
55 |
+
Sets the path to the temporary directory in which files are
|
56 |
+
stored before being moved to the final output directory.
|
57 |
+
Note: You need to use full paths.
|
58 |
+
|
59 |
+
- `FFMPEG_PATH`
|
60 |
+
Sets the full path to an `ffmpeg` executable other than
|
61 |
+
the system default.
|
62 |
+
|
63 |
+
|
64 |
+
Author: Werner Robitza
|
65 |
+
License: MIT
|
66 |
+
Homepage / Issues: https://github.com/slhck/ffmpeg-normalize
|
67 |
+
"""
|
68 |
+
),
|
69 |
+
)
|
70 |
+
|
71 |
+
group_io = parser.add_argument_group("File Input/output")
|
72 |
+
group_io.add_argument("input", nargs="+", help="Input media file(s)")
|
73 |
+
group_io.add_argument(
|
74 |
+
"-o",
|
75 |
+
"--output",
|
76 |
+
nargs="+",
|
77 |
+
help=textwrap.dedent(
|
78 |
+
"""\
|
79 |
+
Output file names. Will be applied per input file.
|
80 |
+
|
81 |
+
If no output file name is specified for an input file, the output files
|
82 |
+
will be written to the default output folder with the name `<input>.<ext>`,
|
83 |
+
where `<ext>` is the output extension (see `-ext` option).
|
84 |
+
|
85 |
+
Example: ffmpeg-normalize 1.wav 2.wav -o 1n.wav 2n.wav
|
86 |
+
"""
|
87 |
+
),
|
88 |
+
)
|
89 |
+
group_io.add_argument(
|
90 |
+
"-of",
|
91 |
+
"--output-folder",
|
92 |
+
type=str,
|
93 |
+
help=textwrap.dedent(
|
94 |
+
"""\
|
95 |
+
Output folder (default: `normalized`)
|
96 |
+
|
97 |
+
This folder will be used for input files that have no explicit output
|
98 |
+
name specified.
|
99 |
+
"""
|
100 |
+
),
|
101 |
+
default="normalized",
|
102 |
+
)
|
103 |
+
|
104 |
+
group_general = parser.add_argument_group("General Options")
|
105 |
+
group_general.add_argument(
|
106 |
+
"-f", "--force", action="store_true", help="Force overwrite existing files"
|
107 |
+
)
|
108 |
+
group_general.add_argument(
|
109 |
+
"-d", "--debug", action="store_true", help="Print debugging output"
|
110 |
+
)
|
111 |
+
group_general.add_argument(
|
112 |
+
"-v", "--verbose", action="store_true", help="Print verbose output"
|
113 |
+
)
|
114 |
+
group_general.add_argument(
|
115 |
+
"-q", "--quiet", action="store_true", help="Only print errors in output"
|
116 |
+
)
|
117 |
+
group_general.add_argument(
|
118 |
+
"-n",
|
119 |
+
"--dry-run",
|
120 |
+
action="store_true",
|
121 |
+
help="Do not run normalization, only print what would be done",
|
122 |
+
)
|
123 |
+
group_general.add_argument(
|
124 |
+
"-pr",
|
125 |
+
"--progress",
|
126 |
+
action="store_true",
|
127 |
+
help="Show progress bar for files and streams",
|
128 |
+
)
|
129 |
+
group_general.add_argument(
|
130 |
+
"--version",
|
131 |
+
action="version",
|
132 |
+
version=f"%(prog)s v{__version__}",
|
133 |
+
help="Print version and exit",
|
134 |
+
)
|
135 |
+
|
136 |
+
group_normalization = parser.add_argument_group("Normalization")
|
137 |
+
group_normalization.add_argument(
|
138 |
+
"-nt",
|
139 |
+
"--normalization-type",
|
140 |
+
type=str,
|
141 |
+
choices=NORMALIZATION_TYPES,
|
142 |
+
help=textwrap.dedent(
|
143 |
+
"""\
|
144 |
+
Normalization type (default: `ebu`).
|
145 |
+
|
146 |
+
EBU normalization performs two passes and normalizes according to EBU
|
147 |
+
R128.
|
148 |
+
|
149 |
+
RMS-based normalization brings the input file to the specified RMS
|
150 |
+
level.
|
151 |
+
|
152 |
+
Peak normalization brings the signal to the specified peak level.
|
153 |
+
"""
|
154 |
+
),
|
155 |
+
default="ebu",
|
156 |
+
)
|
157 |
+
group_normalization.add_argument(
|
158 |
+
"-t",
|
159 |
+
"--target-level",
|
160 |
+
type=float,
|
161 |
+
help=textwrap.dedent(
|
162 |
+
"""\
|
163 |
+
Normalization target level in dB/LUFS (default: -23).
|
164 |
+
|
165 |
+
For EBU normalization, it corresponds to Integrated Loudness Target
|
166 |
+
in LUFS. The range is -70.0 - -5.0.
|
167 |
+
|
168 |
+
Otherwise, the range is -99 to 0.
|
169 |
+
"""
|
170 |
+
),
|
171 |
+
default=-23.0,
|
172 |
+
)
|
173 |
+
group_normalization.add_argument(
|
174 |
+
"-p",
|
175 |
+
"--print-stats",
|
176 |
+
action="store_true",
|
177 |
+
help="Print first pass loudness statistics formatted as JSON to stdout",
|
178 |
+
)
|
179 |
+
|
180 |
+
# group_normalization.add_argument(
|
181 |
+
# '--threshold',
|
182 |
+
# type=float,
|
183 |
+
# help=textwrap.dedent("""\
|
184 |
+
# Threshold below which normalization should not be run.
|
185 |
+
|
186 |
+
# If the stream falls within the threshold, it will simply be copied.
|
187 |
+
# """),
|
188 |
+
# default=0.5
|
189 |
+
# )
|
190 |
+
|
191 |
+
group_ebu = parser.add_argument_group("EBU R128 Normalization")
|
192 |
+
group_ebu.add_argument(
|
193 |
+
"-lrt",
|
194 |
+
"--loudness-range-target",
|
195 |
+
type=float,
|
196 |
+
help=textwrap.dedent(
|
197 |
+
"""\
|
198 |
+
EBU Loudness Range Target in LUFS (default: 7.0).
|
199 |
+
Range is 1.0 - 20.0.
|
200 |
+
"""
|
201 |
+
),
|
202 |
+
default=7.0,
|
203 |
+
)
|
204 |
+
|
205 |
+
group_ebu.add_argument(
|
206 |
+
"-tp",
|
207 |
+
"--true-peak",
|
208 |
+
type=float,
|
209 |
+
help=textwrap.dedent(
|
210 |
+
"""\
|
211 |
+
EBU Maximum True Peak in dBTP (default: -2.0).
|
212 |
+
Range is -9.0 - +0.0.
|
213 |
+
"""
|
214 |
+
),
|
215 |
+
default=-2.0,
|
216 |
+
)
|
217 |
+
|
218 |
+
group_ebu.add_argument(
|
219 |
+
"--offset",
|
220 |
+
type=float,
|
221 |
+
help=textwrap.dedent(
|
222 |
+
"""\
|
223 |
+
EBU Offset Gain (default: 0.0).
|
224 |
+
The gain is applied before the true-peak limiter in the first pass only.
|
225 |
+
The offset for the second pass will be automatically determined based on the first pass statistics.
|
226 |
+
Range is -99.0 - +99.0.
|
227 |
+
"""
|
228 |
+
),
|
229 |
+
default=0.0,
|
230 |
+
)
|
231 |
+
|
232 |
+
group_ebu.add_argument(
|
233 |
+
"--dual-mono",
|
234 |
+
action="store_true",
|
235 |
+
help=textwrap.dedent(
|
236 |
+
"""\
|
237 |
+
Treat mono input files as "dual-mono".
|
238 |
+
|
239 |
+
If a mono file is intended for playback on a stereo system, its EBU R128
|
240 |
+
measurement will be perceptually incorrect. If set, this option will
|
241 |
+
compensate for this effect. Multi-channel input files are not affected
|
242 |
+
by this option.
|
243 |
+
"""
|
244 |
+
),
|
245 |
+
)
|
246 |
+
|
247 |
+
group_acodec = parser.add_argument_group("Audio Encoding")
|
248 |
+
group_acodec.add_argument(
|
249 |
+
"-c:a",
|
250 |
+
"--audio-codec",
|
251 |
+
type=str,
|
252 |
+
help=textwrap.dedent(
|
253 |
+
"""\
|
254 |
+
Audio codec to use for output files.
|
255 |
+
See `ffmpeg -encoders` for a list.
|
256 |
+
|
257 |
+
Will use PCM audio with input stream bit depth by default.
|
258 |
+
"""
|
259 |
+
),
|
260 |
+
)
|
261 |
+
group_acodec.add_argument(
|
262 |
+
"-b:a",
|
263 |
+
"--audio-bitrate",
|
264 |
+
type=str,
|
265 |
+
help=textwrap.dedent(
|
266 |
+
"""\
|
267 |
+
Audio bitrate in bits/s, or with K suffix.
|
268 |
+
|
269 |
+
If not specified, will use codec default.
|
270 |
+
"""
|
271 |
+
),
|
272 |
+
)
|
273 |
+
group_acodec.add_argument(
|
274 |
+
"-ar",
|
275 |
+
"--sample-rate",
|
276 |
+
type=str,
|
277 |
+
help=textwrap.dedent(
|
278 |
+
"""\
|
279 |
+
Audio sample rate to use for output files in Hz.
|
280 |
+
|
281 |
+
Will use input sample rate by default, except for EBU normalization,
|
282 |
+
which will change the input sample rate to 192 kHz.
|
283 |
+
"""
|
284 |
+
),
|
285 |
+
)
|
286 |
+
group_acodec.add_argument(
|
287 |
+
"-koa",
|
288 |
+
"--keep-original-audio",
|
289 |
+
action="store_true",
|
290 |
+
help="Copy original, non-normalized audio streams to output file",
|
291 |
+
)
|
292 |
+
group_acodec.add_argument(
|
293 |
+
"-prf",
|
294 |
+
"--pre-filter",
|
295 |
+
type=str,
|
296 |
+
help=textwrap.dedent(
|
297 |
+
"""\
|
298 |
+
Add an audio filter chain before applying normalization.
|
299 |
+
Multiple filters can be specified by comma-separating them.
|
300 |
+
"""
|
301 |
+
),
|
302 |
+
)
|
303 |
+
group_acodec.add_argument(
|
304 |
+
"-pof",
|
305 |
+
"--post-filter",
|
306 |
+
type=str,
|
307 |
+
help=textwrap.dedent(
|
308 |
+
"""\
|
309 |
+
Add an audio filter chain after applying normalization.
|
310 |
+
Multiple filters can be specified by comma-separating them.
|
311 |
+
|
312 |
+
For EBU, the filter will be applied during the second pass.
|
313 |
+
"""
|
314 |
+
),
|
315 |
+
)
|
316 |
+
|
317 |
+
group_vcodec = parser.add_argument_group("Other Encoding Options")
|
318 |
+
group_vcodec.add_argument(
|
319 |
+
"-vn",
|
320 |
+
"--video-disable",
|
321 |
+
action="store_true",
|
322 |
+
help="Do not write video streams to output",
|
323 |
+
)
|
324 |
+
group_vcodec.add_argument(
|
325 |
+
"-c:v",
|
326 |
+
"--video-codec",
|
327 |
+
type=str,
|
328 |
+
help=textwrap.dedent(
|
329 |
+
"""\
|
330 |
+
Video codec to use for output files (default: 'copy').
|
331 |
+
See `ffmpeg -encoders` for a list.
|
332 |
+
|
333 |
+
Will attempt to copy video codec by default.
|
334 |
+
"""
|
335 |
+
),
|
336 |
+
default="copy",
|
337 |
+
)
|
338 |
+
group_vcodec.add_argument(
|
339 |
+
"-sn",
|
340 |
+
"--subtitle-disable",
|
341 |
+
action="store_true",
|
342 |
+
help="Do not write subtitle streams to output",
|
343 |
+
)
|
344 |
+
group_vcodec.add_argument(
|
345 |
+
"-mn",
|
346 |
+
"--metadata-disable",
|
347 |
+
action="store_true",
|
348 |
+
help="Do not write metadata to output",
|
349 |
+
)
|
350 |
+
group_vcodec.add_argument(
|
351 |
+
"-cn",
|
352 |
+
"--chapters-disable",
|
353 |
+
action="store_true",
|
354 |
+
help="Do not write chapters to output",
|
355 |
+
)
|
356 |
+
|
357 |
+
group_format = parser.add_argument_group("Input/Output options")
|
358 |
+
group_format.add_argument(
|
359 |
+
"-ei",
|
360 |
+
"--extra-input-options",
|
361 |
+
type=str,
|
362 |
+
help=textwrap.dedent(
|
363 |
+
"""\
|
364 |
+
Extra input options list.
|
365 |
+
|
366 |
+
A list of extra ffmpeg command line arguments valid for the input,
|
367 |
+
applied before ffmpeg's `-i`.
|
368 |
+
|
369 |
+
You can either use a JSON-formatted list (i.e., a list of
|
370 |
+
comma-separated, quoted elements within square brackets), or a simple
|
371 |
+
string of space-separated arguments.
|
372 |
+
|
373 |
+
If JSON is used, you need to wrap the whole argument in quotes to
|
374 |
+
prevent shell expansion and to preserve literal quotes inside the
|
375 |
+
string. If a simple string is used, you need to specify the argument
|
376 |
+
with `-e=`.
|
377 |
+
|
378 |
+
Examples: `-e '[ "-f", "mpegts" ]'` or `-e="-f mpegts"`
|
379 |
+
"""
|
380 |
+
),
|
381 |
+
)
|
382 |
+
group_format.add_argument(
|
383 |
+
"-e",
|
384 |
+
"--extra-output-options",
|
385 |
+
type=str,
|
386 |
+
help=textwrap.dedent(
|
387 |
+
"""\
|
388 |
+
Extra output options list.
|
389 |
+
|
390 |
+
A list of extra ffmpeg command line arguments.
|
391 |
+
|
392 |
+
You can either use a JSON-formatted list (i.e., a list of
|
393 |
+
comma-separated, quoted elements within square brackets), or a simple
|
394 |
+
string of space-separated arguments.
|
395 |
+
|
396 |
+
If JSON is used, you need to wrap the whole argument in quotes to
|
397 |
+
prevent shell expansion and to preserve literal quotes inside the
|
398 |
+
string. If a simple string is used, you need to specify the argument
|
399 |
+
with `-e=`.
|
400 |
+
|
401 |
+
Examples: `-e '[ "-vbr", "3" ]'` or `-e="-vbr 3"`
|
402 |
+
"""
|
403 |
+
),
|
404 |
+
)
|
405 |
+
group_format.add_argument(
|
406 |
+
"-ofmt",
|
407 |
+
"--output-format",
|
408 |
+
type=str,
|
409 |
+
help=textwrap.dedent(
|
410 |
+
"""\
|
411 |
+
Media format to use for output file(s).
|
412 |
+
See 'ffmpeg -formats' for a list.
|
413 |
+
|
414 |
+
If not specified, the format will be inferred by ffmpeg from the output
|
415 |
+
file name. If the output file name is not explicitly specified, the
|
416 |
+
extension will govern the format (see '--extension' option).
|
417 |
+
"""
|
418 |
+
),
|
419 |
+
)
|
420 |
+
group_format.add_argument(
|
421 |
+
"-ext",
|
422 |
+
"--extension",
|
423 |
+
type=str,
|
424 |
+
help=textwrap.dedent(
|
425 |
+
"""\
|
426 |
+
Output file extension to use for output files that were not explicitly
|
427 |
+
specified. (Default: `mkv`)
|
428 |
+
"""
|
429 |
+
),
|
430 |
+
default="mkv",
|
431 |
+
)
|
432 |
+
return parser
|
433 |
+
|
434 |
+
|
435 |
+
def _split_options(opts):
|
436 |
+
"""
|
437 |
+
Parse extra options (input or output) into a list
|
438 |
+
"""
|
439 |
+
if not opts:
|
440 |
+
return []
|
441 |
+
try:
|
442 |
+
if opts.startswith("["):
|
443 |
+
try:
|
444 |
+
ret = [str(s) for s in json.loads(opts)]
|
445 |
+
except JSONDecodeError:
|
446 |
+
ret = shlex.split(opts)
|
447 |
+
else:
|
448 |
+
ret = shlex.split(opts)
|
449 |
+
except Exception as e:
|
450 |
+
raise FFmpegNormalizeError(f"Could not parse extra_options: {e}")
|
451 |
+
return ret
|
452 |
+
|
453 |
+
|
454 |
+
def main():
|
455 |
+
cli_args = create_parser().parse_args()
|
456 |
+
|
457 |
+
if cli_args.quiet:
|
458 |
+
logger.setLevel(logging.ERROR)
|
459 |
+
elif cli_args.debug:
|
460 |
+
logger.setLevel(logging.DEBUG)
|
461 |
+
elif cli_args.verbose:
|
462 |
+
logger.setLevel(logging.INFO)
|
463 |
+
|
464 |
+
# parse extra options
|
465 |
+
extra_input_options = _split_options(cli_args.extra_input_options)
|
466 |
+
extra_output_options = _split_options(cli_args.extra_output_options)
|
467 |
+
|
468 |
+
ffmpeg_normalize = FFmpegNormalize(
|
469 |
+
normalization_type=cli_args.normalization_type,
|
470 |
+
target_level=cli_args.target_level,
|
471 |
+
print_stats=cli_args.print_stats,
|
472 |
+
loudness_range_target=cli_args.loudness_range_target,
|
473 |
+
# threshold=cli_args.threshold,
|
474 |
+
true_peak=cli_args.true_peak,
|
475 |
+
offset=cli_args.offset,
|
476 |
+
dual_mono=cli_args.dual_mono,
|
477 |
+
audio_codec=cli_args.audio_codec,
|
478 |
+
audio_bitrate=cli_args.audio_bitrate,
|
479 |
+
sample_rate=cli_args.sample_rate,
|
480 |
+
keep_original_audio=cli_args.keep_original_audio,
|
481 |
+
pre_filter=cli_args.pre_filter,
|
482 |
+
post_filter=cli_args.post_filter,
|
483 |
+
video_codec=cli_args.video_codec,
|
484 |
+
video_disable=cli_args.video_disable,
|
485 |
+
subtitle_disable=cli_args.subtitle_disable,
|
486 |
+
metadata_disable=cli_args.metadata_disable,
|
487 |
+
chapters_disable=cli_args.chapters_disable,
|
488 |
+
extra_input_options=extra_input_options,
|
489 |
+
extra_output_options=extra_output_options,
|
490 |
+
output_format=cli_args.output_format,
|
491 |
+
dry_run=cli_args.dry_run,
|
492 |
+
progress=cli_args.progress,
|
493 |
+
)
|
494 |
+
|
495 |
+
if (
|
496 |
+
cli_args.output is not None
|
497 |
+
and len(cli_args.output) > 0
|
498 |
+
and len(cli_args.input) > len(cli_args.output)
|
499 |
+
):
|
500 |
+
logger.warning(
|
501 |
+
"There are more input files than output file names given. "
|
502 |
+
"Please specify one output file name per input file using -o <output1> <output2> ... "
|
503 |
+
"Will apply default file naming for the remaining ones."
|
504 |
+
)
|
505 |
+
|
506 |
+
for index, input_file in enumerate(cli_args.input):
|
507 |
+
if cli_args.output is not None and index < len(cli_args.output):
|
508 |
+
if cli_args.output_folder and cli_args.output_folder != "normalized":
|
509 |
+
logger.warning(
|
510 |
+
"Output folder {} is ignored for input file {}".format(
|
511 |
+
cli_args.output_folder, input_file
|
512 |
+
)
|
513 |
+
)
|
514 |
+
output_file = cli_args.output[index]
|
515 |
+
output_dir = os.path.dirname(output_file)
|
516 |
+
if output_dir != "" and not os.path.isdir(output_dir):
|
517 |
+
raise FFmpegNormalizeError(
|
518 |
+
f"Output file path {output_dir} does not exist"
|
519 |
+
)
|
520 |
+
else:
|
521 |
+
output_file = os.path.join(
|
522 |
+
cli_args.output_folder,
|
523 |
+
os.path.splitext(os.path.basename(input_file))[0]
|
524 |
+
+ "."
|
525 |
+
+ cli_args.extension,
|
526 |
+
)
|
527 |
+
if not os.path.isdir(cli_args.output_folder) and not cli_args.dry_run:
|
528 |
+
logger.warning(
|
529 |
+
"Output directory '{}' does not exist, will create".format(
|
530 |
+
cli_args.output_folder
|
531 |
+
)
|
532 |
+
)
|
533 |
+
os.makedirs(cli_args.output_folder)
|
534 |
+
|
535 |
+
if os.path.exists(output_file) and not cli_args.force:
|
536 |
+
logger.error(
|
537 |
+
"Output file {} already exists, skipping. Use -f to force overwriting.".format(
|
538 |
+
output_file
|
539 |
+
)
|
540 |
+
)
|
541 |
+
else:
|
542 |
+
ffmpeg_normalize.add_media_file(input_file, output_file)
|
543 |
+
|
544 |
+
ffmpeg_normalize.run_normalization()
|
545 |
+
|
546 |
+
|
547 |
+
if __name__ == "__main__":
|
548 |
+
main()
|
lib/ffmpeg_normalize/_cmd_utils.py
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import division
|
2 |
+
import os
|
3 |
+
import sys
|
4 |
+
import subprocess
|
5 |
+
from platform import system as _current_os
|
6 |
+
import re
|
7 |
+
from ffmpeg_progress_yield import FfmpegProgress
|
8 |
+
|
9 |
+
from ._errors import FFmpegNormalizeError
|
10 |
+
from ._logger import setup_custom_logger
|
11 |
+
|
12 |
+
logger = setup_custom_logger("ffmpeg_normalize")
|
13 |
+
|
14 |
+
CUR_OS = _current_os()
|
15 |
+
IS_WIN = CUR_OS in ["Windows", "cli"]
|
16 |
+
IS_NIX = (not IS_WIN) and any(
|
17 |
+
CUR_OS.startswith(i)
|
18 |
+
for i in ["CYGWIN", "MSYS", "Linux", "Darwin", "SunOS", "FreeBSD", "NetBSD"]
|
19 |
+
)
|
20 |
+
NUL = "NUL" if IS_WIN else "/dev/null"
|
21 |
+
DUR_REGEX = re.compile(
|
22 |
+
r"Duration: (?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.(?P<ms>\d{2})"
|
23 |
+
)
|
24 |
+
|
25 |
+
|
26 |
+
# https://gist.github.com/Hellowlol/5f8545e999259b4371c91ac223409209
|
27 |
+
def to_ms(s=None, des=None, **kwargs):
|
28 |
+
if s:
|
29 |
+
hour = int(s[0:2])
|
30 |
+
minute = int(s[3:5])
|
31 |
+
sec = int(s[6:8])
|
32 |
+
ms = int(s[10:11])
|
33 |
+
else:
|
34 |
+
hour = int(kwargs.get("hour", 0))
|
35 |
+
minute = int(kwargs.get("min", 0))
|
36 |
+
sec = int(kwargs.get("sec", 0))
|
37 |
+
ms = int(kwargs.get("ms"))
|
38 |
+
|
39 |
+
result = (hour * 60 * 60 * 1000) + (minute * 60 * 1000) + (sec * 1000) + ms
|
40 |
+
if des and isinstance(des, int):
|
41 |
+
return round(result, des)
|
42 |
+
return result
|
43 |
+
|
44 |
+
|
45 |
+
class CommandRunner:
|
46 |
+
def __init__(self, cmd, dry=False):
|
47 |
+
self.cmd = cmd
|
48 |
+
self.dry = dry
|
49 |
+
self.output = None
|
50 |
+
|
51 |
+
def run_ffmpeg_command(self):
|
52 |
+
# wrapper for 'ffmpeg-progress-yield'
|
53 |
+
ff = FfmpegProgress(self.cmd, dry_run=self.dry)
|
54 |
+
for progress in ff.run_command_with_progress():
|
55 |
+
yield progress
|
56 |
+
|
57 |
+
self.output = ff.stderr
|
58 |
+
|
59 |
+
def run_command(self):
|
60 |
+
logger.debug(f"Running command: {self.cmd}")
|
61 |
+
|
62 |
+
if self.dry:
|
63 |
+
logger.debug("Dry mode specified, not actually running command")
|
64 |
+
return
|
65 |
+
|
66 |
+
p = subprocess.Popen(
|
67 |
+
self.cmd,
|
68 |
+
stdin=subprocess.PIPE, # Apply stdin isolation by creating separate pipe.
|
69 |
+
stdout=subprocess.PIPE,
|
70 |
+
stderr=subprocess.PIPE,
|
71 |
+
universal_newlines=False,
|
72 |
+
)
|
73 |
+
|
74 |
+
# simple running of command
|
75 |
+
stdout, stderr = p.communicate()
|
76 |
+
|
77 |
+
stdout = stdout.decode("utf8", errors="replace")
|
78 |
+
stderr = stderr.decode("utf8", errors="replace")
|
79 |
+
|
80 |
+
if p.returncode == 0:
|
81 |
+
self.output = stdout + stderr
|
82 |
+
else:
|
83 |
+
raise RuntimeError(
|
84 |
+
f"Error running command {self.cmd}: {str(stderr)}"
|
85 |
+
)
|
86 |
+
|
87 |
+
def get_output(self):
|
88 |
+
return self.output
|
89 |
+
|
90 |
+
|
91 |
+
def which(program):
|
92 |
+
"""
|
93 |
+
Find a program in PATH and return path
|
94 |
+
From: http://stackoverflow.com/q/377017/
|
95 |
+
"""
|
96 |
+
|
97 |
+
def is_exe(fpath):
|
98 |
+
found = os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
99 |
+
if not found and sys.platform == "win32":
|
100 |
+
fpath = fpath + ".exe"
|
101 |
+
found = os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
102 |
+
return found
|
103 |
+
|
104 |
+
fpath, _ = os.path.split(program)
|
105 |
+
if fpath:
|
106 |
+
if is_exe(program):
|
107 |
+
logger.debug("found executable: " + str(program))
|
108 |
+
return program
|
109 |
+
else:
|
110 |
+
for path in os.environ["PATH"].split(os.pathsep):
|
111 |
+
path = os.path.expandvars(os.path.expanduser(path)).strip('"')
|
112 |
+
exe_file = os.path.join(path, program)
|
113 |
+
if is_exe(exe_file):
|
114 |
+
logger.debug("found executable in path: " + str(exe_file))
|
115 |
+
return exe_file
|
116 |
+
|
117 |
+
return None
|
118 |
+
|
119 |
+
|
120 |
+
def dict_to_filter_opts(opts):
|
121 |
+
filter_opts = []
|
122 |
+
for k, v in opts.items():
|
123 |
+
filter_opts.append(f"{k}={v}")
|
124 |
+
return ":".join(filter_opts)
|
125 |
+
|
126 |
+
|
127 |
+
# def get_ffmpeg_exe():
|
128 |
+
# """
|
129 |
+
# Return path to ffmpeg executable
|
130 |
+
# """
|
131 |
+
# ffmpeg_path = os.getenv("FFMPEG_PATH")
|
132 |
+
# if ffmpeg_path:
|
133 |
+
# if os.sep in ffmpeg_path:
|
134 |
+
# ffmpeg_exe = ffmpeg_path
|
135 |
+
# if not os.path.isfile(ffmpeg_exe):
|
136 |
+
# raise FFmpegNormalizeError(f"No file exists at {ffmpeg_exe}")
|
137 |
+
# else:
|
138 |
+
# ffmpeg_exe = which(ffmpeg_path)
|
139 |
+
# if not ffmpeg_exe:
|
140 |
+
# raise FFmpegNormalizeError(
|
141 |
+
# f"Could not find '{ffmpeg_path}' in your $PATH."
|
142 |
+
# )
|
143 |
+
# else:
|
144 |
+
# ffmpeg_exe = which("ffmpeg")
|
145 |
+
|
146 |
+
# if not ffmpeg_exe:
|
147 |
+
# if which("avconv"):
|
148 |
+
# raise FFmpegNormalizeError(
|
149 |
+
# "avconv is not supported. "
|
150 |
+
# "Please install ffmpeg from http://ffmpeg.org instead."
|
151 |
+
# )
|
152 |
+
# else:
|
153 |
+
# raise FFmpegNormalizeError(
|
154 |
+
# "Could not find ffmpeg in your $PATH or $FFMPEG_PATH. "
|
155 |
+
# "Please install ffmpeg from http://ffmpeg.org"
|
156 |
+
# )
|
157 |
+
|
158 |
+
# return ffmpeg_exe
|
159 |
+
|
160 |
+
|
161 |
+
def ffmpeg_has_loudnorm(ffmpeg_path):
|
162 |
+
"""
|
163 |
+
Run feature detection on ffmpeg, returns True if ffmpeg supports
|
164 |
+
the loudnorm filter
|
165 |
+
"""
|
166 |
+
# cmd_runner = CommandRunner([get_ffmpeg_exe(), "-filters"])
|
167 |
+
cmd_runner = CommandRunner([ffmpeg_path, "-filters"])
|
168 |
+
cmd_runner.run_command()
|
169 |
+
output = cmd_runner.get_output()
|
170 |
+
if "loudnorm" in output:
|
171 |
+
return True
|
172 |
+
else:
|
173 |
+
logger.error(
|
174 |
+
"Your ffmpeg version does not support the 'loudnorm' filter. "
|
175 |
+
"Please make sure you are running ffmpeg v3.1 or above."
|
176 |
+
)
|
177 |
+
return False
|
lib/ffmpeg_normalize/_errors.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import logging
|
3 |
+
from ._logger import setup_custom_logger
|
4 |
+
|
5 |
+
logger = setup_custom_logger("ffmpeg_normalize")
|
6 |
+
|
7 |
+
|
8 |
+
class FFmpegNormalizeError(Exception):
|
9 |
+
def __init__(self, message):
|
10 |
+
super(FFmpegNormalizeError, self).__init__(message)
|
11 |
+
if logger.getEffectiveLevel() == logging.DEBUG:
|
12 |
+
logger.error(f"{self.__class__.__name__}: {message}")
|
13 |
+
else:
|
14 |
+
logger.error(message)
|
15 |
+
sys.exit(1)
|
lib/ffmpeg_normalize/_ffmpeg_normalize.py
ADDED
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
from numbers import Number
|
4 |
+
from tqdm import tqdm
|
5 |
+
|
6 |
+
from ._cmd_utils import ffmpeg_has_loudnorm # get_ffmpeg_exe
|
7 |
+
from ._media_file import MediaFile
|
8 |
+
from ._errors import FFmpegNormalizeError
|
9 |
+
from ._logger import setup_custom_logger
|
10 |
+
|
11 |
+
logger = setup_custom_logger("ffmpeg_normalize")
|
12 |
+
|
13 |
+
NORMALIZATION_TYPES = ["ebu", "rms", "peak"]
|
14 |
+
PCM_INCOMPATIBLE_FORMATS = ["mp4", "mp3", "ogg", "webm"]
|
15 |
+
PCM_INCOMPATIBLE_EXTS = ["mp4", "m4a", "mp3", "ogg", "webm"]
|
16 |
+
|
17 |
+
|
18 |
+
def check_range(number, min_r, max_r, name=""):
|
19 |
+
"""
|
20 |
+
Check if a number is within a given range
|
21 |
+
"""
|
22 |
+
try:
|
23 |
+
number = float(number)
|
24 |
+
if number < min_r or number > max_r:
|
25 |
+
raise FFmpegNormalizeError(
|
26 |
+
f"{name} must be within [{min_r},{max_r}]"
|
27 |
+
)
|
28 |
+
return number
|
29 |
+
pass
|
30 |
+
except Exception as e:
|
31 |
+
raise e
|
32 |
+
|
33 |
+
|
34 |
+
class FFmpegNormalize:
|
35 |
+
"""
|
36 |
+
ffmpeg-normalize class.
|
37 |
+
"""
|
38 |
+
|
39 |
+
def __init__(
|
40 |
+
self,
|
41 |
+
normalization_type="ebu",
|
42 |
+
target_level=-23.0,
|
43 |
+
print_stats=False,
|
44 |
+
# threshold=0.5,
|
45 |
+
loudness_range_target=7.0,
|
46 |
+
true_peak=-2.0,
|
47 |
+
offset=0.0,
|
48 |
+
dual_mono=False,
|
49 |
+
audio_codec="pcm_s16le",
|
50 |
+
audio_bitrate=None,
|
51 |
+
sample_rate=None,
|
52 |
+
keep_original_audio=False,
|
53 |
+
pre_filter=None,
|
54 |
+
post_filter=None,
|
55 |
+
video_codec="copy",
|
56 |
+
video_disable=False,
|
57 |
+
subtitle_disable=False,
|
58 |
+
metadata_disable=False,
|
59 |
+
chapters_disable=False,
|
60 |
+
extra_input_options=None,
|
61 |
+
extra_output_options=None,
|
62 |
+
output_format=None,
|
63 |
+
dry_run=False,
|
64 |
+
debug=False,
|
65 |
+
progress=False,
|
66 |
+
ffmpeg_exe=None
|
67 |
+
):
|
68 |
+
# self.ffmpeg_exe = get_ffmpeg_exe()
|
69 |
+
self.ffmpeg_exe = ffmpeg_exe
|
70 |
+
self.has_loudnorm_capabilities = ffmpeg_has_loudnorm(self.ffmpeg_exe)
|
71 |
+
|
72 |
+
self.normalization_type = normalization_type
|
73 |
+
if not self.has_loudnorm_capabilities and self.normalization_type == "ebu":
|
74 |
+
raise FFmpegNormalizeError(
|
75 |
+
"Your ffmpeg version does not support the 'loudnorm' EBU R128 filter. "
|
76 |
+
"Please install ffmpeg v3.1 or above, or choose another normalization type."
|
77 |
+
)
|
78 |
+
|
79 |
+
if self.normalization_type == "ebu":
|
80 |
+
self.target_level = check_range(target_level, -70, -5, name="target_level")
|
81 |
+
else:
|
82 |
+
self.target_level = check_range(target_level, -99, 0, name="target_level")
|
83 |
+
|
84 |
+
self.print_stats = print_stats
|
85 |
+
|
86 |
+
# self.threshold = float(threshold)
|
87 |
+
|
88 |
+
self.loudness_range_target = check_range(
|
89 |
+
loudness_range_target, 1, 20, name="loudness_range_target"
|
90 |
+
)
|
91 |
+
self.true_peak = check_range(true_peak, -9, 0, name="true_peak")
|
92 |
+
self.offset = check_range(offset, -99, 99, name="offset")
|
93 |
+
|
94 |
+
self.dual_mono = True if dual_mono in ["true", True] else False
|
95 |
+
self.audio_codec = audio_codec
|
96 |
+
self.audio_bitrate = audio_bitrate
|
97 |
+
self.sample_rate = int(sample_rate) if sample_rate is not None else None
|
98 |
+
self.keep_original_audio = keep_original_audio
|
99 |
+
self.video_codec = video_codec
|
100 |
+
self.video_disable = video_disable
|
101 |
+
self.subtitle_disable = subtitle_disable
|
102 |
+
self.metadata_disable = metadata_disable
|
103 |
+
self.chapters_disable = chapters_disable
|
104 |
+
|
105 |
+
self.extra_input_options = extra_input_options
|
106 |
+
self.extra_output_options = extra_output_options
|
107 |
+
self.pre_filter = pre_filter
|
108 |
+
self.post_filter = post_filter
|
109 |
+
|
110 |
+
self.output_format = output_format
|
111 |
+
self.dry_run = dry_run
|
112 |
+
self.debug = debug
|
113 |
+
self.progress = progress
|
114 |
+
|
115 |
+
self.stats = []
|
116 |
+
|
117 |
+
if (
|
118 |
+
self.output_format
|
119 |
+
and (self.audio_codec is None or "pcm" in self.audio_codec)
|
120 |
+
and self.output_format in PCM_INCOMPATIBLE_FORMATS
|
121 |
+
):
|
122 |
+
raise FFmpegNormalizeError(
|
123 |
+
f"Output format {self.output_format} does not support PCM audio. "
|
124 |
+
+ "Please choose a suitable audio codec with the -c:a option."
|
125 |
+
)
|
126 |
+
|
127 |
+
if normalization_type not in NORMALIZATION_TYPES:
|
128 |
+
raise FFmpegNormalizeError(
|
129 |
+
f"Normalization type must be one of {NORMALIZATION_TYPES}"
|
130 |
+
)
|
131 |
+
|
132 |
+
if self.target_level and not isinstance(self.target_level, Number):
|
133 |
+
raise FFmpegNormalizeError("target_level must be a number")
|
134 |
+
|
135 |
+
if self.loudness_range_target and not isinstance(
|
136 |
+
self.loudness_range_target, Number
|
137 |
+
):
|
138 |
+
raise FFmpegNormalizeError("loudness_range_target must be a number")
|
139 |
+
|
140 |
+
if self.true_peak and not isinstance(self.true_peak, Number):
|
141 |
+
raise FFmpegNormalizeError("true_peak must be a number")
|
142 |
+
|
143 |
+
if float(target_level) > 0:
|
144 |
+
raise FFmpegNormalizeError("Target level must be below 0")
|
145 |
+
|
146 |
+
self.media_files = []
|
147 |
+
self.file_count = 0
|
148 |
+
|
149 |
+
def add_media_file(self, input_file, output_file):
|
150 |
+
"""
|
151 |
+
Add a media file to normalize
|
152 |
+
|
153 |
+
Arguments:
|
154 |
+
input_file {str} -- Path to input file
|
155 |
+
output_file {str} -- Path to output file
|
156 |
+
"""
|
157 |
+
if not os.path.exists(input_file):
|
158 |
+
raise FFmpegNormalizeError("file " + input_file + " does not exist")
|
159 |
+
|
160 |
+
ext = os.path.splitext(output_file)[1][1:]
|
161 |
+
if (
|
162 |
+
self.audio_codec is None or "pcm" in self.audio_codec
|
163 |
+
) and ext in PCM_INCOMPATIBLE_EXTS:
|
164 |
+
raise FFmpegNormalizeError(
|
165 |
+
f"Output extension {ext} does not support PCM audio. " +
|
166 |
+
"Please choose a suitable audio codec with the -c:a option."
|
167 |
+
)
|
168 |
+
|
169 |
+
mf = MediaFile(self, input_file, output_file)
|
170 |
+
self.media_files.append(mf)
|
171 |
+
|
172 |
+
self.file_count += 1
|
173 |
+
|
174 |
+
def run_normalization(self):
|
175 |
+
"""
|
176 |
+
Run the normalization procedures
|
177 |
+
"""
|
178 |
+
for index, media_file in enumerate(
|
179 |
+
tqdm(self.media_files, desc="File", disable=not self.progress, position=0)
|
180 |
+
):
|
181 |
+
logger.info(
|
182 |
+
f"Normalizing file {media_file} ({index + 1} of {self.file_count})"
|
183 |
+
)
|
184 |
+
|
185 |
+
try:
|
186 |
+
media_file.run_normalization()
|
187 |
+
except Exception as e:
|
188 |
+
if len(self.media_files) > 1:
|
189 |
+
# simply warn and do not die
|
190 |
+
logger.error(
|
191 |
+
"Error processing input file {}, will continue batch-processing. Error was: {}".format(
|
192 |
+
media_file, e
|
193 |
+
)
|
194 |
+
)
|
195 |
+
else:
|
196 |
+
# raise the error so the program will exit
|
197 |
+
raise e
|
198 |
+
|
199 |
+
logger.info(f"Normalized file written to {media_file.output_file}")
|
200 |
+
|
201 |
+
if self.print_stats and self.stats:
|
202 |
+
print(json.dumps(self.stats, indent=4))
|
lib/ffmpeg_normalize/_logger.py
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
from platform import system
|
3 |
+
from tqdm import tqdm
|
4 |
+
import sys
|
5 |
+
|
6 |
+
loggers = {}
|
7 |
+
|
8 |
+
|
9 |
+
# https://stackoverflow.com/questions/38543506/
|
10 |
+
class TqdmLoggingHandler(logging.StreamHandler):
|
11 |
+
def __init__(self):
|
12 |
+
super().__init__(sys.stderr)
|
13 |
+
|
14 |
+
def emit(self, record):
|
15 |
+
try:
|
16 |
+
msg = self.format(record)
|
17 |
+
set_mp_lock()
|
18 |
+
tqdm.write(msg, file=sys.stderr)
|
19 |
+
self.flush()
|
20 |
+
except (KeyboardInterrupt, SystemExit):
|
21 |
+
raise
|
22 |
+
except Exception:
|
23 |
+
self.handleError(record)
|
24 |
+
|
25 |
+
def set_mp_lock():
|
26 |
+
try:
|
27 |
+
from multiprocessing import Lock
|
28 |
+
tqdm.set_lock(Lock())
|
29 |
+
except (ImportError, OSError):
|
30 |
+
# Some python environments do not support multiprocessing
|
31 |
+
# See: https://github.com/slhck/ffmpeg-normalize/issues/156
|
32 |
+
pass
|
33 |
+
|
34 |
+
def setup_custom_logger(name):
|
35 |
+
"""
|
36 |
+
Create a logger with a certain name and level
|
37 |
+
"""
|
38 |
+
global loggers
|
39 |
+
|
40 |
+
if loggers.get(name):
|
41 |
+
return loggers.get(name)
|
42 |
+
|
43 |
+
formatter = logging.Formatter(fmt="%(levelname)s: %(message)s")
|
44 |
+
|
45 |
+
# handler = logging.StreamHandler()
|
46 |
+
handler = TqdmLoggingHandler()
|
47 |
+
handler.setFormatter(formatter)
|
48 |
+
|
49 |
+
# \033[1;30m - black
|
50 |
+
# \033[1;31m - red
|
51 |
+
# \033[1;32m - green
|
52 |
+
# \033[1;33m - yellow
|
53 |
+
# \033[1;34m - blue
|
54 |
+
# \033[1;35m - magenta
|
55 |
+
# \033[1;36m - cyan
|
56 |
+
# \033[1;37m - white
|
57 |
+
|
58 |
+
if system() not in ["Windows", "cli"]:
|
59 |
+
logging.addLevelName(
|
60 |
+
logging.ERROR, f"[1;31m{logging.getLevelName(logging.ERROR)}[1;0m"
|
61 |
+
)
|
62 |
+
logging.addLevelName(
|
63 |
+
logging.WARNING,
|
64 |
+
f"[1;33m{logging.getLevelName(logging.WARNING)}[1;0m",
|
65 |
+
)
|
66 |
+
logging.addLevelName(
|
67 |
+
logging.INFO, f"[1;34m{logging.getLevelName(logging.INFO)}[1;0m"
|
68 |
+
)
|
69 |
+
logging.addLevelName(
|
70 |
+
logging.DEBUG, f"[1;35m{logging.getLevelName(logging.DEBUG)}[1;0m"
|
71 |
+
)
|
72 |
+
|
73 |
+
logger = logging.getLogger(name)
|
74 |
+
logger.setLevel(logging.WARNING)
|
75 |
+
|
76 |
+
# if (logger.hasHandlers()):
|
77 |
+
# logger.handlers.clear()
|
78 |
+
if logger.handlers:
|
79 |
+
logger.handlers = []
|
80 |
+
logger.addHandler(handler)
|
81 |
+
loggers.update(dict(name=logger))
|
82 |
+
|
83 |
+
return logger
|
lib/ffmpeg_normalize/_media_file.py
ADDED
@@ -0,0 +1,371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import re
|
3 |
+
import tempfile
|
4 |
+
import shutil
|
5 |
+
from tqdm import tqdm
|
6 |
+
import shlex
|
7 |
+
|
8 |
+
from ._streams import AudioStream, VideoStream, SubtitleStream
|
9 |
+
from ._errors import FFmpegNormalizeError
|
10 |
+
from ._cmd_utils import NUL, CommandRunner, DUR_REGEX, to_ms
|
11 |
+
from ._logger import setup_custom_logger
|
12 |
+
|
13 |
+
logger = setup_custom_logger("ffmpeg_normalize")
|
14 |
+
|
15 |
+
|
16 |
+
class MediaFile:
|
17 |
+
"""
|
18 |
+
Class that holds a file, its streams and adjustments
|
19 |
+
"""
|
20 |
+
|
21 |
+
def __init__(self, ffmpeg_normalize, input_file, output_file=None):
|
22 |
+
"""
|
23 |
+
Initialize a media file for later normalization.
|
24 |
+
|
25 |
+
Arguments:
|
26 |
+
ffmpeg_normalize {FFmpegNormalize} -- reference to overall settings
|
27 |
+
input_file {str} -- Path to input file
|
28 |
+
|
29 |
+
Keyword Arguments:
|
30 |
+
output_file {str} -- Path to output file (default: {None})
|
31 |
+
"""
|
32 |
+
self.ffmpeg_normalize = ffmpeg_normalize
|
33 |
+
self.skip = False
|
34 |
+
self.input_file = input_file
|
35 |
+
self.output_file = output_file
|
36 |
+
self.streams = {"audio": {}, "video": {}, "subtitle": {}}
|
37 |
+
|
38 |
+
self.parse_streams()
|
39 |
+
|
40 |
+
def _stream_ids(self):
|
41 |
+
return (
|
42 |
+
list(self.streams["audio"].keys())
|
43 |
+
+ list(self.streams["video"].keys())
|
44 |
+
+ list(self.streams["subtitle"].keys())
|
45 |
+
)
|
46 |
+
|
47 |
+
def __repr__(self):
|
48 |
+
return os.path.basename(self.input_file)
|
49 |
+
|
50 |
+
def parse_streams(self):
|
51 |
+
"""
|
52 |
+
Try to parse all input streams from file
|
53 |
+
"""
|
54 |
+
logger.debug(f"Parsing streams of {self.input_file}")
|
55 |
+
|
56 |
+
cmd = [
|
57 |
+
self.ffmpeg_normalize.ffmpeg_exe,
|
58 |
+
"-i",
|
59 |
+
self.input_file,
|
60 |
+
"-c",
|
61 |
+
"copy",
|
62 |
+
"-t",
|
63 |
+
"0",
|
64 |
+
"-map",
|
65 |
+
"0",
|
66 |
+
"-f",
|
67 |
+
"null",
|
68 |
+
NUL,
|
69 |
+
]
|
70 |
+
|
71 |
+
cmd_runner = CommandRunner(cmd)
|
72 |
+
cmd_runner.run_command()
|
73 |
+
output = cmd_runner.get_output()
|
74 |
+
|
75 |
+
logger.debug("Stream parsing command output:")
|
76 |
+
logger.debug(output)
|
77 |
+
|
78 |
+
output_lines = [line.strip() for line in output.split("\n")]
|
79 |
+
|
80 |
+
duration = None
|
81 |
+
for line in output_lines:
|
82 |
+
|
83 |
+
if "Duration" in line:
|
84 |
+
duration_search = DUR_REGEX.search(line)
|
85 |
+
if not duration_search:
|
86 |
+
logger.warning("Could not extract duration from input file!")
|
87 |
+
else:
|
88 |
+
duration = duration_search.groupdict()
|
89 |
+
duration = to_ms(**duration) / 1000
|
90 |
+
logger.debug("Found duration: " + str(duration) + " s")
|
91 |
+
|
92 |
+
if not line.startswith("Stream"):
|
93 |
+
continue
|
94 |
+
|
95 |
+
stream_id_match = re.search(r"#0:([\d]+)", line)
|
96 |
+
if stream_id_match:
|
97 |
+
stream_id = int(stream_id_match.group(1))
|
98 |
+
if stream_id in self._stream_ids():
|
99 |
+
continue
|
100 |
+
else:
|
101 |
+
continue
|
102 |
+
|
103 |
+
if "Audio" in line:
|
104 |
+
logger.debug(f"Found audio stream at index {stream_id}")
|
105 |
+
sample_rate_match = re.search(r"(\d+) Hz", line)
|
106 |
+
sample_rate = (
|
107 |
+
int(sample_rate_match.group(1)) if sample_rate_match else None
|
108 |
+
)
|
109 |
+
bit_depth_match = re.search(r"s(\d+)p?,", line)
|
110 |
+
bit_depth = int(bit_depth_match.group(1)) if bit_depth_match else None
|
111 |
+
self.streams["audio"][stream_id] = AudioStream(
|
112 |
+
self,
|
113 |
+
self.ffmpeg_normalize,
|
114 |
+
stream_id,
|
115 |
+
sample_rate,
|
116 |
+
bit_depth,
|
117 |
+
duration,
|
118 |
+
)
|
119 |
+
|
120 |
+
elif "Video" in line:
|
121 |
+
logger.debug(f"Found video stream at index {stream_id}")
|
122 |
+
self.streams["video"][stream_id] = VideoStream(
|
123 |
+
self, self.ffmpeg_normalize, stream_id
|
124 |
+
)
|
125 |
+
|
126 |
+
elif "Subtitle" in line:
|
127 |
+
logger.debug(f"Found subtitle stream at index {stream_id}")
|
128 |
+
self.streams["subtitle"][stream_id] = SubtitleStream(
|
129 |
+
self, self.ffmpeg_normalize, stream_id
|
130 |
+
)
|
131 |
+
|
132 |
+
if not self.streams["audio"]:
|
133 |
+
raise FFmpegNormalizeError(
|
134 |
+
f"Input file {self.input_file} does not contain any audio streams"
|
135 |
+
)
|
136 |
+
|
137 |
+
if (
|
138 |
+
os.path.splitext(self.output_file)[1].lower() in [".wav", ".mp3", ".aac"]
|
139 |
+
and len(self.streams["audio"].values()) > 1
|
140 |
+
):
|
141 |
+
logger.warning(
|
142 |
+
"Output file only supports one stream. "
|
143 |
+
"Keeping only first audio stream."
|
144 |
+
)
|
145 |
+
first_stream = list(self.streams["audio"].values())[0]
|
146 |
+
self.streams["audio"] = {first_stream.stream_id: first_stream}
|
147 |
+
self.streams["video"] = {}
|
148 |
+
self.streams["subtitle"] = {}
|
149 |
+
|
150 |
+
def run_normalization(self):
|
151 |
+
logger.debug(f"Running normalization for {self.input_file}")
|
152 |
+
|
153 |
+
# run the first pass to get loudness stats
|
154 |
+
self._first_pass()
|
155 |
+
|
156 |
+
# run the second pass as a whole
|
157 |
+
if self.ffmpeg_normalize.progress:
|
158 |
+
with tqdm(total=100, position=1, desc="Second Pass") as pbar:
|
159 |
+
for progress in self._second_pass():
|
160 |
+
pbar.update(progress - pbar.n)
|
161 |
+
else:
|
162 |
+
for _ in self._second_pass():
|
163 |
+
pass
|
164 |
+
|
165 |
+
def _first_pass(self):
|
166 |
+
logger.debug(f"Parsing normalization info for {self.input_file}")
|
167 |
+
|
168 |
+
for index, audio_stream in enumerate(self.streams["audio"].values()):
|
169 |
+
if self.ffmpeg_normalize.normalization_type == "ebu":
|
170 |
+
fun = getattr(audio_stream, "parse_loudnorm_stats")
|
171 |
+
else:
|
172 |
+
fun = getattr(audio_stream, "parse_volumedetect_stats")
|
173 |
+
|
174 |
+
if self.ffmpeg_normalize.progress:
|
175 |
+
with tqdm(
|
176 |
+
total=100,
|
177 |
+
position=1,
|
178 |
+
desc=f"Stream {index + 1}/{len(self.streams['audio'].values())}",
|
179 |
+
) as pbar:
|
180 |
+
for progress in fun():
|
181 |
+
pbar.update(progress - pbar.n)
|
182 |
+
else:
|
183 |
+
for _ in fun():
|
184 |
+
pass
|
185 |
+
|
186 |
+
if self.ffmpeg_normalize.print_stats:
|
187 |
+
stats = [
|
188 |
+
audio_stream.get_stats()
|
189 |
+
for audio_stream in self.streams["audio"].values()
|
190 |
+
]
|
191 |
+
self.ffmpeg_normalize.stats.extend(stats)
|
192 |
+
|
193 |
+
def _get_audio_filter_cmd(self):
|
194 |
+
"""
|
195 |
+
Return filter_complex command and output labels needed
|
196 |
+
"""
|
197 |
+
filter_chains = []
|
198 |
+
output_labels = []
|
199 |
+
|
200 |
+
for audio_stream in self.streams["audio"].values():
|
201 |
+
if self.ffmpeg_normalize.normalization_type == "ebu":
|
202 |
+
normalization_filter = audio_stream.get_second_pass_opts_ebu()
|
203 |
+
else:
|
204 |
+
normalization_filter = audio_stream.get_second_pass_opts_peakrms()
|
205 |
+
|
206 |
+
input_label = f"[0:{audio_stream.stream_id}]"
|
207 |
+
output_label = f"[norm{audio_stream.stream_id}]"
|
208 |
+
output_labels.append(output_label)
|
209 |
+
|
210 |
+
filter_chain = []
|
211 |
+
|
212 |
+
if self.ffmpeg_normalize.pre_filter:
|
213 |
+
filter_chain.append(self.ffmpeg_normalize.pre_filter)
|
214 |
+
|
215 |
+
filter_chain.append(normalization_filter)
|
216 |
+
|
217 |
+
if self.ffmpeg_normalize.post_filter:
|
218 |
+
filter_chain.append(self.ffmpeg_normalize.post_filter)
|
219 |
+
|
220 |
+
filter_chains.append(input_label + ",".join(filter_chain) + output_label)
|
221 |
+
|
222 |
+
filter_complex_cmd = ";".join(filter_chains)
|
223 |
+
|
224 |
+
return filter_complex_cmd, output_labels
|
225 |
+
|
226 |
+
def _second_pass(self):
|
227 |
+
"""
|
228 |
+
Construct the second pass command and run it
|
229 |
+
|
230 |
+
FIXME: make this method simpler
|
231 |
+
"""
|
232 |
+
logger.info(f"Running second pass for {self.input_file}")
|
233 |
+
|
234 |
+
# get the target output stream types depending on the options
|
235 |
+
output_stream_types = ["audio"]
|
236 |
+
if not self.ffmpeg_normalize.video_disable:
|
237 |
+
output_stream_types.append("video")
|
238 |
+
if not self.ffmpeg_normalize.subtitle_disable:
|
239 |
+
output_stream_types.append("subtitle")
|
240 |
+
|
241 |
+
# base command, here we will add all other options
|
242 |
+
cmd = [self.ffmpeg_normalize.ffmpeg_exe, "-y", "-nostdin"]
|
243 |
+
|
244 |
+
# extra options (if any)
|
245 |
+
if self.ffmpeg_normalize.extra_input_options:
|
246 |
+
cmd.extend(self.ffmpeg_normalize.extra_input_options)
|
247 |
+
|
248 |
+
# get complex filter command
|
249 |
+
audio_filter_cmd, output_labels = self._get_audio_filter_cmd()
|
250 |
+
|
251 |
+
# add input file and basic filter
|
252 |
+
cmd.extend(["-i", self.input_file, "-filter_complex", audio_filter_cmd])
|
253 |
+
|
254 |
+
# map metadata, only if needed
|
255 |
+
if self.ffmpeg_normalize.metadata_disable:
|
256 |
+
cmd.extend(["-map_metadata", "-1"])
|
257 |
+
else:
|
258 |
+
# map global metadata
|
259 |
+
cmd.extend(["-map_metadata", "0"])
|
260 |
+
# map per-stream metadata (e.g. language tags)
|
261 |
+
for stream_type in output_stream_types:
|
262 |
+
stream_key = stream_type[0]
|
263 |
+
if stream_type not in self.streams:
|
264 |
+
continue
|
265 |
+
for idx, _ in enumerate(self.streams[stream_type].items()):
|
266 |
+
cmd.extend(
|
267 |
+
[
|
268 |
+
f"-map_metadata:s:{stream_key}:{idx}",
|
269 |
+
f"0:s:{stream_key}:{idx}",
|
270 |
+
]
|
271 |
+
)
|
272 |
+
|
273 |
+
# map chapters if needed
|
274 |
+
if self.ffmpeg_normalize.chapters_disable:
|
275 |
+
cmd.extend(["-map_chapters", "-1"])
|
276 |
+
else:
|
277 |
+
cmd.extend(["-map_chapters", "0"])
|
278 |
+
|
279 |
+
# collect all '-map' and codecs needed for output video based on input video
|
280 |
+
if not self.ffmpeg_normalize.video_disable:
|
281 |
+
for s in self.streams["video"].keys():
|
282 |
+
cmd.extend(["-map", f"0:{s}"])
|
283 |
+
# set codec (copy by default)
|
284 |
+
cmd.extend(["-c:v", self.ffmpeg_normalize.video_codec])
|
285 |
+
|
286 |
+
# ... and map the output of the normalization filters
|
287 |
+
for ol in output_labels:
|
288 |
+
cmd.extend(["-map", ol])
|
289 |
+
|
290 |
+
# set audio codec (never copy)
|
291 |
+
if self.ffmpeg_normalize.audio_codec:
|
292 |
+
cmd.extend(["-c:a", self.ffmpeg_normalize.audio_codec])
|
293 |
+
else:
|
294 |
+
for index, (_, audio_stream) in enumerate(self.streams["audio"].items()):
|
295 |
+
cmd.extend([f"-c:a:{index}", audio_stream.get_pcm_codec()])
|
296 |
+
|
297 |
+
# other audio options (if any)
|
298 |
+
if self.ffmpeg_normalize.audio_bitrate:
|
299 |
+
cmd.extend(["-b:a", str(self.ffmpeg_normalize.audio_bitrate)])
|
300 |
+
if self.ffmpeg_normalize.sample_rate:
|
301 |
+
cmd.extend(["-ar", str(self.ffmpeg_normalize.sample_rate)])
|
302 |
+
else:
|
303 |
+
if self.ffmpeg_normalize.normalization_type == "ebu":
|
304 |
+
logger.warn(
|
305 |
+
"The sample rate will automatically be set to 192 kHz by the loudnorm filter. "
|
306 |
+
"Specify -ar/--sample-rate to override it."
|
307 |
+
)
|
308 |
+
|
309 |
+
# ... and subtitles
|
310 |
+
if not self.ffmpeg_normalize.subtitle_disable:
|
311 |
+
for s in self.streams["subtitle"].keys():
|
312 |
+
cmd.extend(["-map", f"0:{s}"])
|
313 |
+
# copy subtitles
|
314 |
+
cmd.extend(["-c:s", "copy"])
|
315 |
+
|
316 |
+
if self.ffmpeg_normalize.keep_original_audio:
|
317 |
+
highest_index = len(self.streams["audio"])
|
318 |
+
for index, (_, s) in enumerate(self.streams["audio"].items()):
|
319 |
+
cmd.extend(["-map", f"0:a:{index}"])
|
320 |
+
cmd.extend([f"-c:a:{highest_index + index}", "copy"])
|
321 |
+
|
322 |
+
# extra options (if any)
|
323 |
+
if self.ffmpeg_normalize.extra_output_options:
|
324 |
+
cmd.extend(self.ffmpeg_normalize.extra_output_options)
|
325 |
+
|
326 |
+
# output format (if any)
|
327 |
+
if self.ffmpeg_normalize.output_format:
|
328 |
+
cmd.extend(["-f", self.ffmpeg_normalize.output_format])
|
329 |
+
|
330 |
+
# if dry run, only show sample command
|
331 |
+
if self.ffmpeg_normalize.dry_run:
|
332 |
+
cmd.append(self.output_file)
|
333 |
+
cmd_runner = CommandRunner(cmd, dry=True)
|
334 |
+
cmd_runner.run_command()
|
335 |
+
yield 100
|
336 |
+
return
|
337 |
+
|
338 |
+
# create a temporary output file name
|
339 |
+
temp_dir = tempfile.gettempdir()
|
340 |
+
output_file_suffix = os.path.splitext(self.output_file)[1]
|
341 |
+
temp_file_name = os.path.join(
|
342 |
+
temp_dir, next(tempfile._get_candidate_names()) + output_file_suffix
|
343 |
+
)
|
344 |
+
cmd.append(temp_file_name)
|
345 |
+
|
346 |
+
# run the actual command
|
347 |
+
try:
|
348 |
+
cmd_runner = CommandRunner(cmd)
|
349 |
+
try:
|
350 |
+
for progress in cmd_runner.run_ffmpeg_command():
|
351 |
+
yield progress
|
352 |
+
except Exception as e:
|
353 |
+
logger.error(
|
354 |
+
"Error while running command {}! Error: {}".format(
|
355 |
+
" ".join([shlex.quote(c) for c in cmd]), e
|
356 |
+
)
|
357 |
+
)
|
358 |
+
raise e
|
359 |
+
else:
|
360 |
+
# move file from TMP to output file
|
361 |
+
logger.debug(
|
362 |
+
f"Moving temporary file from {temp_file_name} to {self.output_file}"
|
363 |
+
)
|
364 |
+
shutil.move(temp_file_name, self.output_file)
|
365 |
+
except Exception as e:
|
366 |
+
# remove dangling temporary file
|
367 |
+
if os.path.isfile(temp_file_name):
|
368 |
+
os.remove(temp_file_name)
|
369 |
+
raise e
|
370 |
+
|
371 |
+
logger.debug("Normalization finished")
|
lib/ffmpeg_normalize/_streams.py
ADDED
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import re
|
3 |
+
import json
|
4 |
+
import math
|
5 |
+
|
6 |
+
from ._errors import FFmpegNormalizeError
|
7 |
+
from ._cmd_utils import NUL, CommandRunner, dict_to_filter_opts
|
8 |
+
from ._logger import setup_custom_logger
|
9 |
+
|
10 |
+
logger = setup_custom_logger("ffmpeg_normalize")
|
11 |
+
|
12 |
+
|
13 |
+
class MediaStream(object):
|
14 |
+
def __init__(self, ffmpeg_normalize, media_file, stream_type, stream_id):
|
15 |
+
"""
|
16 |
+
Arguments:
|
17 |
+
media_file {MediaFile} -- parent media file
|
18 |
+
stream_type {str} -- stream type
|
19 |
+
stream_id {int} -- Audio stream id
|
20 |
+
"""
|
21 |
+
self.ffmpeg_normalize = ffmpeg_normalize
|
22 |
+
self.media_file = media_file
|
23 |
+
self.stream_type = stream_type
|
24 |
+
self.stream_id = stream_id
|
25 |
+
|
26 |
+
def __repr__(self):
|
27 |
+
return "<{}, {} stream {}>".format(
|
28 |
+
os.path.basename(self.media_file.input_file),
|
29 |
+
self.stream_type,
|
30 |
+
self.stream_id,
|
31 |
+
)
|
32 |
+
|
33 |
+
|
34 |
+
class VideoStream(MediaStream):
|
35 |
+
def __init__(self, ffmpeg_normalize, media_file, stream_id):
|
36 |
+
super(VideoStream, self).__init__(
|
37 |
+
media_file, ffmpeg_normalize, "video", stream_id
|
38 |
+
)
|
39 |
+
|
40 |
+
|
41 |
+
class SubtitleStream(MediaStream):
|
42 |
+
def __init__(self, ffmpeg_normalize, media_file, stream_id):
|
43 |
+
super(SubtitleStream, self).__init__(
|
44 |
+
media_file, ffmpeg_normalize, "subtitle", stream_id
|
45 |
+
)
|
46 |
+
|
47 |
+
|
48 |
+
class AudioStream(MediaStream):
|
49 |
+
def __init__(
|
50 |
+
self,
|
51 |
+
ffmpeg_normalize,
|
52 |
+
media_file,
|
53 |
+
stream_id,
|
54 |
+
sample_rate=None,
|
55 |
+
bit_depth=None,
|
56 |
+
duration=None,
|
57 |
+
):
|
58 |
+
"""
|
59 |
+
Arguments:
|
60 |
+
sample_rate {int} -- in Hz
|
61 |
+
bit_depth {int}
|
62 |
+
duration {int} -- duration in seconds
|
63 |
+
"""
|
64 |
+
super(AudioStream, self).__init__(
|
65 |
+
media_file, ffmpeg_normalize, "audio", stream_id
|
66 |
+
)
|
67 |
+
|
68 |
+
self.loudness_statistics = {"ebu": None, "mean": None, "max": None}
|
69 |
+
|
70 |
+
self.sample_rate = sample_rate
|
71 |
+
self.bit_depth = bit_depth
|
72 |
+
self.duration = duration
|
73 |
+
|
74 |
+
if (
|
75 |
+
self.ffmpeg_normalize.normalization_type == "ebu"
|
76 |
+
and self.duration
|
77 |
+
and self.duration <= 3
|
78 |
+
):
|
79 |
+
logger.warn(
|
80 |
+
"Audio stream has a duration of less than 3 seconds. "
|
81 |
+
"Normalization may not work. "
|
82 |
+
"See https://github.com/slhck/ffmpeg-normalize/issues/87 for more info."
|
83 |
+
)
|
84 |
+
|
85 |
+
def __repr__(self):
|
86 |
+
return "<{}, audio stream {}>".format(
|
87 |
+
os.path.basename(self.media_file.input_file), self.stream_id
|
88 |
+
)
|
89 |
+
|
90 |
+
def get_stats(self):
|
91 |
+
"""
|
92 |
+
Return statistics
|
93 |
+
"""
|
94 |
+
stats = {
|
95 |
+
"input_file": self.media_file.input_file,
|
96 |
+
"output_file": self.media_file.output_file,
|
97 |
+
"stream_id": self.stream_id,
|
98 |
+
}
|
99 |
+
stats.update(self.loudness_statistics)
|
100 |
+
return stats
|
101 |
+
|
102 |
+
def get_pcm_codec(self):
|
103 |
+
if not self.bit_depth:
|
104 |
+
return "pcm_s16le"
|
105 |
+
elif self.bit_depth <= 8:
|
106 |
+
return "pcm_s8"
|
107 |
+
elif self.bit_depth in [16, 24, 32, 64]:
|
108 |
+
return f"pcm_s{self.bit_depth}le"
|
109 |
+
else:
|
110 |
+
logger.warning(
|
111 |
+
f"Unsupported bit depth {self.bit_depth}, falling back to pcm_s16le"
|
112 |
+
)
|
113 |
+
return "pcm_s16le"
|
114 |
+
|
115 |
+
def _get_filter_str_with_pre_filter(self, current_filter):
|
116 |
+
"""
|
117 |
+
Get a filter stringΒ for current_filter, with the pre-filter
|
118 |
+
added before. Applies the input label before.
|
119 |
+
"""
|
120 |
+
input_label = f"[0:{self.stream_id}]"
|
121 |
+
filter_chain = []
|
122 |
+
if self.media_file.ffmpeg_normalize.pre_filter:
|
123 |
+
filter_chain.append(self.media_file.ffmpeg_normalize.pre_filter)
|
124 |
+
filter_chain.append(current_filter)
|
125 |
+
filter_str = input_label + ",".join(filter_chain)
|
126 |
+
return filter_str
|
127 |
+
|
128 |
+
def parse_volumedetect_stats(self):
|
129 |
+
"""
|
130 |
+
Use ffmpeg with volumedetect filter to get the mean volume of the input file.
|
131 |
+
"""
|
132 |
+
logger.info(
|
133 |
+
f"Running first pass volumedetect filter for stream {self.stream_id}"
|
134 |
+
)
|
135 |
+
|
136 |
+
filter_str = self._get_filter_str_with_pre_filter("volumedetect")
|
137 |
+
|
138 |
+
cmd = [
|
139 |
+
self.media_file.ffmpeg_normalize.ffmpeg_exe,
|
140 |
+
"-nostdin",
|
141 |
+
"-y",
|
142 |
+
"-i",
|
143 |
+
self.media_file.input_file,
|
144 |
+
"-filter_complex",
|
145 |
+
filter_str,
|
146 |
+
"-vn",
|
147 |
+
"-sn",
|
148 |
+
"-f",
|
149 |
+
"null",
|
150 |
+
NUL,
|
151 |
+
]
|
152 |
+
|
153 |
+
cmd_runner = CommandRunner(cmd)
|
154 |
+
for progress in cmd_runner.run_ffmpeg_command():
|
155 |
+
yield progress
|
156 |
+
output = cmd_runner.get_output()
|
157 |
+
|
158 |
+
logger.debug("Volumedetect command output:")
|
159 |
+
logger.debug(output)
|
160 |
+
|
161 |
+
mean_volume_matches = re.findall(r"mean_volume: ([\-\d\.]+) dB", output)
|
162 |
+
if mean_volume_matches:
|
163 |
+
self.loudness_statistics["mean"] = float(mean_volume_matches[0])
|
164 |
+
else:
|
165 |
+
raise FFmpegNormalizeError(
|
166 |
+
f"Could not get mean volume for {self.media_file.input_file}"
|
167 |
+
)
|
168 |
+
|
169 |
+
max_volume_matches = re.findall(r"max_volume: ([\-\d\.]+) dB", output)
|
170 |
+
if max_volume_matches:
|
171 |
+
self.loudness_statistics["max"] = float(max_volume_matches[0])
|
172 |
+
else:
|
173 |
+
raise FFmpegNormalizeError(
|
174 |
+
f"Could not get max volume for {self.media_file.input_file}"
|
175 |
+
)
|
176 |
+
|
177 |
+
def parse_loudnorm_stats(self):
|
178 |
+
"""
|
179 |
+
Run a first pass loudnorm filter to get measured data.
|
180 |
+
"""
|
181 |
+
logger.info(f"Running first pass loudnorm filter for stream {self.stream_id}")
|
182 |
+
|
183 |
+
opts = {
|
184 |
+
"i": self.media_file.ffmpeg_normalize.target_level,
|
185 |
+
"lra": self.media_file.ffmpeg_normalize.loudness_range_target,
|
186 |
+
"tp": self.media_file.ffmpeg_normalize.true_peak,
|
187 |
+
"offset": self.media_file.ffmpeg_normalize.offset,
|
188 |
+
"print_format": "json",
|
189 |
+
}
|
190 |
+
|
191 |
+
if self.media_file.ffmpeg_normalize.dual_mono:
|
192 |
+
opts["dual_mono"] = "true"
|
193 |
+
|
194 |
+
filter_str = self._get_filter_str_with_pre_filter(
|
195 |
+
"loudnorm=" + dict_to_filter_opts(opts)
|
196 |
+
)
|
197 |
+
|
198 |
+
cmd = [
|
199 |
+
self.media_file.ffmpeg_normalize.ffmpeg_exe,
|
200 |
+
"-nostdin",
|
201 |
+
"-y",
|
202 |
+
"-i",
|
203 |
+
self.media_file.input_file,
|
204 |
+
"-filter_complex",
|
205 |
+
filter_str,
|
206 |
+
"-vn",
|
207 |
+
"-sn",
|
208 |
+
"-f",
|
209 |
+
"null",
|
210 |
+
NUL,
|
211 |
+
]
|
212 |
+
|
213 |
+
cmd_runner = CommandRunner(cmd)
|
214 |
+
for progress in cmd_runner.run_ffmpeg_command():
|
215 |
+
yield progress
|
216 |
+
output = cmd_runner.get_output()
|
217 |
+
|
218 |
+
logger.debug("Loudnorm first pass command output:")
|
219 |
+
logger.debug(output)
|
220 |
+
|
221 |
+
output_lines = [line.strip() for line in output.split("\n")]
|
222 |
+
|
223 |
+
self.loudness_statistics["ebu"] = AudioStream._parse_loudnorm_output(
|
224 |
+
output_lines
|
225 |
+
)
|
226 |
+
|
227 |
+
@staticmethod
|
228 |
+
def _parse_loudnorm_output(output_lines):
|
229 |
+
loudnorm_start = False
|
230 |
+
loudnorm_end = False
|
231 |
+
for index, line in enumerate(output_lines):
|
232 |
+
if line.startswith("[Parsed_loudnorm"):
|
233 |
+
loudnorm_start = index + 1
|
234 |
+
continue
|
235 |
+
if loudnorm_start and line.startswith("}"):
|
236 |
+
loudnorm_end = index + 1
|
237 |
+
break
|
238 |
+
|
239 |
+
if not (loudnorm_start and loudnorm_end):
|
240 |
+
raise FFmpegNormalizeError(
|
241 |
+
"Could not parse loudnorm stats; no loudnorm-related output found"
|
242 |
+
)
|
243 |
+
|
244 |
+
try:
|
245 |
+
loudnorm_stats = json.loads(
|
246 |
+
"\n".join(output_lines[loudnorm_start:loudnorm_end])
|
247 |
+
)
|
248 |
+
|
249 |
+
logger.debug(f"Loudnorm stats parsed: {json.dumps(loudnorm_stats)}")
|
250 |
+
|
251 |
+
for key in [
|
252 |
+
"input_i",
|
253 |
+
"input_tp",
|
254 |
+
"input_lra",
|
255 |
+
"input_thresh",
|
256 |
+
"output_i",
|
257 |
+
"output_tp",
|
258 |
+
"output_lra",
|
259 |
+
"output_thresh",
|
260 |
+
"target_offset",
|
261 |
+
]:
|
262 |
+
# handle infinite values
|
263 |
+
if float(loudnorm_stats[key]) == -float("inf"):
|
264 |
+
loudnorm_stats[key] = -99
|
265 |
+
elif float(loudnorm_stats[key]) == float("inf"):
|
266 |
+
loudnorm_stats[key] = 0
|
267 |
+
else:
|
268 |
+
# convert to floats
|
269 |
+
loudnorm_stats[key] = float(loudnorm_stats[key])
|
270 |
+
|
271 |
+
return loudnorm_stats
|
272 |
+
except Exception as e:
|
273 |
+
raise FFmpegNormalizeError(
|
274 |
+
f"Could not parse loudnorm stats; wrong JSON format in string: {e}"
|
275 |
+
)
|
276 |
+
|
277 |
+
def get_second_pass_opts_ebu(self):
|
278 |
+
"""
|
279 |
+
Return second pass loudnorm filter options string for ffmpeg
|
280 |
+
"""
|
281 |
+
|
282 |
+
if not self.loudness_statistics["ebu"]:
|
283 |
+
raise FFmpegNormalizeError(
|
284 |
+
"First pass not run, you must call parse_loudnorm_stats first"
|
285 |
+
)
|
286 |
+
|
287 |
+
input_i = float(self.loudness_statistics["ebu"]["input_i"])
|
288 |
+
if input_i > 0:
|
289 |
+
logger.warn(
|
290 |
+
"Input file had measured input loudness greater than zero ({}), capping at 0".format(
|
291 |
+
"input_i"
|
292 |
+
)
|
293 |
+
)
|
294 |
+
self.loudness_statistics["ebu"]["input_i"] = 0
|
295 |
+
|
296 |
+
opts = {
|
297 |
+
"i": self.media_file.ffmpeg_normalize.target_level,
|
298 |
+
"lra": self.media_file.ffmpeg_normalize.loudness_range_target,
|
299 |
+
"tp": self.media_file.ffmpeg_normalize.true_peak,
|
300 |
+
"offset": float(self.loudness_statistics["ebu"]["target_offset"]),
|
301 |
+
"measured_i": float(self.loudness_statistics["ebu"]["input_i"]),
|
302 |
+
"measured_lra": float(self.loudness_statistics["ebu"]["input_lra"]),
|
303 |
+
"measured_tp": float(self.loudness_statistics["ebu"]["input_tp"]),
|
304 |
+
"measured_thresh": float(self.loudness_statistics["ebu"]["input_thresh"]),
|
305 |
+
"linear": "true",
|
306 |
+
"print_format": "json",
|
307 |
+
}
|
308 |
+
|
309 |
+
if self.media_file.ffmpeg_normalize.dual_mono:
|
310 |
+
opts["dual_mono"] = "true"
|
311 |
+
|
312 |
+
return "loudnorm=" + dict_to_filter_opts(opts)
|
313 |
+
|
314 |
+
def get_second_pass_opts_peakrms(self):
|
315 |
+
"""
|
316 |
+
Set the adjustment gain based on chosen option and mean/max volume,
|
317 |
+
return the matching ffmpeg volume filter.
|
318 |
+
"""
|
319 |
+
normalization_type = self.media_file.ffmpeg_normalize.normalization_type
|
320 |
+
target_level = self.media_file.ffmpeg_normalize.target_level
|
321 |
+
|
322 |
+
if normalization_type == "peak":
|
323 |
+
adjustment = 0 + target_level - self.loudness_statistics["max"]
|
324 |
+
elif normalization_type == "rms":
|
325 |
+
adjustment = target_level - self.loudness_statistics["mean"]
|
326 |
+
else:
|
327 |
+
raise FFmpegNormalizeError(
|
328 |
+
"Can only set adjustment for peak and RMS normalization"
|
329 |
+
)
|
330 |
+
|
331 |
+
logger.info(
|
332 |
+
"Adjusting stream {} by {} dB to reach {}".format(
|
333 |
+
self.stream_id, adjustment, target_level
|
334 |
+
)
|
335 |
+
)
|
336 |
+
|
337 |
+
if self.loudness_statistics["max"] + adjustment > 0:
|
338 |
+
logger.warning(
|
339 |
+
"Adjusting will lead to clipping of {} dB".format(
|
340 |
+
self.loudness_statistics["max"] + adjustment
|
341 |
+
)
|
342 |
+
)
|
343 |
+
|
344 |
+
return f"volume={adjustment}dB"
|
lib/ffmpeg_normalize/_version.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
__version__ = "1.22.4"
|
lib/osutils.js
ADDED
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
var _os = require('os');
|
2 |
+
|
3 |
+
window.platform = function(){
|
4 |
+
return process.platform;
|
5 |
+
}
|
6 |
+
|
7 |
+
window.cpuCount = function(){
|
8 |
+
return _os.cpus().length;
|
9 |
+
}
|
10 |
+
|
11 |
+
window.sysUptime = function(){
|
12 |
+
//seconds
|
13 |
+
return _os.uptime();
|
14 |
+
}
|
15 |
+
|
16 |
+
window.processUptime = function(){
|
17 |
+
//seconds
|
18 |
+
return process.uptime();
|
19 |
+
}
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
// Memory
|
24 |
+
window.freemem = function(){
|
25 |
+
return _os.freemem() / ( 1024 * 1024 );
|
26 |
+
}
|
27 |
+
|
28 |
+
window.totalmem = function(){
|
29 |
+
|
30 |
+
return _os.totalmem() / ( 1024 * 1024 );
|
31 |
+
}
|
32 |
+
|
33 |
+
window.freememPercentage = function(){
|
34 |
+
return _os.freemem() / _os.totalmem();
|
35 |
+
}
|
36 |
+
|
37 |
+
window.freeCommand = function(callback){
|
38 |
+
|
39 |
+
// Only Linux
|
40 |
+
require('child_process').exec('free -m', function(error, stdout, stderr) {
|
41 |
+
|
42 |
+
var lines = stdout.split("\n");
|
43 |
+
|
44 |
+
|
45 |
+
var str_mem_info = lines[1].replace( /[\s\n\r]+/g,' ');
|
46 |
+
|
47 |
+
var mem_info = str_mem_info.split(' ')
|
48 |
+
|
49 |
+
total_mem = parseFloat(mem_info[1])
|
50 |
+
free_mem = parseFloat(mem_info[3])
|
51 |
+
buffers_mem = parseFloat(mem_info[5])
|
52 |
+
cached_mem = parseFloat(mem_info[6])
|
53 |
+
|
54 |
+
used_mem = total_mem - (free_mem + buffers_mem + cached_mem)
|
55 |
+
|
56 |
+
callback(used_mem -2);
|
57 |
+
});
|
58 |
+
}
|
59 |
+
|
60 |
+
|
61 |
+
// Hard Disk Drive
|
62 |
+
window.harddrive = function(callback){
|
63 |
+
|
64 |
+
require('child_process').exec('df -k', function(error, stdout, stderr) {
|
65 |
+
|
66 |
+
var total = 0;
|
67 |
+
var used = 0;
|
68 |
+
var free = 0;
|
69 |
+
|
70 |
+
var lines = stdout.split("\n");
|
71 |
+
|
72 |
+
var str_disk_info = lines[1].replace( /[\s\n\r]+/g,' ');
|
73 |
+
|
74 |
+
var disk_info = str_disk_info.split(' ');
|
75 |
+
|
76 |
+
total = Math.ceil((disk_info[1] * 1024)/ Math.pow(1024,2));
|
77 |
+
used = Math.ceil(disk_info[2] * 1024 / Math.pow(1024,2)) ;
|
78 |
+
free = Math.ceil(disk_info[3] * 1024 / Math.pow(1024,2)) ;
|
79 |
+
|
80 |
+
callback(total, free, used);
|
81 |
+
});
|
82 |
+
}
|
83 |
+
|
84 |
+
|
85 |
+
|
86 |
+
// Return process running current
|
87 |
+
window.getProcesses = function(nProcess, callback){
|
88 |
+
|
89 |
+
// if nprocess is undefined then is function
|
90 |
+
if(typeof nProcess === 'function'){
|
91 |
+
|
92 |
+
callback =nProcess;
|
93 |
+
nProcess = 0
|
94 |
+
}
|
95 |
+
|
96 |
+
command = 'ps -eo pcpu,pmem,time,args | sort -k 1 -r | head -n'+10
|
97 |
+
//command = 'ps aux | head -n '+ 11
|
98 |
+
//command = 'ps aux | head -n '+ (nProcess + 1)
|
99 |
+
if (nProcess > 0)
|
100 |
+
command = 'ps -eo pcpu,pmem,time,args | sort -k 1 -r | head -n'+(nProcess + 1)
|
101 |
+
|
102 |
+
require('child_process').exec(command, function(error, stdout, stderr) {
|
103 |
+
|
104 |
+
var that = this
|
105 |
+
|
106 |
+
var lines = stdout.split("\n");
|
107 |
+
lines.shift()
|
108 |
+
lines.pop()
|
109 |
+
|
110 |
+
var result = ''
|
111 |
+
|
112 |
+
|
113 |
+
lines.forEach(function(_item,_i){
|
114 |
+
|
115 |
+
var _str = _item.replace( /[\s\n\r]+/g,' ');
|
116 |
+
|
117 |
+
_str = _str.split(' ')
|
118 |
+
|
119 |
+
// result += _str[10]+" "+_str[9]+" "+_str[2]+" "+_str[3]+"\n"; // process
|
120 |
+
result += _str[1]+" "+_str[2]+" "+_str[3]+" "+_str[4].substring((_str[4].length - 25))+"\n"; // process
|
121 |
+
|
122 |
+
});
|
123 |
+
|
124 |
+
callback(result);
|
125 |
+
});
|
126 |
+
}
|
127 |
+
|
128 |
+
|
129 |
+
|
130 |
+
/*
|
131 |
+
* Returns All the load average usage for 1, 5 or 15 minutes.
|
132 |
+
*/
|
133 |
+
window.allLoadavg = function(){
|
134 |
+
|
135 |
+
var loads = _os.loadavg();
|
136 |
+
|
137 |
+
return loads[0].toFixed(4)+','+loads[1].toFixed(4)+','+loads[2].toFixed(4);
|
138 |
+
}
|
139 |
+
|
140 |
+
/*
|
141 |
+
* Returns the load average usage for 1, 5 or 15 minutes.
|
142 |
+
*/
|
143 |
+
window.loadavg = function(_time){
|
144 |
+
|
145 |
+
if(_time === undefined || (_time !== 5 && _time !== 15) ) _time = 1;
|
146 |
+
|
147 |
+
var loads = _os.loadavg();
|
148 |
+
var v = 0;
|
149 |
+
if(_time == 1) v = loads[0];
|
150 |
+
if(_time == 5) v = loads[1];
|
151 |
+
if(_time == 15) v = loads[2];
|
152 |
+
|
153 |
+
return v;
|
154 |
+
}
|
155 |
+
|
156 |
+
|
157 |
+
window.cpuFree = function(callback){
|
158 |
+
getCPUUsage(callback, true);
|
159 |
+
}
|
160 |
+
|
161 |
+
window.cpuUsage = function(callback){
|
162 |
+
getCPUUsage(callback, false);
|
163 |
+
}
|
164 |
+
|
165 |
+
function getCPUUsage(callback, free){
|
166 |
+
|
167 |
+
var stats1 = getCPUInfo();
|
168 |
+
var startIdle = stats1.idle;
|
169 |
+
var startTotal = stats1.total;
|
170 |
+
|
171 |
+
setTimeout(function() {
|
172 |
+
var stats2 = getCPUInfo();
|
173 |
+
var endIdle = stats2.idle;
|
174 |
+
var endTotal = stats2.total;
|
175 |
+
|
176 |
+
var idle = endIdle - startIdle;
|
177 |
+
var total = endTotal - startTotal;
|
178 |
+
var perc = idle / total;
|
179 |
+
|
180 |
+
if(free === true)
|
181 |
+
callback( perc );
|
182 |
+
else
|
183 |
+
callback( (1 - perc) );
|
184 |
+
|
185 |
+
}, 1000 );
|
186 |
+
}
|
187 |
+
|
188 |
+
function getCPUInfo(callback){
|
189 |
+
var cpus = _os.cpus();
|
190 |
+
|
191 |
+
var user = 0;
|
192 |
+
var nice = 0;
|
193 |
+
var sys = 0;
|
194 |
+
var idle = 0;
|
195 |
+
var irq = 0;
|
196 |
+
var total = 0;
|
197 |
+
|
198 |
+
for(var cpu in cpus){
|
199 |
+
if (!cpus.hasOwnProperty(cpu)) continue;
|
200 |
+
user += cpus[cpu].times.user;
|
201 |
+
nice += cpus[cpu].times.nice;
|
202 |
+
sys += cpus[cpu].times.sys;
|
203 |
+
irq += cpus[cpu].times.irq;
|
204 |
+
idle += cpus[cpu].times.idle;
|
205 |
+
}
|
206 |
+
|
207 |
+
var total = user + nice + sys + idle + irq;
|
208 |
+
|
209 |
+
return {
|
210 |
+
'idle': idle,
|
211 |
+
'total': total
|
212 |
+
};
|
213 |
+
}
|
lib/wavesurfer.js
ADDED
The diff for this file is too large to render.
See raw diff
|
|
lib/xp_error.mp3
ADDED
Binary file (15.8 kB). View file
|
|
main.js
ADDED
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
const PRODUCTION = process.mainModule.filename.includes("resources")
|
3 |
+
const path = PRODUCTION ? "resources/app" : "."
|
4 |
+
|
5 |
+
const remoteMain = require("@electron/remote/main")
|
6 |
+
remoteMain.initialize()
|
7 |
+
|
8 |
+
const fs = require("fs")
|
9 |
+
const {shell, app, BrowserWindow, ipcMain, Menu} = require("electron")
|
10 |
+
const {spawn} = require("child_process")
|
11 |
+
|
12 |
+
let pythonProcess
|
13 |
+
|
14 |
+
if (PRODUCTION) {
|
15 |
+
// pythonProcess = spawn(`${path}/cpython/server.exe`, {stdio: "ignore"})
|
16 |
+
} else {
|
17 |
+
pythonProcess = spawn("python", [`${path}/server.py`], {stdio: "ignore"})
|
18 |
+
}
|
19 |
+
|
20 |
+
let mainWindow
|
21 |
+
let discordClient
|
22 |
+
let discordClientStart = Date.now()
|
23 |
+
|
24 |
+
const createWindow = () => {
|
25 |
+
mainWindow = new BrowserWindow({
|
26 |
+
width: 1200,
|
27 |
+
height: 1000,
|
28 |
+
minHeight: 800,
|
29 |
+
minWidth: 1300,
|
30 |
+
frame: false,
|
31 |
+
webPreferences: {
|
32 |
+
nodeIntegration: true,
|
33 |
+
enableRemoteModule: true,
|
34 |
+
contextIsolation: false
|
35 |
+
},
|
36 |
+
icon: `${__dirname}/assets/x-icon.png`,
|
37 |
+
// show: false,
|
38 |
+
})
|
39 |
+
remoteMain.enable(mainWindow.webContents)
|
40 |
+
|
41 |
+
app.on('browser-window-created', (_, window) => {
|
42 |
+
require("@electron/remote/main").enable(mainWindow.webContents)
|
43 |
+
})
|
44 |
+
|
45 |
+
mainWindow.loadFile("index.html")
|
46 |
+
mainWindow.shell = shell
|
47 |
+
|
48 |
+
mainWindow.on("ready-to-show", () => {
|
49 |
+
mainWindow.show()
|
50 |
+
})
|
51 |
+
|
52 |
+
mainWindow.on("closed", () => mainWindow = null)
|
53 |
+
}
|
54 |
+
|
55 |
+
ipcMain.on("resize", (event, arg) => {
|
56 |
+
mainWindow.setSize(arg.width, arg.height)
|
57 |
+
})
|
58 |
+
ipcMain.on("updatePosition", (event, arg) => {
|
59 |
+
const bounds = mainWindow.getBounds()
|
60 |
+
bounds.x = parseInt(arg.details[0])
|
61 |
+
bounds.y = parseInt(arg.details[1])
|
62 |
+
mainWindow.setBounds(bounds)
|
63 |
+
})
|
64 |
+
ipcMain.on("updateDiscord", (event, arg) => {
|
65 |
+
|
66 |
+
// Disconnect if turned off
|
67 |
+
if (!Object.keys(arg).includes("details")) {
|
68 |
+
if (discordClient) {
|
69 |
+
try {
|
70 |
+
discordClient.disconnect()
|
71 |
+
} catch (e) {}
|
72 |
+
}
|
73 |
+
discordClient = undefined
|
74 |
+
return
|
75 |
+
}
|
76 |
+
|
77 |
+
if (!discordClient) {
|
78 |
+
discordClient = require('discord-rich-presence')('885096702648938546')
|
79 |
+
}
|
80 |
+
|
81 |
+
discordClient.updatePresence({
|
82 |
+
state: 'Generating AI voice acting',
|
83 |
+
details: arg.details,
|
84 |
+
startTimestamp: discordClientStart,
|
85 |
+
largeImageKey: 'xvasynth_512_512',
|
86 |
+
largeImageText: "xVASynth",
|
87 |
+
smallImageKey: 'xvasynth_512_512',
|
88 |
+
smallImageText: "xVASynth",
|
89 |
+
instance: true,
|
90 |
+
})
|
91 |
+
})
|
92 |
+
ipcMain.on("show-context-menu-editor", (event) => {
|
93 |
+
const template = [
|
94 |
+
{
|
95 |
+
label: 'Copy ARPAbet [v3]',
|
96 |
+
click: () => { event.sender.send('context-menu-command', 'context-copy-editor') }
|
97 |
+
},
|
98 |
+
]
|
99 |
+
const menu = Menu.buildFromTemplate(template)
|
100 |
+
menu.popup(BrowserWindow.fromWebContents(event.sender))
|
101 |
+
})
|
102 |
+
|
103 |
+
ipcMain.on("show-context-menu", (event) => {
|
104 |
+
const template = [
|
105 |
+
{
|
106 |
+
label: 'Copy',
|
107 |
+
click: () => { event.sender.send('context-menu-command', 'context-copy') }
|
108 |
+
},
|
109 |
+
{
|
110 |
+
label: 'Paste',
|
111 |
+
click: () => { event.sender.send('context-menu-command', 'context-paste') }
|
112 |
+
},
|
113 |
+
// { type: 'separator' },
|
114 |
+
]
|
115 |
+
const menu = Menu.buildFromTemplate(template)
|
116 |
+
menu.popup(BrowserWindow.fromWebContents(event.sender))
|
117 |
+
})
|
118 |
+
|
119 |
+
// This method will be called when Electron has finished
|
120 |
+
// initialization and is ready to create browser windows.
|
121 |
+
// Some APIs can only be used after this event occurs.
|
122 |
+
app.on("ready", createWindow)
|
123 |
+
|
124 |
+
|
125 |
+
// Quit when all windows are closed.
|
126 |
+
app.on("window-all-closed", () => {
|
127 |
+
// On OS X it is common for applications and their menu bar
|
128 |
+
// to stay active until the user quits explicitly with Cmd + Q
|
129 |
+
if (process.platform !== "darwin") {
|
130 |
+
app.quit()
|
131 |
+
}
|
132 |
+
})
|
133 |
+
|
134 |
+
app.on("activate", () => {
|
135 |
+
// On OS X it"s common to re-create a window in the app when the
|
136 |
+
// dock icon is clicked and there are no other windows open.
|
137 |
+
if (mainWindow === null) {
|
138 |
+
createWindow()
|
139 |
+
}
|
140 |
+
})
|
package.json
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "xVASynth",
|
3 |
+
"version": "1.0.0",
|
4 |
+
"description": "Speech synthesis in the style of voice actors.",
|
5 |
+
"productName": "xVASynth",
|
6 |
+
"main": "main.js",
|
7 |
+
"scripts": {
|
8 |
+
"start": "electron .",
|
9 |
+
"package-windows": "electron-packager ./ --overwrite --prune=true --icon=assets/icon.ico --out=release-builds",
|
10 |
+
"package-linux": "electron-packager ./ --platform=linux --overwrite --prune=true --icon=assets/icon.ico --out=release-builds"
|
11 |
+
},
|
12 |
+
"repository": "https://github.com/DanRuta/xVA-Synth",
|
13 |
+
"author": "DanRuta",
|
14 |
+
"devDependencies": {
|
15 |
+
"electron": "19.0.0",
|
16 |
+
"electron-packager": "^17.1.2"
|
17 |
+
},
|
18 |
+
"dependencies": {
|
19 |
+
"@electron/remote": "^2.0.9",
|
20 |
+
"discord-rich-presence": "0.0.8",
|
21 |
+
"fs-extra": "^9.1.0",
|
22 |
+
"node-fetch": "^2.1.2",
|
23 |
+
"node-nvidia-smi": "^1.0.0",
|
24 |
+
"unzipper": "^0.10.11",
|
25 |
+
"zip-dir": "^2.0.0"
|
26 |
+
}
|
27 |
+
}
|
patreon.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
D0lphin, flyingvelociraptor, Caden Black, Max Loef, LadyVaudry, Thuggysmurf, radbeetle, TomahawkJackson, Solstice_, Bungles, midori95, eldayualien, John Detwiler, Cecell, Wandering Youth, ellia, Retlaw83, Trixie, CHASE MCKELVY, Leif, ionite, Joshua Jones, Jaktt1337, David Keith vun Kannon, Netherworks (Jo-Jo), neci, Rachel Wiles, Imogen, Deer, Linthar, sadfer, Danielle, Hector Medima, Sh1tMagnet, ReaperStoleMyStyle, AshbeeGaming, TCG, Lady Steel, Mikkel Jensen, CookieGalaxy, GrumpyBen, Adrilz, ReyVenom, dog, bourbonicRecluse, ShiningEdge, Dozen9292, manlethamlet, smokeandash, Elias V, EnculerDeTaMere, SKiLLsSoLoN, J, finalfrog, Hound740, Buck, Yael van Dok, ChrisTheStranger, Isabel, Fuzzy Lonesome, Drake, Beto, AceAvenger, bobbigmac, Alexandra Whitton, yic17, Joebobslim, ThatGuyWithaFace, Sergey Trifonov, Zensho, AgitoRivers, beccatoria, valo999, Ne0nFLaSH, Caro Tuts, Jack in the Hinter, Hammerhead96 ., Bewitched, Para, Wht??? Why??, Shadowtigers, PConD, Lulzar, Ryan W, Wyntilda, Gorim, Krazon, Tako-kun, Walt, Katsuki, Ember2528, RetconReality, Hazel Louise Steele, Laura Almeida, Althecow, PatronGuy, squirecrow, cramonty, crash blue, Syrr, David, Hawkbar, John S., Autumn, pimphat, FeralByrd, Comical, Dogmeat114, Dezmar-Sama, Michael Gill, Jacob Garbe, NerfViking, Dinonugget, RedneckJP007, stormalize, Golem, Luckystroker, Hapax, Vahzah Vulom, Tempuc, CAW CAW, stljeffbb, bart, MrJoy, Zoenna, Calvin, Aosana Bluewing, Dan Brookes, CDante, HunterAP, Kadisra, candied_skull, hairahcaz, nairaiwu, Mar, Paraffine, Nawen_Syaka, Amy Parker, Loseron, katiefraggle, Freon, deepbluefrog, myles.app, hanbonzan, Scientist Salari-Ren, Roman Tinkov, zackc1play, An abstract kind of horror, L, Mihu123, Trisket, Aelarr, Flipdark95, Timo Steiner, humocs, Optimist Vamscenes, Patrick VanDusen, praxis22, Rui Orey, Craig Fedynich, FrenchToast, Dorpz, cesm23, BoB, Cutup, Botty Butler, tjn2222, Matthew Warren, Tom Green, Passionate Lobster, Precipitation, Veks, Baki Balcioglu, Fenris, Patrik K., Oddbrother, E.M.A, DrogerKerchva, Camurai, hthek, iggyzee, Moppy, Stee_Muttlet, asbestos my beloved, TrueBlue, something106, woah00z, Sam Darling, JoshuaJSlone, vvvpppmmm, OvrTheTopMan, munchyfly, DarkNemphis, Justin McGough, Billyro, DIY_Rene, kevmasters, Stu, Sasquatch Bill, Inconsistent, Gothic 3 The Age of War, www48, Slothman, mavrodya petrov, ronaldomoon, Kostin Oleksandr Anatoliiovych, Ryan Lippen, Edward Hyde, Echoes, Vape Gwagwa, Kelg Celcs, Kneelers, Meryl Coker, Alan Gonzalez, PTC001, Hector Medima, CinnaMewRoll, Grant Spielbusch, Sean Lyons, Charles Hufnagel, Kirill Akimov, Mister Lyosea, Anthony Crane, Sh1tMagnet
|
plugins.txt
ADDED
File without changes
|
plugins/eg_custom_event/frontendPlugin.js
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use strict"
|
2 |
+
|
3 |
+
|
4 |
+
const postStartFn = (window, data) => {
|
5 |
+
window.appLogger.log("postStartFn")
|
6 |
+
|
7 |
+
const button = document.createElement("button")
|
8 |
+
button.innerHTML = "Custom event"
|
9 |
+
// Style the button with the current game's colour
|
10 |
+
button.style.background = `#${window.currentGame[1]}`
|
11 |
+
|
12 |
+
adv_opts.children[1].appendChild(button)
|
13 |
+
|
14 |
+
button.addEventListener("click", () => {
|
15 |
+
|
16 |
+
fetch(`http://localhost:8008/customEvent`, {
|
17 |
+
method: "Post",
|
18 |
+
body: JSON.stringify({
|
19 |
+
pluginId: "eg_custom_event",
|
20 |
+
data1: "some data",
|
21 |
+
data2: "some more data",
|
22 |
+
// ....
|
23 |
+
})
|
24 |
+
}).then(r=>r.text()).then(() => {
|
25 |
+
window.appLogger.log("custom event finished")
|
26 |
+
console.log("custom event finished")
|
27 |
+
})
|
28 |
+
})
|
29 |
+
|
30 |
+
}
|
31 |
+
|
32 |
+
|
33 |
+
exports.postStartFn = postStartFn
|
plugins/eg_custom_event/main.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
logger = setupData["logger"]
|
2 |
+
|
3 |
+
import time
|
4 |
+
|
5 |
+
def custom_event_fn(data=None):
|
6 |
+
global logger, time
|
7 |
+
print(f'custom_event_fn: {data}')
|
8 |
+
logger.log(f'custom_event_fn: {data}')
|
9 |
+
time.sleep(2)
|
10 |
+
|
plugins/eg_custom_event/plugin.json
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"plugin-name": "Custom events example",
|
3 |
+
"author": "DanRuta",
|
4 |
+
"nexus-link": null,
|
5 |
+
"plugin-version": "1.0",
|
6 |
+
"plugin-short-description": "A demo plugin to show how custom python events work",
|
7 |
+
"min-app-version": "1.0.0",
|
8 |
+
"max-app-version": "1.4.0",
|
9 |
+
"install-requires-restart": false,
|
10 |
+
"uninstall-requires-restart": true,
|
11 |
+
|
12 |
+
"front-end-style-files": [],
|
13 |
+
|
14 |
+
"front-end-hooks": {
|
15 |
+
"start": {
|
16 |
+
"post": {
|
17 |
+
"file": "frontendPlugin.js",
|
18 |
+
"function": "postStartFn"
|
19 |
+
}
|
20 |
+
}
|
21 |
+
},
|
22 |
+
"back-end-hooks": {
|
23 |
+
"custom-event": {
|
24 |
+
"file": "main.py",
|
25 |
+
"function": "custom_event_fn"
|
26 |
+
}
|
27 |
+
}
|
28 |
+
}
|
plugins/test_plugin/custom_event.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
logger = setupData["logger"]
|
2 |
+
|
3 |
+
def custom_event_fn(data=None):
|
4 |
+
global logger
|
5 |
+
logger.log(f'custom_event_fn: {data}')
|