Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f16117e95b | ||
|
|
d499b24315 | ||
|
|
46cc0d1e06 | ||
|
|
a33771e51c | ||
|
|
56c859a8dc | ||
|
|
7a539f65b9 | ||
|
|
446c2f6814 | ||
|
|
ee4477e221 | ||
|
|
d33dde7fc8 | ||
|
|
38683ae9c8 | ||
|
|
3b296bcd3c | ||
|
|
35dcd9710c | ||
|
|
c9e2007526 | ||
|
|
947c4343d7 | ||
|
|
403ffdef3e | ||
|
|
b130ca30d5 | ||
|
|
5c017f8752 | ||
|
|
5a99f8b203 | ||
|
|
f8516e5a14 | ||
|
|
9317203f5a | ||
|
|
3a9fc4554a | ||
|
|
64081dcabd | ||
|
|
4041defb8d | ||
|
|
2bd1a5b43b | ||
|
|
239524ea7d | ||
|
|
08dbeb334d | ||
|
|
8a5db3a687 | ||
|
|
81fea73df4 | ||
|
|
230319c34e | ||
|
|
90af3c2ec5 | ||
|
|
1dc8f807d2 | ||
|
|
7a59e9c20e | ||
|
|
13db476dd0 | ||
|
|
cef6d8cf36 | ||
|
|
be1805e993 | ||
|
|
cb47f763f9 | ||
|
|
990d9b2d9a | ||
|
|
385b5629b6 | ||
|
|
b07697b466 | ||
|
|
9fa61bfe18 | ||
|
|
dac938476a | ||
|
|
9ff23888e0 | ||
|
|
91bf7a220a | ||
|
|
ce0e5c08bf | ||
|
|
698efdcdec | ||
|
|
4959ae36c1 | ||
|
|
284c100860 | ||
|
|
73d9d9450b | ||
|
|
62fd9ce2e5 | ||
|
|
591d86978b | ||
|
|
5576826513 | ||
|
|
e0d753c8a1 | ||
|
|
f8fe5ce167 | ||
|
|
8f5bb216a6 | ||
|
|
c8d2a6fa51 | ||
|
|
7287fede1b | ||
|
|
8e6e18f449 | ||
|
|
fe4110581e | ||
|
|
53e632b70b | ||
|
|
f21f828ab5 | ||
|
|
201f03e4f8 | ||
|
|
d1babe1f6b | ||
|
|
9c5dc99e89 | ||
|
|
0182e8fd01 | ||
|
|
a495c0a239 | ||
|
|
ecb15200dc | ||
|
|
e72a7bf5c9 | ||
|
|
5ce8a6681e | ||
|
|
0b2bfceeb8 | ||
|
|
83bf72a905 | ||
|
|
79c26243d6 | ||
|
|
f5e09de2b3 | ||
|
|
5a18d68d20 | ||
|
|
78b0a51c4e | ||
|
|
c7f9b04826 | ||
|
|
7522fa87d6 | ||
|
|
9a99126f68 | ||
|
|
6052453d26 | ||
|
|
27df5b8038 | ||
|
|
183a37460e | ||
|
|
f15dcd23a7 | ||
|
|
e155d8eb22 | ||
|
|
46a023829d | ||
|
|
239b656633 | ||
|
|
0c6e113aff | ||
|
|
94f90e149e | ||
|
|
cc160820c4 | ||
|
|
af29b1ed75 | ||
|
|
1297a28cb4 | ||
|
|
415b9357b0 | ||
|
|
f46796d121 | ||
|
|
fae005f59d | ||
|
|
66d5090b2e | ||
|
|
65ba0d73ac | ||
|
|
0b232bdb7c | ||
|
|
b51e9cde2f | ||
|
|
1bed9f586e | ||
|
|
e220d70e7e | ||
|
|
1be14414cf | ||
|
|
2819e09063 | ||
|
|
f92ea060b0 | ||
|
|
72a495c108 | ||
|
|
abd4eb8450 | ||
|
|
9309303ccb | ||
|
|
393bff6ab9 | ||
|
|
7c23b043d9 | ||
|
|
c44f17cd38 | ||
|
|
3ed501badc | ||
|
|
231bc21fa7 | ||
|
|
aa794e458b | ||
|
|
72af6c2455 | ||
|
|
a941cffcc0 | ||
|
|
19359f542b | ||
|
|
9d319ecb3a | ||
|
|
f16f6b9498 | ||
|
|
856d67fe5b | ||
|
|
50cafba12f | ||
|
|
3d3047fb93 | ||
|
|
883d8a82d0 | ||
|
|
a23dcf6de7 | ||
|
|
bb55313b9e | ||
|
|
c7cfed60fe | ||
|
|
eef198eae1 | ||
|
|
06b28c1ce7 | ||
|
|
1c09bb985a | ||
|
|
3cb30504e5 | ||
|
|
c0498ed8c9 | ||
|
|
a5550bb254 | ||
|
|
7909327f51 | ||
|
|
03c9d810ff | ||
|
|
e0621a97d6 | ||
|
|
11a116f54c | ||
|
|
82894fe1ff | ||
|
|
44d450a4b7 | ||
|
|
1ed7b860c5 | ||
|
|
87cb4e2754 | ||
|
|
bc3d9a6d4a | ||
|
|
6d50941146 | ||
|
|
2edfd4f517 | ||
|
|
ddbe382e67 | ||
|
|
b8560779d1 | ||
|
|
acde395350 | ||
|
|
8ea93d35cf | ||
|
|
94b4bad972 | ||
|
|
925c73af63 | ||
|
|
73d3bc7495 | ||
|
|
85f204c3f1 | ||
|
|
1a5c10710d | ||
|
|
7e73053fd7 | ||
|
|
190ccd7551 | ||
|
|
bcfcd1e469 | ||
|
|
4556b2b1c7 | ||
|
|
0cec908ada | ||
|
|
10368fc5c0 | ||
|
|
d145da5ca2 | ||
|
|
b6f4731df7 | ||
|
|
cf3381c512 | ||
|
|
24dc04b167 | ||
|
|
cbe02ff8d1 | ||
|
|
37c46a91f3 | ||
|
|
4190269d29 | ||
|
|
9e7a785303 | ||
|
|
fff2fac88d | ||
|
|
18b580db04 | ||
|
|
c13dd9ddd7 | ||
|
|
cea89ddb45 | ||
|
|
eea4bbf4da | ||
|
|
13aff24e0b | ||
|
|
8f871a0601 | ||
|
|
ac24e5fc9a | ||
|
|
f8c9c58d67 | ||
|
|
9a2b4309a5 | ||
|
|
80dd102da8 | ||
|
|
f938f83131 | ||
|
|
6df2b6eeb4 | ||
|
|
af1cab8676 | ||
|
|
adc9bb8cc6 | ||
|
|
4d12520801 | ||
|
|
8bc14d7692 | ||
|
|
80ad19fbe2 | ||
|
|
f07ad31212 | ||
|
|
1e4e18f977 | ||
|
|
37e665c5d8 | ||
|
|
521e60739b | ||
|
|
61f24cf6b0 | ||
|
|
b538564da6 | ||
|
|
5c4726de96 | ||
|
|
2e2714548f | ||
|
|
17b9481fff | ||
|
|
6383b1a134 | ||
|
|
a9433f23f3 | ||
|
|
22f3eb01e5 | ||
|
|
1bd8f6aed9 | ||
|
|
9df1065be0 | ||
|
|
029e5dd1b9 | ||
|
|
4aa4493cc5 | ||
|
|
5a3a20dcba | ||
|
|
73c8c71be9 | ||
|
|
80821081fb | ||
|
|
20361d4a0c | ||
|
|
4d512f4677 | ||
|
|
51e0b21682 | ||
|
|
07053f8076 | ||
|
|
79e8e49d06 | ||
|
|
e7a8fff709 | ||
|
|
398de26248 | ||
|
|
e0527ca8a3 | ||
|
|
c2e1095bb8 | ||
|
|
03148509e4 | ||
|
|
bfe14f3524 | ||
|
|
8536eae276 | ||
|
|
67693f66e0 | ||
|
|
590eb23e78 | ||
|
|
1b4e982955 | ||
|
|
5a81e4818b | ||
|
|
1a5945235d | ||
|
|
88c97ea700 | ||
|
|
d593bfe52e | ||
|
|
0e61d0aa24 | ||
|
|
834f7a4454 | ||
|
|
a8ed41a53b | ||
|
|
2c2c31ef09 | ||
|
|
75a3b68551 | ||
|
|
5d43b23eb7 | ||
|
|
bcd84a4c20 | ||
|
|
a56a2bae5c | ||
|
|
82f4c29c7d | ||
|
|
8e466ee119 | ||
|
|
6ba6549f0f | ||
|
|
630cc61057 | ||
|
|
99f36bd4fa | ||
|
|
d7ac6c401a | ||
|
|
7dd2fd836c | ||
|
|
ac884fa10c | ||
|
|
afc9543f6d | ||
|
|
f4ac29699a | ||
|
|
0c9034c1f3 | ||
|
|
75db4f6340 | ||
|
|
dc2b5dc2fb | ||
|
|
d6fa4103f9 | ||
|
|
bb314fa60d | ||
|
|
fbffe0ac47 | ||
|
|
362683a645 | ||
|
|
ad239e9815 | ||
|
|
a33e05ea01 | ||
|
|
55a9353a55 | ||
|
|
23d813021b | ||
|
|
5ced67d5ee | ||
|
|
b19b6f9f48 | ||
|
|
97ddb39218 | ||
|
|
f4590d5245 | ||
|
|
37941ab114 | ||
|
|
ecb4e6b35e | ||
|
|
2f77da7110 | ||
|
|
33de756042 | ||
|
|
e65187e474 | ||
|
|
7a6061491d | ||
|
|
4ffc975b13 | ||
|
|
d118ef1efb | ||
|
|
d084abc113 | ||
|
|
0c5e1c44c8 | ||
|
|
0c95094892 | ||
|
|
a8311005aa | ||
|
|
471c67dd3a | ||
|
|
76a50cf47f | ||
|
|
75e753d235 | ||
|
|
0bec11ebd2 | ||
|
|
40911c6453 | ||
|
|
31b7456744 | ||
|
|
45af56393e | ||
|
|
1d61d1552d | ||
|
|
f2abd0d5a8 | ||
|
|
9d59eb15c8 | ||
|
|
5e12e4ae24 | ||
|
|
be392cee00 | ||
|
|
18b1d49b72 | ||
|
|
fb6c9f65af | ||
|
|
a280c38a81 | ||
|
|
c2a175f005 | ||
|
|
dbba59ef0b | ||
|
|
f1464ec29d | ||
|
|
9ba7f2e454 | ||
|
|
d66e461f8a | ||
|
|
16a78a90ef | ||
|
|
e534cedc62 | ||
|
|
efb59a071f | ||
|
|
db0171da55 | ||
|
|
372aa493d6 | ||
|
|
aafae8da82 | ||
|
|
2e56cc91f3 | ||
|
|
c6cf8afce3 | ||
|
|
375b2ba31e | ||
|
|
e36c67efe4 | ||
|
|
bb26cb8f28 | ||
|
|
e68ec7d733 | ||
|
|
37be9472ca | ||
|
|
8dbb64c683 | ||
|
|
6f75fe1c28 | ||
|
|
a1b22c4ca1 | ||
|
|
24e6076b40 | ||
|
|
cf3635ea48 | ||
|
|
7f98ef3905 | ||
|
|
3d9d10a456 | ||
|
|
8ae881e3b0 | ||
|
|
57a6b3ba39 | ||
|
|
da7ecfbb6b | ||
|
|
341984315d | ||
|
|
d105ca02a5 | ||
|
|
b7aa3a7c5c | ||
|
|
5cb09eb813 | ||
|
|
c267bb198e | ||
|
|
28cb6ca3f0 | ||
|
|
9a0896f692 | ||
|
|
d76b6c2903 | ||
|
|
c66fcee5e1 | ||
|
|
84db50fc18 | ||
|
|
e5afa958cb | ||
|
|
4775d0db34 | ||
|
|
0a1a81a962 | ||
|
|
7e6a972198 | ||
|
|
2bc0660770 | ||
|
|
4c016a49a3 | ||
|
|
cf625469f5 | ||
|
|
a1452bf0b7 | ||
|
|
458b0c38f2 | ||
|
|
2a40143eca | ||
|
|
61f36d5b0d | ||
|
|
cc5a665ad6 | ||
|
|
633e14d153 | ||
|
|
9a177c5a9c | ||
|
|
a8ec5e19ce | ||
|
|
a40c7a7729 | ||
|
|
c1c7b8cf65 | ||
|
|
df764f71ef | ||
|
|
1f0cf09822 | ||
|
|
526e71a46e | ||
|
|
569b4a7403 | ||
|
|
4494ef24c0 | ||
|
|
8b373d50dc | ||
|
|
cf93ef1de0 | ||
|
|
98d4d4fd25 | ||
|
|
c6b7b39218 | ||
|
|
2560c29771 | ||
|
|
0be7eae436 | ||
|
|
6b43c40a8c | ||
|
|
94c9257dee | ||
|
|
eaf38309a2 | ||
|
|
44bd45a624 | ||
|
|
9047dcb139 | ||
|
|
7e2d3455d2 | ||
|
|
d44d721d69 | ||
|
|
bf68a89c11 | ||
|
|
5257497b57 | ||
|
|
6fac1aa5f9 | ||
|
|
4c97c83d25 | ||
|
|
61e6c2c105 | ||
|
|
0755aa033b | ||
|
|
fe44f3608a | ||
|
|
f1c53866ba | ||
|
|
a9fc8699d8 | ||
|
|
e63b882e52 | ||
|
|
21064be53a | ||
|
|
c03ab37dd2 | ||
|
|
b03e08405e | ||
|
|
c31e1f68b5 | ||
|
|
d542e6ba14 | ||
|
|
0f26ca4bbf | ||
|
|
16152fe135 | ||
|
|
f03f359f41 | ||
|
|
e40470fbea | ||
|
|
d5c20262a8 | ||
|
|
6a992cb8f4 | ||
|
|
e347f15046 | ||
|
|
41a059e517 | ||
|
|
797a4b996a | ||
|
|
e081442bf3 | ||
|
|
bccd7ee971 | ||
|
|
4cba3af3b5 | ||
|
|
f016c6cd90 | ||
|
|
da767a5472 | ||
|
|
633113f909 | ||
|
|
45e3ed9237 | ||
|
|
a97ad658c1 | ||
|
|
062483df1a | ||
|
|
92ce5e24bc | ||
|
|
b32c68df29 | ||
|
|
84312bb6b4 | ||
|
|
6f4bec7ab2 | ||
|
|
6f75ab9f60 | ||
|
|
50f335255e | ||
|
|
037d774f2c | ||
|
|
465afd57c8 | ||
|
|
af97384003 | ||
|
|
6aa7a182d1 | ||
|
|
279dd54694 | ||
|
|
7fc89885eb | ||
|
|
646c615ccf | ||
|
|
2a87d2d662 | ||
|
|
cf9b736da3 | ||
|
|
411b93f55c | ||
|
|
bc0ebc03a6 | ||
|
|
8b016533c4 | ||
|
|
07ec2325cc | ||
|
|
3029319c68 | ||
|
|
475546133b | ||
|
|
af0bb11c4e | ||
|
|
6a840a591c | ||
|
|
e86109aa14 | ||
|
|
1d81376097 | ||
|
|
eb4dabbbf4 | ||
|
|
b1bc5b1825 | ||
|
|
c2077f03ca | ||
|
|
073aa403f2 | ||
|
|
4a10a6659b | ||
|
|
ff6d0ae695 | ||
|
|
f3ea73dec9 | ||
|
|
493a603542 | ||
|
|
8538b96cd2 | ||
|
|
ba76cb24ce | ||
|
|
d74c1ecd60 | ||
|
|
1631caf1e8 | ||
|
|
e2788cac1e | ||
|
|
762ca1e427 | ||
|
|
f12e6e80a7 | ||
|
|
9fd928f6e7 | ||
|
|
84c19ed5a9 | ||
|
|
862fe96003 | ||
|
|
98a74e9625 | ||
|
|
0dbff4786f | ||
|
|
e10f713283 | ||
|
|
e65b262116 | ||
|
|
5cc57254b7 | ||
|
|
8b9a0b8b45 | ||
|
|
51b5a95563 | ||
|
|
9e45aded81 | ||
|
|
8c71ce67fe | ||
|
|
38b06e6f63 | ||
|
|
f3fa28f84b | ||
|
|
cbe8d813a9 | ||
|
|
3c123d3678 | ||
|
|
fc22fb8add | ||
|
|
6ee7362b46 | ||
|
|
a518f86eab | ||
|
|
43ec92eb07 | ||
|
|
ddf965aee3 | ||
|
|
f32feedc1f | ||
|
|
af4c63f1ee | ||
|
|
21cd171719 | ||
|
|
856c0c1baa | ||
|
|
09977956f5 | ||
|
|
2c7feb31e5 | ||
|
|
cead53a808 | ||
|
|
e7996ebc7a | ||
|
|
6815ba0f2e | ||
|
|
51fa4ce9e9 | ||
|
|
043648e24a | ||
|
|
b76ff028f8 | ||
|
|
14214cc141 | ||
|
|
872638a198 | ||
|
|
5d2720c118 | ||
|
|
460216819a | ||
|
|
412d1442a6 | ||
|
|
5a1dd31ab6 | ||
|
|
ce84f153ca | ||
|
|
ab3570c899 | ||
|
|
42f08cd19c | ||
|
|
21fb1b58d9 | ||
|
|
d4741edf94 | ||
|
|
e3c7144272 | ||
|
|
27a4fe2338 | ||
|
|
a0913db3c9 | ||
|
|
645c0d3a67 | ||
|
|
e3878b595b | ||
|
|
825e4a7d12 | ||
|
|
6c20f45154 | ||
|
|
97cda27c7e | ||
|
|
a075495a0a | ||
|
|
b205657be3 | ||
|
|
483d6c8e79 | ||
|
|
9bd4665e6b | ||
|
|
62e1ab7e89 | ||
|
|
740c54db1c | ||
|
|
e188467730 | ||
|
|
743f23240e | ||
|
|
66dadbca25 | ||
|
|
8125b46dbe | ||
|
|
d4bd46f203 | ||
|
|
f13d6d336b | ||
|
|
1fa2f2e110 | ||
|
|
9ad4005e52 | ||
|
|
6d6b92d4b2 | ||
|
|
bf5d913cae | ||
|
|
f52ccd3931 | ||
|
|
37e7c8002d | ||
|
|
ad7382ee68 | ||
|
|
2428bfe357 | ||
|
|
74f3acc51f | ||
|
|
90a98f140b | ||
|
|
885311ecd8 | ||
|
|
c2952e9758 | ||
|
|
7e6e18c6d9 | ||
|
|
47eafafeda | ||
|
|
5edaab1356 | ||
|
|
7bde34d2ce | ||
|
|
0aa4638f68 | ||
|
|
22dd9c741c | ||
|
|
e000b8bb87 | ||
|
|
2d26f309df | ||
|
|
22d3e53a28 | ||
|
|
0ea646dbf3 | ||
|
|
282e3ce84c | ||
|
|
772afd47a1 | ||
|
|
1612835c5d | ||
|
|
ea551d3a84 | ||
|
|
47001203cd | ||
|
|
27ff5a12b0 | ||
|
|
0a89407e4d | ||
|
|
a579c5e6d2 | ||
|
|
5a21d14672 | ||
|
|
f4f5f82bb0 | ||
|
|
7b03804f9e | ||
|
|
f46493e6d8 | ||
|
|
2a64f6ad2b | ||
|
|
3368f8b016 | ||
|
|
121585ae84 | ||
|
|
3e6c11740d | ||
|
|
6b111494b3 | ||
|
|
b376a27da2 | ||
|
|
eeec4de703 | ||
|
|
49f98033b3 | ||
|
|
c1470930f4 | ||
|
|
1dec4f027b | ||
|
|
a9ef8edcb4 | ||
|
|
299238be7d | ||
|
|
29c7ea1526 | ||
|
|
e974e14a10 | ||
|
|
59c654bacd | ||
|
|
969b4e3f46 | ||
|
|
86c64fc9ba | ||
|
|
b8c0db6f56 | ||
|
|
57982cb810 | ||
|
|
6387007f56 | ||
|
|
cdf8d695e2 | ||
|
|
833bf3da4f | ||
|
|
f68086db0b | ||
|
|
1f7842ef5a | ||
|
|
9bb0d1fa58 | ||
|
|
015b9033ec | ||
|
|
7f261bb5e9 | ||
|
|
00b93cfe1b | ||
|
|
c0d01ee931 | ||
|
|
d0ceb2bb65 | ||
|
|
3231e61d56 | ||
|
|
32f3b721c2 | ||
|
|
8c588939d2 | ||
|
|
ff1077f282 | ||
|
|
36d0e92ed9 | ||
|
|
8dfb84d7e2 | ||
|
|
4caecdc5eb | ||
|
|
40f2fbf54e | ||
|
|
ab37dd8849 | ||
|
|
03b02e0e4c | ||
|
|
351f46c7b5 | ||
|
|
71ba584e7c | ||
|
|
b3f53e93cc | ||
|
|
0ed3cb75b5 | ||
|
|
c29de18ad4 | ||
|
|
ec1d0e8c5d | ||
|
|
12bf008b3f | ||
|
|
ecdc10ece5 | ||
|
|
360f70cb0a | ||
|
|
51c91c3d70 | ||
|
|
81a9f4c847 | ||
|
|
cdce7511dc | ||
|
|
6d4b6d336b | ||
|
|
6c4a9b19c7 | ||
|
|
82ecf3eecc | ||
|
|
d750959f52 | ||
|
|
bd74de53da | ||
|
|
fcec9d8f5b | ||
|
|
85e5e6ab86 | ||
|
|
d15931bdda | ||
|
|
abc9cf1fb7 | ||
|
|
2b60f5b0da | ||
|
|
e524063ec2 | ||
|
|
d93906a75f | ||
|
|
ad57deed20 | ||
|
|
d0866e3c6c | ||
|
|
af907a299d | ||
|
|
7928af1bb7 | ||
|
|
4d837a0219 | ||
|
|
e50817a234 | ||
|
|
c3bb58d2af | ||
|
|
c17959f142 | ||
|
|
def2d51c5f | ||
|
|
6166d4f751 | ||
|
|
cd164ae258 | ||
|
|
f089e3e85b | ||
|
|
d008c7ab0a | ||
|
|
a199adfbd6 | ||
|
|
61998b4d39 | ||
|
|
55799b2de8 | ||
|
|
ece55ffe06 | ||
|
|
dbc2582fa6 | ||
|
|
535c9b652a | ||
|
|
6c28d58b4a | ||
|
|
c9a222932c | ||
|
|
7010369966 | ||
|
|
525a337835 | ||
|
|
2bd1ef843e | ||
|
|
a9d95fc563 | ||
|
|
1a37ae4ba5 | ||
|
|
8c6fc4853d | ||
|
|
f34ebbd675 | ||
|
|
64cffcdf80 | ||
|
|
2731a559f4 | ||
|
|
a91a83268f | ||
|
|
5d406fbbfb | ||
|
|
cefd1e045b | ||
|
|
bec4e905d3 | ||
|
|
f5e99a5e72 | ||
|
|
631ea6dd8a | ||
|
|
08a04ba506 | ||
|
|
c78927b80a | ||
|
|
04e08c58b8 | ||
|
|
893747c9a9 | ||
|
|
d52dcf86d5 | ||
|
|
ad0eab1224 | ||
|
|
62d1cc05d3 | ||
|
|
b8292da92f | ||
|
|
180adb45dd | ||
|
|
5c0f08d49e | ||
|
|
571872dbe2 | ||
|
|
467bfbea55 | ||
|
|
5a3cbb6366 | ||
|
|
2351e418fd | ||
|
|
b987ff38a7 | ||
|
|
43899028e3 | ||
|
|
2c1ca8b2a3 | ||
|
|
139162c4e6 | ||
|
|
7da78ec045 | ||
|
|
11c70c7432 | ||
|
|
c2822edb1b | ||
|
|
cba7ed7b88 | ||
|
|
471a26f2a4 | ||
|
|
34d1b81ae4 | ||
|
|
f1b4d4b8b6 | ||
|
|
c05237143b | ||
|
|
c5846b832a | ||
|
|
7a38730c56 | ||
|
|
0b1e6f8711 | ||
|
|
8c38e925dd | ||
|
|
df70481934 | ||
|
|
e43c58a56e | ||
|
|
8dc9304a4c | ||
|
|
102424669b | ||
|
|
5210d075c1 | ||
|
|
30db974439 | ||
|
|
c2a558110b | ||
|
|
83ac18b83f | ||
|
|
6e36415746 | ||
|
|
fe7ffb535c | ||
|
|
80b7fccb50 | ||
|
|
2790ee9860 | ||
|
|
74ce2584cc | ||
|
|
8dd11541b4 | ||
|
|
d1317b49f0 | ||
|
|
a50d5a5028 | ||
|
|
33264554cd | ||
|
|
4a116e359a | ||
|
|
36bfb17eda | ||
|
|
697fb098c3 | ||
|
|
51a8e93df7 | ||
|
|
257fa59e70 | ||
|
|
7dbad85fc3 | ||
|
|
510894c793 | ||
|
|
a85058a3ac | ||
|
|
e54c7e4362 | ||
|
|
1f2ad303a0 | ||
|
|
d4a04096c8 | ||
|
|
dd45008e88 | ||
|
|
e9d9f54de6 | ||
|
|
3230397a82 | ||
|
|
840889ac9a | ||
|
|
d3e061b127 | ||
|
|
a428f67a2e | ||
|
|
b223e7b247 | ||
|
|
9296843508 | ||
|
|
f0c9e3e6bb | ||
|
|
22de1bf0c6 | ||
|
|
621f5434a7 | ||
|
|
81c8354cf8 | ||
|
|
3bc55a91e5 | ||
|
|
fc857f464c | ||
|
|
50aa6de96c | ||
|
|
10299194f8 | ||
|
|
d80d1ace3c | ||
|
|
f19f4e9a29 | ||
|
|
7e4ceef0e8 | ||
|
|
c1bc1f26cd | ||
|
|
70d385133d | ||
|
|
13b2eb5fe3 | ||
|
|
ff4b9d9e99 | ||
|
|
a22830f700 | ||
|
|
374c794fa9 | ||
|
|
d2adbd9c1e | ||
|
|
1dc4b8bf50 | ||
|
|
0eba72fc47 | ||
|
|
f2c87dcdab | ||
|
|
4a727ce14a | ||
|
|
6e15d43e63 | ||
|
|
dedc13b3b6 | ||
|
|
7cb206e531 | ||
|
|
4f80b39cd0 | ||
|
|
d5fa6bb46d | ||
|
|
332582baea | ||
|
|
2bd7ebc514 | ||
|
|
e5acc8bdbd | ||
|
|
7718bfd3ea | ||
|
|
002c5ad9db | ||
|
|
2c14847708 | ||
|
|
3647d69007 | ||
|
|
56a7ea1d8f | ||
|
|
863a7cd841 | ||
|
|
2e1709ab58 | ||
|
|
9c4efdc135 | ||
|
|
28675c976c | ||
|
|
afe3b9fe0b | ||
|
|
2e4d52170d | ||
|
|
0e2cc25571 | ||
|
|
abaa56e8f4 | ||
|
|
d0dc9f3212 | ||
|
|
67dcc411ad | ||
|
|
b11a087dc8 | ||
|
|
e068115eef | ||
|
|
0ec580539a | ||
|
|
e9feae12ad | ||
|
|
e183e3a7bb | ||
|
|
8a2b669da9 | ||
|
|
67d2b1c5ea | ||
|
|
f3b44424ef | ||
|
|
3b5c4b9b68 | ||
|
|
e452a926f2 | ||
|
|
b65291d44d | ||
|
|
b28ecaf43e |
No files matched your search
@@ -0,0 +1 @@
|
||||
ko_fi: erew123
|
||||
@@ -1,13 +1,14 @@
|
||||
---
|
||||
name: Bug report/Support request
|
||||
name: Support request OR Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
🔴 If you have installed AllTalk in a custom Python environment, I will only be able to provide limited assistance/support. AllTalk draws on a variety of scripts and libraries that are not written or managed by myself, and they may fail, error or give strange results in custom built python environments.
|
||||
|
||||
Please generate a diagnostics report and upload the "diagnostics.log".
|
||||
🔴 Please generate a diagnostics report and upload the "diagnostics.log" as this helps me understand your configuration.
|
||||
|
||||
https://github.com/erew123/alltalk_tts/tree/main?#-how-to-make-a-diagnostics-report-file
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Support request OR Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
🔴 If you have installed AllTalk in a custom Python environment, I will only be able to provide limited assistance/support. AllTalk draws on a variety of scripts and libraries that are not written or managed by myself, and they may fail, error or give strange results in custom built python environments.
|
||||
|
||||
🔴 Please generate a diagnostics report and upload the "diagnostics.log" as this helps me understand your configuration.
|
||||
|
||||
https://github.com/erew123/alltalk_tts/tree/main?#-how-to-make-a-diagnostics-report-file
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behaviour:
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Text/logs**
|
||||
If applicable, copy/paste in your logs here from the console.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
AllTalk was updated: [approx. date]
|
||||
Custom Python environment: [yes/no give details if yes]
|
||||
Text-generation-webUI was updated: [approx. date]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,73 @@
|
||||
name: 'Setting up docker'
|
||||
|
||||
inputs:
|
||||
DOCKERHUB_USERNAME:
|
||||
description: "Docker hub username"
|
||||
required: true
|
||||
DOCKERHUB_REPO_NAME:
|
||||
description: "Docker hub repo name"
|
||||
required: true
|
||||
DOCKERHUB_TOKEN:
|
||||
description: "Docker hub token"
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@v1.3.1
|
||||
with:
|
||||
tool-cache: true
|
||||
swap-storage: true
|
||||
large-packages: true
|
||||
- name: Read variables
|
||||
shell: bash
|
||||
run: |
|
||||
source ./docker/variables.sh
|
||||
for key in "${!ALLTALK_VARS[@]}"
|
||||
do
|
||||
echo "${key}=${ALLTALK_VARS[${key}]}"
|
||||
echo "${key}=${ALLTALK_VARS[${key}]}" >> $GITHUB_ENV
|
||||
done
|
||||
|
||||
- name: Extract version from Git Tag
|
||||
shell: bash {0}
|
||||
run: |
|
||||
# Use the git tag if existing, otherwise 'latest':
|
||||
DOCKER_TAG=$( echo "${{ github.ref }}" | grep -q tags | cut -d'/' -f 3 )
|
||||
if [ -z "${DOCKER_TAG}" ]; then
|
||||
DOCKER_TAG="latest"
|
||||
fi
|
||||
echo "Using DOCKER_TAG=${DOCKER_TAG}"
|
||||
echo "DOCKER_TAG=${DOCKER_TAG}" >> $GITHUB_ENV
|
||||
|
||||
- name: Extract Docker Hub User and Repo Name
|
||||
shell: bash
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ inputs.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_REPO_NAME: ${{ inputs.DOCKERHUB_REPO_NAME }}
|
||||
run: |
|
||||
if [ -n "${DOCKERHUB_USERNAME}" ]; then
|
||||
echo "Using Github repository variable 'DOCKERHUB_USERNAME' as Docker hub username with value: '${DOCKERHUB_USERNAME}'"
|
||||
echo "DOCKERHUB_USERNAME=${DOCKERHUB_USERNAME}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Using Github repository name as Docker hub username"
|
||||
echo "DOCKERHUB_USERNAME=$(echo ${{ github.repository }} | cut -d'/' -f 1)" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
if [ -n "${DOCKERHUB_REPO_NAME}" ]; then
|
||||
echo "Using Github repository variable 'DOCKERHUB_REPO_NAME' as Docker hub repo name with value: '${DOCKERHUB_REPO_NAME}'"
|
||||
echo "DOCKERHUB_REPO_NAME=${DOCKERHUB_REPO_NAME}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Using Github repository name as Docker hub repo name"
|
||||
echo "DOCKERHUB_REPO_NAME=$(echo ${{ github.repository }} | cut -d'/' -f 2)" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ inputs.DOCKERHUB_USERNAME }}
|
||||
password: ${{ inputs.DOCKERHUB_TOKEN }}
|
||||
@@ -0,0 +1,124 @@
|
||||
name: Publish Docker v2
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ published ]
|
||||
push:
|
||||
branches:
|
||||
- 'alltalkbeta'
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
# Limit the concurrency of entire workflow runs for a specific branch:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build_base_environment_docker_image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup docker
|
||||
uses: ./.github/actions/docker-setup
|
||||
with:
|
||||
DOCKERHUB_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_REPO_NAME: ${{ vars.DOCKERHUB_REPO_NAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPO_NAME }}_environment
|
||||
tags: |
|
||||
# Use sematic versioning on push tag event:
|
||||
type=semver,pattern={{version}}
|
||||
# set latest tag for main and alltalkbeta branch:
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') || github.ref == format('refs/heads/{0}', 'alltalkbeta') }}
|
||||
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: changes
|
||||
with:
|
||||
base: 'alltalkbeta'
|
||||
filters: |
|
||||
docker-base:
|
||||
- 'docker/variables.sh'
|
||||
- 'docker/base/Dockerfile'
|
||||
docker-workflow:
|
||||
- '.github/workflows/publish-docker*.yml'
|
||||
|
||||
- name: Build and push base Docker image
|
||||
if: |
|
||||
steps.changes.outputs.docker-base == 'true' ||
|
||||
steps.changes.outputs.docker-workflow == 'true' ||
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'push' && contains(github.ref, 'refs/tags/'))
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./docker/base
|
||||
file: ./docker/base/Dockerfile
|
||||
platforms: "linux/amd64"
|
||||
push: true
|
||||
build-args: |
|
||||
CUDA_VERSION=${{ env.CUDA_VERSION }}
|
||||
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ env.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPO_NAME }}_environment:buildcache
|
||||
cache-to: type=registry,ref=${{ env.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPO_NAME }}_environment:buildcache,mode=max
|
||||
|
||||
build_main_docker_image:
|
||||
needs: build_base_environment_docker_image
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
tts: [ xtts, vits, piper ]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup docker
|
||||
uses: ./.github/actions/docker-setup
|
||||
with:
|
||||
DOCKERHUB_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_REPO_NAME: ${{ vars.DOCKERHUB_REPO_NAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPO_NAME }}
|
||||
flavor: |
|
||||
suffix=-${{ matrix.tts }},onlatest=true
|
||||
tags: |
|
||||
# Use sematic versioning on push tag event:
|
||||
type=semver,pattern={{version}}
|
||||
# set latest tag for main and alltalkbeta branch:
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') || github.ref == format('refs/heads/{0}', 'alltalkbeta') }}
|
||||
|
||||
- name: Build and push base Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: "linux/amd64"
|
||||
push: true
|
||||
build-args: |
|
||||
TTS_MODEL=${{ matrix.tts }}
|
||||
ALLTALK_DIR=${{ env.ALLTALK_DIR }}
|
||||
DEEPSPEED_VERSION=${{ env.DEEPSPEED_VERSION }}
|
||||
DOCKER_TAG=${{ env.DOCKER_TAG }}
|
||||
DOCKER_REPOSITORY=${{ env.DOCKERHUB_USERNAME }}/
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ env.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPO_NAME }}:buildcache-${{ matrix.tts }}
|
||||
cache-to: type=registry,ref=${{ env.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPO_NAME }}:buildcache-${{ matrix.tts }},mode=max
|
||||
@@ -0,0 +1,53 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
||||
|
||||
name: Python CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.11.9
|
||||
uses: actions/setup-python@v3
|
||||
if: ${{ hashFiles('./test') != '' }}
|
||||
with:
|
||||
python-version: "3.11.9"
|
||||
- name: Install dependencies
|
||||
if: ${{ hashFiles('./test') != '' }}
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
if [ -f system/requirements/requirements_unit_test.txt ]; then
|
||||
pip install -r system/requirements/requirements_unit_test.txt;
|
||||
fi
|
||||
- name: Test with pytest
|
||||
if: ${{ hashFiles('./test') != '' }}
|
||||
run: |
|
||||
pytest test --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov-report=xml --cov-report=html
|
||||
- name: Surface failing tests
|
||||
if: always()
|
||||
uses: pmeier/pytest-results-action@v0.6.0
|
||||
with:
|
||||
# A list of JUnit XML files, directories containing the former, and wildcard
|
||||
# patterns to process.
|
||||
# See @actions/glob for supported patterns.
|
||||
path: junit/test-results.xml
|
||||
|
||||
# (Optional) Add a summary of the results at the top of the report
|
||||
summary: true
|
||||
|
||||
# (Optional) Select which results should be included in the report.
|
||||
# Follows the same syntax as `pytest -r`
|
||||
display-options: fEX
|
||||
|
||||
# (Optional) Fail the workflow if no JUnit XML was found.
|
||||
fail-on-empty: false
|
||||
|
||||
# (Optional) Title of the test results section in the workflow summary
|
||||
title: Test results
|
||||
@@ -1,8 +1,176 @@
|
||||
voices/*.*
|
||||
models/*.*
|
||||
outputs/*.*
|
||||
finetune/*.*
|
||||
config.json
|
||||
voices/*
|
||||
models/*
|
||||
outputs/*
|
||||
finetune/*
|
||||
alltalk_environment/*
|
||||
system/config/at_github_sha.json
|
||||
system/tts_engines/tts_engines.json
|
||||
confignew.json
|
||||
models.json
|
||||
diagnostics.log
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
|
||||
.idea/*
|
||||
|
||||
# Docker build
|
||||
docker/deepspeed/build
|
||||
@@ -0,0 +1,115 @@
|
||||
# Docker
|
||||
The Docker image currently works on Windows and Linux supporting NVIDIA GPUs.
|
||||
|
||||
## System requirements
|
||||
- AMD compatible Windows or Linux system
|
||||
- CUDA 12.8
|
||||
- Nvidia Docker Container Toolkit
|
||||
- At least 25GB of free disk space
|
||||
|
||||
## Quickstart
|
||||
Use `docker-start.sh` to pull the latest docker image using the XTTS model from Docker Hub and
|
||||
visit `http://localhost:7851/`. See instructions below for passing more arguments to the start script.
|
||||
|
||||
## Installing Prerequisites
|
||||
### Docker for Linux
|
||||
#### Ubuntu Specific Setup for GPUs
|
||||
1. Make sure the latest nvidia drivers are installed: `sudo ubuntu-drivers install`
|
||||
1. Install Docker your preferred way. One way to do it is to follow the official documentation [here](https://docs.docker.com/engine/install/ubuntu/#uninstall-old-versions).
|
||||
- Start by uninstalling the old versions
|
||||
- Follow the "apt" repository installation method
|
||||
- Check that everything is working with the "hello-world" container
|
||||
1. If, when launching the docker contain, you have an error message saying that the GPU cannot be used, you might have to install [Nvidia Docker Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html).
|
||||
- Install with the "apt" method
|
||||
- Run the docker configuration command
|
||||
```sudo nvidia-ctk runtime configure --runtime=docker```
|
||||
- Restart docker
|
||||
|
||||
### Docker for Windows (WSL2)
|
||||
#### Windows Specific Setup for GPUs
|
||||
> Make sure your Nvidia drivers are up to date: https://www.nvidia.com/download/index.aspx
|
||||
1. Install WSL2 in PowerShell with `wsl --install` and restart
|
||||
2. Open PowerShell, type and enter ```ubuntu```. It should now load you into wsl2
|
||||
3. Remove the original nvidia cache key: `sudo apt-key del 7fa2af80`
|
||||
4. Download CUDA toolkit keyring: `wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-keyring_1.1-1_all.deb`
|
||||
5. Install keyring: `sudo dpkg -i cuda-keyring_1.1-1_all.deb`
|
||||
6. Update package list: `sudo apt-get update`
|
||||
7. Install CUDA toolkit: `sudo apt-get -y install cuda-toolkit-12-8`
|
||||
8. Install Docker Desktop using WSL2 as the backend
|
||||
9. Restart
|
||||
10. If you wish to monitor the terminal remotely via SSH, follow [this guide](https://www.hanselman.com/blog/how-to-ssh-into-wsl2-on-windows-10-from-an-external-machine).
|
||||
11. Open PowerShell, type ```ubuntu```, [then follow below](#quickstart)
|
||||
|
||||
## Arguments for starting the Docker container
|
||||
To make it as simple as possible to use Docker, the script `docker-start.sh` can be used. It provides the
|
||||
following optional arguments:
|
||||
|
||||
- `--config` lets you choose a config JSON file which can subset of `confignew.json`. This allows you to change only
|
||||
few values and leave the rest as defined in the default `confignew.json` file.
|
||||
- Example: `docker-start.sh --config /my/config/file.json` with content `{"branding": "My Brand "}` will just change
|
||||
the branding in `confignew.json`.
|
||||
- `--voices` lets you add voices for the TTS engine in WAV format. You have to specify the folder containing all
|
||||
voice files.
|
||||
- Example: `docker-start.sh --voices /my/voices/dir`
|
||||
- `--rvc-voices` similar to voices, this option lets you pick the folder containing the [RVC models](https://github.com/erew123/alltalk_tts/wiki/RVC-(Retrieval%E2%80%90based-Voice-Conversion)).
|
||||
- Example: `docker-start.sh --rvc_voices /my/rvc/voices/dir`
|
||||
- `--no-ui` allows you to not expose port 7852 for the gradio interface and sets `launch_gradio` and `gradio_interface` to `false`.
|
||||
- `--auto-cleanup` periodically deletes all generated WAV files that are older than 1min.
|
||||
- `--with-multi-engine-manager` enables the use of the [multi engine manager (MEM)](https://github.com/erew123/alltalk_tts/wiki/Multi-Engine-Manager)
|
||||
which allows for more parallel requests. By default, one TTS engine is started. Optionally, you can pass the
|
||||
file path of a JSON file which can be a subset of `mem_config.json` with more fine-grained configuration options.
|
||||
- Example: `docker-start.sh --with-multi_engine_manager` to use MEM with default settings or
|
||||
`docker-start.sh --with-multi_engine_manager /my/config/file.json` to pass a JSON file containing more settings.
|
||||
- `--tag` allows to choose the docker tag of the image to run. Defaults to `latest-xtts`.
|
||||
- Example: `docker-start.sh --tag mytag`
|
||||
- `--docker-repository` allows to choose another Docker repository for pulling the image from. Use an empty
|
||||
string for the local repo.
|
||||
- Since the above commands only address the most important options, you might pass additional arbitrary docker arguments
|
||||
to the `docker-start.sh`.
|
||||
|
||||
Of course, like for any other Docker image, you can also directly use `docker run` directly and use
|
||||
[docker-start.sh](https://github.com/erew123/alltalk_tts/blob/alltalkbeta/docker-start.sh) as an inspiration.
|
||||
|
||||
### Using environment variables for setting config values
|
||||
Instead of using config JSON files, there are config settings that can be directly set using environment (ENV) variables
|
||||
when starting the application. This is also what is used internally in `docker-start.sh`.
|
||||
|
||||
Since `docker-start.sh` allows to pass arbitrary params, it can also be used to set env variables, e.g:
|
||||
|
||||
```
|
||||
./docker-start.sh -e ALLTALK_BRANDING="My Brand " -e ALLTALK_API_PORT_NUMBER=9876
|
||||
```
|
||||
|
||||
or when using Docker directly:
|
||||
```
|
||||
docker run -e ALLTALK_BRANDING="My Brand " -e ALLTALK_API_PORT_NUMBER=9876 ...
|
||||
```
|
||||
|
||||
Consult [docker_default_confignew.json](https://github.com/erew123/alltalk_tts/blob/alltalkbeta/docker_default_confignew.json)
|
||||
and [docker_default_mem_config.json](https://github.com/erew123/alltalk_tts/blob/alltalkbeta/docker_default_mem_config.json)
|
||||
for a list of all available env variables.
|
||||
|
||||
|
||||
## Building your own images (advanced)
|
||||
> Be aware that building the images requires good hardware and bandwidth. Expect this process to require >
|
||||
> 40 GB of disk space and > 30min execution time depending on your hardware.
|
||||
|
||||
Under normal circumstances, there should be no need to build the Docker images locally. However, if needed for some
|
||||
reason, you may want to use `docker-build.sh` with the following arguments:
|
||||
|
||||
- `--tts_model` allows to choose the TTS model that is used by default. Valid values are `piper`, `vits`, `xtts`. Defaults to `xtts`.
|
||||
- Example: `docker-build.sh --tts_model piper`
|
||||
- `--tag` allows to choose the docker tag. Defaults to `latest-xtts`.
|
||||
- Example: `docker-build.sh --tag mytag`
|
||||
- `--docker-repository` allows to choose another Docker repository for tagging the image from. Use an empty
|
||||
string for the local repo.
|
||||
- `--local-deepspeed-build` should only be used in rare cases where DeepSpeed needs to be rebuilt. Defaults to `false`.
|
||||
- `--clean` allows remove existing dependency build like conda environment or DeepSpeed. Defaults to `false`.
|
||||
- Example: `docker-build.sh --clean`
|
||||
|
||||
|
||||
# Older images
|
||||
- CUDA 12.6:
|
||||
- XTTS Dockerhub hash: sha256:fd4741714af0c82e4e7a66bf9ae47c98f3e51ebefdb3af5795b47d41ca5fb159
|
||||
- VITS Dockerhub hash: sha256:9f411e8fdb997486bc217fcd316bf6b163c39736655ea3400028ad4fcc5c60f9
|
||||
- Piper: Dockerhub hash: sha256:6ae2520051bb00a0356dc4ed80a33bf6784cb567b59759af46046ddcf356b765
|
||||
@@ -1,285 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <torch/extension.h>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include "quantization.h"
|
||||
|
||||
template <typename T>
|
||||
at::Tensor ds_quantize(at::Tensor& vals, int groups, int bits)
|
||||
{
|
||||
auto t_size = vals.sizes();
|
||||
int size = 1;
|
||||
for (auto dim : t_size) size *= dim;
|
||||
|
||||
if ((((size / groups) - 1) / 4096 + 1) <= 256) {
|
||||
launch_fake_quantize_kernel(
|
||||
(T*)vals.data_ptr(), size, groups, bits, at::cuda::getCurrentCUDAStream());
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
at::Tensor ds_sr_quantize(at::Tensor& vals, int groups, int bits)
|
||||
{
|
||||
auto t_size = vals.sizes();
|
||||
int size = 1;
|
||||
for (auto dim : t_size) size *= dim;
|
||||
|
||||
if (((size / groups) / 4 / 1024) <= 256) {
|
||||
launch_sr_fake_quantize_kernel(
|
||||
(T*)vals.data_ptr(), size, groups, bits, at::cuda::getCurrentCUDAStream());
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
at::Tensor ds_quantize_asym(at::Tensor& vals, int groups, int bits)
|
||||
{
|
||||
auto t_size = vals.sizes();
|
||||
int size = 1;
|
||||
for (auto dim : t_size) size *= dim;
|
||||
|
||||
if ((((size / groups) - 1) / 4096 + 1) <= 256) {
|
||||
launch_fake_quantize_kernel_asym(
|
||||
(T*)vals.data_ptr(), size, groups, bits, at::cuda::getCurrentCUDAStream());
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
at::Tensor ds_sr_quantize_asym(at::Tensor& vals, int groups, int bits)
|
||||
{
|
||||
auto t_size = vals.sizes();
|
||||
int size = 1;
|
||||
for (auto dim : t_size) size *= dim;
|
||||
|
||||
if (((size / groups) / 4 / 1024) <= 256) {
|
||||
launch_sr_fake_quantize_kernel_asym(
|
||||
(T*)vals.data_ptr(), size, groups, bits, at::cuda::getCurrentCUDAStream());
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
std::vector<at::Tensor> quantize_kernel(at::Tensor& input_vals,
|
||||
int groups,
|
||||
int numBits,
|
||||
quantize::Type quantType)
|
||||
{
|
||||
auto dtype = at::kFloat;
|
||||
auto params_options = at::TensorOptions()
|
||||
.dtype(dtype)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
const int param_elems = (quantize::requires_offset(quantType)) ? 2 : 1;
|
||||
auto params = torch::empty({groups, param_elems}, params_options);
|
||||
|
||||
auto output_options = at::TensorOptions()
|
||||
.dtype(at::kChar)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
|
||||
auto output_sizes = input_vals.sizes().vec();
|
||||
output_sizes[output_sizes.size() - 1] /= numBits == 8 ? 1 : 2;
|
||||
auto output = torch::empty(output_sizes, output_options);
|
||||
|
||||
const int elems_per_group = at::numel(input_vals) / groups;
|
||||
|
||||
launch_quant((int8_t*)output.data_ptr(),
|
||||
(float*)params.data_ptr(),
|
||||
(__half*)input_vals.data_ptr(),
|
||||
groups,
|
||||
elems_per_group,
|
||||
numBits,
|
||||
quantType,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
|
||||
return {output, params};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
at::Tensor dequantize(at::Tensor& quantized_data,
|
||||
at::Tensor& params,
|
||||
int groups,
|
||||
int num_bits,
|
||||
quantize::Type quant_type)
|
||||
{
|
||||
auto dtype = (std::is_same<T, float>::value) ? torch::kFloat32 : torch::kFloat16;
|
||||
auto output_options = at::TensorOptions()
|
||||
.dtype(dtype)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
|
||||
auto output_sizes = quantized_data.sizes().vec();
|
||||
output_sizes[output_sizes.size() - 1] *= num_bits == 8 ? 1 : 2;
|
||||
auto output = torch::empty(output_sizes, output_options);
|
||||
|
||||
const int total_elems = at::numel(output);
|
||||
const int elems_per_group = total_elems / groups;
|
||||
|
||||
launch_dequantize_kernel((T*)output.data_ptr(),
|
||||
(const int8_t*)quantized_data.data_ptr(),
|
||||
(const float*)params.data_ptr(),
|
||||
quant_type,
|
||||
num_bits,
|
||||
elems_per_group,
|
||||
total_elems,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
at::Tensor dequantize_int4_to_half_experimental(at::Tensor& data_in,
|
||||
at::Tensor& scale_buffer,
|
||||
at::Tensor& min_val_buffer,
|
||||
int num_group,
|
||||
int group_size)
|
||||
{
|
||||
auto output_options = at::TensorOptions().dtype(at::kHalf).device(at::kCUDA);
|
||||
auto output = torch::empty({num_group, group_size}, output_options);
|
||||
|
||||
launch_dequantize_int4_to_half_experimental((uint8_t*)data_in.data_ptr(),
|
||||
(half*)output.data_ptr(),
|
||||
(half*)scale_buffer.data_ptr(),
|
||||
(half*)min_val_buffer.data_ptr(),
|
||||
num_group,
|
||||
group_size,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::vector<at::Tensor> ds_swizzle_quant(at::Tensor& input_vals,
|
||||
int groups,
|
||||
int num_bits,
|
||||
quantize::Type quant_type,
|
||||
int pipeline_size,
|
||||
int nodes,
|
||||
int devices_per_node)
|
||||
{
|
||||
auto scales_options = at::TensorOptions()
|
||||
.dtype(at::kFloat)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
const int scales_elems = (quantize::requires_offset(quant_type)) ? 2 : 1;
|
||||
auto scales = torch::empty({groups, scales_elems}, scales_options);
|
||||
|
||||
auto output_options = at::TensorOptions()
|
||||
.dtype(at::kChar)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
|
||||
const int quantization_scalar = 8 / num_bits;
|
||||
const int compressed_vals = at::numel(input_vals) / quantization_scalar;
|
||||
|
||||
auto output = torch::empty({compressed_vals}, output_options);
|
||||
const int elems_per_group = at::numel(input_vals) / groups;
|
||||
|
||||
launch_swizzled_quant((int8_t*)output.data_ptr(),
|
||||
(float*)scales.data_ptr(),
|
||||
(__half*)input_vals.data_ptr(),
|
||||
num_bits,
|
||||
quant_type,
|
||||
groups,
|
||||
elems_per_group,
|
||||
pipeline_size,
|
||||
nodes,
|
||||
devices_per_node,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
|
||||
return {output, scales};
|
||||
}
|
||||
|
||||
std::vector<at::Tensor> quantized_reduction(at::Tensor& input_vals,
|
||||
at::Tensor& input_scales,
|
||||
int in_groups,
|
||||
int out_groups,
|
||||
int num_bits,
|
||||
quantize::Type quant_type,
|
||||
int devices_per_node)
|
||||
{
|
||||
auto scales_options = at::TensorOptions()
|
||||
.dtype(at::kFloat)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
const int scales_elems = (quantize::requires_offset(quant_type)) ? 2 : 1;
|
||||
auto scales = torch::empty({out_groups, scales_elems}, scales_options);
|
||||
|
||||
auto output_options = at::TensorOptions()
|
||||
.dtype(at::kChar)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
|
||||
std::vector<int64_t> sz_vector(input_vals.sizes().begin(), input_vals.sizes().end());
|
||||
sz_vector[sz_vector.size() - 1] = sz_vector.back() / devices_per_node; // num of GPU per nodes
|
||||
at::IntArrayRef sz(sz_vector);
|
||||
auto output = torch::empty(sz, output_options);
|
||||
|
||||
const int elems_per_in_tensor = at::numel(input_vals) / devices_per_node;
|
||||
const int elems_per_in_group = elems_per_in_tensor / (in_groups / devices_per_node);
|
||||
const int elems_per_out_group = elems_per_in_tensor / out_groups;
|
||||
|
||||
// old code:
|
||||
// std::vector<long int> sz(input_vals.sizes().begin(), input_vals.sizes().end());
|
||||
// sz[sz.size() - 1] = sz.back() / devices_per_node; // num of GPU per nodes
|
||||
// const int elems_per_in_tensor = at::numel(input_vals) / devices_per_node;
|
||||
// auto output = torch::empty(sz, output_options);
|
||||
|
||||
// const int elems_per_in_group = elems_per_in_tensor / (in_groups / devices_per_node);
|
||||
// const int elems_per_out_group = elems_per_in_tensor / out_groups;
|
||||
|
||||
launch_dequant_reduce((int8_t*)output.data_ptr(),
|
||||
(float*)scales.data_ptr(),
|
||||
(const int8_t*)input_vals.data_ptr(),
|
||||
(const float*)input_scales.data_ptr(),
|
||||
devices_per_node,
|
||||
num_bits,
|
||||
quant_type,
|
||||
out_groups,
|
||||
elems_per_out_group,
|
||||
elems_per_in_tensor,
|
||||
in_groups / devices_per_node,
|
||||
elems_per_in_group,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
return {output, scales};
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m)
|
||||
{
|
||||
m.def("ds_quantize_fp32", &ds_quantize<float>, "DeepSpeed Quantize with fp32 (CUDA)");
|
||||
m.def("ds_quantize_fp16", &ds_quantize<__half>, "DeepSpeed Quantize with fp16 (CUDA)");
|
||||
m.def("ds_sr_quantize_fp32", &ds_sr_quantize<float>, "DeepSpeed Quantize with fp32 (CUDA)");
|
||||
m.def("ds_sr_quantize_fp16", &ds_sr_quantize<__half>, "DeepSpeed Quantize with fp16 (CUDA)");
|
||||
m.def("ds_quantize_asym_fp32", &ds_quantize_asym<float>, "DeepSpeed Quantize with fp32 (CUDA)");
|
||||
m.def(
|
||||
"ds_quantize_asym_fp16", &ds_quantize_asym<__half>, "DeepSpeed Quantize with fp16 (CUDA)");
|
||||
m.def("ds_sr_quantize_asym_fp32",
|
||||
&ds_sr_quantize_asym<float>,
|
||||
"DeepSpeed Quantize with fp32 (CUDA)");
|
||||
m.def("ds_sr_quantize_asym_fp16",
|
||||
&ds_sr_quantize_asym<__half>,
|
||||
"DeepSpeed Quantize with fp16 (CUDA)");
|
||||
pybind11::enum_<quantize::Type>(m, "QuantizationType")
|
||||
.value("Symmetric", quantize::Type::Symmetric)
|
||||
.value("Asymmetric", quantize::Type::Asymmetric)
|
||||
.export_values();
|
||||
m.def("quantize", &quantize_kernel);
|
||||
m.def("dequantize", &dequantize<__half>);
|
||||
m.def("dequantize_fp32", &dequantize<float>);
|
||||
m.def("dequantize_int4_to_half_experimental",
|
||||
&dequantize_int4_to_half_experimental,
|
||||
"Dequantize int4 to half (experimental)");
|
||||
m.def("swizzle_quant", &ds_swizzle_quant);
|
||||
m.def("quantized_reduction", &quantized_reduction);
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import os
|
||||
import torch
|
||||
import deepspeed
|
||||
import subprocess
|
||||
import argparse
|
||||
import psutil
|
||||
from .ops.op_builder.all_ops import ALL_OPS
|
||||
from .git_version_info import installed_ops, torch_info
|
||||
from deepspeed.accelerator import get_accelerator
|
||||
|
||||
GREEN = '\033[92m'
|
||||
RED = '\033[91m'
|
||||
YELLOW = '\033[93m'
|
||||
END = '\033[0m'
|
||||
SUCCESS = f"{GREEN} [SUCCESS] {END}"
|
||||
OKAY = f"{GREEN}[OKAY]{END}"
|
||||
WARNING = f"{YELLOW}[WARNING]{END}"
|
||||
FAIL = f'{RED}[FAIL]{END}'
|
||||
INFO = '[INFO]'
|
||||
|
||||
color_len = len(GREEN) + len(END)
|
||||
okay = f"{GREEN}[OKAY]{END}"
|
||||
warning = f"{YELLOW}[WARNING]{END}"
|
||||
|
||||
|
||||
def op_report(verbose=True):
|
||||
max_dots = 23
|
||||
max_dots2 = 11
|
||||
h = ["op name", "installed", "compatible"]
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
print("DeepSpeed C++/CUDA extension op report")
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
|
||||
print("NOTE: Ops not installed will be just-in-time (JIT) compiled at\n"
|
||||
" runtime if needed. Op compatibility means that your system\n"
|
||||
" meet the required dependencies to JIT install the op.")
|
||||
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
print("JIT compiled ops requires ninja")
|
||||
ninja_status = OKAY if ninja_installed() else FAIL
|
||||
print('ninja', "." * (max_dots - 5), ninja_status)
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
print(h[0], "." * (max_dots - len(h[0])), h[1], "." * (max_dots2 - len(h[1])), h[2])
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
installed = f"{GREEN}[YES]{END}"
|
||||
no = f"{YELLOW}[NO]{END}"
|
||||
for op_name, builder in ALL_OPS.items():
|
||||
dots = "." * (max_dots - len(op_name))
|
||||
is_compatible = OKAY if builder.is_compatible(verbose) else no
|
||||
is_installed = installed if installed_ops.get(op_name, False) else no
|
||||
dots2 = '.' * ((len(h[1]) + (max_dots2 - len(h[1]))) - (len(is_installed) - color_len))
|
||||
print(op_name, dots, is_installed, dots2, is_compatible)
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
|
||||
|
||||
def ninja_installed():
|
||||
try:
|
||||
import ninja # noqa: F401 # type: ignore
|
||||
except ImportError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def nvcc_version():
|
||||
import torch.utils.cpp_extension
|
||||
cuda_home = torch.utils.cpp_extension.CUDA_HOME
|
||||
if cuda_home is None:
|
||||
return f"{RED} [FAIL] cannot find CUDA_HOME via torch.utils.cpp_extension.CUDA_HOME={torch.utils.cpp_extension.CUDA_HOME} {END}"
|
||||
try:
|
||||
output = subprocess.check_output([cuda_home + "/bin/nvcc", "-V"], universal_newlines=True)
|
||||
except FileNotFoundError:
|
||||
return f"{RED} [FAIL] nvcc missing {END}"
|
||||
output_split = output.split()
|
||||
release_idx = output_split.index("release")
|
||||
release = output_split[release_idx + 1].replace(',', '').split(".")
|
||||
return ".".join(release)
|
||||
|
||||
|
||||
def get_shm_size():
|
||||
try:
|
||||
temp_dir = os.getenv('TEMP') or os.getenv('TMP') or os.path.join(os.path.expanduser('~'), 'tmp')
|
||||
shm_stats = psutil.disk_usage(temp_dir)
|
||||
shm_size = shm_stats.total
|
||||
shm_hbytes = human_readable_size(shm_size)
|
||||
warn = []
|
||||
if shm_size < 512 * 1024**2:
|
||||
warn.append(
|
||||
f" {YELLOW} [WARNING] Shared memory size might be too small, consider increasing it. {END}"
|
||||
)
|
||||
# Add additional warnings specific to your use case if needed.
|
||||
return shm_hbytes, warn
|
||||
except Exception as e:
|
||||
return "UNKNOWN", [f"Error getting shared memory size: {e}"]
|
||||
|
||||
def human_readable_size(size):
|
||||
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
i = 0
|
||||
while size >= 1024 and i < len(units) - 1:
|
||||
size /= 1024
|
||||
i += 1
|
||||
return f'{size:.2f} {units[i]}'
|
||||
|
||||
|
||||
def debug_report():
|
||||
max_dots = 33
|
||||
|
||||
report = [("torch install path", torch.__path__), ("torch version", torch.__version__),
|
||||
("deepspeed install path", deepspeed.__path__),
|
||||
("deepspeed info", f"{deepspeed.__version__}, {deepspeed.__git_hash__}, {deepspeed.__git_branch__}")]
|
||||
if get_accelerator().device_name() == 'cuda':
|
||||
hip_version = getattr(torch.version, "hip", None)
|
||||
report.extend([("torch cuda version", torch.version.cuda), ("torch hip version", hip_version),
|
||||
("nvcc version", (None if hip_version else nvcc_version())),
|
||||
("deepspeed wheel compiled w.", f"torch {torch_info['version']}, " +
|
||||
(f"hip {torch_info['hip_version']}" if hip_version else f"cuda {torch_info['cuda_version']}"))
|
||||
])
|
||||
else:
|
||||
report.extend([("deepspeed wheel compiled w.", f"torch {torch_info['version']} ")])
|
||||
|
||||
report.append(("shared memory (/dev/shm) size", get_shm_size()))
|
||||
|
||||
print("DeepSpeed general environment info:")
|
||||
for name, value in report:
|
||||
warns = []
|
||||
if isinstance(value, tuple):
|
||||
value, warns = value
|
||||
print(name, "." * (max_dots - len(name)), value)
|
||||
if warns:
|
||||
for warn in warns:
|
||||
print(warn)
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--hide_operator_status',
|
||||
action='store_true',
|
||||
help='Suppress display of installation and compatibility statuses of DeepSpeed operators. ')
|
||||
parser.add_argument('--hide_errors_and_warnings', action='store_true', help='Suppress warning and error messages.')
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def main(hide_operator_status=False, hide_errors_and_warnings=False):
|
||||
if not hide_operator_status:
|
||||
op_report(verbose=not hide_errors_and_warnings)
|
||||
debug_report()
|
||||
|
||||
|
||||
def cli_main():
|
||||
args = parse_arguments()
|
||||
main(hide_operator_status=args.hide_operator_status, hide_errors_and_warnings=args.hide_errors_and_warnings)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,299 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <torch/extension.h>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include "quantization.h"
|
||||
|
||||
template <typename T>
|
||||
at::Tensor ds_quantize(at::Tensor& vals, int groups, int bits)
|
||||
{
|
||||
auto t_size = vals.sizes();
|
||||
int size = 1;
|
||||
for (auto dim : t_size) size *= dim;
|
||||
|
||||
if ((((size / groups) - 1) / 4096 + 1) <= 256) {
|
||||
launch_fake_quantize_kernel(
|
||||
(T*)vals.data_ptr(), size, groups, bits, at::cuda::getCurrentCUDAStream());
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
at::Tensor ds_sr_quantize(at::Tensor& vals, int groups, int bits)
|
||||
{
|
||||
auto t_size = vals.sizes();
|
||||
int size = 1;
|
||||
for (auto dim : t_size) size *= dim;
|
||||
|
||||
if (((size / groups) / 4 / 1024) <= 256) {
|
||||
launch_sr_fake_quantize_kernel(
|
||||
(T*)vals.data_ptr(), size, groups, bits, at::cuda::getCurrentCUDAStream());
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
at::Tensor ds_quantize_asym(at::Tensor& vals, int groups, int bits)
|
||||
{
|
||||
auto t_size = vals.sizes();
|
||||
int size = 1;
|
||||
for (auto dim : t_size) size *= dim;
|
||||
|
||||
if ((((size / groups) - 1) / 4096 + 1) <= 256) {
|
||||
launch_fake_quantize_kernel_asym(
|
||||
(T*)vals.data_ptr(), size, groups, bits, at::cuda::getCurrentCUDAStream());
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
at::Tensor ds_sr_quantize_asym(at::Tensor& vals, int groups, int bits)
|
||||
{
|
||||
auto t_size = vals.sizes();
|
||||
int size = 1;
|
||||
for (auto dim : t_size) size *= dim;
|
||||
|
||||
if (((size / groups) / 4 / 1024) <= 256) {
|
||||
launch_sr_fake_quantize_kernel_asym(
|
||||
(T*)vals.data_ptr(), size, groups, bits, at::cuda::getCurrentCUDAStream());
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
std::vector<at::Tensor> quantize_kernel(at::Tensor& input_vals,
|
||||
int groups,
|
||||
int numBits,
|
||||
quantize::Type quantType)
|
||||
{
|
||||
auto dtype = at::kFloat;
|
||||
auto params_options = at::TensorOptions()
|
||||
.dtype(dtype)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
const int param_elems = (quantize::requires_offset(quantType)) ? 2 : 1;
|
||||
auto params = torch::empty({groups, param_elems}, params_options);
|
||||
|
||||
auto output_options = at::TensorOptions()
|
||||
.dtype(at::kChar)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
|
||||
auto output_sizes = input_vals.sizes().vec();
|
||||
output_sizes[output_sizes.size() - 1] /= numBits == 8 ? 1 : 2;
|
||||
auto output = torch::empty(output_sizes, output_options);
|
||||
|
||||
const int elems_per_group = at::numel(input_vals) / groups;
|
||||
|
||||
launch_quant((int8_t*)output.data_ptr(),
|
||||
(float*)params.data_ptr(),
|
||||
(__half*)input_vals.data_ptr(),
|
||||
groups,
|
||||
elems_per_group,
|
||||
numBits,
|
||||
quantType,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
|
||||
return {output, params};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
at::Tensor dequantize(at::Tensor& quantized_data,
|
||||
at::Tensor& params,
|
||||
int groups,
|
||||
int num_bits,
|
||||
quantize::Type quant_type)
|
||||
{
|
||||
auto dtype = (std::is_same<T, float>::value) ? torch::kFloat32 : torch::kFloat16;
|
||||
auto output_options = at::TensorOptions()
|
||||
.dtype(dtype)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
|
||||
auto output_sizes = quantized_data.sizes().vec();
|
||||
output_sizes[output_sizes.size() - 1] *= num_bits == 8 ? 1 : 2;
|
||||
auto output = torch::empty(output_sizes, output_options);
|
||||
|
||||
const int total_elems = at::numel(output);
|
||||
const int elems_per_group = total_elems / groups;
|
||||
|
||||
launch_dequantize_kernel((T*)output.data_ptr(),
|
||||
(const int8_t*)quantized_data.data_ptr(),
|
||||
(const float*)params.data_ptr(),
|
||||
quant_type,
|
||||
num_bits,
|
||||
elems_per_group,
|
||||
total_elems,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
at::Tensor dequantize_int4_to_half_experimental(at::Tensor& data_in,
|
||||
at::Tensor& scale_buffer,
|
||||
at::Tensor& min_val_buffer,
|
||||
int num_group,
|
||||
int group_size)
|
||||
{
|
||||
auto output_options = at::TensorOptions().dtype(at::kHalf).device(at::kCUDA);
|
||||
auto output = torch::empty({num_group, group_size}, output_options);
|
||||
|
||||
launch_dequantize_int4_to_half_experimental((uint8_t*)data_in.data_ptr(),
|
||||
(half*)output.data_ptr(),
|
||||
(half*)scale_buffer.data_ptr(),
|
||||
(half*)min_val_buffer.data_ptr(),
|
||||
num_group,
|
||||
group_size,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
at::Tensor dequantize_int8_to_half_experimental(at::Tensor& data_in,
|
||||
at::Tensor& scale_buffer,
|
||||
at::Tensor& min_val_buffer,
|
||||
int num_group,
|
||||
int group_size)
|
||||
{
|
||||
auto output_options = at::TensorOptions().dtype(at::kHalf).device(at::kCUDA);
|
||||
auto output = torch::empty({num_group, group_size}, output_options);
|
||||
|
||||
launch_dequantize_int8_to_half_experimental((uint8_t*)data_in.data_ptr(),
|
||||
(half*)output.data_ptr(),
|
||||
(half*)scale_buffer.data_ptr(),
|
||||
(half*)min_val_buffer.data_ptr(),
|
||||
num_group,
|
||||
group_size,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::vector<at::Tensor> ds_swizzle_quant(at::Tensor& input_vals,
|
||||
int groups,
|
||||
int num_bits,
|
||||
quantize::Type quant_type,
|
||||
int pipeline_size,
|
||||
int nodes,
|
||||
int devices_per_node)
|
||||
{
|
||||
auto scales_options = at::TensorOptions()
|
||||
.dtype(at::kFloat)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
const int scales_elems = (quantize::requires_offset(quant_type)) ? 2 : 1;
|
||||
auto scales = torch::empty({groups, scales_elems}, scales_options);
|
||||
|
||||
auto output_options = at::TensorOptions()
|
||||
.dtype(at::kChar)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
|
||||
const int quantization_scalar = 8 / num_bits;
|
||||
const int compressed_vals = at::numel(input_vals) / quantization_scalar;
|
||||
|
||||
auto output = torch::empty({compressed_vals}, output_options);
|
||||
const int elems_per_group = at::numel(input_vals) / groups;
|
||||
|
||||
launch_swizzled_quant((int8_t*)output.data_ptr(),
|
||||
(float*)scales.data_ptr(),
|
||||
(__half*)input_vals.data_ptr(),
|
||||
num_bits,
|
||||
quant_type,
|
||||
groups,
|
||||
elems_per_group,
|
||||
pipeline_size,
|
||||
nodes,
|
||||
devices_per_node,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
|
||||
return {output, scales};
|
||||
}
|
||||
|
||||
std::vector<at::Tensor> quantized_reduction(at::Tensor& input_vals,
|
||||
at::Tensor& input_scales,
|
||||
int in_groups,
|
||||
int out_groups,
|
||||
int num_bits,
|
||||
quantize::Type quant_type,
|
||||
int devices_per_node)
|
||||
{
|
||||
auto scales_options = at::TensorOptions()
|
||||
.dtype(at::kFloat)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
const int scales_elems = (quantize::requires_offset(quant_type)) ? 2 : 1;
|
||||
auto scales = torch::empty({out_groups, scales_elems}, scales_options);
|
||||
|
||||
auto output_options = at::TensorOptions()
|
||||
.dtype(at::kChar)
|
||||
.layout(at::kStrided)
|
||||
.device(at::kCUDA)
|
||||
.requires_grad(false);
|
||||
|
||||
std::vector<int64_t> sz_vector(input_vals.sizes().begin(), input_vals.sizes().end());
|
||||
sz_vector[sz_vector.size() - 1] = sz_vector.back() / devices_per_node; // num of GPU per nodes
|
||||
at::IntArrayRef sz(sz_vector);
|
||||
auto output = torch::empty(sz, output_options);
|
||||
|
||||
const int elems_per_in_tensor = at::numel(input_vals) / devices_per_node;
|
||||
const int elems_per_in_group = elems_per_in_tensor / (in_groups / devices_per_node);
|
||||
const int elems_per_out_group = elems_per_in_tensor / out_groups;
|
||||
|
||||
launch_dequant_reduce((int8_t*)output.data_ptr(),
|
||||
(float*)scales.data_ptr(),
|
||||
(const int8_t*)input_vals.data_ptr(),
|
||||
(const float*)input_scales.data_ptr(),
|
||||
devices_per_node,
|
||||
num_bits,
|
||||
quant_type,
|
||||
out_groups,
|
||||
elems_per_out_group,
|
||||
elems_per_in_tensor,
|
||||
in_groups / devices_per_node,
|
||||
elems_per_in_group,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
return {output, scales};
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m)
|
||||
{
|
||||
m.def("ds_quantize_fp32", &ds_quantize<float>, "DeepSpeed Quantize with fp32 (CUDA)");
|
||||
m.def("ds_quantize_fp16", &ds_quantize<__half>, "DeepSpeed Quantize with fp16 (CUDA)");
|
||||
m.def("ds_sr_quantize_fp32", &ds_sr_quantize<float>, "DeepSpeed Quantize with fp32 (CUDA)");
|
||||
m.def("ds_sr_quantize_fp16", &ds_sr_quantize<__half>, "DeepSpeed Quantize with fp16 (CUDA)");
|
||||
m.def("ds_quantize_asym_fp32", &ds_quantize_asym<float>, "DeepSpeed Quantize with fp32 (CUDA)");
|
||||
m.def(
|
||||
"ds_quantize_asym_fp16", &ds_quantize_asym<__half>, "DeepSpeed Quantize with fp16 (CUDA)");
|
||||
m.def("ds_sr_quantize_asym_fp32",
|
||||
&ds_sr_quantize_asym<float>,
|
||||
"DeepSpeed Quantize with fp32 (CUDA)");
|
||||
m.def("ds_sr_quantize_asym_fp16",
|
||||
&ds_sr_quantize_asym<__half>,
|
||||
"DeepSpeed Quantize with fp16 (CUDA)");
|
||||
pybind11::enum_<quantize::Type>(m, "QuantizationType")
|
||||
.value("Symmetric", quantize::Type::Symmetric)
|
||||
.value("Asymmetric", quantize::Type::Asymmetric)
|
||||
.export_values();
|
||||
m.def("quantize", &quantize_kernel);
|
||||
m.def("dequantize", &dequantize<__half>);
|
||||
m.def("dequantize_fp32", &dequantize<float>);
|
||||
m.def("dequantize_int4_to_half_experimental",
|
||||
&dequantize_int4_to_half_experimental,
|
||||
"Dequantize int4 to half (experimental)");
|
||||
m.def("dequantize_int8_to_half_experimental",
|
||||
&dequantize_int8_to_half_experimental,
|
||||
"Dequantize int8 to half (experimental)");
|
||||
m.def("swizzle_quant", &ds_swizzle_quant);
|
||||
m.def("quantized_reduction", &quantized_reduction);
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import os
|
||||
import torch
|
||||
import deepspeed
|
||||
import subprocess
|
||||
import argparse
|
||||
import psutil
|
||||
from .ops.op_builder.all_ops import ALL_OPS
|
||||
from .git_version_info import installed_ops, torch_info
|
||||
from deepspeed.accelerator import get_accelerator
|
||||
|
||||
GREEN = '\033[92m'
|
||||
RED = '\033[91m'
|
||||
YELLOW = '\033[93m'
|
||||
END = '\033[0m'
|
||||
SUCCESS = f"{GREEN} [SUCCESS] {END}"
|
||||
OKAY = f"{GREEN}[OKAY]{END}"
|
||||
WARNING = f"{YELLOW}[WARNING]{END}"
|
||||
FAIL = f'{RED}[FAIL]{END}'
|
||||
INFO = '[INFO]'
|
||||
|
||||
color_len = len(GREEN) + len(END)
|
||||
okay = f"{GREEN}[OKAY]{END}"
|
||||
warning = f"{YELLOW}[WARNING]{END}"
|
||||
|
||||
|
||||
def op_report(verbose=True):
|
||||
max_dots = 23
|
||||
max_dots2 = 11
|
||||
h = ["op name", "installed", "compatible"]
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
print("DeepSpeed C++/CUDA extension op report")
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
|
||||
print("NOTE: Ops not installed will be just-in-time (JIT) compiled at\n"
|
||||
" runtime if needed. Op compatibility means that your system\n"
|
||||
" meet the required dependencies to JIT install the op.")
|
||||
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
print("JIT compiled ops requires ninja")
|
||||
ninja_status = OKAY if ninja_installed() else FAIL
|
||||
print('ninja', "." * (max_dots - 5), ninja_status)
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
print(h[0], "." * (max_dots - len(h[0])), h[1], "." * (max_dots2 - len(h[1])), h[2])
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
installed = f"{GREEN}[YES]{END}"
|
||||
no = f"{YELLOW}[NO]{END}"
|
||||
for op_name, builder in ALL_OPS.items():
|
||||
dots = "." * (max_dots - len(op_name))
|
||||
is_compatible = OKAY if builder.is_compatible(verbose) else no
|
||||
is_installed = installed if installed_ops.get(op_name, False) else no
|
||||
dots2 = '.' * ((len(h[1]) + (max_dots2 - len(h[1]))) - (len(is_installed) - color_len))
|
||||
print(op_name, dots, is_installed, dots2, is_compatible)
|
||||
print("-" * (max_dots + max_dots2 + len(h[0]) + len(h[1])))
|
||||
|
||||
|
||||
def ninja_installed():
|
||||
try:
|
||||
import ninja # noqa: F401 # type: ignore
|
||||
except ImportError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def nvcc_version():
|
||||
import torch.utils.cpp_extension
|
||||
cuda_home = torch.utils.cpp_extension.CUDA_HOME
|
||||
if cuda_home is None:
|
||||
return f"{RED} [FAIL] cannot find CUDA_HOME via torch.utils.cpp_extension.CUDA_HOME={torch.utils.cpp_extension.CUDA_HOME} {END}"
|
||||
try:
|
||||
output = subprocess.check_output([cuda_home + "/bin/nvcc", "-V"], universal_newlines=True)
|
||||
except FileNotFoundError:
|
||||
return f"{RED} [FAIL] nvcc missing {END}"
|
||||
output_split = output.split()
|
||||
release_idx = output_split.index("release")
|
||||
release = output_split[release_idx + 1].replace(',', '').split(".")
|
||||
return ".".join(release)
|
||||
|
||||
|
||||
def get_shm_size():
|
||||
try:
|
||||
temp_dir = os.getenv('TEMP') or os.getenv('TMP') or os.path.join(os.path.expanduser('~'), 'tmp')
|
||||
shm_stats = psutil.disk_usage(temp_dir)
|
||||
shm_size = shm_stats.total
|
||||
shm_hbytes = human_readable_size(shm_size)
|
||||
warn = []
|
||||
if shm_size < 512 * 1024**2:
|
||||
warn.append(
|
||||
f" {YELLOW} [WARNING] Shared memory size might be too small, consider increasing it. {END}"
|
||||
)
|
||||
# Add additional warnings specific to your use case if needed.
|
||||
return shm_hbytes, warn
|
||||
except Exception as e:
|
||||
return "UNKNOWN", [f"Error getting shared memory size: {e}"]
|
||||
|
||||
|
||||
def human_readable_size(size):
|
||||
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
i = 0
|
||||
while size >= 1024 and i < len(units) - 1:
|
||||
size /= 1024
|
||||
i += 1
|
||||
return f'{size:.2f} {units[i]}'
|
||||
|
||||
|
||||
def debug_report():
|
||||
max_dots = 33
|
||||
|
||||
report = [("torch install path", torch.__path__), ("torch version", torch.__version__),
|
||||
("deepspeed install path", deepspeed.__path__),
|
||||
("deepspeed info", f"{deepspeed.__version__}, {deepspeed.__git_hash__}, {deepspeed.__git_branch__}")]
|
||||
if get_accelerator().device_name() == 'cuda':
|
||||
hip_version = getattr(torch.version, "hip", None)
|
||||
report.extend([("torch cuda version", torch.version.cuda), ("torch hip version", hip_version),
|
||||
("nvcc version", (None if hip_version else nvcc_version())),
|
||||
("deepspeed wheel compiled w.", f"torch {torch_info['version']}, " +
|
||||
(f"hip {torch_info['hip_version']}" if hip_version else f"cuda {torch_info['cuda_version']}"))
|
||||
])
|
||||
else:
|
||||
report.extend([("deepspeed wheel compiled w.", f"torch {torch_info['version']} ")])
|
||||
|
||||
report.append(("shared memory (/dev/shm) size", get_shm_size()))
|
||||
|
||||
print("DeepSpeed general environment info:")
|
||||
for name, value in report:
|
||||
warns = []
|
||||
if isinstance(value, tuple):
|
||||
value, warns = value
|
||||
print(name, "." * (max_dots - len(name)), value)
|
||||
if warns:
|
||||
for warn in warns:
|
||||
print(warn)
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--hide_operator_status',
|
||||
action='store_true',
|
||||
help='Suppress display of installation and compatibility statuses of DeepSpeed operators. ')
|
||||
parser.add_argument('--hide_errors_and_warnings', action='store_true', help='Suppress warning and error messages.')
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def main(hide_operator_status=False, hide_errors_and_warnings=False):
|
||||
if not hide_operator_status:
|
||||
op_report(verbose=not hide_errors_and_warnings)
|
||||
debug_report()
|
||||
|
||||
|
||||
def cli_main():
|
||||
args = parse_arguments()
|
||||
main(hide_operator_status=args.hide_operator_status, hide_errors_and_warnings=args.hide_errors_and_warnings)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,18 +0,0 @@
|
||||
These are the modified files you would need for a MANUAL build of DeepSpeed 11.1 or 11.2.
|
||||
|
||||
They were created by a mix of instructions from erew123 and S95Sedan.
|
||||
|
||||
|
||||
- You still have to download the DeepSpeed v11.1 or v11.2 installation files from Microsoft to use these files.
|
||||
|
||||
https://github.com/microsoft/DeepSpeed/releases/tag/v0.11.2
|
||||
|
||||
|
||||
- These files were provided by S95Sedan here:
|
||||
|
||||
https://github.com/S95Sedan/Deepspeed-Windows
|
||||
|
||||
|
||||
- They are related to the manual build of DeepSpeed for AllTalk, which is linked here:
|
||||
|
||||
https://github.com/erew123/alltalk_tts/tree/main?tab=readme-ov-file#-option-2---a-bit-more-complicated
|
||||
@@ -0,0 +1,217 @@
|
||||
ARG DOCKER_REPOSITORY
|
||||
ARG DOCKER_TAG=latest
|
||||
FROM ${DOCKER_REPOSITORY}alltalk_tts_environment:${DOCKER_TAG}
|
||||
|
||||
# Argument to choose the model: piper, vits, xtts
|
||||
ARG TTS_MODEL="xtts"
|
||||
ENV TTS_MODEL=$TTS_MODEL
|
||||
|
||||
ARG DEEPSPEED_VERSION=0.17.2
|
||||
ENV DEEPSPEED_VERSION=$DEEPSPEED_VERSION
|
||||
|
||||
ENV GRADIO_SERVER_NAME="0.0.0.0"
|
||||
|
||||
# Switch for using the multi engine manager:
|
||||
ENV ALLTALK_ENABLE_MULTI_ENGINE_MANAGER=false
|
||||
|
||||
# Default settings used in docker_default_confignew.json:
|
||||
ENV ALLTALK_DELETE_OUTPUT_WAVS="1"
|
||||
ENV ALLTALK_GRADIO_INTERFACE=true
|
||||
ENV ALLTALK_RVC_ENABLED=true
|
||||
ENV ALLTALK_RVC_F0METHOD="rmvpe"
|
||||
|
||||
# Default settings used in docker_default_mem_config.json:
|
||||
ENV ALLTALK_MEM_AUTO_START_ENGINES=1
|
||||
ENV ALLTALK_MEM_MAX_INSTANCES=1
|
||||
|
||||
WORKDIR ${ALLTALK_DIR}
|
||||
|
||||
##############################################################################
|
||||
# Download TTS models:
|
||||
##############################################################################
|
||||
COPY system/tts_engines/piper/available_models.json system/tts_engines/piper/
|
||||
COPY system/tts_engines/vits/available_models.json system/tts_engines/vits/
|
||||
COPY system/tts_engines/xtts/available_models.json system/tts_engines/xtts/
|
||||
RUN <<EOR
|
||||
available_models="system/tts_engines/${TTS_MODEL}/available_models.json"
|
||||
first_start_model=$(cat ${available_models} | jq -r '.first_start_model')
|
||||
model=$(cat ${available_models} | jq ".models[] | select(.model_name==\"${first_start_model}\")")
|
||||
folder_path=$( echo "${model}" | jq -r '.folder_path // empty' )
|
||||
files_to_download=$( echo "${model}" | jq '.files_to_download[]?' )
|
||||
github_rls_url=$( echo "${model}" | jq '.github_rls_url // empty' )
|
||||
|
||||
# Special case for vits: firstrun.py expects the ZIP download directly in the model folder.
|
||||
# When firstrun.py unzips the file, the correct folder will be created automatically.
|
||||
if [[ "$TTS_MODEL" == "vits" ]]; then
|
||||
folder_path=""
|
||||
fi
|
||||
|
||||
# Merging both fields:
|
||||
files_to_download="${files_to_download} ${github_rls_url}"
|
||||
target_dir="models/${TTS_MODEL}/${folder_path}"
|
||||
|
||||
echo "$files_to_download" | xargs -n 1 curl --create-dirs --output-dir "${target_dir}" -LO
|
||||
EOR
|
||||
|
||||
##############################################################################
|
||||
# Download all RVC models:
|
||||
##############################################################################
|
||||
COPY system/tts_engines/rvc_files.json system/tts_engines/
|
||||
RUN jq -r '.[]' system/tts_engines/rvc_files.json | xargs -n 1 curl --create-dirs --output-dir models/rvc_base -LO
|
||||
|
||||
##############################################################################
|
||||
# Install python dependencies (cannot use --no-deps because requirements are not complete)
|
||||
##############################################################################
|
||||
COPY system/config/*.whl system/config/
|
||||
COPY system/requirements/requirements_standalone.txt system/requirements/requirements_standalone.txt
|
||||
COPY system/requirements/requirements_parler.txt system/requirements/requirements_parler.txt
|
||||
ENV PIP_CACHE_DIR=${ALLTALK_DIR}/pip_cache
|
||||
RUN <<EOR
|
||||
conda activate alltalk
|
||||
|
||||
mkdir -p ${ALLTALK_DIR}/pip_cache
|
||||
pip install --no-cache-dir --cache-dir=${ALLTALK_DIR}/pip_cache -r system/requirements/requirements_standalone.txt
|
||||
pip install --no-cache-dir --cache-dir=${ALLTALK_DIR}/pip_cache --upgrade gradio==4.32.2
|
||||
|
||||
# By default, version 1.9.10 is used causing this warning on startup: 'FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated'
|
||||
pip install --no-cache-dir --cache-dir=${ALLTALK_DIR}/pip_cache local-attention==1.11.1
|
||||
|
||||
# Parler:
|
||||
pip install --no-cache-dir --cache-dir=${ALLTALK_DIR}/pip_cache -r system/requirements/requirements_parler.txt
|
||||
|
||||
conda clean --all --force-pkgs-dirs -y && pip cache purge
|
||||
EOR
|
||||
|
||||
##############################################################################
|
||||
# Install DeepSpeed
|
||||
##############################################################################
|
||||
RUN mkdir -p /tmp/deepspeed
|
||||
COPY docker/deepspeed/build*/*.whl /tmp/deepspeed/
|
||||
RUN <<EOR
|
||||
DEEPSPEED_WHEEL=$(realpath -q /tmp/deepspeed/*.whl)
|
||||
conda activate alltalk
|
||||
|
||||
# Download DeepSpeed wheel if it was not built locally:
|
||||
if [ -z "${DEEPSPEED_WHEEL}" ] || [ ! -f $DEEPSPEED_WHEEL ] ; then
|
||||
echo "Downloading pre-built DeepSpeed wheel"
|
||||
CURL_ERROR=$( { curl --output-dir /tmp/deepspeed -fLO "https://github.com/erew123/alltalk_tts/releases/download/DeepSpeed-for-docker/deepspeed-0.17.2+15f054d9-cp311-cp311-linux_x86_64.whl" ; } 2>&1 )
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed to download DeepSpeed: $CURL_ERROR"
|
||||
exit 1
|
||||
fi
|
||||
DEEPSPEED_WHEEL=$(realpath -q /tmp/deepspeed/*.whl)
|
||||
fi
|
||||
|
||||
echo "Using precompiled DeepSpeed wheel at ${DEEPSPEED_WHEEL}"
|
||||
CFLAGS="-I$CONDA_PREFIX/include/" LDFLAGS="-L$CONDA_PREFIX/lib/" \
|
||||
pip install --no-cache-dir ${DEEPSPEED_WHEEL}
|
||||
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed to install pip dependencies: $RESULT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm ${DEEPSPEED_WHEEL}
|
||||
conda clean --all --force-pkgs-dirs -y && pip cache purge
|
||||
EOR
|
||||
|
||||
##############################################################################
|
||||
# Writing scripts to start alltalk:
|
||||
##############################################################################
|
||||
RUN <<EOR
|
||||
cat << EOF > start_alltalk.sh
|
||||
#!/usr/bin/env bash
|
||||
source ~/.bashrc
|
||||
|
||||
replace_env_vars() {
|
||||
echo "{}" > /tmp/empty.json
|
||||
jq "\$( cat \$1 ) | del(.. | select(. == null))" /tmp/empty.json > \$1.tmp
|
||||
mv \$1.tmp \$1
|
||||
rm -f /tmp/empty.json
|
||||
}
|
||||
|
||||
merge_json_files() {
|
||||
# Merging JSON config with docker default values followed by values set on startup:
|
||||
jq -s '.[0] * .[1] * .[2]' \$1 docker_default_\$1 docker_\$1 > \$1.tmp
|
||||
mv \$1.tmp \$1
|
||||
}
|
||||
|
||||
replace_env_vars docker_default_confignew.json
|
||||
merge_json_files confignew.json
|
||||
|
||||
# Script for deleting WAV files that are older than 1 minute:
|
||||
cat << EOF2 > ${ALLTALK_DIR}/cleanup-wavs.sh
|
||||
#!/usr/bin/env bash
|
||||
[ \"\$ALLTALK_AUTO_CLEANUP\" = \"true\" ] && find ${ALLTALK_DIR}/outputs -type f -name "*.wav" -mmin +1 -exec rm {} \;
|
||||
EOF2
|
||||
chmod u+x ${ALLTALK_DIR}/cleanup-wavs.sh
|
||||
|
||||
# Starting the cron job:
|
||||
cron
|
||||
|
||||
source ${ALLTALK_DIR}/conda_env.sh
|
||||
|
||||
if [ "\$ALLTALK_ENABLE_MULTI_ENGINE_MANAGER" = "true" ] ; then
|
||||
echo "Starting alltalk using multi engine manager"
|
||||
replace_env_vars docker_default_mem_config.json
|
||||
merge_json_files mem_config.json
|
||||
python tts_mem.py
|
||||
else
|
||||
echo "Starting alltalk"
|
||||
python script.py
|
||||
fi
|
||||
EOF
|
||||
cat << EOF > start_finetune.sh
|
||||
#!/usr/bin/env bash
|
||||
source ~/.bashrc
|
||||
export TRAINER_TELEMETRY=0
|
||||
source ${ALLTALK_DIR}/conda_env.sh
|
||||
python finetune.py
|
||||
EOF
|
||||
cat << EOF > start_diagnostics.sh
|
||||
#!/usr/bin/env bash
|
||||
source ~/.bashrc
|
||||
source ${ALLTALK_DIR}/conda_env.sh
|
||||
python diagnostics.py
|
||||
EOF
|
||||
chmod +x start_alltalk.sh
|
||||
chmod +x start_finetune.sh
|
||||
chmod +x start_diagnostics.sh
|
||||
EOR
|
||||
|
||||
COPY --chown=alltalk:alltalk . .
|
||||
|
||||
RUN mkdir -p ${ALLTALK_DIR}/outputs ${ALLTALK_DIR}/.triton/autotune
|
||||
|
||||
##############################################################################
|
||||
# Enable deepspeed for all models:
|
||||
##############################################################################
|
||||
RUN find . -name model_settings.json -exec sed -i -e 's/"deepspeed_enabled": false/"deepspeed_enabled": true/g' {} \;
|
||||
|
||||
##############################################################################
|
||||
# Cronjob running every minute to delete output WAV files:
|
||||
##############################################################################
|
||||
USER root
|
||||
RUN <<EOR
|
||||
echo "* * * * * ${ALLTALK_DIR}/cleanup-wavs.sh" > /etc/cron.d/cleanup-wavs
|
||||
crontab -u alltalk /etc/cron.d/cleanup-wavs
|
||||
chmod u+s /usr/sbin/cron
|
||||
EOR
|
||||
|
||||
USER alltalk
|
||||
|
||||
##############################################################################
|
||||
# Create script to execute firstrun.py and run it:
|
||||
##############################################################################
|
||||
RUN echo $'#!/usr/bin/env bash \n\
|
||||
source ~/.bashrc \n\
|
||||
source ${ALLTALK_DIR}/conda_env.sh \n\
|
||||
python ./system/config/firstrun.py $@' > ./start_firstrun.sh
|
||||
|
||||
RUN chmod +x start_firstrun.sh
|
||||
RUN ./start_firstrun.sh --tts_model $TTS_MODEL
|
||||
|
||||
##############################################################################
|
||||
# Start alltalk:
|
||||
##############################################################################
|
||||
ENTRYPOINT ["bash", "-c", "./start_alltalk.sh"]
|
||||
@@ -1,708 +1,170 @@
|
||||
# AllTalk TTS
|
||||
AllTalk is an updated version of the Coqui_tts extension for Text Generation web UI. Features include:
|
||||
# AllTalk TTS v2
|
||||
**Documentation/Wiki:** Refer to the built-in documentation or view the extensive [Wiki here](https://github.com/erew123/alltalk_tts/wiki).
|
||||
|
||||
- **Custom Start-up Settings:** Adjust your default start-up settings. [Screenshot](https://github.com/erew123/alltalk_tts#screenshots)
|
||||
- **Narrarator:** Use different voices for main character and narration. [Example Narration](https://vocaroo.com/18nrv7FR6wuA)
|
||||
- **Low VRAM mode:** Great for people with small GPU memory or if your VRAM is filled by your LLM. [Screenshot](https://github.com/erew123/alltalk_tts#screenshots)
|
||||
- **DeepSpeed:** A 3-4x performance boost generating TTS. [DeepSpeed Windows/Linux Instructions](https://github.com/erew123/alltalk_tts?tab=readme-ov-file#deepspeed-installation-options) [Screenshot](https://github.com/erew123/alltalk_tts/assets/35898566/548619c8-5f1b-47d0-a73d-54d2fee3f3db)
|
||||
- **Local/Custom models:** Use any of the XTTSv2 models (API Local and XTTSv2 Local).
|
||||
- **Optional wav file maintenance:** Configurable deletion of old output wav files. [Screenshot](https://github.com/erew123/alltalk_tts#screenshots)
|
||||
- **Finetuning** Train the model specifically on a voice of your choosing for better reproduction.
|
||||
- **Documentation:** Fully documented with a built in webpage. [Screenshot](https://github.com/erew123/alltalk_tts#screenshots)
|
||||
- **Console output** Clear command line output for any warnings or issues.
|
||||
- **API Suite and 3rd Party support via JSON calls** Can be used with 3rd party applications via JSON calls.
|
||||
- **Can be run as a standalone app** Not just inside of text-generation-webui.
|
||||
**Known Errors:** See the [Known Errors page here](https://github.com/erew123/alltalk_tts/wiki/Error-Messages-List) for troubleshooting.
|
||||
|
||||
## Index
|
||||
**Issues & Support:** Report issues or bugs by opening a [support ticket here](https://github.com/erew123/alltalk_tts/issues).
|
||||
|
||||
- 🟩 [Installation](https://github.com/erew123/alltalk_tts?#-installation-on-text-generation-web-ui)
|
||||
- 🟪 [Updating & problems with updating](https://github.com/erew123/alltalk_tts?#-updating)
|
||||
- 🟫 [Screenshots](https://github.com/erew123/alltalk_tts#-screenshots)
|
||||
- 🟨 [Help with problems](https://github.com/erew123/alltalk_tts?#-help-with-problems)
|
||||
- ⚫ [Finetuning a model](https://github.com/erew123/alltalk_tts?#-finetuning-a-model)
|
||||
- 🔵🟢🟡 [DeepSpeed Installation (Windows & Linux)](https://github.com/erew123/alltalk_tts?#-deepspeed-installation-options)
|
||||
- 🟦 [Running AllTalk as a standalone app](https://github.com/erew123/alltalk_tts?#-running-alltalk-as-a-standalone-app)
|
||||
- 🟠 [API Suite and JSON-CURL](https://github.com/erew123/alltalk_tts?#-api-suite-and-json-curl)
|
||||
- 🔴 [Future to-do list & Upcoming updates](https://github.com/erew123/alltalk_tts?#-future-to-do-list)
|
||||
**Feature Requests:** View current feature requests on the [Feature Requests page here](https://github.com/erew123/alltalk_tts/discussions/74).
|
||||
|
||||
## 🔄 Updates
|
||||
Please check the below link to find a list of all recent updates and changes.
|
||||
#### 🔄 **Updates list & bug fixes list** can be found [here](https://github.com/erew123/alltalk_tts/issues/25)
|
||||
**Community Discussions:** Join discussions on AllTalk V2 in the [Discussion Board here](https://github.com/erew123/alltalk_tts/discussions/245).
|
||||
|
||||
## 🟩 Installation on Text generation web UI
|
||||
This has been tested on the current Dec 2023 release of Text generation webUI. If you have not updated it for a while, you may wish to update Text generation webUI, [instructions here](https://github.com/oobabooga/text-generation-webui?tab=readme-ov-file#how-to-install)
|
||||
**Please note, my available time has become VERY limited due to unexpected family commitments [Please read here for details](https://github.com/erew123/alltalk_tts/issues/377). So please DYOR, look at the [Wiki here ](https://github.com/erew123/alltalk_tts/wiki) and refer to the TTS manufacturer (Links in the Gradio interface) for issues specific to THEIR TTS engines.**
|
||||
|
||||
If you want to watch a video of how to do the below [link here](https://youtu.be/9BPKuwaav5w)
|
||||
I would also like to say a big thank you to anyone whom has contributed to this project either with PR's (list [here](https://github.com/erew123/alltalk_tts/graphs/contributors)) or in any discussion forums/issues tickets. Your input, guidance, help and thoughts are greatly appreciated.
|
||||
|
||||
1) In a command prompt/terminal window you need to move into your Text generation webUI folder:<br><br>
|
||||
`cd text-generation-webui`
|
||||
### [💖 Sponsor this Project on Ko-fi](https://ko-fi.com/erew123)
|
||||
|
||||
2) Start the Text generation webUI Python environment for your OS with whichever **one** of the below is correct for your OS:<br><br>
|
||||
`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`<br><br> Loading Text-generation-webui's Python Environment is $\textcolor{red}{\textsf{VERY IMPORTANT}}$. If you are uncertain what a loaded Python environment looks like, image [here](https://github.com/erew123/alltalk_tts/issues/25#issuecomment-1869344442) and video [here](https://www.youtube.com/watch?v=9BPKuwaav5w)
|
||||
---
|
||||
|
||||
3) Move into your extensions folder:<br><br>
|
||||
`cd extensions`
|
||||
## AllTalk V2 Core Functionality
|
||||
- Comprehensive setup utilities for Windows & Linux
|
||||
- Multiple TTS engine support
|
||||
- Coqui XTTS TTS
|
||||
- Coqui VITS TTS
|
||||
- Piper TTS
|
||||
- Parler TTS
|
||||
- F5 TTS
|
||||
- Other TTS engines can be coded in
|
||||
- Retrieval-based Voice Conversion (RVC) pipeline
|
||||
- Easy integration of new TTS engines (some coding required)
|
||||
- Customizable settings for each TTS engine
|
||||
- In-app model downloads for each engine
|
||||
- Gradio web interface for easy management
|
||||
- Standalone application or integration with Text-generation-webui, SillyTavern, KoboldCPP, HomeAssistant
|
||||
- Narrator function for using different voices for characters and narration
|
||||
- Audio Transcoding to multiple formats (mp3, opus, etc.)
|
||||
- About 50 Gradio interface themes
|
||||
- Custom start-up settings
|
||||
- Clear console output for troubleshooting
|
||||
- Binds to all available IP addresses (0.0.0.0)
|
||||
- Fully customizable Global API settings
|
||||
- Transcription & Audio File Transcription
|
||||
|
||||
4) Once there git clone this repository:<br><br>
|
||||
`git clone https://github.com/erew123/alltalk_tts`
|
||||
#### Documentation and Support
|
||||
- Built-in documentation with web interface
|
||||
- GitHub Wiki documentation
|
||||
|
||||
5) Move into the **alltalk_tts** folder:<br><br>
|
||||
`cd alltalk_tts`
|
||||
#### Performance and Optimization (Depending on TTS engine used)
|
||||
- DeepSpeed integration for 2-3x performance boost (Depending on TTS engine)
|
||||
- Low VRAM mode for systems with limited GPU memory
|
||||
|
||||
6) Install one of the two requirements files. Whichever one of the two is correct for your machine type:<br><br>
|
||||
**Nvidia graphics card machines** - `pip install -r requirements_nvidia.txt`<br><br>
|
||||
**Other machines (mac, amd etc)** - `pip install -r requirements_other.txt`
|
||||
#### Voice Customization and Enhancement
|
||||
- Model Finetuning for improved voice reproduction
|
||||
- XTTS Multiple audio sample TTS generation for better voice reproduction
|
||||
|
||||
7) **(Optional DeepSpeed)** If you have an Nvidia Graphics card on a system running Linux or Windows and wish to use **DeepSpeed** please follow these instructions [here](https://github.com/erew123/alltalk_tts?#-deepspeed-installation-options). **However**, I would highly reccommend before you install DeepSpeed, you start text-generation-webui up, confirm AllTalk starts correctly and everything is working, as DeepSpeed can add another layer of complications troubleshooting any potential start-up issues. If necessary you can `pip uninstall deepspeed`.
|
||||
#### Bulk Operations and Management
|
||||
- Bulk TTS Generator/Editor for large-scale audio production
|
||||
|
||||
8) You can now start move back to the main Text generation webUI folder `cd ..` (a few times), start Text generation webUI with whichever **one** of the startup scripts is correct for your OS (`start_windows.bat`,`./start_linux.sh`, `start_macos.sh` or `start_wsl.bat`) and load the AllTalk extension in the Text generation webUI **session** tab.<br><br> Starting Text-generation-webui with its correct start-up script is $\textcolor{red}{\textsf{VERY IMPORTANT}}$.
|
||||
|
||||
10) Please read the note below about start-up times and also the note about ensuring your character cards are set up [correctly](https://github.com/erew123/alltalk_tts#the-one-thing-i-cant-easily-work-around)
|
||||
#### API and Integration
|
||||
- Comprehensive API Suite
|
||||
- OpenAI-compatible endpoint/API for broader software compatibility
|
||||
- JSON call support for third-party applications
|
||||
|
||||
11) Some extra voices downloadable [here](https://drive.google.com/file/d/1bYdZdr3L69kmzUN3vSiqZmLRD7-A3M47/view?usp=drive_link)
|
||||
#### Experimental Features
|
||||
- Multi Engine Manager (MEM) for running multiple TTS instances simultaneously & queuing requests between them.
|
||||
|
||||
#### 🟩 Other installation notes
|
||||
On first startup, AllTalk will download the Coqui XTTSv2 2.0.2 model to its **models** folder (1.8GB space required). Check the command prompt/terminal window if you want to know what its doing. After it says "Model Loaded" the Text generation webUI is usually available on its IP address a few seconds later, for you to connect to in your browser.
|
||||
#### Screenshots
|
||||
Screenshots are available [here](https://github.com/erew123/alltalk_tts/discussions/237)
|
||||
|
||||
Once the extension is loaded, please find all documentation and settings on the link provided in the interface (as shown in the screenshot below).
|
||||
---
|
||||
|
||||
**Where to find voices** https://aiartes.com/voiceai or https://commons.wikimedia.org/ or interviews on youtube etc. Instructions on how to cut down and prepare a voice sample are within the built in documentation.
|
||||
### 🟥 Platform-Specific Notes
|
||||
|
||||
#### 🟩 The one thing I cant easily work around
|
||||
Messages intended for the Narrator should be enclosed in asterisks `*` and those for the character inside quotation marks `"`. However, AI systems often deviate from these rules, resulting in text that is neither in quotes nor asterisks. Sometimes, text may appear with only a single asterisk, and AI models may vary their formatting mid-conversation. For example, they might use asterisks initially and then switch to unmarked text. A properly formatted line should look like this:
|
||||
#### Windows
|
||||
- Requires Git, Microsoft C++ Build Tools, and Windows SDK.
|
||||
- DeepSpeed works with NVIDIA GPUs.
|
||||
|
||||
`"`Hey! I'm so excited to finally meet you. I've heard so many great things about you and I'm eager to pick your brain about computers.`"` `*`She walked across the room and picked up her cup of coffee`*`
|
||||
#### Linux
|
||||
- Requires specific packages based on your distribution (Debian-based or RPM-based).
|
||||
- DeepSpeed works with NVIDIA GPUs.
|
||||
- Limited experimental support for AMD GPUs (mainly for XTTS).
|
||||
|
||||
Most narrator/character systems switch voices upon encountering an asterisk or quotation marks, which is somewhat effective. AllTalk has undergone several revisions in its sentence splitting and identification methods. While some irregularities and AI deviations in message formatting are inevitable, any line beginning or ending with an asterisk should now be recognized as Narrator dialogue. Lines enclosed in double quotes are identified as Character dialogue. For any other text, you can choose how AllTalk handles it: whether it should be interpreted as Character or Narrator dialogue (most AI systems tend to lean more towards one format when generating text not enclosed in quotes or asterisks).
|
||||
#### Mac (Theoretical)
|
||||
- Installation process is untested and theoretical.
|
||||
- No GPU acceleration for AllTalk TTS engines.
|
||||
- Some TTS engines may have limited or no support on Mac.
|
||||
|
||||
With improvements to the splitter/processor, I'm confident it's functioning well. You can monitor what AllTalk identifies as Narrator lines on the command line and adjust its behavior if needed (Text Not Inside - Function).
|
||||
For more details on Mac support limitations, please refer to the [Mac Support Disclaimer](https://github.com/erew123/alltalk_tts/wiki/Install-%E2%80%90-Manual-Installation-Guide).
|
||||
|
||||
## 🟪 Updating
|
||||
This is pretty much a repeat of the installation process.
|
||||
#### GPU Support
|
||||
GPU support is provided by the developer of the individual TTS engine. If their TTS engine support's X GPU, then I can support X GPU, if it doesnt support X GPU, then I cannot support X GPU. Most of the engines will run on CPU, but some may be very slow on CPU.
|
||||
|
||||
1) In a command prompt/terminal window you need to move into your Text generation webUI folder:<br><br>
|
||||
`cd text-generation-webui` and start the Python environment for your OS with whichever **one** of the below is correct for your OS:<br><br>
|
||||
`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`<br>
|
||||
- NVIDIA GPUs: Full support on Windows and Linux.
|
||||
- AMD GPUs: Limited experimental support on Linux (mainly for XTTS).
|
||||
- Intel ARC GPUs: No specific support currently.
|
||||
- Apple Silicon (M1/M2): No GPU acceleration for AllTalk TTS engines currently.
|
||||
|
||||
2) Move into your extensions and alltalk_tts folder:<br><br>
|
||||
`cd extensions` then `cd alltalk_tts`
|
||||
---
|
||||
|
||||
3) At the command prompt/terminal, type:<br><br>
|
||||
`git pull`
|
||||
### 🟩 Quick Setup (Recommended for most users)
|
||||
|
||||
4) Install the correct requirements for your machine:<br><br>
|
||||
**Nvidia graphics card machines** - `pip install -r requirements_nvidia.txt`<br><br>
|
||||
**Other machines (mac, amd etc)** - `pip install -r requirements_other.txt`
|
||||
For a fast and straightforward installation and recommended:
|
||||
|
||||
#### 🟪 Updating "git pull" error
|
||||
- [Standalone Installation](https://github.com/erew123/alltalk_tts/wiki/Install-%E2%80%90-Standalone-Installation)
|
||||
- For users who want to run AllTalk TTS as a standalone application.
|
||||
- Available for Windows and Linux.
|
||||
- Most users will want a Standalone Installation and should use this choice.
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary><br>
|
||||
|
||||
I did leave a mistake in the `/extensions/alltalk_tts/.gitignore` file at one point. If your `git pull` doesnt work, you can either follow the Problems Updating section below, or edit the `.gitignore` file and **replace its entire contents** with the below, save the file, then re-try the `git pull`<br><br>
|
||||
```
|
||||
voices/*.*
|
||||
models/*.*
|
||||
outputs/*.*
|
||||
finetune/*.*
|
||||
config.json
|
||||
confignew.json
|
||||
models.json
|
||||
diagnostics.log
|
||||
```
|
||||
</details>
|
||||
- [Text-generation-webui Installation](https://github.com/erew123/alltalk_tts/wiki/Install-%E2%80%90-Text%E2%80%90generation%E2%80%90webui-Installation)
|
||||
- For users who want to integrate AllTalk TTS as a part of [Text-generation-webui](https://github.com/oobabooga/text-generation-webui).
|
||||
- Available for Windows and Linux.
|
||||
- Includes information on the optional/alternative TGWUI Remote Extension.
|
||||
- **CURRENTLY YOU SHOULD USE THE STANDALONE INSTALL METHOD AND TGWUI REMOTE EXTENSION**
|
||||
|
||||
#### 🟪 Updating other problems
|
||||
These methods use scripts that automate most of the installation process, making it easier for users to get started quickly.
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary><br>
|
||||
### 🟩 Manual Installation
|
||||
|
||||
If you do experience any problems, the simplest method to resolve this will be:
|
||||
For users who prefer more control over the installation process or need to troubleshoot:
|
||||
|
||||
1) re-name the existing `alltalk_tts` folder to something like `alltalk_tts.old`
|
||||
- [Manual Installation Guide](https://github.com/erew123/alltalk_tts/wiki/Install-%E2%80%90-Manual-Installation-Guide)
|
||||
- Detailed step-by-step instructions for manual installation.
|
||||
- Available for Windows, Linux, and Mac (theoretical).
|
||||
- Recommended for advanced users or those with specific setup requirements.
|
||||
|
||||
2) Start a console/terminal then:<br><br>
|
||||
`cd text-generation-webui` and start your python environment `cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`
|
||||
### 🟩 Google Colab Installation
|
||||
|
||||
3) Move into the extensions folder, same as if you were doing a fresh installation:<br><br>
|
||||
`cd extensions` then<br><br>
|
||||
`git clone https://github.com/erew123/alltalk_tts`
|
||||
For users who want to run AllTalk TTS in a cloud environment:
|
||||
|
||||
This will download a fresh installation.
|
||||
- [Google Colab Installation](https://github.com/erew123/alltalk_tts/wiki/Google-COLAB)
|
||||
- Instructions for setting up and running AllTalk TTS in Google Colab.
|
||||
- Ideal for users who want to try AllTalk TTS without installing it locally.
|
||||
|
||||
3) Move into the **alltalk_tts** folder:<br><br>
|
||||
`cd alltalk_tts`
|
||||
---
|
||||
|
||||
4) Install the correct requirements for your machine:<br><br>
|
||||
**Nvidia graphics card machines** - `pip install -r requirements_nvidia.txt`<br><br>
|
||||
**Other machines (mac, amd etc)** - `pip install -r requirements_other.txt`
|
||||
### 🟪 Updating
|
||||
|
||||
5) Before starting it up, copy/merge the `models`, `voices` and `outputs` folders over from the `alltalk_tts.old` folder to the newly created `alltalk_tts` folder. This will keep your voices history and also stop it re-downloading the model again.
|
||||
As long as you did the `git clone` method to setup initially, you will be able to go into the folder and use `git pull` to download updates.
|
||||
|
||||
You can now start text-generation-webui or AllTalk (standalone) and it should start up fine. You will need to re-set any saved configuration changes on the configuration page.
|
||||
It is recommended you use `atsetup` to re-apply the settings file for any necessary package updates.
|
||||
|
||||
Assuming its all working fine and you are happy, you can delete the old alltalk_tts.old folder.
|
||||
</details>
|
||||
---
|
||||
|
||||
## 🟫 Screenshots
|
||||
| |  |
|
||||
|:---:|:---:|
|
||||
| |  |
|
||||
| |  |
|
||||
### 🟨 Diagnostics Help with Issues/Start-up problems etc.
|
||||
|
||||
## 🟨 Help with problems
|
||||
#### 🔄 **Minor updates/bug fixes list** can be found [here](https://github.com/erew123/alltalk_tts/issues/25)
|
||||
If you are having issues with starting AllTalk, it may well be because some of the 3rd Party packages versions have changed, or something is not right in your Python environment.
|
||||
|
||||
#### 🟨 How to make a diagnostics report file
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
|
||||
1) Open a command prompt window, move into your **text-generation-webui** folder, you can now start the Python environment for text-generation-webui:<br><br>
|
||||
`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`
|
||||
- **Known Errors List:** [Wiki page is here](https://github.com/erew123/alltalk_tts/wiki/Error-Messages-List)
|
||||
- **Diagnostics tool Instructions:** [Wiki page is here](https://github.com/erew123/alltalk_tts/wiki/AllTalk-Diagnostics-Tool)
|
||||
|
||||
2) Move into the **alltalk_tts** folder:<br><br>
|
||||
`cd extensions` and then `cd alltalk_tts`
|
||||
Whilst its impossible to constantly ensure that everything is going to work perfectly, after installation, you can use the diagnostics tool to:
|
||||
|
||||
3) Run the diagnostics and select the requirements file name you installed AllTalk with:<br><br>
|
||||
`python diagnostics.py`
|
||||
1) Generate a `diagnostics.log` file which contains information about your Python environment setup and performs various checks to ensure everything is installed.
|
||||
2) Identify possible issues by comparing **your** `diagnostics.log` file to the **AllTalk base** `basediagnostics.log` stored in the `alltalk_tts/system/config/` folder.
|
||||
3) Provide some semi-automated repair of your Python environment.
|
||||
|
||||
4) You will have an on screen output showing your environment setttings, file versions request vs whats installed and details of your graphics card (if Nvidia). This will also create a file called `diagnostics.log` in the `alltalk_tts` folder, that you can upload if you need to create a support ticket on here.<br><br>
|
||||
---
|
||||
|
||||

|
||||
<br><br></details>
|
||||
### 🆘 Support Requests, Troubleshooting, BETA Discussions & Feature requests
|
||||
**Documentation & Known Error codes**, please refer to the built in documentation or the [Wiki here ](https://github.com/erew123/alltalk_tts/wiki).
|
||||
|
||||
If you wish to code something yourself though, thats perfectly to do and youre welcome to discuss that with me if needed.
|
||||
|
||||
#### 🟨 [AllTalk Startup] Warning TTS Subprocess has NOT started up yet, Will keep trying for 120 seconds maximum. Please wait. It times out after 120 seconds.
|
||||
General discussions on the BETA should be [here in the discussion board](https://github.com/erew123/alltalk_tts/discussions/245)
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary><br>
|
||||
When the subprocess is starting 2x things are occurring:<br><br>
|
||||
If you have a specifc technical problem, please open an issue ticket [here in the issues area](https://github.com/erew123/alltalk_tts/issues).
|
||||
|
||||
**A)** Its trying to load the voice model into your graphics card VRAM (assuming you have a Nvidia Graphics card, otherwise its your system RAM)<br>
|
||||
**B)** Its trying to start up the mini-webserver and send the "ready" signal back to the main process.
|
||||
**Please note, my available time has become VERY limited due to unexpected family commitments. So please DYOR, look at the [Wiki here ](https://github.com/erew123/alltalk_tts/wiki) and refer to the TTS manufacturer (Links in the Gradio interface) for issues specific to THEIR TTS engines.**
|
||||
|
||||
Before giving other possibilities a go, some people with **old machines** are finding their startup times are **very** slow 2-3 minutes. Ive extended the allowed time within the script from 1 minute to 2 minutes. **If you have an older machine** and wish to try extending this further, you can do so by editing `script.py` and changing line 251 `timeout = 120` changing the timeout to a larger value e.g `timeout = 240` (4 minutes).
|
||||
|
||||
**Note:** If you need to create a support ticket, please create a `diagnostics.log` report file to submit with a support request. Details on doing this are above.
|
||||
|
||||
Other possibilities for this issue are:
|
||||
|
||||
1) You are starting AllTalk in both your `CMD FLAG.txt` and `settings.yaml` file. The `CMD FLAG.txt` you would have manually edited and the `settings.yaml` is the one you change and save in the `session` tab of text-generation-webui and you can `Save UI defaults to settings.yaml`. Please only have one of those two starting up AllTalk.
|
||||
|
||||
2) You are not starting text-generation-webui with its normal Python environment. Please start it with start_{your OS version} as detailed [here](https://github.com/oobabooga/text-generation-webui#how-to-install) (`start_windows.bat`,`./start_linux.sh`, `start_macos.sh` or `start_wsl.bat`) OR (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat` and then `python server.py`).
|
||||
|
||||
3) You have installed the wrong version of DeepSpeed on your system, for the wrong version of Python/Text-generation-webui. You can go to your text-generation-webui folder in a terminal/command prompt and run the correct cmd version for your OS e.g. (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`) and then you can type `pip uninstall deepspeed` then try loading it again. If that works, please see here for the correct instructions for installing DeepSpeed [here](https://github.com/erew123/alltalk_tts#-deepspeed-installation-options).
|
||||
|
||||
4) You have an old version of text-generation-webui (pre Dec 2023) I have not tested on older versions of text-generation-webui, so cannot confirm viability on older versions. For instructions on updating the text-generation-webui, please look [here](https://github.com/oobabooga/text-generation-webui#how-to-install) (`update_linux.sh`, `update_windows.bat`, `update_macos.sh`, or `update_wsl.bat`).
|
||||
|
||||
5) You already have something running on port 7851 on your computer, so the mini-webserver cant start on that port. You can change this port number by editing the `confignew.json` file and changing `"port_number": "7851"` to `"port_number": "7602"` or any port number you wish that isn’t reserved. Only change the number and save the file, do not change the formatting of the document. This will at least discount that you have something else clashing on the same port number.
|
||||
|
||||
6) You have antivirus/firewalling that is blocking that port from being accessed. If you had to do something to allow text-generation-webui through your antivirus/firewall, you will have to do that for this too.
|
||||
|
||||
7) You have quite old graphics drivers and may need to update them.
|
||||
|
||||
8) Something within text-generation-webui is not playing nicely for some reason. You can go to your text-generation-webui folder in a terminal/command prompt and run the correct cmd version for your OS e.g. (`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`) and then you can type `python extensions\alltalk_tts\script.py` and see if AllTalk starts up correctly. If it does then something else is interfering.
|
||||
|
||||
9) Something else is already loaded into your VRAM or there is a crashed python process. Either check your task manager for erroneous Python processes or restart your machine and try again.
|
||||
|
||||
10) You are running DeepSpeed on a Linux machine and although you are starting with `./start_linux.sh` AllTalk is failing there on starting. This is because text-generation-webui will overwrite some environment variables when it loads its python environment. To see if this is the problem, from a terminal go into your text-generation-webui folder and `./cmd_linux.sh` then set your environment variable again e.g. `export CUDA_HOME=/usr/local/cuda` (this may vary depending on your OS, but this is the standard one for Linux, and assuming you have installed the CUDA toolkit), then `python server.py` and see if it starts up. If you want to edit the environment permanently you can do so, I have not managed to write full instructions yet, but here is the conda guide [here](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#set-env-vars).
|
||||
|
||||
11) You have built yourself a custom Python environment and something is funky with it. This is very hard to diagnose as its not a standard environment. You may want to updating text-generation-webui and re installing its requirements file (whichever one you use that comes down with text-generation-webui).
|
||||
</details>
|
||||
|
||||
#### 🟨 I think AllTalks requirements file has installed something another extension doesn't like
|
||||
<details>
|
||||
<summary>Click to expand</summary><br>
|
||||
|
||||
Ive paid very close attention to **not** impact what Text-generation-webui is requesting on a factory install. This is one of the requirements of submitting an extension to Text-generation-webui. If you want to look at a comparison of a factory fresh text-generation-webui installed packages (with cuda 12.1, though AllTalk's requirements were set on cuda 11.8) you can find that comparison [here](https://github.com/erew123/alltalk_tts/issues/23). This comparison shows that AllTalk is requesting the same package version numbers as Text-generation-webui or even lower version numbers (meaning AllTalk will not update them to a later version). What other extensions do, I cant really account for that.
|
||||
|
||||
I will note that the TTS engine downgrades Pandas data validator to 1.5.3 though its unlikely to cause any issues. You can upgrade it back to text-generation-webui default (december 2023) with `pip install pandas==2.1.4` when inside of the python environment. I have noticed no ill effects from it being a lower or higher version, as far as AllTalk goes. This is also the same behaviour as the Coqui_tts extension that comes with Text-generation-webui.
|
||||
|
||||
Other people are reporting issues with extensions not starting with errors about Pydantic e.g. ```pydantic.errors.PydanticImportError: BaseSettings` has been moved to the pydantic-settings package. See https://docs.pydantic.dev/2.5/migration/#basesettings-has-moved-to-pydantic-settings for more details.```
|
||||
|
||||
Im not sure if the Pydantic version has been recently updated by the Text-generation-webui installer, but this is nothing to do with AllTalk. The other extension you are having an issue with, need to be updated to work with Pydantic 2.5.x. AllTalk was updated in mid december to work with 2.5.x. I am not specifically condoning doing this, as it may have other knock on effects, but within the text-gen Python environment, you can use `pip install pydantic==2.5.0` or `pip install pydantic==1.10.13` to change the version of Pydantic installed.
|
||||
</details>
|
||||
|
||||
#### 🟨 I activated DeepSpeed in the settings page, but I didnt install DeepSpeed yet and now I have issues starting up
|
||||
<details>
|
||||
<summary>Click to expand</summary><br>
|
||||
|
||||
You can either follow the [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating) and fresh install your config. Or you can edit the `confignew.json` file within the `alltalk_tts` folder. You would look for '"deepspeed_activate": true,' and change the word true to false `"deepspeed_activate": false,' ,then save the file and try starting again.<br><br>
|
||||
|
||||
If you want to use DeepSpeed, you need an Nvidia Graphics card and to install DeepSpeed on your system. Instructions are [here](https://github.com/erew123/alltalk_tts#-deepspeed-installation-options)
|
||||
</details>
|
||||
|
||||
#### 🟨 I am having problems updating/some other issue where it wont start up/Im sure this is a bug
|
||||
<details>
|
||||
<summary>Click to expand</summary><br>
|
||||
|
||||
Please see [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating). If that doesnt help you can raise an ticket [here](https://github.com/erew123/alltalk_tts/issues). It would be handy to have any log files from the console where your error is being shown. I can only losely support custom built Python environments and give general pointers. Please create a `diagnostics.log` report file to submit with a support request.<br><br>
|
||||
|
||||
Also, is your text-generation-webui up to date? [instructions here](https://github.com/oobabooga/text-generation-webui?tab=readme-ov-file#how-to-install)
|
||||
</details>
|
||||
|
||||
#### 🟨 I am having problems getting AllTalk to start after changing settings or making a custom setup/model setup.
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary><br>
|
||||
|
||||
I would suggest following [Problems Updating](https://github.com/erew123/alltalk_tts#-problems-updating) and if you still have issues after that, you can raise an issue [here](https://github.com/erew123/alltalk_tts/issues)
|
||||
</details>
|
||||
|
||||
#### 🟨 I see some red "asyncio" messages
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary><br>
|
||||
|
||||
As far as I am aware, these are to do with the chrome browser the gradio text-generation-webui in some way. I raised an issue about this on the text-generation-webui [here](https://github.com/oobabooga/text-generation-webui/issues/4788) where you can see that AllTalk is not loaded and the messages persist. Either way, this is more a warning than an actual issue, so shouldnt affect any functionality of either AllTalk or text-generation-webui, they are more just an annoyance.
|
||||
</details>
|
||||
|
||||
#### 🟨 I have multiple GPU's and I have problems running Finetuning
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary><br>
|
||||
|
||||
Finetuning pulls in various other scripts and some of those scripts can have issues with multiple Nvidia GPU's being present. Until the people that created those other scripts fix up their code, there is a workaround to temporarily tell your system to only use the 1x of your Nvidia GPU's. To do this:
|
||||
|
||||
- **Windows** - You will start the script with `set CUDA_VISIBLE_DEVICES=0 && python finetune.py`<br>
|
||||
After you have completed training, you can reset back with `set CUDA_VISIBLE_DEVICES=`<br>
|
||||
|
||||
- **Linux** - You will start the script with `CUDA_VISIBLE_DEVICES=0 python finetune.py`<br>
|
||||
After you have completed training, you can reset back with `unset CUDA_VISIBLE_DEVICES`<br>
|
||||
|
||||
Rebooting your system will also unset this. The setting is only applied temporarily.
|
||||
|
||||
Depending on which of your Nvidia GPU's is the more powerful one, you can change the `0` to `1` or whichever of your GPU's is the most powerful.
|
||||
|
||||
</details>
|
||||
|
||||
## ⚫ Finetuning a model
|
||||
If you have a voice that the model doesnt quite reproduce correctly, or indeed you just want to improve the reproduced voice, then finetuning is a way to train your "XTTSv2 local" model **(stored in `/alltalk_tts/models/xxxxx/`)** on a specific voice. For this you will need:
|
||||
|
||||
- An Nvidia graphics card. (Please see this [note](https://github.com/erew123/alltalk_tts#-i-have-multiple-gpus-and-i-have-problems-running-finetuning) if you have multiple Nvidia GPU's).
|
||||
- To install a few portions of the Nvidia CUDA 11.8 Toolkit (this will not impact text-generation-webui's cuda setup.
|
||||
- 18GB of disk space free (most of this is used temporarily)
|
||||
- At least 2 minutes of good quality speech from your chosen speaker in mp3, wav or flacc format, in one or more files (have tested as far as 20 minutes worth of audio).
|
||||
|
||||
#### ⚫ How will this work/How complicated is it?
|
||||
Everything has been done to make this as simple as possible. At its simplest, you can literally just download a large chunk of audio from an interview, and tell the finetuning to strip through it, find spoken parts and build your dataset. You can literally click 4 buttons, then copy a few files and you are done. At it's more complicated end you will clean up the audio a little beforehand, but its still only 4x buttons and copying a few files.
|
||||
|
||||
#### ⚫ The audio you will use
|
||||
I would suggest that if its in an interview format, you cut out the interviewer speaking in audacity or your chosen audio editing package. You dont have to worry about being perfect with your cuts, the finetuning Step 1 will go and find spoken audio and cut it out for you. Is there is music over the spoken parts, for best quality you would cut out those parts, though its not 100% necessary. As always, try to avoid bad quality audio with noises in it (humming sounds, hiss etc). You can try something like [Audioenhancer](https://audioenhancer.ai/) to try clean up noisier audio. There is no need to down-sample any of the audio, all of that is handled for you. Just give the finetuning some good quality audio to work with.
|
||||
|
||||
#### ⚫ Important requirements CUDA 11.8
|
||||
As mentioned you must have a small portion of the Nvidia CUDA Toolkit **11.8** installed. Not higher or lower versions. Specifically **11.8**. You do not have to uninstall any other versions, change any graphics drivers, reinstall torch or anything like that. To keep the download+install as small as possible, you will need to:
|
||||
- Download the **xxx (network)** install of the Nvidia Cuda Toolkit 11.8 from [here](https://developer.nvidia.com/cuda-11-8-0-download-archive)
|
||||
- Run the installer. At minimum, you need to [minimally] install the `nvcc` compiler and the `CUBLAS` development and runtime libraries:
|
||||
- Select **Custom Advanced** as your installation type.
|
||||
- Uncheck all the checkboxes in the list.
|
||||
- Now check the following elements:
|
||||
- `CUDA` > `Development` > `Compiler` > `nvcc`
|
||||
- `CUDA` > `Development` > `Compiler` > `Libraries` > `CUBLAS`
|
||||
- `CUDA` > `Runtime` > `Libraries` > `CUBLAS`
|
||||
- You can now proceed through the install.
|
||||
- When that has installed, open a terminal/command prompt and type `nvcc --version`. If it reports back `Cuda compilation tools, release 11.8.` you are good to go. **Specifically, 11.8**. If not continue to the next step.
|
||||
- For both Windows and Linux, you need to ensure that `nvcc` and the 11.8 cuda library files are in your environments search path. You can undo the changes below after finetuning if you prefer:
|
||||
- **Windows**: Edit the Windows `Path` environment variable and add `C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin`
|
||||
- **Linux**: The path may be different depending on what flavour of Linux you are running, so you may need to seek out specific instructions on the internet. Generic paths **may** be:
|
||||
- `export LD_LIBRARY_PATH=/usr/local/cuda-11.8/lib64:${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}` and
|
||||
- `export LD_LIBRARY_PATH=/usr/local/cuda-11.8/bin`
|
||||
- Add these to your '~/.bashrc' if you want this to be permanent and not something you have to set each time you open a new terminal.
|
||||
- When you have made the changes, open a **new** terminal/command prompt (in order to load the new search paths) and `nvcc --version`. It should report back `Cuda compilation tools, release 11.8.` at which point, you are good to go.
|
||||
- If it doesn't report that, check you have correctly set the search environment paths, dont have overlapping other versions of cuda paths etc.
|
||||
|
||||
#### ⚫ Starting Finetuning
|
||||
**NOTE:** Please make sure you have started AllTalk at least once after updating, so that it downloads the additional files needed for finetuning.
|
||||
|
||||
1) Close all other applications that are using your GPU/VRAM and copy your audio samples into:<br><br>
|
||||
`/alltalk_tts/finetune/put-voice-samples-in-here/`
|
||||
3) In a command prompt/terminal window you need to move into your Text generation webUI folder:<br><br>
|
||||
`cd text-generation-webui`
|
||||
|
||||
4) Start the Text generation webUI Python environment for your OS:<br><br>
|
||||
`cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`
|
||||
|
||||
5) You can double check your search path environment still works correctly with `nvcc --version`. It should report back 11.8:<br><br>
|
||||
`Cuda compilation tools, release 11.8.`
|
||||
|
||||
7) Move into your extensions folder:<br><br>
|
||||
`cd extensions`
|
||||
|
||||
8) Move into the **alltalk_tts** folder:<br><br>
|
||||
`cd alltalk_tts`
|
||||
|
||||
9) Install the finetune requirements file: `pip install -r requirements_finetune.txt`
|
||||
|
||||
10) Type `python finetune.py` and it should start up.
|
||||
11) Follow the on-screen instructions when the web interface starts up.
|
||||
12) When you have finished finetuning, the final tab will tell you what to do with your files and how to move your newly trained model to the correct location on disk.
|
||||
|
||||
#### ⚫ Using a Finetuned model in Text-generation-webui
|
||||
|
||||
At the end of the finetune process, you will have an option to `Compact and move model to /trainedmodel/` this will compact the raw training file and move it to `/model/trainedmodel/`. When AllTalk starts up within Text-generation-webui, if it finds a model in this location a new loader will appear in the interface for `XTTSv2 FT` and you can use this to load your finetuned model. <br><br>**Be careful** not to train a new model from the base model, then overwrite your current `/model/trainedmodel/` **if** you want a seperately trained model. This is why there is an `OPTION B` to move your just trained model to `/models/lastfinetuned/`.
|
||||
|
||||
#### ⚫ Training one model with multiple voices
|
||||
|
||||
At the end of the finetune process, you will have an option to `Compact and move model to /trainedmodel/` this will compact the raw training file and move it to `/model/trainedmodel/`. This model will become available when you start up finetuning. You will have a choice to train the Base Model or the `Existing finetuned model` (which is the one in `/model/trainedmodel/`). So you can use this to keep further training this model with additional voices, then copying it back to `/model/trainedmodel/` at the end of training.
|
||||
|
||||
#### ⚫ Do I need to keep the raw training data/model?
|
||||
|
||||
If you've compacted and moved your model, its highly unlikely you would want to keep that data, however the choice is there to keep it if you wish. It will be between 5-10GB in size, so most people will want to delete it.
|
||||
|
||||
## 🔵🟢🟡 DeepSpeed Installation Options
|
||||
**NOTE**: You **DO NOT** need to set Text-generation-webUI's **--deepspeed** setting for AllTalk to be able to use DeepSpeed. These are two completely separate things and incorrectly setting that on Text-generation-webUI may cause other complications.
|
||||
|
||||
#### 🔵 Linux Installation
|
||||
<details>
|
||||
<summary>Click to expand: Linux DeepSpeed installation</summary>
|
||||
|
||||
➡️DeepSpeed requires an Nvidia Graphics card!⬅️
|
||||
|
||||
1) Preferably use your built in package manager to install CUDA tools. Alternatively download and install the Nvidia Cuda Toolkit for Linux [Nvidia Cuda Toolkit 11.8 or 12.1](https://developer.nvidia.com/cuda-toolkit-archive)<br><br>
|
||||
2) Open a terminal console.<br><br>
|
||||
3) Install libaio-dev (however your Linux version installs things) e.g. `sudo apt install libaio-dev`<br><br>
|
||||
4) Move into your Text generation webUI folder e.g. `cd text-generation-webui`<br><br>
|
||||
5) Start the Text generation webUI Python environment `./cmd_linux.sh`<br><br>
|
||||
6) Text generation webUI **overwrites** the **CUDA_HOME** environment variable each time you `./cmd_linux.sh` or `./start_linux.sh`, so you will need to either permanently change within the python environment OR set CUDA_HOME it each time you `./cmd_linux.sh`. Details to change it each time are on the next step. Below is a link to Conda's manual and changing environment variables permanently though its possible changing it permanently could affect other extensions, you would have to test.<br> <br>
|
||||
[Conda manual - Environment variables](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#set-env-vars)<br><br>
|
||||
7) You can temporarily set the **CUDA_HOME** environment with (Standard paths on Ubuntu, but could vary on other Linux flavours):<br><br>
|
||||
`export CUDA_HOME=/etc/alternatives/cuda`<br><br>
|
||||
**every** time you run `./cmd_linux.sh`.<br> <br>
|
||||
If you try to start DeepSpeed with the CUDA_HOME path set incorrectly, expect an error similar to `[Errno 2] No such file or directory: /home/yourname/text-generation-webui/installer_files/env/bin/nvcc`<br> <br>
|
||||
9) Now install deepspeed with pip install deepspeed<br><br>
|
||||
10) You can now start Text generation webUI `python server.py` ensuring to activate your extensions.<br><br>
|
||||
Just to reiterate, starting Text-generation-webUI with `./start_linux.sh` will overwrite the CUDA_HOME variable unless you have permanently changed it, hence always starting it with `./cmd_linux.sh` **then** setting the environment variable manually (step 7) and **then** `python server.py`, which is how you would need to run it each time, unless you permanently set the environment variable for CUDA_HOME within Text-generation-webUI's standard Python environment.
|
||||
<br><br>
|
||||
**Removal** - If it became necessary to uninstall DeepSpeed, you can do so with `./cmd_linux.sh` and then `pip uninstall deepspeed`<br><br>
|
||||
</details>
|
||||
|
||||
#### 🟢🟡 Windows Installation
|
||||
DeepSpeed v11.2 will work on the current default text-generation-webui Python 3.11 environment! You have 2x options for how to setup DeepSpeed on Windows. A quick way (🟢Option 1) and a long way (🟡Option 2).
|
||||
|
||||
Thanks to [@S95Sedan](https://github.com/S95Sedan) - They managed to get DeepSpeed 11.2 working on Windows via making some edits to the original Microsoft DeepSpeed v11.2 installation. The original post is [here](https://github.com/oobabooga/text-generation-webui/issues/4734#issuecomment-1843984142).
|
||||
|
||||
#### 🟢 OPTION 1 - Quick and easy!
|
||||
<details>
|
||||
<summary>Click to expand: Pre-Compiled Wheel Deepspeed v11.2 (Python 3.11 and 3.10)</summary>
|
||||
➡️DeepSpeed requires an Nvidia Graphics card!⬅️<br>
|
||||
|
||||
1) Download the correct wheel version for your Python/Cuda from [here](https://github.com/erew123/alltalk_tts/releases/tag/deepspeed) and save the file it inside your **text-generation-webui** folder.
|
||||
|
||||
2) Open a command prompt window, move into your **text-generation-webui folder**, you can now start the Python environment for text-generation-webui:<br><br>
|
||||
`cmd_windows.bat`
|
||||
|
||||
3) With the file that you saved in the **text-generation-webui folder** you now type the following, replacing YOUR-VERSION with the name of the file you have:<br><br>
|
||||
`pip install "deepspeed-0.11.2+YOUR-VERSION-win_amd64.whl"`
|
||||
|
||||
5) This should install through cleanly and you should now have DeepSpeed v11.2 installed within the Python 3.11/3.10 environment of text-generation-webui.
|
||||
|
||||
6) When you start up text-generation-webui, and AllTalk starts, you should see **[AllTalk Startup] DeepSpeed Detected**
|
||||
|
||||
7) Within AllTalk, you will now have a checkbox for **Activate DeepSpeed** though remember you can only change **1x setting every 15 or so seconds**, so dont try to activate DeepSpeed **and** LowVRAM/Change your model simultantiously. Do one of those, wait 15-20 seconds until the change is confirmed in the terminal/command prompt, then you can change the other. When you are happy it works, you can set the default start-up settings in the settings page.
|
||||
<br><br>
|
||||
**Removal** - If it became necessary to uninstall DeepSpeed, you can do so with `cmd_windows.bat` and then `pip uninstall deepspeed`<br><br>
|
||||
</details>
|
||||
|
||||
#### 🟡 OPTION 2 - A bit more complicated!
|
||||
<details>
|
||||
<summary>Click to expand: Manual Build DeepSpeed v11.2 (Python 3.11 and 3.10)</summary>
|
||||
➡️DeepSpeed requires an Nvidia Graphics card!⬅️<br><br>
|
||||
|
||||
This will take about 1 hour to complete and about 6GB of disk space.<br>
|
||||
|
||||
1. Download the 11.2 release of [DeepSpeed](https://github.com/microsoft/DeepSpeed/releases/tag/v0.11.2) extract it to a folder.
|
||||
2. Install Visual C++ build tools, such as [VS2019 C++ x64/x86](https://learn.microsoft.com/en-us/visualstudio/releases/2019/redistribution#vs2019-download) build tools.
|
||||
3. Download and install the [Nvidia Cuda Toolkit 11.8 or 12.1](https://developer.nvidia.com/cuda-toolkit-archive)
|
||||
4. **OPTIONAL** If you do not have an python environment already created and you are **not** going to use Text-generation-webui's environment, you can install [Miniconda](https://docs.conda.io/projects/miniconda/en/latest/miniconda-install.html), then at a command prompt, create and activate your environment with:<br><br>
|
||||
```conda create -n pythonenv python=3.11```<br>
|
||||
```activate pythonenv```<br>
|
||||
|
||||
5. Launch the Command Prompt cmd with Administrator privilege as it requires admin to allow creating symlink folders.
|
||||
6. If you are using the **Text-generation-webui** python environment, then in the `text-generation-webui` folder you will run `cmd_windows.bat` to start the python evnironment.<br><br>
|
||||
Otherwise Install PyTorch, 2.1.0 with CUDA 11.8 or 12.1 into your Python 3.1x.x environment e.g:<br><br>
|
||||
```activate pythonenv``` (activate your python environment)<br>
|
||||
```conda install pytorch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 pytorch-cuda=12.1 -c pytorch -c nvidia```<br>
|
||||
or<br>
|
||||
```conda install pytorch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 pytorch-cuda=11.8 -c pytorch -c nvidia```<br>
|
||||
|
||||
9. Set your CUDA Windows environment variables in the command prompt to ensure that CUDA_HOME and CUDA_PATH are set to your Nvidia Cuda Toolkit path. (The folder above the bin folder that nvcc.exe is installed in). Examples are:<br><br>
|
||||
```set CUDA_HOME=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.1```<br>
|
||||
```set CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.1```<br>
|
||||
or<br>
|
||||
```set CUDA_HOME=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8```<br>
|
||||
```set CUDA_PATH=C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8```<br>
|
||||
|
||||
10. Navigate to wherever you extracted the deepspeed folder in the Command Prompt:<br><br>
|
||||
```cd c:\DeepSpeed-0.11.2``` (wherever you extracted it to)
|
||||
|
||||
11. Modify the following files:<br>
|
||||
**(These modified files are included in the git-pull of AllTalk, in the DeepSpeed Windows folder and so can just be copied over the top of the exsting folders/files, but if you want to modify them yourself, please follow the below)**<br>
|
||||
|
||||
deepspeed-0.11.2/build_win.bat - at the top of the file, add:<br><br>
|
||||
```set DS_BUILD_EVOFORMER_ATTN=0```
|
||||
|
||||
deepspeed-0.11.2/csrc/quantization/pt_binding.cpp - lines 244-250 - change to:
|
||||
```
|
||||
std::vector<int64_t> sz_vector(input_vals.sizes().begin(), input_vals.sizes().end());
|
||||
sz_vector[sz_vector.size() - 1] = sz_vector.back() / devices_per_node; // num of GPU per nodes
|
||||
at::IntArrayRef sz(sz_vector);
|
||||
auto output = torch::empty(sz, output_options);
|
||||
|
||||
const int elems_per_in_tensor = at::numel(input_vals) / devices_per_node;
|
||||
const int elems_per_in_group = elems_per_in_tensor / (in_groups / devices_per_node);
|
||||
const int elems_per_out_group = elems_per_in_tensor / out_groups;
|
||||
```
|
||||
|
||||
deepspeed-0.11.2/csrc/transformer/inference/csrc/pt_binding.cpp
|
||||
lines 541-542 - change to:
|
||||
```
|
||||
{static_cast<unsigned>(hidden_dim * InferenceContext::Instance().GetMaxTokenLength()),
|
||||
static_cast<unsigned>(k * InferenceContext::Instance().GetMaxTokenLength()),
|
||||
```
|
||||
|
||||
lines 550-551 - change to:
|
||||
```
|
||||
{static_cast<unsigned>(hidden_dim * InferenceContext::Instance().GetMaxTokenLength()),
|
||||
static_cast<unsigned>(k * InferenceContext::Instance().GetMaxTokenLength()),
|
||||
```
|
||||
line 1581 - change to:
|
||||
```
|
||||
at::from_blob(intermediate_ptr, {input.size(0), input.size(1), static_cast<int64_t>(mlp_1_out_neurons)}, options);
|
||||
```
|
||||
|
||||
deepspeed-0.11.2/deepspeed/env_report.py
|
||||
line 10 - add:
|
||||
```
|
||||
import psutil
|
||||
```
|
||||
line 83 - 100 - change to:
|
||||
```
|
||||
def get_shm_size():
|
||||
try:
|
||||
temp_dir = os.getenv('TEMP') or os.getenv('TMP') or os.path.join(os.path.expanduser('~'), 'tmp')
|
||||
shm_stats = psutil.disk_usage(temp_dir)
|
||||
shm_size = shm_stats.total
|
||||
shm_hbytes = human_readable_size(shm_size)
|
||||
warn = []
|
||||
if shm_size < 512 * 1024**2:
|
||||
warn.append(
|
||||
f" {YELLOW} [WARNING] Shared memory size might be too small, consider increasing it. {END}"
|
||||
)
|
||||
# Add additional warnings specific to your use case if needed.
|
||||
return shm_hbytes, warn
|
||||
except Exception as e:
|
||||
return "UNKNOWN", [f"Error getting shared memory size: {e}"]
|
||||
```
|
||||
|
||||
10. While still in your command line with python environment enabled run:<br>
|
||||
```build_win.bat``` and wait 10-20 minutes.
|
||||
|
||||
11. Now `cd dist` to go into your dist folder and you can now `pip install deepspeed-YOURFILENAME.whl` (or whatever your WHL file is called).
|
||||
<br><br>
|
||||
**Removal** - If it became necessary to uninstall DeepSpeed, you can do so with `cmd_windows.bat` and then `pip uninstall deepspeed`<br><br>
|
||||
</details>
|
||||
|
||||
## 🟦 Running AllTalk as a standalone app
|
||||
AllTalk will run as a standalone app, as long as you install its requirements files into whatever Python environment you are using. You can follow the steps to install the AllTalk's requirements into whatever Python environment you wish. Because I dont know what Python environment you are wanting to use, I can only give you a loose set of installation instructions.
|
||||
|
||||
Please note, at time of writing, the TTS engine requires Python **3.9.x** to **3.11.x** [TTS Engine details here](https://pypi.org/project/TTS/). AllTalk and its requirements are tested on Python 3.11.x.
|
||||
|
||||
#### 🟦 (Option 1) I already have AllTalk installed as an extension of Text-generation-webui
|
||||
If you already have AllTalk as a extension of Text-generation-webui, and wish to run it as standalone, load Text-generation-webui's Python environment `cmd_windows.bat`, `./cmd_linux.sh`, `cmd_macos.sh` or `cmd_wsl.bat`, move into the AllTalk folder `cd extensions` > `cd alltalk_tts` and start AllTalk with `python script.py`. There is nothing beyond this you would need to do.
|
||||
|
||||
#### 🟦 (Option 2) I wish to do a custom install of AllTalk
|
||||
1) In your chosen disk location git clone this repository:<br><br>
|
||||
`git clone https://github.com/erew123/alltalk_tts`
|
||||
|
||||
2) Move into the **alltalk_tts** folder:<br><br>
|
||||
`cd alltalk_tts`
|
||||
|
||||
3) If you have a custom Python environment, now is the time to load it!
|
||||
|
||||
4) Install one of the two requirements files. Whichever one of the two is correct for your machine type:<br><br>
|
||||
**Nvidia graphics card machines** - `pip install -r requirements_nvidia.txt`<br><br>
|
||||
**Other machines (mac, amd etc)** - `pip install -r requirements_other.txt`
|
||||
|
||||
5) Start AllTalk with `python script.py`
|
||||
|
||||
Deepspeed and other such things can be installed. Please read the relevant instructions for those items, however, make the relevant changes to load your correct Python environment when installing any requirements files and starting AllTalk.
|
||||
|
||||
## 🟠 API Suite and JSON-CURL
|
||||
### 🟠Overview
|
||||
The Text-to-Speech (TTS) Generation API allows you to generate speech from text input using various configuration options. This API supports both character and narrator voices, providing flexibility for creating dynamic and engaging audio content.
|
||||
|
||||
#### 🟠 Ready Endpoint<br>
|
||||
Check if the Text-to-Speech (TTS) service is ready to accept requests.
|
||||
|
||||
- URL: `http://127.0.0.1:7851/api/ready`<br> - Method: `GET`<br>
|
||||
|
||||
`curl -X GET "http://127.0.0.1:7851/api/ready"`
|
||||
|
||||
Response: `Ready`
|
||||
|
||||
#### 🟠 Voices List Endpoint<br>
|
||||
Retrieve a list of available voices for generating speech.
|
||||
|
||||
- URL: `http://127.0.0.1:7851/api/voices`<br> - Method: `GET`<br>
|
||||
|
||||
`curl -X GET "http://127.0.0.1:7851/api/voices"`
|
||||
|
||||
JSON return: `{"voices": ["voice1.wav", "voice2.wav", "voice3.wav"]}`
|
||||
|
||||
#### 🟠 Preview Voice Endpoint
|
||||
Generate a preview of a specified voice with hardcoded settings.
|
||||
|
||||
- URL: `http://127.0.0.1:7851/api/previewvoice/`<br> - Method: `POST`<br> - Content-Type: `application/x-www-form-urlencoded`<br>
|
||||
|
||||
`curl -X POST "http://127.0.0.1:7851/api/previewvoice/" -F "voice=female_01.wav"`
|
||||
|
||||
Replace `female_01.wav` with the name of the voice sample you want to hear.
|
||||
|
||||
JSON return: `{"status": "generate-success", "output_file_path": "/path/to/outputs/api_preview_voice.wav", "output_file_url": "http://127.0.0.1:7851/audio/api_preview_voice.wav"}`
|
||||
|
||||
#### 🟠 Switching Model Endpoint<br>
|
||||
|
||||
- URL: `http://127.0.0.1:7851/api/reload`<br> - Method: `POST`<br><br>
|
||||
`curl -X POST "http://127.0.0.1:7851/api/reload?tts_method=API%20Local"`<br>
|
||||
`curl -X POST "http://127.0.0.1:7851/api/reload?tts_method=API%20TTS"`<br>
|
||||
`curl -X POST "http://127.0.0.1:7851/api/reload?tts_method=XTTSv2%20Local"`<br>
|
||||
|
||||
Switch between the 3 models respectively.
|
||||
|
||||
`curl -X POST "http://127.0.0.1:7851/api/reload?tts_method=XTTSv2%20FT"`<br>
|
||||
|
||||
If you have a finetuned model in `/models/trainedmodel/` (will error otherwise)
|
||||
|
||||
JSON return `{"status": "model-success"}`
|
||||
|
||||
#### 🟠 Switch DeepSpeed Endpoint<br>
|
||||
|
||||
- URL: `http://127.0.0.1:7851/api/deepspeed`<br> - Method: `POST`<br><br>
|
||||
`curl -X POST "http://127.0.0.1:7851/api/deepspeed?new_deepspeed_value=True"`
|
||||
|
||||
Replace True with False to disable DeepSpeed mode.
|
||||
|
||||
JSON return `{"status": "deepspeed-success"}`
|
||||
|
||||
#### 🟠 Switching Low VRAM Endpoint<br>
|
||||
|
||||
- URL: `http://127.0.0.1:7851/api/lowvramsetting`<br> - Method: `POST`<br><br>
|
||||
`curl -X POST "http://127.0.0.1:7851/api/lowvramsetting?new_low_vram_value=True"`
|
||||
|
||||
Replace True with False to disable Low VRAM mode.
|
||||
|
||||
JSON return `{"status": "lowvram-success"}`
|
||||
|
||||
### 🟠 TTS Generation Endpoint
|
||||
|
||||
- URL: `http://127.0.0.1:7851/api/tts-generate`<br> - Method: `POST`<br> - Content-Type: `application/x-www-form-urlencoded`<br>
|
||||
|
||||
### 🟠 Example command lines
|
||||
Standard TTS speech Example (standard text) generating a time-stamped file<br>
|
||||
|
||||
`curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=All of this is text spoken by the character. This is text not inside quotes, though that doesnt matter in the slightest" -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=false" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`<br>
|
||||
|
||||
Narrator Example (standard text) generating a time-stamped file
|
||||
|
||||
`curl -X POST "http://127.0.0.1:7851/api/tts-generate" -d "text_input=*This is text spoken by the narrator* \"This is text spoken by the character\". This is text not inside quotes." -d "text_filtering=standard" -d "character_voice_gen=female_01.wav" -d "narrator_enabled=true" -d "narrator_voice_gen=male_01.wav" -d "text_not_inside=character" -d "language=en" -d "output_file_name=myoutputfile" -d "output_file_timestamp=true" -d "autoplay=true" -d "autoplay_volume=0.8"`<br>
|
||||
|
||||
Note that if your text that needs to be generated contains double quotes you will need to escape them with `\"` (Please see the narrator example).
|
||||
|
||||
### 🟠 Request Parameters
|
||||
🟠 **text_input**: The text you want the TTS engine to produce. Use escaped double quotes for character speech and asterisks for narrator speech if using the narrator function. Example:
|
||||
|
||||
`-d "text_input=*This is text spoken by the narrator* \"This is text spoken by the character\". This is text not inside quotes."`
|
||||
|
||||
🟠 **text_filtering**: Filter for text. Options:
|
||||
|
||||
- **none** No filtering. Whatever is sent will go over to the TTS engine as raw text, which may result in some odd sounds with some special characters.<br>
|
||||
- **standard** Human-readable text and a basic level of filtering, just to clean up some special characters.<br>
|
||||
- **html** HTML content. Where you are using HTML entity's like "<br>
|
||||
|
||||
`-d "text_filtering=none"`<br>
|
||||
`-d "text_filtering=standard"`<br>
|
||||
`-d "text_filtering=html"`<br>
|
||||
|
||||
Example:
|
||||
|
||||
- **Standard Example**: `*This is text spoken by the narrator* "This is text spoken by the character" This is text not inside quotes.`<br>
|
||||
- **HTML Example**: `*This is text spoken by the narrator* "This is text spoken by the character" This is text not inside quotes.`<br>
|
||||
- **None**: `Will just pass whatever characters/text you send at it.`<br>
|
||||
|
||||
🟠 **character_voice_gen**: The WAV file name for the character's voice.<br>
|
||||
|
||||
`-d "character_voice_gen=female_01.wav"`
|
||||
|
||||
🟠 **narrator_enabled**: Enable or disable the narrator function. If true, minimum text filtering is set to standard. Anything between double quotes is considered the character's speech, and anything between asterisks is considered the narrator's speech.
|
||||
|
||||
`-d "narrator_enabled=true"`<br>
|
||||
`-d "narrator_enabled=false"`
|
||||
|
||||
🟠 **narrator_voice_gen**: The WAV file name for the narrator's voice.
|
||||
|
||||
`-d "narrator_voice_gen=male_01.wav"`
|
||||
|
||||
🟠 **text_not_inside**: Specify the handling of lines not inside double quotes or asterisks, for the narrator feature. Options:
|
||||
|
||||
- **character**: Treat as character speech.<br>
|
||||
- **narrator**: Treat as narrator speech.<br>
|
||||
|
||||
`-d "text_not_inside=character"`<br>
|
||||
`-d "text_not_inside=narrator"`
|
||||
|
||||
🟠 **language**: Choose the language for TTS. Options:
|
||||
|
||||
`ar` Arabic<br>
|
||||
`zh-cn` Chinese (Simplified)<br>
|
||||
`cs` Czech<br>
|
||||
`nl` Dutch<br>
|
||||
`en` English<br>
|
||||
`fr` French<br>
|
||||
`de` German<br>
|
||||
`hu` Hungarian<br>
|
||||
`it` Italian<br>
|
||||
`ja` Japanese<br>
|
||||
`ko` Korean<br>
|
||||
`pl` Polish<br>
|
||||
`pt` Portuguese<br>
|
||||
`ru` Russian<br>
|
||||
`es` Spanish<br>
|
||||
`tr` Turkish<br>
|
||||
|
||||
`-d "language=en"`<br>
|
||||
|
||||
🟠 **output_file_name**: The name of the output file (excluding the .wav extension).
|
||||
|
||||
`-d "output_file_name=myoutputfile"`<br>
|
||||
|
||||
🟠 **output_file_timestamp**: Add a timestamp to the output file name. If true, each file will have a unique timestamp; otherwise, the same file name will be overwritten each time you generate TTS.
|
||||
|
||||
`-d "output_file_timestamp=true"`<br>
|
||||
`-d "output_file_timestamp=false"`
|
||||
|
||||
🟠 **autoplay**: Enable or disable playing the generated TTS to your standard sound output device at time of TTS generation.
|
||||
|
||||
`-d "autoplay=true"`<br>
|
||||
`-d "autoplay=false"`
|
||||
|
||||
🟠 **autoplay_volume**: Set the autoplay volume. Should be between 0.1 and 1.0. Needs to be specified in the JSON request even if autoplay is false.
|
||||
|
||||
`-d "autoplay_volume=0.8"`
|
||||
|
||||
### 🟠 TTS Generation Response
|
||||
The API returns a JSON object with the following properties:
|
||||
|
||||
- **status** Indicates whether the generation was successful (generate-success) or failed (generate-failure).<br>
|
||||
- **output_file_path** The on-disk location of the generated WAV file.<br>
|
||||
- **output_file_url** The HTTP location for accessing the generated WAV file for browser playback.<br>
|
||||
- **output_cache_url** The HTTP location for accessing the generated WAV file as a pushed download.<br>
|
||||
|
||||
Example JSON TTS Generation Response:
|
||||
|
||||
`{"status":"generate-success","output_file_path":"C:\\text-generation-webui\\extensions\\alltalk_tts\\outputs\\myoutputfile_1704141936.wav","output_file_url":"http://127.0.0.1:7851/audio/myoutputfile_1704141936.wav","output_cache_url":"http://127.0.0.1:7851/audiocache/myoutputfile_1704141936.wav"}`
|
||||
|
||||
### 🔴 Future to-do list
|
||||
- Voice output within the command prompt/terminal (TBD).
|
||||
- Correct a few spelling mistakes in the documentation.
|
||||
- Possibly add some additional TTS engines (TBD).
|
||||
- Have a break!
|
||||
---
|
||||
@@ -0,0 +1,851 @@
|
||||
@echo off
|
||||
cd /D "%~dp0"
|
||||
setlocal enabledelayedexpansion
|
||||
@set "PATH=%PATH%;%SystemRoot%\system32"
|
||||
|
||||
:: Generate the ESC character
|
||||
for /F %%a in ('echo prompt $E ^| cmd') do set "ESC=%%a"
|
||||
|
||||
:: Standard Colors
|
||||
set "BLACK=%ESC%[30m"
|
||||
set "RED=%ESC%[31m"
|
||||
set "GREEN=%ESC%[32m"
|
||||
set "YELLOW=%ESC%[33m"
|
||||
set "BLUE=%ESC%[34m"
|
||||
set "MAGENTA=%ESC%[35m"
|
||||
set "CYAN=%ESC%[36m"
|
||||
set "WHITE=%ESC%[37m"
|
||||
set "L_BLACK=%ESC%[90m"
|
||||
set "L_RED=%ESC%[91m"
|
||||
set "L_GREEN=%ESC%[92m"
|
||||
set "L_YELLOW=%ESC%[93m"
|
||||
set "L_BLUE=%ESC%[94m"
|
||||
set "L_MAGENTA=%ESC%[95m"
|
||||
set "L_CYAN=%ESC%[96m"
|
||||
set "L_WHITE=%ESC%[97m"
|
||||
set "RESET=%ESC%[0m"
|
||||
|
||||
echo "%CD%"| findstr /C:" " >nul && echo. && echo %L_BLUE%ALLTALK WINDOWS SETUP UTILITY%RESET% && echo. && echo. && echo You are trying to install AllTalk in a folder that has a space in the folder path e.g. && echo. && echo C:\%L_RED%program files%RESET%\alltalk_tts && echo. && echo This causes errors with Conda and Python scripts. Please follow this link for reference: && echo. && echo %L_CYAN%https://docs.anaconda.com/free/working-with-conda/reference/faq/#installing-anaconda%RESET% && echo. && echo Please use a folder path that has no spaces in it e.g. && echo. && echo C:\myfiles\alltalk_tts\ && echo. && echo. && pause && goto :end
|
||||
|
||||
@rem Check for special characters in installation path
|
||||
set "SPCHARMESSAGE="WARNING: Special characters were detected in the installation path!" " This can cause the installation to fail!""
|
||||
echo "%CD%"| findstr /R /C:"[!#\$%&()\*+,;<=>?@\[\]\^`{|}~]" >nul && (
|
||||
call :PrintBigMessage %SPCHARMESSAGE%
|
||||
)
|
||||
set SPCHARMESSAGE=
|
||||
|
||||
@rem Check if curl is available
|
||||
curl --version >nul 2>&1
|
||||
if "%ERRORLEVEL%" NEQ "0" (
|
||||
echo curl is not available on this system. Please install curl then re-run the script https://curl.se/
|
||||
echo or perform a manual installation of a Conda Python environment.
|
||||
goto end
|
||||
)
|
||||
|
||||
:: Check for "-silent" install command-line argument
|
||||
if "%1"=="-silent" goto InstallCustomStandalone
|
||||
|
||||
:MainMenu
|
||||
cls
|
||||
echo.
|
||||
echo %L_BLUE%ALLTALK WINDOWS SETUP UTILITY%RESET%
|
||||
echo.
|
||||
echo INSTALLATION TYPE
|
||||
echo 1) I am using AllTalk as part of %L_GREEN%Text-generation-webui%RESET%
|
||||
echo 2) I am using AllTalk as a %L_BLUE%Standalone Application%RESET%
|
||||
echo.
|
||||
echo 9) %L_RED%Exit/Quit%RESET%
|
||||
echo.
|
||||
echo.
|
||||
set /p UserOption=" Enter your choice: "
|
||||
|
||||
if "%UserOption%"=="1" goto WebUIMenu
|
||||
if "%UserOption%"=="2" goto StandaloneMenu
|
||||
if "%UserOption%"=="9" goto End
|
||||
goto MainMenu
|
||||
|
||||
:WebUIMenu
|
||||
cls
|
||||
echo.
|
||||
echo %L_BLUE%TEXT-GENERATION-WEBUI SETUP%RESET%
|
||||
echo.
|
||||
echo Please ensure you have started your Text-generation-webui Python
|
||||
echo environment. If you have NOT done this, please run %L_GREEN%cmd_windows.bat%RESET%
|
||||
echo in the %L_GREEN%text-generation-webui%RESET% folder and then re-run this script.
|
||||
echo.
|
||||
echo BASE REQUIREMENTS
|
||||
echo 1) Apply/Re-Apply the requirements for %L_GREEN%Text-generation-webui%RESET%.
|
||||
echo.
|
||||
echo OPTIONAL
|
||||
echo 2) Git Pull the latest AllTalk updates from Github
|
||||
echo.
|
||||
echo DEEPSPEED FOR %L_YELLOW=%PyTorch 2.1.x%RESET%
|
||||
echo 4) Install DeepSpeed v11.2 for CUDA %L_GREEN%11.8%RESET% and Python-3.11.x and %L_YELLOW%PyTorch 2.1.x%RESET%.
|
||||
echo 5) Install DeepSpeed v11.2 for CUDA %L_GREEN%12.1%RESET% and Python-3.11.x and %L_YELLOW%PyTorch 2.1.x%RESET%.
|
||||
echo.
|
||||
echo DEEPSPEED FOR %L_YELLOW=%PyTorch 2.2.x%RESET% (March 2024 builds of Text-gen-webui and later)
|
||||
echo 6) Install DeepSpeed v14.0 for CUDA %L_GREEN%12.1%RESET% and Python-3.11.x and %L_YELLOW%PyTorch 2.2.x%RESET%.
|
||||
echo 7) Install DeepSpeed v14.0 for CUDA %L_GREEN%11.8%RESET% and Python-3.11.x and %L_YELLOW%PyTorch 2.2.x%RESET%.
|
||||
echo.
|
||||
echo U) Uninstall DeepSpeed.
|
||||
echo.
|
||||
echo. OTHER
|
||||
echo 8) Generate a diagnostics file.
|
||||
echo.
|
||||
echo 9) %L_RED%Exit/Quit%RESET%
|
||||
echo.
|
||||
set /p WebUIOption="Enter your choice: "
|
||||
if "%WebUIOption%"=="1" goto InstallNvidiaTextGen
|
||||
if "%WebUIOption%"=="2" goto TGGitpull
|
||||
if "%WebUIOption%"=="4" goto InstallDeepSpeed118TextGen
|
||||
if "%WebUIOption%"=="5" goto InstallDeepSpeed121TextGen
|
||||
if "%WebUIOption%"=="6" goto InstallDeepSpeed121TextGenPytorch221
|
||||
if "%WebUIOption%"=="7" goto InstallDeepSpeed118TextGenPytorch221
|
||||
if "%WebUIOption%"=="U" goto UnInstallDeepSpeed
|
||||
if "%WebUIOption%"=="u" goto UnInstallDeepSpeed
|
||||
if "%WebUIOption%"=="8" goto GenerateDiagsTextGen
|
||||
if "%WebUIOption%"=="9" goto End
|
||||
goto WebUIMenu
|
||||
|
||||
:StandaloneMenu
|
||||
cls
|
||||
echo.
|
||||
echo %L_BLUE%ALLTALK STANDALONE APPLICATION SETUP%RESET%
|
||||
echo.
|
||||
echo BASE REQUIREMENTS
|
||||
echo 1) Install AllTalk as a Standalone Application
|
||||
echo.
|
||||
echo OPTIONAL
|
||||
echo 2) Git Pull the latest AllTalk updates from Github
|
||||
echo 3) Re-Apply/Update the requirements file
|
||||
echo 4) Delete AllTalk's custom Python environment
|
||||
echo 5) Purge the PIP cache
|
||||
echo.
|
||||
echo. OTHER
|
||||
echo 8) Generate a diagnostics file
|
||||
echo.
|
||||
echo 9) %L_RED%Exit/Quit%RESET%
|
||||
echo.
|
||||
set /p StandaloneOption="Enter your choice: "
|
||||
if "%StandaloneOption%"=="1" goto InstallCustomStandalone
|
||||
if "%StandaloneOption%"=="2" goto STGitpull
|
||||
if "%StandaloneOption%"=="3" goto STReapplyrequirements
|
||||
if "%StandaloneOption%"=="4" goto STDeleteCustomStandalone
|
||||
if "%StandaloneOption%"=="5" goto STPurgepipcache
|
||||
if "%StandaloneOption%"=="8" goto GenerateDiagsStandalone
|
||||
if "%StandaloneOption%"=="9" goto EndStandalone
|
||||
goto StandaloneMenu
|
||||
|
||||
:InstallNvidiaTextGen
|
||||
pip install -r system\requirements\requirements_textgen.txt
|
||||
pip install -r system\requirements\requirements_textgen2.txt
|
||||
pip install -r system\requirements\requirements_parler.txt
|
||||
echo ** Faiss **
|
||||
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y pytorch::faiss-cpu
|
||||
echo ** FFmpeg **
|
||||
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y -c conda-forge "ffmpeg=*=*gpl*"
|
||||
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y -c conda-forge "ffmpeg=*=h*_*" --no-deps
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo There was an error installing the requirements.
|
||||
echo Have you started your Text-gen-webui Python environment
|
||||
echo with cmd_{yourOS} before running atsetup.bat?
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
Echo.
|
||||
echo.
|
||||
Echo Requirements installed successfully.
|
||||
Echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
|
||||
:TGGitpull
|
||||
git pull
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo There was an error pulling from Github.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
Echo.
|
||||
echo AllTalk Updated from Github. Please re-apply
|
||||
echo the latest requirements file. Option 1
|
||||
Echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
|
||||
:InstallDeepSpeed118TextGen
|
||||
echo Downloading DeepSpeed...
|
||||
curl -LO https://github.com/erew123/alltalk_tts/releases/download/deepspeed/deepspeed-0.11.2+cuda118-cp311-cp311-win_amd64.whl
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo Failed to download DeepSpeed wheel file.
|
||||
echo Please check your internet connection or try again later.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
echo DeepSpeed wheel file downloaded successfully.
|
||||
echo Installing DeepSpeed...
|
||||
pip install deepspeed-0.11.2+cuda118-cp311-cp311-win_amd64.whl
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo Failed to install DeepSpeed.
|
||||
echo Please check if the wheel file is compatible with your system.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
Echo.
|
||||
echo.
|
||||
Echo DeepSpeed installed successfully.
|
||||
Echo.
|
||||
del deepspeed-0.11.2+cuda118-cp311-cp311-win_amd64.whl
|
||||
pause
|
||||
goto WebUIMenu
|
||||
|
||||
:InstallDeepSpeed121TextGen
|
||||
echo Downloading DeepSpeed...
|
||||
curl -LO https://github.com/erew123/alltalk_tts/releases/download/deepspeed/deepspeed-0.11.2+cuda121-cp311-cp311-win_amd64.whl
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo Failed to download DeepSpeed wheel file.
|
||||
echo Please check your internet connection or try again later.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
echo DeepSpeed wheel file downloaded successfully.
|
||||
echo Installing DeepSpeed...
|
||||
pip install deepspeed-0.11.2+cuda121-cp311-cp311-win_amd64.whl
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo Failed to install DeepSpeed.
|
||||
echo Please check if the wheel file is compatible with your system.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
Echo.
|
||||
echo.
|
||||
Echo DeepSpeed installed successfully.
|
||||
Echo.
|
||||
del deepspeed-0.11.2+cuda121-cp311-cp311-win_amd64.whl
|
||||
pause
|
||||
goto WebUIMenu
|
||||
|
||||
:InstallDeepSpeed121TextGenPytorch221
|
||||
echo Downloading DeepSpeed...
|
||||
curl -LO https://github.com/erew123/alltalk_tts/releases/download/DeepSpeed-14.0/deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo Failed to download DeepSpeed wheel file.
|
||||
echo Please check your internet connection or try again later.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
echo DeepSpeed wheel file downloaded successfully.
|
||||
echo Installing DeepSpeed...
|
||||
pip install deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo Failed to install DeepSpeed.
|
||||
echo Please check if the wheel file is compatible with your system.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
Echo.
|
||||
echo.
|
||||
Echo DeepSpeed installed successfully.
|
||||
Echo.
|
||||
del deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
|
||||
pause
|
||||
goto WebUIMenu
|
||||
|
||||
:InstallDeepSpeed118TextGenPytorch221
|
||||
echo Downloading DeepSpeed...
|
||||
curl -LO https://github.com/erew123/alltalk_tts/releases/download/DeepSpeed-14.0/deepspeed-0.14.0+cu118-cp311-cp311-win_amd64.whl
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo Failed to download DeepSpeed wheel file.
|
||||
echo Please check your internet connection or try again later.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
echo DeepSpeed wheel file downloaded successfully.
|
||||
echo Installing DeepSpeed...
|
||||
pip install deepspeed-0.14.0+cu118-cp311-cp311-win_amd64.whl
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo Failed to install DeepSpeed.
|
||||
echo Please check if the wheel file is compatible with your system.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
Echo.
|
||||
echo.
|
||||
Echo DeepSpeed installed successfully.
|
||||
Echo.
|
||||
del deepspeed-0.14.0+cu118-cp311-cp311-win_amd64.whl
|
||||
pause
|
||||
goto WebUIMenu
|
||||
|
||||
:UnInstallDeepSpeed
|
||||
pip uninstall deepspeed
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo There was an error uninstalling DeepSpeed.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
echo.
|
||||
Echo DeepSpeed uninstalled successfully.
|
||||
Echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
|
||||
:GenerateDiagsTextGen
|
||||
Python diagnostics.py
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo There was an error running diagnostics. Have you correctly started your
|
||||
echo Text-generation-webui Python environment with %L_GREEN%cmd_windows.bat%RESET%?
|
||||
echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
)
|
||||
Echo.
|
||||
echo.
|
||||
Echo Diagnostics.log generated. Please scroll up to look over the log.
|
||||
Echo.
|
||||
pause
|
||||
goto WebUIMenu
|
||||
|
||||
:InstallCustomStandalone
|
||||
set PATH=%PATH%;%SystemRoot%\system32
|
||||
|
||||
@rem Check if curl is available
|
||||
curl --version >nul 2>&1
|
||||
if "%ERRORLEVEL%" NEQ "0" (
|
||||
echo curl is not available on this system. Please install curl then re-run the script https://curl.se/
|
||||
echo or perform a manual installation of a Conda Python environment.
|
||||
goto end
|
||||
)
|
||||
echo "%CD%"| findstr /C:" " >nul && echo This script relies on Miniconda which can not be silently installed under a path with spaces. Please correct your folder names and re-try installation. && goto exit
|
||||
|
||||
@rem Check for special characters in installation path
|
||||
set "SPCHARMESSAGE=WARNING: Special characters were detected in the installation path! This can cause the installation to fail!"
|
||||
echo "%CD%"| findstr /R /C:"[!#\$%&()\*+,;<=>?@\[\]\^`{|}~]" >nul && (
|
||||
call :PrintBigMessage %SPCHARMESSAGE%
|
||||
)
|
||||
set SPCHARMESSAGE=
|
||||
|
||||
@rem fix failed install when installing to a separate drive
|
||||
set TMP=%cd%\alltalk_environment
|
||||
set TEMP=%cd%\alltalk_environment
|
||||
|
||||
@rem deactivate existing conda envs as needed to avoid conflicts
|
||||
(call conda deactivate && call conda deactivate && call conda deactivate) 2>nul
|
||||
|
||||
@rem config
|
||||
set INSTALL_DIR=%cd%\alltalk_environment
|
||||
set CONDA_ROOT_PREFIX=%cd%\alltalk_environment\conda
|
||||
set INSTALL_ENV_DIR=%cd%\alltalk_environment\env
|
||||
set MINICONDA_DOWNLOAD_URL=https://repo.anaconda.com/miniconda/Miniconda3-py311_24.4.0-0-Windows-x86_64.exe
|
||||
set conda_exists=F
|
||||
|
||||
@rem figure out whether git and conda need to be installed
|
||||
call "%CONDA_ROOT_PREFIX%\_conda.exe" --version >nul 2>&1
|
||||
if "%ERRORLEVEL%" EQU "0" (
|
||||
set conda_exists=T
|
||||
call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%"
|
||||
goto RunScript
|
||||
)
|
||||
|
||||
@rem download and install conda if not installed
|
||||
echo Downloading Miniconda from %MINICONDA_DOWNLOAD_URL% to %INSTALL_DIR%\miniconda_installer.exe
|
||||
mkdir "%INSTALL_DIR%"
|
||||
call curl -Lk "%MINICONDA_DOWNLOAD_URL%" > "%INSTALL_DIR%\miniconda_installer.exe" || ( echo. && echo Miniconda failed to download. && goto end )
|
||||
echo Installing Miniconda to %CONDA_ROOT_PREFIX%
|
||||
start /wait "" "%INSTALL_DIR%\miniconda_installer.exe" /InstallationType=JustMe /NoShortcuts=1 /AddToPath=0 /RegisterPython=0 /NoRegistry=1 /S /D=%CONDA_ROOT_PREFIX%
|
||||
echo Miniconda version:
|
||||
call "%CONDA_ROOT_PREFIX%\_conda.exe" --version || ( echo. && echo Miniconda not found. && goto end )
|
||||
|
||||
@rem create the installer env
|
||||
echo Packages to install: %PACKAGES_TO_INSTALL%
|
||||
call "%CONDA_ROOT_PREFIX%\_conda.exe" create --no-shortcuts -y -k --prefix "%INSTALL_ENV_DIR%" python=3.11.9 || ( echo. && echo Conda environment creation failed. && goto end )
|
||||
|
||||
@rem check if conda environment was actually created
|
||||
if not exist "%INSTALL_ENV_DIR%\python.exe" ( echo. && echo Conda environment is empty. && goto end )
|
||||
|
||||
@rem activate installer env
|
||||
call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%" || ( echo. && echo Miniconda hook not found. && goto end )
|
||||
rem Install required packages
|
||||
|
||||
:install_pytorch
|
||||
echo ** Installing PyTorch 2.2.1 **
|
||||
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y pytorch==2.2.1 torchvision==0.17.1 torchaudio==2.2.1 pytorch-cuda=12.1 -c pytorch -c nvidia
|
||||
echo.
|
||||
if errorlevel 1 (
|
||||
echo PyTorch installation failed, errorlevel was %errorlevel%. There should be a Conda
|
||||
echo error message above with a code and/or text explanation of the issue. Please note
|
||||
echo the error code to help with diagnotics.
|
||||
echo.
|
||||
echo Generally speaking though, errors could be caused by:
|
||||
echo.
|
||||
echo 1. Internet connection issues/unable to download PyTorch from Conda's website.
|
||||
echo 2. Disk space related issues - Check your disk space on this drive.
|
||||
echo 3. Incorrect or missing Conda environment - Restart installation.
|
||||
echo 4. Permissions issues - Check you have enough rights on this system.
|
||||
echo 5. Firewall or proxy settings blocking access to Conda's servers.
|
||||
echo 6. Antivirus or security software interference.
|
||||
echo 7. Other issues not mentioned above.
|
||||
echo.
|
||||
echo Known errors are in the AllTalk Github Wiki > Error Messages Help/Support
|
||||
echo.
|
||||
choice /C YN /M "Do you want to retry the Pytorch installation?"
|
||||
if errorlevel 2 goto End
|
||||
if errorlevel 1 goto install_pytorch
|
||||
) else (
|
||||
echo PyTorch installation was successful.
|
||||
echo.
|
||||
)
|
||||
|
||||
:install_faiss
|
||||
echo ** Installing Faiss **
|
||||
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y pytorch::faiss-cpu
|
||||
echo.
|
||||
if errorlevel 1 (
|
||||
echo Faiss installation failed, errorlevel was %errorlevel%. There should be a Conda
|
||||
echo error message above with a code and/or text explanation of the issue. Please note
|
||||
echo the error code to help with diagnotics.
|
||||
echo.
|
||||
echo Generally speaking though, errors could be caused by:
|
||||
echo.
|
||||
echo 1. Internet connection issues/unable to download Faiss from Conda's website.
|
||||
echo 2. Disk space related issues - Check your disk space on this drive.
|
||||
echo 3. Incorrect or missing Conda environment - Restart installation.
|
||||
echo 4. Permissions issues - Check you have enough rights on this system.
|
||||
echo 5. Firewall or proxy settings blocking access to Conda's servers.
|
||||
echo 6. Antivirus or security software interference.
|
||||
echo 7. Your Conda policy is set to Strict see the Github Wiki for help with this.
|
||||
echo 8. Other issues not mentioned above.
|
||||
echo.
|
||||
echo Known errors are in the AllTalk Github Wiki > Error Messages Help/Support
|
||||
echo.
|
||||
choice /C YN /M "Do you want to retry the Faiss installation?"
|
||||
if errorlevel 2 goto End
|
||||
if errorlevel 1 goto install_faiss
|
||||
) else (
|
||||
echo Faiss installation was successful.
|
||||
echo.
|
||||
)
|
||||
|
||||
:install_ffmpeg
|
||||
echo ** Installing FFmpeg **
|
||||
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y -c conda-forge "ffmpeg=*=*gpl*"
|
||||
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" install -y -c conda-forge "ffmpeg=*=h*_*" --no-deps
|
||||
echo.
|
||||
if errorlevel 1 (
|
||||
echo FFmpeg installation failed, errorlevel was %errorlevel%. There should be a Conda
|
||||
echo error message above with a code and/or text explanation of the issue. Please note
|
||||
echo the error code to help with diagnotics.
|
||||
echo.
|
||||
echo Generally speaking though, errors could be caused by:
|
||||
echo.
|
||||
echo 1. Internet connection issues/unable to download FFmpeg from Conda's website.
|
||||
echo 2. Disk space related issues - Check your disk space on this drive.
|
||||
echo 3. Incorrect or missing Conda environment - Restart installation.
|
||||
echo 4. Permissions issues - Check you have enough rights on this system.
|
||||
echo 5. Firewall or proxy settings blocking access to Conda's servers.
|
||||
echo 6. Antivirus or security software interference.
|
||||
echo 7. Your Conda policy is set to Strict see the Github Wiki for help with this.
|
||||
echo 8. Other issues not mentioned above.
|
||||
echo.
|
||||
echo Known errors are in the AllTalk Github Wiki > Error Messages Help/Support
|
||||
echo.
|
||||
choice /C YN /M "Do you want to retry the FFmpeg installation?"
|
||||
if errorlevel 2 goto End
|
||||
if errorlevel 1 goto install_ffmpeg
|
||||
) else (
|
||||
echo FFmpeg installation was successful.
|
||||
echo.
|
||||
)
|
||||
|
||||
echo ** Requirements file **
|
||||
pip install -r system\requirements\requirements_standalone.txt
|
||||
echo.
|
||||
|
||||
:update_gradio
|
||||
echo ** Updating Gradio **
|
||||
pip install --upgrade gradio==4.44.1
|
||||
echo.
|
||||
if errorlevel 1 (
|
||||
echo Gradio update failed, errorlevel was %errorlevel%. There should be an error
|
||||
echo message above with a code and/or text explanation of the issue. Please note
|
||||
echo the error code to help with diagnotics.
|
||||
echo.
|
||||
echo Generally speaking though, errors could be caused by:
|
||||
echo.
|
||||
echo 1. Internet connection issues/unable to download Gradio from PyPI.
|
||||
echo 2. Disk space related issues - Check your disk space on this drive.
|
||||
echo 3. Incorrect or missing Python environment - Restart installation.
|
||||
echo 4. Permissions issues - Check you have enough rights on this system.
|
||||
echo 5. Firewall or proxy settings blocking access to PyPI servers.
|
||||
echo 6. Antivirus or security software interference.
|
||||
echo 7. Other issues not mentioned above.
|
||||
echo.
|
||||
echo Known errors are in the AllTalk Github Wiki > Error Messages Help/Support
|
||||
echo.
|
||||
choice /C YN /M "Do you want to retry the Gradio update?"
|
||||
if errorlevel 2 goto End
|
||||
if errorlevel 1 goto update_gradio
|
||||
) else (
|
||||
echo Gradio update was successful.
|
||||
echo.
|
||||
)
|
||||
|
||||
:install_deepspeed
|
||||
echo ** Downloading DeepSpeed **
|
||||
curl -LO https://github.com/erew123/alltalk_tts/releases/download/DeepSpeed-14.0/deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
|
||||
echo.
|
||||
if errorlevel 1 (
|
||||
echo DeepSpeed download failed, errorlevel was %errorlevel%. There should be an error
|
||||
echo message above with a code and/or text explanation of the issue. Please note
|
||||
echo the error code to help with diagnotics.
|
||||
echo.
|
||||
echo Generally speaking though, errors could be caused by:
|
||||
echo.
|
||||
echo 1. Internet connection issues/unable to reach GitHub.
|
||||
echo 2. Disk space related issues - Check your disk space on this drive.
|
||||
echo 3. Permissions issues - Check you have enough rights on this system.
|
||||
echo 4. Firewall or proxy settings blocking access to GitHub.
|
||||
echo 5. Antivirus or security software interference.
|
||||
echo 6. The file may have been moved or deleted from the GitHub repository.
|
||||
echo 7. Other issues not mentioned above.
|
||||
echo.
|
||||
echo Known errors are in the AllTalk Github Wiki > Error Messages Help/Support
|
||||
echo.
|
||||
choice /C YN /M "Do you want to retry the DeepSpeed download?"
|
||||
if errorlevel 2 goto End
|
||||
if errorlevel 1 goto install_deepspeed
|
||||
) else (
|
||||
echo DeepSpeed download was successful.
|
||||
echo.
|
||||
)
|
||||
|
||||
echo ** Installing DeepSpeed **
|
||||
pip install deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
|
||||
echo.
|
||||
if errorlevel 1 (
|
||||
echo DeepSpeed installation failed, errorlevel was %errorlevel%. There should be an error
|
||||
echo message above with a code and/or text explanation of the issue. Please note
|
||||
echo the error code to help with diagnotics. General issues may be:
|
||||
echo.
|
||||
echo Generally speaking though, errors could be caused by:
|
||||
echo.
|
||||
echo 1. The DeepSpeed wheel file was not downloaded successfully in the previous step.
|
||||
echo 2. Microsoft C++ development tools for Python are not installed correctly.
|
||||
echo 3. Disk space related issues - Check your disk space on this drive.
|
||||
echo 4. Incorrect or missing Python environment - Restart installation.
|
||||
echo 5. Permissions issues - Check you have enough rights on this system.
|
||||
echo 6. Antivirus or security software interference.
|
||||
echo 7. Other issues not mentioned above.
|
||||
echo.
|
||||
echo Known errors are in the AllTalk Github Wiki > Error Messages Help/Support
|
||||
echo.
|
||||
echo Please ensure you have followed the instructions to install the Microsoft C++ development
|
||||
echo tools for Python, which is detailed on the AllTalk GitHub page.
|
||||
echo.
|
||||
choice /C YN /M "Do you want to retry the DeepSpeed installation?"
|
||||
if errorlevel 2 goto End
|
||||
if errorlevel 1 goto install_deepspeed
|
||||
) else (
|
||||
echo DeepSpeed installation was successful.
|
||||
del deepspeed-0.14.0+ce78a63-cp311-cp311-win_amd64.whl
|
||||
echo.
|
||||
)
|
||||
|
||||
echo ** Installing Parler **
|
||||
pip install -r system\requirements\requirements_parler.txt
|
||||
echo Clean Environment
|
||||
call "%CONDA_ROOT_PREFIX%\Scripts\conda.exe" clean --all --force-pkgs-dirs -y
|
||||
echo.
|
||||
|
||||
echo.
|
||||
echo Installation process completed.
|
||||
|
||||
@rem Create start_environment.bat to run AllTalk environment
|
||||
echo @echo off > start_environment.bat
|
||||
echo cd /D "%~dp0" >> start_environment.bat
|
||||
echo set CONDA_ROOT_PREFIX=%cd%\alltalk_environment\conda >> start_environment.bat
|
||||
echo set INSTALL_ENV_DIR=%cd%\alltalk_environment\env >> start_environment.bat
|
||||
echo call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%" >> start_environment.bat
|
||||
@rem Create start_alltalk.bat to run AllTalk
|
||||
echo @echo off > start_alltalk.bat
|
||||
echo cd /D "%~dp0" >> start_alltalk.bat
|
||||
echo set CONDA_ROOT_PREFIX=%cd%\alltalk_environment\conda >> start_alltalk.bat
|
||||
echo set INSTALL_ENV_DIR=%cd%\alltalk_environment\env >> start_alltalk.bat
|
||||
echo call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%" >> start_alltalk.bat
|
||||
echo call python script.py >> start_alltalk.bat
|
||||
@rem Create start_finetune.bat to run AllTalk
|
||||
echo @echo off > start_finetune.bat
|
||||
echo cd /D "%~dp0" >> start_finetune.bat
|
||||
echo set CONDA_ROOT_PREFIX=%cd%\alltalk_environment\conda >> start_finetune.bat
|
||||
echo set INSTALL_ENV_DIR=%cd%\alltalk_environment\env >> start_finetune.bat
|
||||
echo call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%" >> start_finetune.bat
|
||||
echo call python finetune.py >> start_finetune.bat
|
||||
echo @echo off > start_diagnostics.bat
|
||||
echo cd /D "%~dp0" >> start_diagnostics.bat
|
||||
echo set CONDA_ROOT_PREFIX=%cd%\alltalk_environment\conda >> start_diagnostics.bat
|
||||
echo set INSTALL_ENV_DIR=%cd%\alltalk_environment\env >> start_diagnostics.bat
|
||||
echo call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%" >> start_diagnostics.bat
|
||||
echo call python diagnostics.py >> start_diagnostics.bat
|
||||
Echo.
|
||||
Echo Run %L_YELLOW%start_alltalk.bat%RESET% to start AllTalk.
|
||||
Echo Run %L_YELLOW%start_diagnostics.bat%RESET% to start the diagnostics.
|
||||
Echo Run %L_YELLOW%start_finetune.bat%RESET% to start Finetuning.
|
||||
Echo Run %L_YELLOW%start_environment.bat%RESET% to start the AllTalk Python environment.
|
||||
Echo.
|
||||
Echo Documentation is built into the Gradio interface. Please explore the documentation for
|
||||
Echo tips, troubleshooing and explanations as most common questions are answered there.
|
||||
Echo.
|
||||
if "%1"=="-silent" goto End
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
|
||||
:STDeleteCustomStandalone
|
||||
@rem Check if the alltalk_environment directory exists
|
||||
if not exist "%cd%\alltalk_environment\" (
|
||||
echo.
|
||||
echo %L_GREEN%alltalk_environment%RESET% directory does not exist. No need to delete.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
@rem Check if a Conda environment is active
|
||||
if not defined CONDA_PREFIX goto NoCondaEnvDeleteCustomStandalone
|
||||
@rem Deactivate Conda environment if it's active
|
||||
Echo Exiting the Conda Environment. Please run %L_GREEN%atsetup.bat%RESET% again and delete the environment.
|
||||
call conda deactivate
|
||||
:NoCondaEnvDeleteCustomStandalone
|
||||
echo Deleting "alltalk_environment". Please wait.
|
||||
rd /s /q "alltalk_environment"
|
||||
del start_alltalk.bat
|
||||
del start_environment.bat
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo Failed to delete alltalk_environment folder.
|
||||
echo Please make sure it is not in use and try again.
|
||||
echo Worst case, manually delete the folder called 'alltalk_environment'
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
echo.
|
||||
Echo.
|
||||
echo Environment %L_GREEN%alltalk_environment%RESET% deleted. Please set up the environment again.
|
||||
Echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
|
||||
:GenerateDiagsStandalone
|
||||
cd /D "%~dp0"
|
||||
set CONDA_ROOT_PREFIX=%cd%\alltalk_environment\conda
|
||||
set INSTALL_ENV_DIR=%cd%\alltalk_environment\env
|
||||
@rem Check if the Conda environment exists
|
||||
if not exist "%INSTALL_ENV_DIR%\python.exe" (
|
||||
echo.
|
||||
echo The Conda environment at "%INSTALL_ENV_DIR%" does not exist.
|
||||
echo Please install the environment before proceeding.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
@rem Attempt to activate the Conda environment
|
||||
call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%"
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo Failed to activate the Conda environment.
|
||||
echo Please check your installation and try again.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
@rem Run diagnostics
|
||||
Python diagnostics.py
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo There was an error running diagnostics.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
Echo.
|
||||
Echo.
|
||||
Echo Diagnostics.log generated. Please scroll up to look over the log.
|
||||
Echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
|
||||
:STReapplyrequirements
|
||||
cd /D "%~dp0"
|
||||
set CONDA_ROOT_PREFIX=%cd%\alltalk_environment\conda
|
||||
set INSTALL_ENV_DIR=%cd%\alltalk_environment\env
|
||||
@rem Check if the Conda environment exists
|
||||
if not exist "%INSTALL_ENV_DIR%\python.exe" (
|
||||
echo.
|
||||
echo The Conda environment at "%INSTALL_ENV_DIR%" does not exist.
|
||||
echo Please install the environment before proceeding.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
@rem Attempt to activate the Conda environment
|
||||
call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%"
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo Failed to activate the Conda environment.
|
||||
echo Please check your installation and try again.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
@rem Run Reapply requirements
|
||||
echo.
|
||||
echo Re-installing requirements. If you have problems after this, it may indicate
|
||||
echo that the custom Python environment is damaged in some way. As such it may be
|
||||
echo best to delete the custom Python environment and re-run the entire setup
|
||||
echo routine again to rebuild AllTalk's Python environment. Please press a key
|
||||
echo to continue re-applying the Requirements for a standalone installation.
|
||||
echo.
|
||||
pause
|
||||
goto install_faiss
|
||||
|
||||
:STPurgepipcache
|
||||
cd /D "%~dp0"
|
||||
set CONDA_ROOT_PREFIX=%cd%\alltalk_environment\conda
|
||||
set INSTALL_ENV_DIR=%cd%\alltalk_environment\env
|
||||
@rem Check if the Conda environment exists
|
||||
if not exist "%INSTALL_ENV_DIR%\python.exe" (
|
||||
echo.
|
||||
echo The Conda environment at "%INSTALL_ENV_DIR%" does not exist.
|
||||
echo Please install the environment before proceeding.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
@rem Attempt to activate the Conda environment
|
||||
call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%"
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo Failed to activate the Conda environment.
|
||||
echo Please check your installation and try again.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
@rem Clear the PIP cache
|
||||
echo.
|
||||
echo Purging the PIP cache of downloaded files.
|
||||
echo.
|
||||
pip cache purge
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo There was an error.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
Echo.
|
||||
Echo The PIP cache has been purged.
|
||||
Echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
|
||||
:STGitpull
|
||||
cd /D "%~dp0"
|
||||
set CONDA_ROOT_PREFIX=%cd%\alltalk_environment\conda
|
||||
set INSTALL_ENV_DIR=%cd%\alltalk_environment\env
|
||||
@rem Check if the Conda environment exists
|
||||
if not exist "%INSTALL_ENV_DIR%\python.exe" (
|
||||
echo.
|
||||
echo The Conda environment at "%INSTALL_ENV_DIR%" does not exist.
|
||||
echo Please install the environment before proceeding.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
@rem Attempt to activate the Conda environment
|
||||
call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%"
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo Failed to activate the Conda environment.
|
||||
echo Please check your installation and try again.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
@rem Pull from Github
|
||||
echo.
|
||||
echo Pulling the latest updates. Please re-apply
|
||||
echo the latest requirements file. Option 3
|
||||
echo.
|
||||
git pull
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo There was an error pulling from Github.
|
||||
echo Press any key to return to the menu.
|
||||
echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
)
|
||||
Echo.
|
||||
echo AllTalk Updated from Github. Please re-apply
|
||||
echo the latest requirements file. Option 3
|
||||
Echo.
|
||||
pause
|
||||
goto StandaloneMenu
|
||||
|
||||
:EndStandalone
|
||||
echo Exiting AllTalk Setup Utility...
|
||||
echo.
|
||||
Echo Remember, after installation you can....
|
||||
Echo.
|
||||
Echo Run %L_YELLOW%start_alltalk.bat%RESET% to start AllTalk.
|
||||
Echo Run %L_YELLOW%start_finetune.bat%RESET% to start Finetuning.
|
||||
Echo Run %L_YELLOW%start_environment.bat%RESET% to start the AllTalk Python environment.
|
||||
Echo Run %L_YELLOW%start_diagnostics.bat%RESET% to start generate a diagnostics file.
|
||||
Echo.
|
||||
Echo Documentation is built into the Gradio interface. Please explore the documentation for
|
||||
Echo tips, troubleshooing and explanations as most common questions are answered there.
|
||||
exit /b
|
||||
|
||||
:End
|
||||
echo Exiting AllTalk Setup Utility...
|
||||
Echo.
|
||||
Echo Documentation is built into the Gradio interface and the AllTalk Wiki page on Github.
|
||||
Echo Please explore the documentation for tips, troubleshooing and explanations on the most
|
||||
Echo common questions.
|
||||
exit /b
|
||||
@@ -0,0 +1,562 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ANSI color codes
|
||||
RED='\033[1;31m'
|
||||
GREEN='\033[1;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[1;34m'
|
||||
CYAN='\033[1;36m'
|
||||
MAGENTA='\033[1;35m'
|
||||
WHITE='\033[1;37m'
|
||||
L_RED='\033[1;91m'
|
||||
L_GREEN='\033[1;92m'
|
||||
L_YELLOW='\033[1;93m'
|
||||
L_BLUE='\033[1;94m'
|
||||
L_CYAN='\033[1;96m'
|
||||
L_MAGENTA='\033[1;95m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Navigate to the script's directory
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Function to check if curl is installed
|
||||
check_curl() {
|
||||
if ! command -v curl &> /dev/null; then
|
||||
echo "curl is not available on this system. Please install curl then re-run the script https://curl.se/"
|
||||
echo "or perform a manual installation of a Conda Python environment."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if the current directory path contains a space
|
||||
containsSpace=false
|
||||
currentPath=$(pwd)
|
||||
if echo "$currentPath" | grep -q ' '; then
|
||||
containsSpace=true
|
||||
fi
|
||||
|
||||
if [ "$containsSpace" = true ]; then
|
||||
echo
|
||||
echo -e " ${L_BLUE}ALLTALK LINUX SETUP UTILITY${NC}"
|
||||
echo
|
||||
echo
|
||||
echo -e " You are trying to install AllTalk in a folder that has a space in the"
|
||||
echo -e " folder path e.g."
|
||||
echo
|
||||
echo -e " /home/${L_RED}program files${NC}/alltalk_tts"
|
||||
echo
|
||||
echo -e " This causes errors with Conda and Python scripts. Please follow this"
|
||||
echo -e " link for reference:"
|
||||
echo
|
||||
echo -e " ${L_CYAN}https://docs.anaconda.com/free/working-with-conda/reference/faq/#installing-anaconda${NC}"
|
||||
echo
|
||||
echo -e " Please use a folder path that has no spaces in it e.g."
|
||||
echo
|
||||
echo -e " /home/myfiles/alltalk_tts/"
|
||||
echo
|
||||
echo
|
||||
read -p "Press Enter to continue..."
|
||||
exit 1
|
||||
else
|
||||
# Continue with the main menu
|
||||
echo "Continue with the main menu."
|
||||
fi
|
||||
|
||||
# Main Menu
|
||||
main_menu() {
|
||||
while true; do
|
||||
clear
|
||||
echo
|
||||
echo -e " ${L_BLUE}ALLTALK LINUX SETUP UTILITY${NC}"
|
||||
echo
|
||||
echo " INSTALLATION TYPE"
|
||||
echo -e " 1) I am using AllTalk as part of ${L_GREEN}Text-generation-webui${NC}"
|
||||
echo -e " 2) I am using AllTalk as a ${L_GREEN}Standalone Application${NC}"
|
||||
echo
|
||||
echo -e " 9)${L_RED} Exit/Quit${NC}"
|
||||
echo
|
||||
read -p " Enter your choice: " user_option
|
||||
|
||||
case $user_option in
|
||||
1) webui_menu ;;
|
||||
2) standalone_menu ;;
|
||||
9) exit 0 ;;
|
||||
*) echo "Invalid option"; sleep 2 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Text-generation-webui Menu
|
||||
webui_menu() {
|
||||
while true; do
|
||||
clear
|
||||
echo
|
||||
echo -e " ${L_BLUE}TEXT-GENERATION-WEBUI SETUP${NC}"
|
||||
echo
|
||||
echo -e " Please ensure you have started your Text-generation-webui Python"
|
||||
echo -e " environment. If you have NOT done this, please run ${L_GREEN}/cmd_linux.sh${NC}"
|
||||
echo -e " in the ${L_GREEN}text-generation-webui${NC} folder and then re-run this script."
|
||||
echo
|
||||
echo " BASE REQUIREMENTS"
|
||||
echo -e " 1) Apply/Re-Apply the requirements for an ${L_GREEN}Text-generation-webui${NC}"
|
||||
echo
|
||||
echo " OPTIONAL"
|
||||
echo " 2) Git Pull the latest AllTalk updates from Github"
|
||||
echo
|
||||
echo " DEEPSPEED"
|
||||
echo " 4) Install DeepSpeed."
|
||||
echo " 5) Uninstall DeepSpeed."
|
||||
echo
|
||||
echo " OTHER"
|
||||
echo " 6) Generate a diagnostics file."
|
||||
echo
|
||||
echo -e " 9)${L_RED} Exit/Quit${NC}"
|
||||
echo
|
||||
read -p " Enter your choice: " webui_option
|
||||
|
||||
case $webui_option in
|
||||
1) install_nvidia_textgen ;;
|
||||
2) tg_gitpull ;;
|
||||
4) install_deepspeed ;;
|
||||
5) uninstall_deepspeed ;;
|
||||
6) generate_diagnostics_textgen ;;
|
||||
9) exit 0 ;;
|
||||
*) echo "Invalid option"; sleep 2 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
install_nvidia_textgen() {
|
||||
local requirements_file="system/requirements/requirements_textgen.txt"
|
||||
echo " Installing Requirements from $requirements_file..."
|
||||
if ! pip install -r "$requirements_file"; then
|
||||
echo
|
||||
echo " There was an error pulling from Github."
|
||||
echo " Please check the output for details."
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
echo
|
||||
echo " Requirements installed successfully."
|
||||
|
||||
echo " Installing additional requirements..."
|
||||
if ! pip install -r system/requirements/requirements_textgen2.txt; then
|
||||
echo
|
||||
echo " There was an error installing additional requirements."
|
||||
echo " Please check the output for details."
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
|
||||
if ! conda install -y pytorch::faiss-cpu; then
|
||||
echo
|
||||
echo " There was an error installing faiss-cpu."
|
||||
echo " Please check the output for details."
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
|
||||
if ! conda install -y conda-forge::ffmpeg; then
|
||||
echo
|
||||
echo " There was an error installing ffmpeg."
|
||||
echo " Please check the output for details."
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
|
||||
echo
|
||||
echo " Additional requirements installed successfully."
|
||||
echo
|
||||
echo -e " To install ${L_YELLOW}DeepSpeed${NC} on Linux, there are additional"
|
||||
echo -e " steps required. Please see the Github or documentation on DeepSeed."
|
||||
echo
|
||||
read -p " Press any key to continue. " -n 1
|
||||
echo
|
||||
}
|
||||
|
||||
|
||||
tg_gitpull() {
|
||||
echo
|
||||
if ! git pull; then
|
||||
echo
|
||||
echo " There was an error installing the requirements."
|
||||
echo " Please check the output for details."
|
||||
echo
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
echo
|
||||
echo " AllTalk Updated from Github. Please re-apply"
|
||||
echo " the latest requirements file. (Option 1)"
|
||||
echo
|
||||
read -p " Press any key to continue. " -n 1
|
||||
echo
|
||||
}
|
||||
|
||||
# Function to install DeepSpeed
|
||||
install_deepspeed() {
|
||||
clear
|
||||
echo
|
||||
echo -e " ${L_BLUE}DEEPSPEED INSTALLATION REQUIREMENTS${NC}"
|
||||
echo
|
||||
echo -e " - Please go see this link as the instructions are there.${NC}"
|
||||
echo -e " The instructions there are making it as simple as posisble.${NC}"
|
||||
echo -e " ${L_GREEN}https://github.com/erew123/alltalk_tts/releases/tag/DeepSpeed-14.2-Linux${NC}"
|
||||
echo
|
||||
read -p " Have you completed all the above steps? (y/n): " confirm
|
||||
|
||||
if [ "$confirm" != "y" ]; then
|
||||
echo -e " ${RED}DeepSpeed installation cannot proceed without completing the prerequisites.${NC}"
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e "\n ${GREEN}Proceeding with DeepSpeed installation...${NC}"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e " ${RED}There was an error installing DeepSpeed.${NC}"
|
||||
return
|
||||
fi
|
||||
|
||||
echo -e " ${GREEN}DeepSpeed installed successfully.${NC}"
|
||||
read -p " Press any key to continue. " -n 1
|
||||
echo
|
||||
}
|
||||
|
||||
uninstall_deepspeed() {
|
||||
echo "Uninstalling DeepSpeed..."
|
||||
pip uninstall -y deepspeed
|
||||
if [ $? -ne 0 ]; then
|
||||
echo
|
||||
echo " There was an error uninstalling DeepSpeed."
|
||||
echo
|
||||
echo " Press any key to return to the menu."
|
||||
read -n 1
|
||||
return
|
||||
fi
|
||||
echo
|
||||
echo " DeepSpeed uninstalled successfully."
|
||||
echo
|
||||
echo " Press any key to continue."
|
||||
read -n 1
|
||||
}
|
||||
|
||||
generate_diagnostics_textgen() {
|
||||
# Run diagnostics
|
||||
if ! python diagnostics.py; then
|
||||
echo
|
||||
echo " There was an error running diagnostics."
|
||||
echo
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
echo
|
||||
echo " Diagnostics log file generated successfully."
|
||||
echo " Please see diagnostics.log"
|
||||
echo
|
||||
read -p " Press any key to continue. " -n 1
|
||||
echo
|
||||
}
|
||||
|
||||
# Standalone Menu
|
||||
standalone_menu() {
|
||||
while true; do
|
||||
clear
|
||||
echo
|
||||
echo -e " ${L_BLUE}ALLTALK STANDALONE APPLICATION SETUP${NC}"
|
||||
echo
|
||||
echo " BASE REQUIREMENTS"
|
||||
echo " 1) Install AllTalk as a Standalone Application"
|
||||
echo
|
||||
echo " OPTIONAL"
|
||||
echo " 2) Git Pull the latest AllTalk updates from Github"
|
||||
echo " 3) Re-Apply/Update the requirements file"
|
||||
echo " 4) Delete AllTalk's custom Python environment"
|
||||
echo " 5) Purge the PIP cache"
|
||||
echo
|
||||
echo " OTHER"
|
||||
echo " 8) Generate a diagnostics file"
|
||||
echo
|
||||
echo -e " 9)${L_RED} Exit/Quit${NC}"
|
||||
echo
|
||||
read -p " Enter your choice: " standalone_option
|
||||
|
||||
case $standalone_option in
|
||||
1) install_custom_standalone ;;
|
||||
2) gitpull_standalone ;;
|
||||
3) reapply_standalone ;;
|
||||
4) delete_custom_standalone ;;
|
||||
5) pippurge_standalone ;;
|
||||
8) generate_diagnostics_standalone ;;
|
||||
9) exit 0 ;;
|
||||
*) echo "Invalid option"; sleep 2 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
install_custom_standalone() {
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
if [[ "$(pwd)" =~ " " ]]; then
|
||||
echo "This script relies on Miniconda which can not be silently installed under a path with spaces."
|
||||
exit
|
||||
fi
|
||||
|
||||
# Deactivate existing conda envs as needed to avoid conflicts
|
||||
{ conda deactivate && conda deactivate && conda deactivate; } 2> /dev/null
|
||||
|
||||
OS_ARCH=$(uname -m)
|
||||
case "${OS_ARCH}" in
|
||||
x86_64*) OS_ARCH="x86_64" ;;
|
||||
arm64* | aarch64*) OS_ARCH="aarch64" ;;
|
||||
*) echo "Unknown system architecture: $OS_ARCH! This script runs only on x86_64 or arm64" && exit ;;
|
||||
esac
|
||||
|
||||
# Config
|
||||
INSTALL_DIR="$(pwd)/alltalk_environment"
|
||||
CONDA_ROOT_PREFIX="${INSTALL_DIR}/conda"
|
||||
INSTALL_ENV_DIR="${INSTALL_DIR}/env"
|
||||
MINICONDA_DOWNLOAD_URL="https://repo.anaconda.com/miniconda/Miniconda3-py311_24.4.0-0-Linux-${OS_ARCH}.sh"
|
||||
if [ ! -x "${CONDA_ROOT_PREFIX}/bin/conda" ]; then
|
||||
echo "Downloading Miniconda from $MINICONDA_DOWNLOAD_URL to ${INSTALL_DIR}/miniconda_installer.sh"
|
||||
mkdir -p "${INSTALL_DIR}"
|
||||
curl -L "${MINICONDA_DOWNLOAD_URL}" -o "${INSTALL_DIR}/miniconda_installer.sh"
|
||||
chmod +x "${INSTALL_DIR}/miniconda_installer.sh"
|
||||
bash "${INSTALL_DIR}/miniconda_installer.sh" -b -p "${CONDA_ROOT_PREFIX}"
|
||||
echo "Miniconda installed."
|
||||
fi
|
||||
|
||||
if [ ! -d "${INSTALL_ENV_DIR}" ]; then
|
||||
"${CONDA_ROOT_PREFIX}/bin/conda" create -y --prefix "${INSTALL_ENV_DIR}" -c conda-forge python=3.11.9
|
||||
echo "Conda environment created at ${INSTALL_ENV_DIR}."
|
||||
fi
|
||||
|
||||
# Activate the environment and install requirements
|
||||
source "${CONDA_ROOT_PREFIX}/etc/profile.d/conda.sh"
|
||||
conda activate "${INSTALL_ENV_DIR}"
|
||||
# pip install torch==2.2.1+cu121 torchaudio==2.2.1+cu121 --upgrade --force-reinstall --extra-index-url https://download.pytorch.org/whl/cu121 --no-cache-dir
|
||||
conda install -y pytorch=2.2.1 torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia
|
||||
conda install -y nvidia/label/cuda-12.1.0::cuda-toolkit=12.1
|
||||
conda install -y pytorch::faiss-cpu
|
||||
conda install -y -c conda-forge "ffmpeg=*=*gpl*"
|
||||
conda install -y -c conda-forge "ffmpeg=*=h*_*" --no-deps
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
echo " Fix Nvidia's broken symlinks in the /env/lib folder"
|
||||
echo
|
||||
# Define the environment path
|
||||
env_path="${INSTALL_ENV_DIR}/lib"
|
||||
|
||||
echo " Installing additional requirements."
|
||||
echo
|
||||
pip install -r system/requirements/requirements_standalone.txt
|
||||
curl -LO https://github.com/erew123/alltalk_tts/releases/download/DeepSpeed-14.0/deepspeed-0.14.2+cu121torch2.2-cp311-cp311-manylinux_2_24_x86_64.whl
|
||||
pip install --upgrade gradio==4.44.1
|
||||
echo Installing DeepSpeed...
|
||||
pip install deepspeed-0.14.2+cu121torch2.2-cp311-cp311-manylinux_2_24_x86_64.whl
|
||||
rm deepspeed-0.14.2+cu121torch2.2-cp311-cp311-manylinux_2_24_x86_64.whl
|
||||
pip install -r system/requirements/requirements_parler.txt
|
||||
conda clean --all --force-pkgs-dirs -y
|
||||
# Create start_environment.sh to run AllTalk
|
||||
cat << EOF > start_environment.sh
|
||||
#!/bin/bash
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
if [[ "$(pwd)" =~ " " ]]; then echo This script relies on Miniconda which can not be silently installed under a path with spaces. && exit; fi
|
||||
# deactivate existing conda envs as needed to avoid conflicts
|
||||
{ conda deactivate && conda deactivate && conda deactivate; } 2> /dev/null
|
||||
# config
|
||||
CONDA_ROOT_PREFIX="$(pwd)/alltalk_environment/conda"
|
||||
INSTALL_ENV_DIR="$(pwd)/alltalk_environment/env"
|
||||
# environment isolation
|
||||
export PYTHONNOUSERSITE=1
|
||||
unset PYTHONPATH
|
||||
unset PYTHONHOME
|
||||
export CUDA_PATH="$INSTALL_ENV_DIR"
|
||||
export CUDA_HOME="$CUDA_PATH"
|
||||
# activate env
|
||||
bash --init-file <(echo "source \"$CONDA_ROOT_PREFIX/etc/profile.d/conda.sh\" && conda activate \"$INSTALL_ENV_DIR\"")
|
||||
EOF
|
||||
cat << EOF > start_alltalk.sh
|
||||
#!/bin/bash
|
||||
source "${CONDA_ROOT_PREFIX}/etc/profile.d/conda.sh"
|
||||
conda activate "${INSTALL_ENV_DIR}"
|
||||
python script.py
|
||||
EOF
|
||||
cat << EOF > start_finetune.sh
|
||||
#!/bin/bash
|
||||
export TRAINER_TELEMETRY=0
|
||||
source "${CONDA_ROOT_PREFIX}/etc/profile.d/conda.sh"
|
||||
conda activate "${INSTALL_ENV_DIR}"
|
||||
python finetune.py
|
||||
EOF
|
||||
cat << EOF > start_diagnostics.sh
|
||||
#!/bin/bash
|
||||
source "${CONDA_ROOT_PREFIX}/etc/profile.d/conda.sh"
|
||||
conda activate "${INSTALL_ENV_DIR}"
|
||||
python diagnostics.py
|
||||
EOF
|
||||
chmod +x start_alltalk.sh
|
||||
chmod +x start_environment.sh
|
||||
chmod +x start_finetune.sh
|
||||
chmod +x start_diagnostics.sh
|
||||
echo
|
||||
echo
|
||||
echo -e " Run ${L_YELLOW}./start_alltalk.sh${NC} to start AllTalk."
|
||||
echo -e " Run ${L_YELLOW}./start_diagnostics.sh${NC} to start Diagnostics."
|
||||
echo -e " Run ${L_YELLOW}./start_finetune.sh${NC} to start Finetuning."
|
||||
echo -e " Run ${L_YELLOW}./start_environment.sh${NC} to start the AllTalk Python environment."
|
||||
echo
|
||||
echo -e " To install ${L_YELLOW}DeepSpeed${NC} on Linux, there are additional"
|
||||
echo -e " steps required. Please see the Github or documentation on DeepSeed."
|
||||
echo
|
||||
read -p " Press any key to continue. " -n 1
|
||||
}
|
||||
|
||||
delete_custom_standalone() {
|
||||
local env_dir="$PWD/alltalk_environment"
|
||||
# Check if the alltalk_environment directory exists
|
||||
if [ ! -d "$env_dir" ]; then
|
||||
echo " \"$env_dir\" directory does not exist. No need to delete."
|
||||
read -p " Press any key to continue. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
# Check if a Conda environment is active and deactivate it
|
||||
if [ -n "$CONDA_PREFIX" ]; then
|
||||
echo " Exiting the Conda environment. You may need to start ./atstart.sh again"
|
||||
conda deactivate
|
||||
fi
|
||||
echo "Deleting \"$env_dir\". Please wait."
|
||||
rm -rf "$env_dir"
|
||||
if [ -d "$env_dir" ]; then
|
||||
echo " Failed to delete \"$env_dir\" folder."
|
||||
echo " Please make sure it is not in use and try again."
|
||||
else
|
||||
echo " Environment \"$env_dir\" deleted successfully."
|
||||
fi
|
||||
read -p " Press any key to continue. " -n 1
|
||||
echo
|
||||
}
|
||||
|
||||
generate_diagnostics_standalone() {
|
||||
local env_dir="$PWD/alltalk_environment"
|
||||
local conda_root_prefix="${env_dir}/conda"
|
||||
local install_env_dir="${env_dir}/env"
|
||||
if [ ! -d "${install_env_dir}" ]; then
|
||||
echo
|
||||
echo " The Conda environment at '${install_env_dir}' does not exist."
|
||||
echo " Please install the environment before proceeding."
|
||||
echo
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
source "${conda_root_prefix}/etc/profile.d/conda.sh"
|
||||
conda activate "${install_env_dir}"
|
||||
if ! python diagnostics.py; then
|
||||
echo
|
||||
echo " There was an error running diagnostics."
|
||||
echo
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
echo
|
||||
echo " Diagnostics completed successfully."
|
||||
read -p " Press any key to continue. " -n 1
|
||||
echo
|
||||
}
|
||||
|
||||
gitpull_standalone() {
|
||||
local env_dir="$PWD/alltalk_environment"
|
||||
local conda_root_prefix="${env_dir}/conda"
|
||||
local install_env_dir="${env_dir}/env"
|
||||
if [ ! -d "${install_env_dir}" ]; then
|
||||
echo
|
||||
echo " The Conda environment at '${install_env_dir}' does not exist."
|
||||
echo " Please install the environment before proceeding."
|
||||
echo
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
source "${conda_root_prefix}/etc/profile.d/conda.sh"
|
||||
conda activate "${install_env_dir}"
|
||||
if ! git pull; then
|
||||
echo
|
||||
echo " There was an error pulling from Github."
|
||||
echo
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
echo
|
||||
echo " AllTalk Updated from Github. Please re-apply."
|
||||
echo " the latest requirements file. (Option 3)"
|
||||
echo
|
||||
read -p " Press any key to continue. " -n 1
|
||||
echo
|
||||
}
|
||||
|
||||
pippurge_standalone() {
|
||||
if ! pip cache purge; then
|
||||
echo
|
||||
echo " There was an error."
|
||||
echo
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
echo
|
||||
echo " The PIP cache has been purged."
|
||||
echo
|
||||
read -p " Press any key to continue. " -n 1
|
||||
echo
|
||||
}
|
||||
|
||||
reapply_standalone() {
|
||||
local env_dir="$PWD/alltalk_environment"
|
||||
local conda_root_prefix="${env_dir}/conda"
|
||||
local install_env_dir="${env_dir}/env"
|
||||
if [ ! -d "${install_env_dir}" ]; then
|
||||
echo
|
||||
echo " The Conda environment at '${install_env_dir}' does not exist."
|
||||
echo " Please install the environment before proceeding."
|
||||
echo
|
||||
read -p " Press any key to return to the menu. " -n 1
|
||||
echo
|
||||
return
|
||||
fi
|
||||
source "${conda_root_prefix}/etc/profile.d/conda.sh"
|
||||
conda activate "${install_env_dir}"
|
||||
echo
|
||||
echo " Downloading and installing PyTorch. This step can take a long time"
|
||||
echo " depending on your internet connection and hard drive speed. Please"
|
||||
echo " be patient."
|
||||
pip install torch>=2.2.1+cu121 torchaudio>=2.2.1+cu121 --upgrade --force-reinstall --extra-index-url https://download.pytorch.org/whl/cu121
|
||||
echo
|
||||
echo " Installing additional requirements."
|
||||
echo
|
||||
pip install -r system/requirements/requirements_standalone.txt
|
||||
pip install -r system/requirements/requirements_parler.txt
|
||||
echo
|
||||
echo " Requirements have been re-applied/updated."
|
||||
echo
|
||||
read -p " Press any key to continue. " -n 1
|
||||
echo
|
||||
}
|
||||
|
||||
# Check for "-silent" install command-line argument
|
||||
if [[ $1 == "-silent" ]]; then
|
||||
install_custom_standalone
|
||||
else
|
||||
# Start the main menu
|
||||
main_menu
|
||||
fi
|
||||
@@ -0,0 +1,414 @@
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from filelock import FileLock
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Callable, MutableSequence
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, AliasGenerator, AliasChoices, Field
|
||||
|
||||
class AlltalkConfigTheme(BaseModel):
|
||||
# Map 'class' to 'clazz' and vice versa
|
||||
model_config = ConfigDict(
|
||||
alias_generator=AliasGenerator(
|
||||
validation_alias=lambda field_name:
|
||||
{
|
||||
"clazz": AliasChoices("clazz", "class"),
|
||||
}.get(field_name, None),
|
||||
serialization_alias=lambda field_name: "class" if field_name == "clazz" else field_name,
|
||||
)
|
||||
)
|
||||
file: str | None = None
|
||||
clazz: str = "gradio/base"
|
||||
|
||||
class AlltalkConfigRvcSettings(BaseModel):
|
||||
rvc_enabled: bool = False
|
||||
rvc_char_model_file: str = "Disabled"
|
||||
rvc_narr_model_file: str = "Disabled"
|
||||
split_audio: bool = True
|
||||
autotune: bool = False
|
||||
pitch: int = 0
|
||||
filter_radius: int = 3
|
||||
index_rate: float = 0.75
|
||||
rms_mix_rate: int = 1
|
||||
protect: float = 0.5
|
||||
hop_length: int = 130
|
||||
f0method: str = "fcpe"
|
||||
embedder_model: str = "hubert"
|
||||
training_data_size: int = 45000
|
||||
model_cache_size: int = 1
|
||||
|
||||
class AlltalkConfigTgwUi(BaseModel):
|
||||
tgwui_activate_tts: bool = True
|
||||
tgwui_autoplay_tts: bool = True
|
||||
tgwui_narrator_enabled: str = "false"
|
||||
tgwui_non_quoted_text_is: str = "character"
|
||||
tgwui_deepspeed_enabled: bool = False
|
||||
tgwui_language: str = "English"
|
||||
tgwui_lowvram_enabled: bool = False
|
||||
tgwui_pitch_set: int = 0
|
||||
tgwui_temperature_set: float = 0.75
|
||||
tgwui_repetitionpenalty_set: int = 10
|
||||
tgwui_generationspeed_set: int = 1
|
||||
tgwui_narrator_voice: str = "female_01.wav"
|
||||
tgwui_show_text: bool = True
|
||||
tgwui_character_voice: str = "female_01.wav"
|
||||
tgwui_rvc_char_voice: str = "Disabled"
|
||||
tgwui_rvc_char_pitch: int = 0
|
||||
tgwui_rvc_narr_voice: str = "Disabled"
|
||||
tgwui_rvc_narr_pitch: int = 0
|
||||
|
||||
class AlltalkConfigApiDef(BaseModel):
|
||||
api_port_number: int = 7851
|
||||
api_allowed_filter: str = "[^a-zA-Z0-9\\s.,;:!?\\-\\'\"$\\u0400-\\u04FF\\u00C0-\\u017F\\u0150\\u0151\\u0170\\u0171\\u011E\\u011F\\u0130\\u0131\\u0900-\\u097F\\u2018\\u2019\\u201C\\u201D\\u3001\\u3002\\u3040-\\u309F\\u30A0-\\u30FF\\u4E00-\\u9FFF\\u3400-\\u4DBF\\uF900-\\uFAFF\\u0600-\\u06FF\\u0750-\\u077F\\uFB50-\\uFDFF\\uFE70-\\uFEFF\\uAC00-\\uD7A3\\u1100-\\u11FF\\u3130-\\u318F\\uFF01\\uFF0c\\uFF1A\\uFF1B\\uFF1F]"
|
||||
api_length_stripping: int = 3
|
||||
api_max_characters: int = 2000
|
||||
api_use_legacy_api: bool = False
|
||||
api_legacy_ip_address: str = "127.0.0.1"
|
||||
api_text_filtering: str = "standard"
|
||||
api_narrator_enabled: str = "false"
|
||||
api_text_not_inside: str = "character"
|
||||
api_language: str = "en"
|
||||
api_output_file_name: str = "myoutputfile"
|
||||
api_output_file_timestamp: bool = True
|
||||
api_autoplay: bool = False
|
||||
api_autoplay_volume: float = 0.5
|
||||
|
||||
class AlltalkConfigDebug(BaseModel):
|
||||
debug_transcode: bool = False
|
||||
debug_tts: bool = False
|
||||
debug_openai: bool = False
|
||||
debug_concat: bool = False
|
||||
debug_tts_variables: bool = False
|
||||
debug_rvc: bool = False
|
||||
debug_func: bool = False
|
||||
debug_api: bool = False
|
||||
debug_fullttstext: bool = False
|
||||
debug_narrator: bool = False
|
||||
debug_gradio_IP: bool = False
|
||||
debug_transcribe: bool = False
|
||||
debug_proxy: bool = False
|
||||
|
||||
class AlltalkConfigGradioPages(BaseModel):
|
||||
Generate_Help_page: bool = True
|
||||
Voice2RVC_page: bool = True
|
||||
TTS_Generator_page: bool = True
|
||||
TTS_Engines_Settings_page: bool = True
|
||||
alltalk_documentation_page: bool = True
|
||||
api_documentation_page: bool = True
|
||||
|
||||
class AlltalkAvailableEngine(BaseModel):
|
||||
name: str = ""
|
||||
selected_model: str = ""
|
||||
|
||||
class AlltalkConfigProxyEndpoint(BaseModel):
|
||||
enabled: bool = False
|
||||
external_port: int = 0
|
||||
external_ip: str = "0.0.0.0"
|
||||
cert_name: str = ""
|
||||
|
||||
class AlltalkConfigProxySettings(BaseModel):
|
||||
proxy_enabled: bool = False
|
||||
start_on_startup: bool = False
|
||||
gradio_endpoint: AlltalkConfigProxyEndpoint = AlltalkConfigProxyEndpoint(external_port=444)
|
||||
api_endpoint: AlltalkConfigProxyEndpoint = AlltalkConfigProxyEndpoint(external_port=443)
|
||||
cert_validation: bool = True
|
||||
logging_enabled: bool = True
|
||||
log_level: str = "INFO"
|
||||
|
||||
class AbstractJsonConfig(ABC):
|
||||
def __init__(self, config_path: Path | str, file_check_interval: int):
|
||||
super().__init__()
|
||||
self.__delegate = None
|
||||
self.__config_path = Path(config_path) if type(config_path) is str else config_path
|
||||
self.__last_read_time = 0 # Track when we last read the file
|
||||
self.__file_check_interval = file_check_interval
|
||||
|
||||
def get_config_path(self):
|
||||
return self.__config_path
|
||||
|
||||
def reload(self):
|
||||
self._load_config()
|
||||
return self
|
||||
|
||||
def _reload_on_change(self):
|
||||
# Check if config file has been modified and reload if needed
|
||||
if self.__file_check_interval != 0 and time.time() - self.__last_read_time >= self.__file_check_interval:
|
||||
try:
|
||||
most_recent_modification = self.get_config_path().stat().st_mtime
|
||||
if most_recent_modification > self.__last_read_time:
|
||||
self.reload()
|
||||
except Exception as e:
|
||||
print(f"Error checking config file: {e}")
|
||||
|
||||
def _load_config(self):
|
||||
self.__last_read_time = self.get_config_path().stat().st_mtime
|
||||
def __load():
|
||||
with open(self.get_config_path(), "r") as configfile:
|
||||
json_string = configfile.read()
|
||||
# The delegate is the actual config loaded:
|
||||
self.__delegate = self._handle_loaded_config(json_string)
|
||||
self.__with_lock_and_backup(self.get_config_path(), False, __load)
|
||||
|
||||
def _save_file(self, path: Path | None | str, default=None, indent=4):
|
||||
file_path = (Path(path) if type(path) is str else path) if path is not None else self.get_config_path()
|
||||
|
||||
def __save():
|
||||
with open(file_path, 'w') as file:
|
||||
file.write(self.__delegate.model_dump_json(indent=indent, by_alias=True))
|
||||
|
||||
self.__with_lock_and_backup(file_path, True, __save)
|
||||
|
||||
def __with_lock_and_backup(self, path: Path, backup: bool, callable: Callable[[], None]):
|
||||
lock_path = path.with_suffix('.lock')
|
||||
backup_path = None
|
||||
try:
|
||||
with FileLock(lock_path):
|
||||
# Create backup:
|
||||
if path.exists() and backup:
|
||||
backup_path = path.with_suffix('.backup')
|
||||
shutil.copy(path, backup_path)
|
||||
|
||||
try:
|
||||
callable()
|
||||
except Exception as e:
|
||||
if backup_path and backup_path.exists():
|
||||
shutil.copy(backup_path, path)
|
||||
raise Exception(f"Failed to save config: {e}")
|
||||
finally:
|
||||
# Cleanup lock and backup files:
|
||||
if lock_path.exists():
|
||||
lock_path.unlink()
|
||||
|
||||
if backup and backup_path and backup_path.exists():
|
||||
backup_path.unlink()
|
||||
|
||||
@abstractmethod
|
||||
def _handle_loaded_config(self, json_string: str):
|
||||
pass
|
||||
|
||||
def to_dict(self):
|
||||
return self.__delegate.model_dump(by_alias=True)
|
||||
|
||||
# Delegation to the loaded config data
|
||||
@property
|
||||
def _xtra(self): return [o for o in dir(self.__delegate) if not o.startswith('_')]
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key in self._xtra: return getattr(self.__delegate, key)
|
||||
raise AttributeError(key)
|
||||
|
||||
def __getattribute__(self, key):
|
||||
if key.startswith("_"):
|
||||
# Private and protected attributes always are on self:
|
||||
return super().__getattribute__(key)
|
||||
if key in self._xtra:
|
||||
# Delegate attribute
|
||||
return getattr(self.__delegate, key)
|
||||
# Get own attribute:
|
||||
return super().__getattribute__(key)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key.startswith("_"):
|
||||
# Private and protected attributes always are on self:
|
||||
super().__setattr__(key, value)
|
||||
elif key in dir(self.__delegate):
|
||||
# Delegate attribute
|
||||
setattr(self.__delegate, key, value)
|
||||
else:
|
||||
# Set own attribute:
|
||||
super().__setattr__(key, value)
|
||||
|
||||
def __dir__(self):
|
||||
def custom_dir(c, add): return dir(type(c)) + list(c.__dict__.keys()) + add
|
||||
return custom_dir(self, self._xtra)
|
||||
|
||||
class AlltalkNewEnginesConfigFields:
|
||||
engines_available: MutableSequence[AlltalkAvailableEngine] = Field(default_factory=list)
|
||||
|
||||
class AlltalkNewEnginesConfigModel(BaseModel, AlltalkNewEnginesConfigFields):
|
||||
|
||||
def get_engine_names_available(self):
|
||||
return [engine.name for engine in self.engines_available]
|
||||
|
||||
def get_engines_matching(self, condition: Callable[[AlltalkAvailableEngine], bool]):
|
||||
return [x for x in self.engines_available if condition(x)]
|
||||
|
||||
class AlltalkNewEnginesConfig(AbstractJsonConfig, AlltalkNewEnginesConfigFields):
|
||||
__instance = None
|
||||
__this_dir = Path(__file__).parent.resolve()
|
||||
|
||||
def __init__(self, config_path: Path | str = os.path.join(__this_dir, "system", "tts_engines", "new_engines.json")):
|
||||
super().__init__(config_path, 5)
|
||||
self._load_config()
|
||||
|
||||
@staticmethod
|
||||
def get_instance():
|
||||
if AlltalkNewEnginesConfig.__instance is None:
|
||||
AlltalkNewEnginesConfig.__instance = AlltalkNewEnginesConfig()
|
||||
AlltalkNewEnginesConfig.__instance._reload_on_change()
|
||||
return AlltalkNewEnginesConfig.__instance
|
||||
|
||||
def _handle_loaded_config(self, json_string: str):
|
||||
return AlltalkNewEnginesConfigModel.model_validate_json(json_string)
|
||||
|
||||
class AlltalkTTSEnginesConfigFields:
|
||||
engines_available: MutableSequence[AlltalkAvailableEngine] = Field(default_factory=list)
|
||||
engine_loaded: str = ""
|
||||
selected_model: str = ""
|
||||
|
||||
class AlltalkTTSEnginesConfigModel(BaseModel, AlltalkTTSEnginesConfigFields):
|
||||
|
||||
def get_engine_names_available(self):
|
||||
return [engine.name for engine in self.engines_available]
|
||||
|
||||
def is_valid_engine(self, engine_name):
|
||||
return engine_name in self.get_engine_names_available()
|
||||
|
||||
def change_engine(self, requested_engine):
|
||||
if requested_engine == self.engine_loaded:
|
||||
return self
|
||||
for engine in self.engines_available:
|
||||
if engine.name == requested_engine:
|
||||
self.engine_loaded = requested_engine
|
||||
self.selected_model = engine.selected_model
|
||||
return self
|
||||
return self
|
||||
|
||||
class AlltalkTTSEnginesConfig(AbstractJsonConfig, AlltalkTTSEnginesConfigFields):
|
||||
__instance = None
|
||||
__this_dir = Path(__file__).parent.resolve()
|
||||
|
||||
def __init__(self, config_path: Path | str = os.path.join(__this_dir, "system", "tts_engines", "tts_engines.json")):
|
||||
super().__init__(config_path, 5)
|
||||
self._load_config()
|
||||
|
||||
@staticmethod
|
||||
def get_instance(force_reload = False):
|
||||
if AlltalkTTSEnginesConfig.__instance is None:
|
||||
force_reload = False
|
||||
AlltalkTTSEnginesConfig.__instance = AlltalkTTSEnginesConfig()
|
||||
|
||||
if force_reload:
|
||||
AlltalkTTSEnginesConfig.__instance.reload()
|
||||
else:
|
||||
AlltalkTTSEnginesConfig.__instance._reload_on_change()
|
||||
return AlltalkTTSEnginesConfig.__instance
|
||||
|
||||
def _handle_loaded_config(self, json_string: str):
|
||||
cfg = AlltalkTTSEnginesConfigModel.model_validate_json(json_string)
|
||||
cfg.engines_available = self.__handle_loaded_config_engines(cfg.engines_available)
|
||||
return cfg
|
||||
|
||||
def __handle_loaded_config_engines(self, available_engines):
|
||||
available_engine_names = [engine.name for engine in available_engines]
|
||||
|
||||
# Getting the engines that are not already part of the available engines:
|
||||
new_engines_config = AlltalkNewEnginesConfig.get_instance()
|
||||
new_engines = new_engines_config.get_engines_matching(lambda eng: eng.name not in available_engine_names)
|
||||
|
||||
# Merge engines:
|
||||
return available_engines + new_engines
|
||||
|
||||
def save(self, path: Path | str | None = None):
|
||||
self._save_file(path)
|
||||
|
||||
class AlltalkConfigFields:
|
||||
branding: str = "AllTalk "
|
||||
delete_output_wavs: str = "Disabled"
|
||||
gradio_interface: bool = True
|
||||
output_folder: str = "outputs"
|
||||
gradio_port_number: int = 7852
|
||||
firstrun_model: bool = True
|
||||
firstrun_splash: bool = True
|
||||
launch_gradio: bool = True
|
||||
transcode_audio_format: str = "Disabled"
|
||||
theme: AlltalkConfigTheme = AlltalkConfigTheme()
|
||||
rvc_settings: AlltalkConfigRvcSettings = AlltalkConfigRvcSettings()
|
||||
tgwui: AlltalkConfigTgwUi = AlltalkConfigTgwUi()
|
||||
api_def: AlltalkConfigApiDef = AlltalkConfigApiDef()
|
||||
debugging: AlltalkConfigDebug = AlltalkConfigDebug()
|
||||
gradio_pages: AlltalkConfigGradioPages = AlltalkConfigGradioPages()
|
||||
proxy_settings: AlltalkConfigProxySettings = AlltalkConfigProxySettings()
|
||||
|
||||
class AlltalkConfigModel(BaseModel, AlltalkConfigFields):
|
||||
__this_dir = Path(__file__).parent.resolve()
|
||||
|
||||
def get_output_directory(self):
|
||||
return self.__this_dir / self.output_folder
|
||||
|
||||
class AlltalkConfig(AbstractJsonConfig, AlltalkConfigFields):
|
||||
__instance = None
|
||||
__this_dir = Path(__file__).parent.resolve()
|
||||
|
||||
def __init__(self, config_path: Path | str = __this_dir / "confignew.json"):
|
||||
super().__init__(config_path, 5)
|
||||
self._load_config()
|
||||
|
||||
@staticmethod
|
||||
def default_config_path():
|
||||
return AlltalkConfig.__this_dir / "confignew.json"
|
||||
|
||||
@staticmethod
|
||||
def get_instance(force_reload = False):
|
||||
if AlltalkConfig.__instance is None:
|
||||
force_reload = False
|
||||
AlltalkConfig.__instance = AlltalkConfig()
|
||||
|
||||
if force_reload:
|
||||
AlltalkConfig.__instance.reload()
|
||||
else:
|
||||
AlltalkConfig.__instance._reload_on_change()
|
||||
return AlltalkConfig.__instance
|
||||
|
||||
def save(self, path: Path | str | None = None):
|
||||
self._save_file(path)
|
||||
|
||||
def _handle_loaded_config(self, json_string: str):
|
||||
model = AlltalkConfigModel.model_validate_json(json_string)
|
||||
model.get_output_directory().mkdir(parents=True, exist_ok=True)
|
||||
return model
|
||||
|
||||
class AlltalkMultiEngineManagerConfigFields:
|
||||
base_port: int = 7001
|
||||
api_server_port: int = 7851
|
||||
auto_start_engines: int = 0
|
||||
max_instances: int = 8
|
||||
gradio_interface_port: int = 7500
|
||||
max_retries: int = 12
|
||||
initial_wait: float = 2
|
||||
backoff_factor: float = 1.2
|
||||
debug_mode: bool = False
|
||||
max_queue_time: int = 60, # Maximum time a request can wait in the queue (in seconds)
|
||||
queue_check_interval: float = 0.1, # Time between checks for available instances (in seconds)
|
||||
tts_request_timeout: int = 30, # Timeout for individual TTS requests (in seconds)
|
||||
text_length_factor: float = 0.2, # Increase timeout by 20% per 100 characters
|
||||
concurrent_request_factor: float = 0.5, # Increase timeout by 50% per concurrent request
|
||||
diminishing_factor: float = 0.5, # Reduce additional time for long-running requests by 50%
|
||||
queue_position_factor: float = 1.0 # Add 100% of base timeout for each queue position
|
||||
|
||||
class AlltalkMultiEngineManagerConfigModel(BaseModel, AlltalkMultiEngineManagerConfigFields):
|
||||
pass
|
||||
|
||||
class AlltalkMultiEngineManagerConfig(AbstractJsonConfig, AlltalkMultiEngineManagerConfigFields):
|
||||
__instance = None
|
||||
__this_dir = Path(__file__).parent.resolve()
|
||||
|
||||
def __init__(self, config_path: Path | str = os.path.join(__this_dir, "mem_config.json")):
|
||||
super().__init__(config_path, 0)
|
||||
self._load_config()
|
||||
|
||||
@staticmethod
|
||||
def get_instance():
|
||||
if AlltalkMultiEngineManagerConfig.__instance is None:
|
||||
AlltalkMultiEngineManagerConfig.__instance = AlltalkMultiEngineManagerConfig()
|
||||
AlltalkMultiEngineManagerConfig.__instance._reload_on_change()
|
||||
return AlltalkMultiEngineManagerConfig.__instance
|
||||
|
||||
def _handle_loaded_config(self, json_string: str):
|
||||
return AlltalkMultiEngineManagerConfigModel.model_validate_json(json_string)
|
||||
|
||||
def save(self, path: Path | str | None = None):
|
||||
self._save_file(path)
|
||||
@@ -1 +1,110 @@
|
||||
{"activate": true, "autoplay": true, "branding": "AllTalk ", "narrator_enabled": false, "deepspeed_activate": false, "delete_output_wavs": "Disabled", "ip_address": "127.0.0.1", "language": "English", "low_vram": false, "local_temperature": "0.7", "local_repetition_penalty": "10.0", "tts_model_loaded": true, "tts_model_name": "tts_models/multilingual/multi-dataset/xtts_v2", "narrator_voice": "female_01.wav", "output_folder_wav": "extensions/alltalk_tts/outputs/", "output_folder_wav_standalone": "outputs/", "port_number": "7851", "remove_trailing_dots": false, "show_text": true, "tts_method_api_local": false, "tts_method_api_tts": false, "tts_method_xtts_local": true, "voice": "female_01.wav"}
|
||||
{
|
||||
"branding": "AllTalk ",
|
||||
"delete_output_wavs": "Disabled",
|
||||
"gradio_interface": true,
|
||||
"output_folder": "outputs",
|
||||
"gradio_port_number": 7852,
|
||||
"firstrun_model": true,
|
||||
"firstrun_splash": true,
|
||||
"launch_gradio": true,
|
||||
"transcode_audio_format": "Disabled",
|
||||
"theme": {
|
||||
"file": null,
|
||||
"class": "gradio/base"
|
||||
},
|
||||
"rvc_settings": {
|
||||
"rvc_enabled": false,
|
||||
"rvc_char_model_file": "Disabled",
|
||||
"rvc_narr_model_file": "Disabled",
|
||||
"split_audio": true,
|
||||
"autotune": false,
|
||||
"pitch": 0,
|
||||
"filter_radius": 3,
|
||||
"index_rate": 0.75,
|
||||
"rms_mix_rate": 1,
|
||||
"protect": 0.5,
|
||||
"hop_length": 130,
|
||||
"f0method": "fcpe",
|
||||
"embedder_model": "hubert",
|
||||
"training_data_size": 45000,
|
||||
"model_cache_size": 1
|
||||
},
|
||||
"tgwui": {
|
||||
"tgwui_activate_tts": true,
|
||||
"tgwui_autoplay_tts": true,
|
||||
"tgwui_narrator_enabled": "false",
|
||||
"tgwui_non_quoted_text_is": "character",
|
||||
"tgwui_deepspeed_enabled": false,
|
||||
"tgwui_language": "English",
|
||||
"tgwui_lowvram_enabled": false,
|
||||
"tgwui_pitch_set": 0,
|
||||
"tgwui_temperature_set": 0.75,
|
||||
"tgwui_repetitionpenalty_set": 10,
|
||||
"tgwui_generationspeed_set": 1,
|
||||
"tgwui_narrator_voice": "female_01.wav",
|
||||
"tgwui_show_text": true,
|
||||
"tgwui_character_voice": "female_01.wav",
|
||||
"tgwui_rvc_char_voice": "Disabled",
|
||||
"tgwui_rvc_char_pitch": 0,
|
||||
"tgwui_rvc_narr_voice": "Disabled",
|
||||
"tgwui_rvc_narr_pitch": 0
|
||||
},
|
||||
"api_def": {
|
||||
"api_port_number": 7851,
|
||||
"api_allowed_filter": "[^a-zA-Z0-9\\s.,;:!?\\-\\'\"$\\u0400-\\u04FF\\u00C0-\\u017F\\u0150\\u0151\\u0170\\u0171\\u011E\\u011F\\u0130\\u0131\\u0900-\\u097F\\u2018\\u2019\\u201C\\u201D\\u3001\\u3002\\u3040-\\u309F\\u30A0-\\u30FF\\u4E00-\\u9FFF\\u3400-\\u4DBF\\uF900-\\uFAFF\\u0600-\\u06FF\\u0750-\\u077F\\uFB50-\\uFDFF\\uFE70-\\uFEFF\\uAC00-\\uD7A3\\u1100-\\u11FF\\u3130-\\u318F\\uFF01\\uFF0c\\uFF1A\\uFF1B\\uFF1F]",
|
||||
"api_length_stripping": 3,
|
||||
"api_max_characters": 2000,
|
||||
"api_use_legacy_api": false,
|
||||
"api_legacy_ip_address": "127.0.0.1",
|
||||
"api_text_filtering": "standard",
|
||||
"api_narrator_enabled": "false",
|
||||
"api_text_not_inside": "character",
|
||||
"api_language": "en",
|
||||
"api_output_file_name": "myoutputfile",
|
||||
"api_output_file_timestamp": true,
|
||||
"api_autoplay": false,
|
||||
"api_autoplay_volume": 0.5
|
||||
},
|
||||
"debugging": {
|
||||
"debug_transcode": false,
|
||||
"debug_tts": false,
|
||||
"debug_openai": false,
|
||||
"debug_concat": false,
|
||||
"debug_tts_variables": false,
|
||||
"debug_rvc": false,
|
||||
"debug_func": false,
|
||||
"debug_api": false,
|
||||
"debug_fullttstext": false,
|
||||
"debug_narrator": false,
|
||||
"debug_gradio_IP": false,
|
||||
"debug_transcribe": false,
|
||||
"debug_proxy": false
|
||||
},
|
||||
"gradio_pages": {
|
||||
"Generate_Help_page": true,
|
||||
"Voice2RVC_page": true,
|
||||
"TTS_Generator_page": true,
|
||||
"TTS_Engines_Settings_page": true,
|
||||
"alltalk_documentation_page": true,
|
||||
"api_documentation_page": true
|
||||
},
|
||||
"proxy_settings": {
|
||||
"proxy_enabled": false,
|
||||
"start_on_startup": false,
|
||||
"gradio_endpoint": {
|
||||
"enabled": false,
|
||||
"external_port": 444,
|
||||
"external_ip": "0.0.0.0",
|
||||
"cert_name": ""
|
||||
},
|
||||
"api_endpoint": {
|
||||
"enabled": false,
|
||||
"external_port": 443,
|
||||
"external_ip": "0.0.0.0",
|
||||
"cert_name": ""
|
||||
},
|
||||
"cert_validation": true,
|
||||
"logging_enabled": true,
|
||||
"log_level": "INFO"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
cd $SCRIPT_DIR
|
||||
|
||||
. ${SCRIPT_DIR=}/docker/variables.sh
|
||||
|
||||
TTS_MODEL=xtts
|
||||
DOCKER_TAG=latest-${TTS_MODEL}
|
||||
CLEAN=false
|
||||
LOCAL_DEEPSPEED_BUILD=false
|
||||
DOCKER_REPOSITORY=erew123
|
||||
|
||||
# Create required build directories if they don't exist
|
||||
mkdir -p ${SCRIPT_DIR=}/docker/deepspeed/build
|
||||
echo "Created build directories"
|
||||
|
||||
# Parse arguments
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--cuda-version)
|
||||
CUDA_VERSION="$2"
|
||||
shift
|
||||
;;
|
||||
--python-version)
|
||||
PYTHON_VERSION="$2"
|
||||
shift
|
||||
;;
|
||||
--tts_model)
|
||||
TTS_MODEL="$2"
|
||||
shift
|
||||
;;
|
||||
--deepspeed-version)
|
||||
DEEPSPEED_VERSION="$2"
|
||||
shift
|
||||
;;
|
||||
--tag)
|
||||
DOCKER_TAG="$2"
|
||||
shift
|
||||
;;
|
||||
--docker-repository)
|
||||
if [ -n "$2" ] && ! [[ $2 =~ ^--.* ]]; then
|
||||
DOCKER_REPOSITORY="$2"
|
||||
shift
|
||||
else
|
||||
DOCKER_REPOSITORY=""
|
||||
fi
|
||||
;;
|
||||
--local-deepspeed-build)
|
||||
LOCAL_DEEPSPEED_BUILD=true
|
||||
;;
|
||||
--clean)
|
||||
CLEAN=true
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "Invalid argument ($1)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Append slash if missing:
|
||||
DOCKER_REPOSITORY=$(echo "$DOCKER_REPOSITORY" | sed 's![^/]$!&/!')
|
||||
|
||||
if [ "$CLEAN" = true ]; then
|
||||
rm -rf ${SCRIPT_DIR=}/docker/deepspeed/build
|
||||
# Recreate directories after clean
|
||||
mkdir -p ${SCRIPT_DIR=}/docker/deepspeed/build
|
||||
echo "Cleaned and recreated build directories"
|
||||
fi
|
||||
|
||||
echo "Building base environment image"
|
||||
$SCRIPT_DIR/docker/base/build-base-env.sh \
|
||||
--cuda-version ${CUDA_VERSION} \
|
||||
--python-version ${PYTHON_VERSION} \
|
||||
--docker-repository ${DOCKER_REPOSITORY} \
|
||||
--tag ${DOCKER_TAG}
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to build base environment image"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$LOCAL_DEEPSPEED_BUILD" = true ]; then
|
||||
echo "Building DeepSpeed"
|
||||
$SCRIPT_DIR/docker/deepspeed/build-deepspeed.sh \
|
||||
--python-version ${PYTHON_VERSION} \
|
||||
--docker-repository ${DOCKER_REPOSITORY} \
|
||||
--tag ${DOCKER_TAG}
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to build DeepSpeed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Starting docker build process using TTS model '${TTS_MODEL}' and docker tag '${DOCKER_TAG}'"
|
||||
echo "Building for CUDA $CUDA_VERSION using python ${PYTHON_VERSION}"
|
||||
|
||||
docker buildx \
|
||||
build \
|
||||
--progress=plain \
|
||||
--build-arg TTS_MODEL=$TTS_MODEL \
|
||||
--build-arg DEEPSPEED_VERSION=$DEEPSPEED_VERSION \
|
||||
--build-arg DOCKER_TAG=$DOCKER_TAG \
|
||||
--build-arg DOCKER_REPOSITORY=$DOCKER_REPOSITORY \
|
||||
-t ${DOCKER_REPOSITORY}alltalk_tts:${DOCKER_TAG} \
|
||||
.
|
||||
|
||||
echo "Docker build process finished. Use docker-start.sh to start the container."
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
. ${SCRIPT_DIR=}/docker/variables.sh
|
||||
|
||||
ALLTALK_GRADIO_INTERFACE=true
|
||||
ALLTALK_ENABLE_MULTI_ENGINE_MANAGER=false
|
||||
MULTI_ENGINE_MANAGER_CONFIG=
|
||||
DOCKER_TAG=latest-xtts
|
||||
DOCKER_REPOSITORY=erew123
|
||||
ALLTALK_DIR=/home/alltalk
|
||||
ALLTALK_AUTO_CLEANUP=false
|
||||
declare -a ADDITIONAL_ARGS=()
|
||||
|
||||
# Parse arguments
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--config)
|
||||
CONFIG="$2"
|
||||
shift
|
||||
;;
|
||||
--voices)
|
||||
VOICES="$2"
|
||||
shift
|
||||
;;
|
||||
--rvc-voices)
|
||||
RVC_VOICES="$2"
|
||||
shift
|
||||
;;
|
||||
--no-ui)
|
||||
ALLTALK_GRADIO_INTERFACE=false
|
||||
;;
|
||||
--auto-cleanup)
|
||||
ALLTALK_AUTO_CLEANUP=true
|
||||
;;
|
||||
--with-multi-engine-manager)
|
||||
ALLTALK_ENABLE_MULTI_ENGINE_MANAGER=true
|
||||
if [ -n "$2" ] && ! [[ $2 =~ ^--.* ]]; then
|
||||
MULTI_ENGINE_MANAGER_CONFIG=$2
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
--tag)
|
||||
DOCKER_TAG="$2"
|
||||
shift
|
||||
;;
|
||||
--docker-repository)
|
||||
if [ -n "$2" ] && ! [[ $2 =~ ^--.* ]]; then
|
||||
DOCKER_REPOSITORY="$2"
|
||||
shift
|
||||
else
|
||||
DOCKER_REPOSITORY=""
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# Allow to pass arbitrary arguments to docker as well to be flexible:
|
||||
ADDITIONAL_ARGS+=( $1 )
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Append slash if missing:
|
||||
DOCKER_REPOSITORY=$(echo "$DOCKER_REPOSITORY" | sed 's![^/]$!&/!')
|
||||
|
||||
# Compose docker arguments based on user input to the script:
|
||||
declare -a DOCKER_ARGS=()
|
||||
|
||||
if [[ -n $CONFIG ]]; then
|
||||
# Mount the config file to docker_confignew.json:
|
||||
DOCKER_ARGS+=( -v ${CONFIG}:${ALLTALK_DIR}/docker_confignew.json )
|
||||
fi
|
||||
|
||||
if [[ -n $VOICES ]]; then
|
||||
DOCKER_ARGS+=( -v ${VOICES}:${ALLTALK_DIR}/voices )
|
||||
fi
|
||||
|
||||
if [[ -n $RVC_VOICES ]]; then
|
||||
DOCKER_ARGS+=( -v ${RVC_VOICES}:${ALLTALK_DIR}/models/rvc_voices )
|
||||
fi
|
||||
|
||||
if [ "$ALLTALK_GRADIO_INTERFACE" = true ] ; then
|
||||
if [ "$ALLTALK_ENABLE_MULTI_ENGINE_MANAGER" = true ] ; then
|
||||
DOCKER_ARGS+=( -p 7500:7500 )
|
||||
else
|
||||
DOCKER_ARGS+=( -p 7852:7852 )
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n $MULTI_ENGINE_MANAGER_CONFIG ]]; then
|
||||
DOCKER_ARGS+=( -v ${MULTI_ENGINE_MANAGER_CONFIG}:${ALLTALK_DIR}/docker_mem_config.json )
|
||||
fi
|
||||
|
||||
# Pass env variables:
|
||||
DOCKER_ARGS+=( -e ALLTALK_ENABLE_MULTI_ENGINE_MANAGER=${ALLTALK_ENABLE_MULTI_ENGINE_MANAGER} )
|
||||
DOCKER_ARGS+=( -e ALLTALK_GRADIO_INTERFACE=${ALLTALK_GRADIO_INTERFACE} )
|
||||
DOCKER_ARGS+=( -e ALLTALK_LAUNCH_GRADIO=${ALLTALK_GRADIO_INTERFACE} )
|
||||
DOCKER_ARGS+=( -e ALLTALK_AUTO_CLEANUP=${ALLTALK_AUTO_CLEANUP} )
|
||||
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
-it \
|
||||
-p 7851:7851 \
|
||||
--gpus=all \
|
||||
--name alltalk \
|
||||
"${DOCKER_ARGS[@]}" \
|
||||
"${ADDITIONAL_ARGS[@]}" \
|
||||
${DOCKER_REPOSITORY}alltalk_tts:${DOCKER_TAG} &> /dev/stdout
|
||||
@@ -0,0 +1,99 @@
|
||||
FROM continuumio/miniconda3:24.7.1-0 AS build
|
||||
|
||||
ARG CUDA_VERSION=12.8.1
|
||||
ENV CUDA_VERSION=$CUDA_VERSION
|
||||
|
||||
ARG PYTHON_VERSION=3.11.11
|
||||
ENV PYTHON_VERSION=$PYTHON_VERSION
|
||||
|
||||
SHELL ["/bin/bash", "-l", "-c"]
|
||||
ENV SHELL=/bin/bash
|
||||
ENV HOST=0.0.0.0
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV CUDA_DOCKER_ARCH=all
|
||||
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||
ENV CONDA_AUTO_UPDATE_CONDA="false"
|
||||
|
||||
ENV ALLTALK_DIR=/home/alltalk
|
||||
|
||||
##############################################################################
|
||||
# Installation/Basic Utilities
|
||||
##############################################################################
|
||||
RUN <<EOR
|
||||
apt-get update
|
||||
apt-get upgrade -y
|
||||
apt-get install --no-install-recommends -y \
|
||||
espeak-ng \
|
||||
cron \
|
||||
curl \
|
||||
wget \
|
||||
jq \
|
||||
vim
|
||||
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
EOR
|
||||
|
||||
# Create a non-root user and switch to it for security
|
||||
RUN useradd -ms /bin/bash alltalk
|
||||
USER alltalk
|
||||
|
||||
##############################################################################
|
||||
# Create a conda environment and install dependencies:
|
||||
##############################################################################
|
||||
RUN <<EOR
|
||||
CUDA_SHORT_VERSION=${CUDA_VERSION%.*}
|
||||
conda create -y -n "alltalk" -c conda-forge python=${PYTHON_VERSION}
|
||||
conda activate alltalk
|
||||
conda install -y \
|
||||
gcc_linux-64 \
|
||||
gxx_linux-64 \
|
||||
libaio \
|
||||
faiss-gpu=1.9.0 \
|
||||
cuda=${CUDA_VERSION} cuda-tools=${CUDA_VERSION} cuda-toolkit=${CUDA_VERSION} cuda-version=${CUDA_SHORT_VERSION} \
|
||||
cuda-command-line-tools=${CUDA_VERSION} cuda-compiler=${CUDA_VERSION} cuda-runtime=${CUDA_VERSION} \
|
||||
cuda-libraries=${CUDA_VERSION} cuda-libraries-dev=${CUDA_VERSION} cuda-libraries-static=${CUDA_VERSION} \
|
||||
cuda-cudart=${CUDA_SHORT_VERSION} cuda-cudart_linux-64=${CUDA_SHORT_VERSION} \
|
||||
conda-forge::nccl=2.27.5.1 \
|
||||
cudnn=9.10 \
|
||||
py-cpuinfo=9 \
|
||||
conda-forge::ffmpeg=7.1.0 \
|
||||
conda-forge::portaudio=19.7.0 \
|
||||
-c pytorch \
|
||||
-c nvidia \
|
||||
-c anaconda
|
||||
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed to install conda dependencies: $RESULT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
conda clean -a && pip cache purge
|
||||
EOR
|
||||
|
||||
RUN <<EOR
|
||||
CUDA_SHORT_VERSION=${CUDA_VERSION%.*}
|
||||
CUDA_SHORT_VERSION_NO_DOT=${CUDA_SHORT_VERSION//./}
|
||||
|
||||
conda activate alltalk
|
||||
mkdir -p ${ALLTALK_DIR}/pip_cache
|
||||
pip install --no-cache-dir --cache-dir=${ALLTALK_DIR}/pip_cache \
|
||||
torch==2.7.1 torchvision==0.22.1 torchaudio==2.7.1 --index-url https://download.pytorch.org/whl/cu${CUDA_SHORT_VERSION_NO_DOT}
|
||||
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed to install pip dependencies: $RESULT"
|
||||
exit 1
|
||||
fi
|
||||
EOR
|
||||
|
||||
RUN <<EOR
|
||||
# See https://gist.github.com/xkortex/5ae49d7e6e969405bd2c3152a949c1f1
|
||||
cat << EOF > ${ALLTALK_DIR}/conda_env.sh
|
||||
#!/usr/bin/env bash
|
||||
source ~/.bashrc
|
||||
__conda_setup="\$('/opt/conda/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
|
||||
eval "\$__conda_setup"
|
||||
unset __conda_setup
|
||||
|
||||
conda activate alltalk
|
||||
EOF
|
||||
EOR
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
cd $SCRIPT_DIR
|
||||
|
||||
. ${SCRIPT_DIR=}/../variables.sh
|
||||
|
||||
DOCKER_TAG=latest
|
||||
DOCKER_REPOSITORY=erew123
|
||||
|
||||
# Parse arguments
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--cuda-version)
|
||||
CUDA_VERSION="$2"
|
||||
shift
|
||||
;;
|
||||
--python-version)
|
||||
PYTHON_VERSION="$2"
|
||||
shift
|
||||
;;
|
||||
--tag)
|
||||
DOCKER_TAG="$2"
|
||||
shift
|
||||
;;
|
||||
--docker-repository)
|
||||
if [ -n "$2" ] && ! [[ $2 =~ ^--.* ]]; then
|
||||
DOCKER_REPOSITORY="$2"
|
||||
shift
|
||||
else
|
||||
DOCKER_REPOSITORY=""
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument '$1'"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Append slash if missing:
|
||||
DOCKER_REPOSITORY=$(echo "$DOCKER_REPOSITORY" | sed 's![^/]$!&/!')
|
||||
|
||||
echo "Building conda environment using python ${PYTHON_VERSION} with CUDA ${CUDA_VERSION}"
|
||||
|
||||
docker buildx \
|
||||
build \
|
||||
--progress=plain \
|
||||
--build-arg CUDA_VERSION=$CUDA_VERSION \
|
||||
--build-arg PYTHON_VERSION=$PYTHON_VERSION \
|
||||
-t ${DOCKER_REPOSITORY}alltalk_tts_environment:${DOCKER_TAG} \
|
||||
.
|
||||
@@ -0,0 +1,52 @@
|
||||
ARG DOCKER_REPOSITORY
|
||||
ARG DOCKER_TAG=latest
|
||||
FROM ${DOCKER_REPOSITORY}alltalk_tts_environment:${DOCKER_TAG}
|
||||
|
||||
ARG DEEPSPEED_VERSION=0.17.2
|
||||
ENV DEEPSPEED_VERSION=$DEEPSPEED_VERSION
|
||||
|
||||
WORKDIR ${ALLTALK_DIR}
|
||||
|
||||
##############################################################################
|
||||
# Install python dependencies
|
||||
##############################################################################
|
||||
ENV PIP_CACHE_DIR=/tmp/pip_cache
|
||||
RUN <<EOR
|
||||
conda activate alltalk
|
||||
|
||||
pip install \
|
||||
deepspeed-kernels
|
||||
EOR
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Clone DeepSpeed sources
|
||||
##############################################################################
|
||||
RUN <<EOR
|
||||
git clone https://github.com/microsoft/DeepSpeed.git ${ALLTALK_DIR}/DeepSpeed
|
||||
cd ${ALLTALK_DIR}/DeepSpeed
|
||||
git checkout .
|
||||
git checkout "tags/v${DEEPSPEED_VERSION}" -b "v${DEEPSPEED_VERSION}"
|
||||
EOR
|
||||
|
||||
##############################################################################
|
||||
# Create DeepSpeed build file
|
||||
##############################################################################
|
||||
RUN <<EOR
|
||||
cat << EOF > ${ALLTALK_DIR}/build_deepspeed.sh
|
||||
#!/usr/bin/env bash
|
||||
source ${ALLTALK_DIR}/conda_env.sh
|
||||
mkdir -p ${ALLTALK_DIR}/build
|
||||
|
||||
cd ${ALLTALK_DIR}/DeepSpeed
|
||||
DS_BUILD_OPS=1 python setup.py build_ext -j8 bdist_wheel
|
||||
mv ${ALLTALK_DIR}/DeepSpeed/dist/*.whl ${ALLTALK_DIR}/build
|
||||
EOF
|
||||
EOR
|
||||
|
||||
RUN chmod +x ${ALLTALK_DIR}/build_deepspeed.sh
|
||||
|
||||
##############################################################################
|
||||
# Build DeepSpeed:
|
||||
##############################################################################
|
||||
ENTRYPOINT ["bash", "-c", "-l", "$ALLTALK_DIR/build_deepspeed.sh"]
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
cd $SCRIPT_DIR
|
||||
|
||||
. ${SCRIPT_DIR=}/../variables.sh
|
||||
|
||||
DOCKER_TAG=latest
|
||||
DOCKER_REPOSITORY=erew123
|
||||
ALLTALK_DIR=/home/alltalk
|
||||
|
||||
# Parse arguments
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--python-version)
|
||||
PYTHON_VERSION="$2"
|
||||
shift
|
||||
;;
|
||||
--deepspeed-version)
|
||||
DEEPSPEED_VERSION="$2"
|
||||
shift
|
||||
;;
|
||||
--tag)
|
||||
DOCKER_TAG="$2"
|
||||
shift
|
||||
;;
|
||||
--docker-repository)
|
||||
if [ -n "$2" ] && ! [[ $2 =~ ^--.* ]]; then
|
||||
DOCKER_REPOSITORY="$2"
|
||||
shift
|
||||
else
|
||||
DOCKER_REPOSITORY=""
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument '$1'"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Append slash if missing:
|
||||
DOCKER_REPOSITORY=$(echo "$DOCKER_REPOSITORY" | sed 's![^/]$!&/!')
|
||||
|
||||
PYTHON_SHORT_VERSION=${PYTHON_VERSION%.*}
|
||||
PYTHON_VERSION_NO_DOT=${PYTHON_SHORT_VERSION//./}
|
||||
if [[ -n $(find build -name "deepspeed-${DEEPSPEED_VERSION}*-cp${PYTHON_VERSION_NO_DOT}-cp${PYTHON_VERSION_NO_DOT}-*.whl") ]]
|
||||
then
|
||||
echo "DeepSpeed was already built - skipping..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Building DeepSpeed $DEEPSPEED_VERSION using python ${PYTHON_VERSION}"
|
||||
|
||||
rm -rf build # make sure to properly clean up
|
||||
mkdir -p build
|
||||
|
||||
docker buildx \
|
||||
build \
|
||||
--build-arg DOCKER_REPOSITORY=$DOCKER_REPOSITORY \
|
||||
--build-arg DEEPSPEED_VERSION=$DEEPSPEED_VERSION \
|
||||
-t ${DOCKER_REPOSITORY}alltalk_deepspeed:${DOCKER_TAG} \
|
||||
.
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
-it \
|
||||
--gpus=all \
|
||||
--name deepspeed \
|
||||
-v $SCRIPT_DIR/build:${ALLTALK_DIR}/build \
|
||||
${DOCKER_REPOSITORY}alltalk_deepspeed:${DOCKER_TAG}
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
declare -A ALLTALK_VARS
|
||||
ALLTALK_VARS["CUDA_VERSION"]=12.8.1
|
||||
ALLTALK_VARS["PYTHON_VERSION"]=3.11.11
|
||||
ALLTALK_VARS["DEEPSPEED_VERSION"]=0.17.2
|
||||
|
||||
# Export single variables (needed by Docker locally)
|
||||
for key in "${!ALLTALK_VARS[@]}"
|
||||
do
|
||||
export "${key}=${ALLTALK_VARS[${key}]}"
|
||||
done
|
||||
|
||||
# Export the entire associative array (needed by Github action)
|
||||
export ALLTALK_VARS
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"branding": $ENV.ALLTALK_BRANDING,
|
||||
"delete_output_wavs": $ENV.ALLTALK_DELETE_OUTPUT_WAVS,
|
||||
"gradio_interface": $ENV.ALLTALK_GRADIO_INTERFACE,
|
||||
"output_folder": $ENV.ALLTALK_OUTPUT_FOLDER,
|
||||
"gradio_port_number": $ENV.ALLTALK_GRADIO_PORT_NUMBER,
|
||||
"launch_gradio": $ENV.ALLTALK_LAUNCH_GRADIO,
|
||||
"transcode_audio_format": $ENV.ALLTALK_TRANSCODE_AUDIO_FORMAT,
|
||||
"rvc_settings": {
|
||||
"rvc_enabled": $ENV.ALLTALK_RVC_ENABLED,
|
||||
"rvc_char_model_file": $ENV.ALLTALK_RVC_CHAR_MODEL_FILE,
|
||||
"rvc_narr_model_file": $ENV.ALLTALK_RVC_NARR_MODEL_FILE,
|
||||
"split_audio": $ENV.ALLTALK_RVC_SPLIT_AUDIO,
|
||||
"autotune": $ENV.ALLTALK_RVC_AUTOTUNE,
|
||||
"pitch": $ENV.ALLTALK_RVC_PITCH,
|
||||
"filter_radius": $ENV.ALLTALK_RVC_FILTER_RADIUS,
|
||||
"index_rate": $ENV.ALLTALK_RVC_INDEX_RATE,
|
||||
"rms_mix_rate": $ENV.ALLTALK_RVC_RMS_MIX_RATE,
|
||||
"protect": $ENV.ALLTALK_RVC_PROTECT,
|
||||
"hop_length": $ENV.ALLTALK_RVC_HOP_LENGTH,
|
||||
"f0method": $ENV.ALLTALK_RVC_F0METHOD,
|
||||
"embedder_model": $ENV.ALLTALK_RVC_EMBEDDER_MODEL,
|
||||
"training_data_size": $ENV.ALLTALK_RVC_TRAINING_DATA_SIZE,
|
||||
"model_cache_size": $ENV.ALLTALK_RVC_MODEL_CACHE_SIZE
|
||||
},
|
||||
"api_def": {
|
||||
"api_port_number": $ENV.ALLTALK_API_PORT_NUMBER,
|
||||
"api_allowed_filter": $ENV.ALLTALK_API_ALLOWED_FILTER,
|
||||
"api_length_stripping": $ENV.ALLTALK_API_LENGTH_STRIPPING,
|
||||
"api_max_characters": $ENV.ALLTALK_API_MAX_CHARACTERS,
|
||||
"api_use_legacy_api": $ENV.ALLTALK_API_USE_LEGACY_API,
|
||||
"api_text_filtering": $ENV.ALLTALK_API_TEXT_FILTERING,
|
||||
"api_narrator_enabled": $ENV.ALLTALK_API_NARRATOR_ENABLED,
|
||||
"api_text_not_inside": $ENV.ALLTALK_API_TEXT_NOT_INSIDE,
|
||||
"api_language": $ENV.ALLTALK_API_LANGUAGE,
|
||||
"api_output_file_name": $ENV.ALLTALK_API_OUTPUT_FILE_NAME,
|
||||
"api_output_file_timestamp": $ENV.ALLTALK_API_OUTPUT_FILE_TIMESTAMP,
|
||||
"api_autoplay": $ENV.ALLTALK_API_AUTOPLAY,
|
||||
"api_autoplay_volume": $ENV.ALLTALK_API_AUTOPLAY_VOLUME
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"base_port": $ENV.ALLTALK_MEM_BASE_PORT,
|
||||
"api_server_port": $ENV.ALLTALK_MEM_API_SERVER_PORT,
|
||||
"auto_start_engines": $ENV.ALLTALK_MEM_AUTO_START_ENGINES,
|
||||
"max_instances": $ENV.ALLTALK_MEM_MAX_INSTANCES,
|
||||
"gradio_interface_port": $ENV.ALLTALK_MEM_GRADIO_INTERFACE_PORT,
|
||||
"max_retries": $ENV.ALLTALK_MEM_MAX_RETRIES,
|
||||
"initial_wait": $ENV.ALLTALK_MEM_INITIAL_WAIT,
|
||||
"backoff_factor": $ENV.ALLTALK_MEM_BACKOFF_FACTOR,
|
||||
"debug_mode": $ENV.ALLTALK_MEM_DEBUG_MODE,
|
||||
"max_queue_time": $ENV.ALLTALK_MEM_MAX_QUEUE_TIME,
|
||||
"queue_check_interval": $ENV.ALLTALK_MEM_QUEUE_CHECK_INTERVAL,
|
||||
"tts_request_timeout": $ENV.ALLTALK_MEM_TTS_REQUEST_TIMEOUT,
|
||||
"text_length_factor": $ENV.ALLTALK_MEM_TEXT_LENGTH_FACTOR,
|
||||
"concurrent_request_factor": $ENV.ALLTALK_MEM_CONCURRENT_REQUEST_FACTOR,
|
||||
"diminishing_factor": $ENV.ALLTALK_MEM_DIMINISHING_FACTOR,
|
||||
"queue_position_factor": $ENV.ALLTALK_MEM_QUEUE_POSITION_FACTOR
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,26 +0,0 @@
|
||||
import torch
|
||||
import os
|
||||
MODEL_PATH = "best_model.pth"
|
||||
# Check if best_model.pth exists
|
||||
if not os.path.exists(MODEL_PATH):
|
||||
print(f"\nCannot find \033[94m{MODEL_PATH}\033[0m in the current folder.")
|
||||
|
||||
# Check if model.pth exists as an alternative
|
||||
ALTERNATIVE_PATH = "model.pth"
|
||||
if os.path.exists(ALTERNATIVE_PATH):
|
||||
print(f"\nIf your model is currently called \033[93m{ALTERNATIVE_PATH}\033[0m please rename it to \033[94mbest_model.pth\033[0m.")
|
||||
else:
|
||||
print(f"Please put \033[91mbest_model.pth\033[0m in the current folder or rename your model file to \033[94mbest_model.pth\033[0m.")
|
||||
exit(1)
|
||||
# Attempt to load the model
|
||||
try:
|
||||
checkpoint = torch.load(MODEL_PATH, map_location=torch.device("cpu"))
|
||||
except Exception as e:
|
||||
print("Error loading checkpoint:", e)
|
||||
raise
|
||||
del checkpoint["optimizer"]
|
||||
for key in list(checkpoint["model"].keys()):
|
||||
if "dvae" in key:
|
||||
del checkpoint["model"][key]
|
||||
torch.save(checkpoint, "model.pth")
|
||||
print(f"\nYour compacted model is created \033[93mmodel.pth\033[0m.")
|
||||
@@ -0,0 +1,329 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "H-WKVp4Uukcl"
|
||||
},
|
||||
"source": [
|
||||
"#Install Server Requirements\n",
|
||||
"This should be run on a minimum of a T4 runtime, though it will run on a CPU only session, however long TTS generations may time out/error.\n",
|
||||
"\n",
|
||||
"This is a **work in progress**. Known issues:\n",
|
||||
"\n",
|
||||
"- The 1st TTS generation has a brief stutter.\n",
|
||||
"- RVC is not yet working.\n",
|
||||
"- Transcoding/ffmpeg isnt working.\n",
|
||||
"- Things yet to do on selecting your first model and other configuration setups.\n",
|
||||
"\n",
|
||||
"If you enable DeepSpeed for XTTS models, DeepSpeed has to compile on the 1st TTS generation which can take about 90 seconds. After that it should be fine."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {
|
||||
"cellView": "form",
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "W6ls-RHTQeZv",
|
||||
"outputId": "25c62fb3-26ef-4493-8647-1d8e49bf5f01"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"************************************\n",
|
||||
"** Server requirements installed ***\n",
|
||||
"*** Please proceed to next step ****\n",
|
||||
"************************************\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"#@markdown Click the `Play button` to the left of this message to install the requirements.<br><br>\n",
|
||||
"#@markdown **OPTIONAL** Mounting your Google Drive allows you to drag and drop samples/models via the `drive/Mydrive` path. This allows you<br>\n",
|
||||
"#@markdown to store or use specific audio samples or finetuned models.<br>\n",
|
||||
"#@markdown **Audio samples** need to be placed in `alltalk_tts/voices`<br>\n",
|
||||
"#@markdown **XTTS models** need to be placed in `alltalk_tts/models/xtts/{yourmodelfolderhere}`<br>\n",
|
||||
"mount_gdrive = False #@param{type:\"boolean\"}\n",
|
||||
"\n",
|
||||
"if mount_gdrive:\n",
|
||||
" from google.colab import drive\n",
|
||||
" drive.mount('/content/drive')\n",
|
||||
"from IPython.display import clear_output\n",
|
||||
"print(\"*******************************************************************\")\n",
|
||||
"print(\"** Installing server requirements. This will take 5-10 minutes ****\")\n",
|
||||
"print(\"*******************************************************************\")\n",
|
||||
"!apt install libasound2-dev portaudio19-dev libportaudio2 libportaudiocpp0 libaio-dev espeak-ng > '/dev/null' 2>&1\n",
|
||||
"clear_output()\n",
|
||||
"print(\"************************\")\n",
|
||||
"print(\"*** Cloning AllTalk ****\")\n",
|
||||
"print(\"************************\\n\")\n",
|
||||
"!git clone -b alltalkbeta https://github.com/erew123/alltalk_tts\n",
|
||||
"clear_output()\n",
|
||||
"print(\"\\n********************************\")\n",
|
||||
"print(\"*** Installing Requirements ****\")\n",
|
||||
"print(\"********************************\\n\")\n",
|
||||
"!python -m pip install --upgrade \"pip<24.1\"\n",
|
||||
"!pip install --quiet -r /content/alltalk_tts/system/requirements/requirements_colab.txt\n",
|
||||
"clear_output()\n",
|
||||
"print(\"\\n*****************************\")\n",
|
||||
"print(\"*** Installing DeepSpeed ****\")\n",
|
||||
"print(\"*****************************\\n\")\n",
|
||||
"!pip install deepspeed\n",
|
||||
"!pip install orjson\n",
|
||||
"!pip install faiss-cpu\n",
|
||||
"!pip install fairseq\n",
|
||||
"clear_output()\n",
|
||||
"print(\"\\n******************************\")\n",
|
||||
"print(\"*** Installing Cloudflare ****\")\n",
|
||||
"print(\"******************************\\n\")\n",
|
||||
"# Install cloudflare\n",
|
||||
"!wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb > '/dev/null' 2>&1\n",
|
||||
"!apt install ./cloudflared-linux-amd64.deb aria2 > '/dev/null' 2>&1\n",
|
||||
"!rm cloudflared-linux-amd64.deb > '/dev/null' 2>&1\n",
|
||||
"!python -m spacy download en_core_web_md\n",
|
||||
"clear_output()\n",
|
||||
"print(\"\\n***************************\")\n",
|
||||
"print(\"*** Installing Cutlass ****\")\n",
|
||||
"print(\"***************************\\n\")\n",
|
||||
"# Clone the CUTLASS repository\n",
|
||||
"!git clone https://github.com/NVIDIA/cutlass.git\n",
|
||||
"!export CUTLASSPATH=/content/cutlass\n",
|
||||
"!sudo curl -L https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz -o /usr/local/bin/ffmpeg.tar.xz\n",
|
||||
"clear_output()\n",
|
||||
"%cd /usr/local/bin/\n",
|
||||
"clear_output()\n",
|
||||
"!7z e /usr/local/bin/ffmpeg.tar.xz -y\n",
|
||||
"clear_output()\n",
|
||||
"!7z e /usr/local/bin/ffmpeg.tar -y\n",
|
||||
"clear_output()\n",
|
||||
"!sudo chmod a+rx /usr/local/bin/ffmpeg\n",
|
||||
"clear_output()\n",
|
||||
"!pip uninstall jax -y\n",
|
||||
"clear_output()\n",
|
||||
"print(\"************************************\")\n",
|
||||
"print(\"** Server requirements installed ***\")\n",
|
||||
"print(\"*** Please proceed to next step ****\")\n",
|
||||
"print(\"************************************\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "2nk26r66RQ23"
|
||||
},
|
||||
"source": [
|
||||
"\n",
|
||||
"# Start AllTalk TTS Server\n",
|
||||
"\n",
|
||||
"This will start the AllTalk API and Gradio Web interface. From here you can download models, generate TTS and use external applications via the API address.\n",
|
||||
"\n",
|
||||
"The AllTalk API address is what you would use in Kobold, SillyTavern, TGWUI's Remote extension etc if you want to generate TTS with those applications."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"cellView": "form",
|
||||
"id": "9PCQhSoiWvhL"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#@markdown Click the `Play button` to the left of this message to start AllTalk API and Gradio Interface<br>\n",
|
||||
"import re\n",
|
||||
"import time\n",
|
||||
"import json\n",
|
||||
"import threading\n",
|
||||
"import time\n",
|
||||
"\n",
|
||||
"def keep_alive():\n",
|
||||
" while True:\n",
|
||||
" time.sleep(60) # Run every 60 seconds (adjust as needed)\n",
|
||||
"\n",
|
||||
"keep_alive_thread = threading.Thread(target=keep_alive)\n",
|
||||
"keep_alive_thread.start()\n",
|
||||
"\n",
|
||||
"Tunnel = \"cloudflare\"\n",
|
||||
"host = \"127.0.0.1\"\n",
|
||||
"ports = [7851, 7852]\n",
|
||||
"tunnel_urls = []\n",
|
||||
"\n",
|
||||
"# Starting tunnels for each port.\n",
|
||||
"for port in ports:\n",
|
||||
" if Tunnel == \"cloudflare\":\n",
|
||||
" !nohup cloudflared tunnel --url http://{host}:{port} > lt_{port}.log 2>&1 &\n",
|
||||
" else:\n",
|
||||
" !nohup npx lt -p {port} > lt_{port}.log 2>&1 &\n",
|
||||
"\n",
|
||||
"# Wait a couple of seconds for the tunnels to initialize.\n",
|
||||
"time.sleep(10)\n",
|
||||
"\n",
|
||||
"# Extract URLs for each tunnel.\n",
|
||||
"for port in ports:\n",
|
||||
" log_file = f'lt_{port}.log'\n",
|
||||
" with open(log_file, 'r') as testwritefile:\n",
|
||||
" log_content = testwritefile.read()\n",
|
||||
"\n",
|
||||
" # Use regular expressions to find the URL.\n",
|
||||
" if Tunnel == \"cloudflare\":\n",
|
||||
" url_match = re.search(r'(https://[-a-z0-9]+\\.trycloudflare\\.com)', log_content)\n",
|
||||
" else:\n",
|
||||
" url_match = re.search(r'your url is: (https?://\\S+)', log_content)\n",
|
||||
"\n",
|
||||
" if url_match:\n",
|
||||
" tunnel_url = url_match.group(1)\n",
|
||||
" tunnel_urls.append(tunnel_url)\n",
|
||||
" else:\n",
|
||||
" print(f\"URL for port {port} not found.\")\n",
|
||||
"\n",
|
||||
"# Save the tunnel URLs to a JSON file.\n",
|
||||
"try:\n",
|
||||
" # Try to open the JSON file for reading.\n",
|
||||
" with open('/content/alltalk_tts/googlecolab.json', 'r') as f:\n",
|
||||
" data = json.load(f)\n",
|
||||
"except FileNotFoundError:\n",
|
||||
" # If the file doesn't exist, create an empty dictionary.\n",
|
||||
" data = {}\n",
|
||||
"\n",
|
||||
"data['google_ip_address'] = tunnel_urls\n",
|
||||
"\n",
|
||||
"# Write the modified data (or newly created data) back to the file.\n",
|
||||
"with open('/content/alltalk_tts/googlecolab.json', 'w') as f:\n",
|
||||
" json.dump(data, f)\n",
|
||||
"\n",
|
||||
"host = \"0.0.0.0\"\n",
|
||||
"\n",
|
||||
"if Tunnel == \"localtunnel\":\n",
|
||||
" print(\"Before you copy the link above click on it and copy that ip:\")\n",
|
||||
" !curl ipv4.icanhazip.com\n",
|
||||
"\n",
|
||||
"# Start API server.\n",
|
||||
"!python /content/alltalk_tts/script.py\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"# Start XTTS model Finetuning\n",
|
||||
"\n",
|
||||
"Starts the Finetuning application for XTTS models.\n",
|
||||
"\n",
|
||||
"You can either run the `Start AllTalk` to download the base XTTS model(s) for finetuning. Or you can use the folder icon on the left hand side of the screen and upload an XTTS model that you want to finetune. If you are manually uploading a model, you would place your model files in `models/xtts/{yourmodelfolderhere}` and you will need all the models files in that folder `config.json, dvae.pth, mel_stats.pth, model.pth, speakers_xtts.pth, vocab.json`. Without 1x model available, Finetuning will not start.\n",
|
||||
"\n",
|
||||
"Likewise you can download your finetuned model from there, OR copy it to your Google Drive after finetuning, for later use in AllTalk. To access the Finetuning Gradio interface, connect to the `Google Colab Finetuning url` when Finetuning has started."
|
||||
],
|
||||
"metadata": {
|
||||
"id": "gDzNPMwM7qvs"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"#@markdown Click the `Play button` to the left of this message to start Finetuning<br>\n",
|
||||
"import re\n",
|
||||
"import time\n",
|
||||
"import json\n",
|
||||
"import threading\n",
|
||||
"import time\n",
|
||||
"\n",
|
||||
"def keep_alive():\n",
|
||||
" while True:\n",
|
||||
" time.sleep(60) # Run every 60 seconds (adjust as needed)\n",
|
||||
"\n",
|
||||
"keep_alive_thread = threading.Thread(target=keep_alive)\n",
|
||||
"keep_alive_thread.start()\n",
|
||||
"\n",
|
||||
"Tunnel = \"cloudflare\"\n",
|
||||
"host = \"127.0.0.1\"\n",
|
||||
"ports = [7052]\n",
|
||||
"tunnel_urls = []\n",
|
||||
"\n",
|
||||
"# Starting tunnels for each port.\n",
|
||||
"for port in ports:\n",
|
||||
" if Tunnel == \"cloudflare\":\n",
|
||||
" !nohup cloudflared tunnel --url http://{host}:{port} > lt_{port}.log 2>&1 &\n",
|
||||
" else:\n",
|
||||
" !nohup npx lt -p {port} > lt_{port}.log 2>&1 &\n",
|
||||
"\n",
|
||||
"# Wait a couple of seconds for the tunnels to initialize.\n",
|
||||
"time.sleep(10)\n",
|
||||
"\n",
|
||||
"# Extract URLs for each tunnel.\n",
|
||||
"for port in ports:\n",
|
||||
" log_file = f'lt_{port}.log'\n",
|
||||
" with open(log_file, 'r') as testwritefile:\n",
|
||||
" log_content = testwritefile.read()\n",
|
||||
"\n",
|
||||
" # Use regular expressions to find the URL.\n",
|
||||
" if Tunnel == \"cloudflare\":\n",
|
||||
" url_match = re.search(r'(https://[-a-z0-9]+\\.trycloudflare\\.com)', log_content)\n",
|
||||
" else:\n",
|
||||
" url_match = re.search(r'your url is: (https?://\\S+)', log_content)\n",
|
||||
"\n",
|
||||
" if url_match:\n",
|
||||
" tunnel_url = url_match.group(1)\n",
|
||||
" tunnel_urls.append(tunnel_url)\n",
|
||||
" print(f\"Google Colab Finetuning url: {tunnel_url}\\n\")\n",
|
||||
" print(f\"********************************************************************\")\n",
|
||||
" print(f\"**** Use the above URL to connect to Finetuning on Google Colab ****\")\n",
|
||||
" print(f\"********************************************************************\")\n",
|
||||
" print(f\"************* Now starting the Finetuning Application **************\")\n",
|
||||
" print(f\"********************************************************************\\n\")\n",
|
||||
" else:\n",
|
||||
" print(f\"URL for port {port} not found.\")\n",
|
||||
"\n",
|
||||
"# Save the tunnel URLs to a JSON file.\n",
|
||||
"try:\n",
|
||||
" # Try to open the JSON file for reading.\n",
|
||||
" with open('/content/alltalk_tts/googlecolab.json', 'r') as f:\n",
|
||||
" data = json.load(f)\n",
|
||||
"except FileNotFoundError:\n",
|
||||
" # If the file doesn't exist, create an empty dictionary.\n",
|
||||
" data = {}\n",
|
||||
"\n",
|
||||
"data['google_ip_address'] = tunnel_urls\n",
|
||||
"\n",
|
||||
"# Write the modified data (or newly created data) back to the file.\n",
|
||||
"with open('/content/alltalk_tts/googlecolab.json', 'w') as f:\n",
|
||||
" json.dump(data, f)\n",
|
||||
"\n",
|
||||
"host = \"0.0.0.0\"\n",
|
||||
"\n",
|
||||
"if Tunnel == \"localtunnel\":\n",
|
||||
" print(\"Before you copy the link above click on it and copy that ip:\")\n",
|
||||
" !curl ipv4.icanhazip.com\n",
|
||||
"\n",
|
||||
"# Start API server.\n",
|
||||
"!python /content/alltalk_tts/finetune.py\n"
|
||||
],
|
||||
"metadata": {
|
||||
"cellView": "form",
|
||||
"id": "Dewvq5s38Sfd"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"accelerator": "GPU",
|
||||
"colab": {
|
||||
"gpuType": "T4",
|
||||
"provenance": []
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"base_port": 7001,
|
||||
"api_server_port": 7851,
|
||||
"auto_start_engines": 0,
|
||||
"max_instances": 8,
|
||||
"gradio_interface_port": 7500,
|
||||
"max_retries": 12,
|
||||
"initial_wait": 2.0,
|
||||
"backoff_factor": 1.2,
|
||||
"debug_mode": false,
|
||||
"max_queue_time": 60,
|
||||
"queue_check_interval": 0.1,
|
||||
"tts_request_timeout": 30,
|
||||
"text_length_factor": 0.2,
|
||||
"concurrent_request_factor": 0.5,
|
||||
"diminishing_factor": 0.5,
|
||||
"queue_position_factor": 1.0
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"base_path": "models",
|
||||
"model_path": "xttsv2_2.0.2",
|
||||
"files_to_download": {
|
||||
"LICENSE.txt": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/LICENSE.txt?download=true",
|
||||
"README.md": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/README.md?download=true",
|
||||
"config.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/config.json?download=true",
|
||||
"model.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/model.pth?download=true",
|
||||
"dvae.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/dvae.pth?download=true",
|
||||
"mel_stats.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/mel_stats.pth?download=true",
|
||||
"speakers_xtts.pth": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/speakers_xtts.pth?download=true",
|
||||
"vocab.json": "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.2/vocab.json?download=true"
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
import importlib.metadata as metadata
|
||||
import json
|
||||
from packaging import version
|
||||
|
||||
#################################################################
|
||||
#### LOAD PARAMS FROM confignew.json - REQUIRED FOR BRANDING ####
|
||||
#################################################################
|
||||
# STARTUP VARIABLE - Create "this_dir" variable as the current script directory
|
||||
this_dir = Path(__file__).parent.resolve()
|
||||
|
||||
|
||||
# load standard config file in and get settings
|
||||
def load_config(file_path):
|
||||
with open(file_path, "r") as config_file:
|
||||
config = json.load(config_file)
|
||||
return config
|
||||
|
||||
|
||||
config_file_path = this_dir / "confignew.json"
|
||||
params = load_config(config_file_path)
|
||||
|
||||
# Define the path to the modeldownload config file file
|
||||
modeldownload_config_file_path = this_dir / "modeldownload.json"
|
||||
|
||||
# Check if the JSON file exists
|
||||
if modeldownload_config_file_path.exists():
|
||||
with open(modeldownload_config_file_path, "r") as config_file:
|
||||
settings = json.load(config_file)
|
||||
|
||||
# Extract settings from the loaded JSON
|
||||
base_path = Path(settings.get("base_path", ""))
|
||||
model_path = Path(settings.get("model_path", ""))
|
||||
files_to_download = settings.get("files_to_download", {})
|
||||
else:
|
||||
# Default settings if the JSON file doesn't exist or is empty
|
||||
print(
|
||||
f"[{params['branding']}Startup] \033[91mWarning\033[0m modeldownload.json is missing so please re-download it and save it in the coquii_tts main folder."
|
||||
)
|
||||
print(
|
||||
f"[{params['branding']}Startup] \033[91mWarning\033[0m API Local and XTTSv2 Local will error unless this is corrected."
|
||||
)
|
||||
|
||||
# Read the version specifier from requirements_nvidia.txt
|
||||
with open(this_dir / "requirements_nvidia.txt", "r") as req_file:
|
||||
requirements = req_file.readlines()
|
||||
|
||||
tts_version_required = None
|
||||
for req in requirements:
|
||||
if req.startswith("TTS>="):
|
||||
tts_version_required = req.strip().split(">=")[1]
|
||||
break
|
||||
|
||||
if tts_version_required is None:
|
||||
raise ValueError(
|
||||
f"[{params['branding']}Startup] \033[91mWarning\033[0m Could not find TTS version specifier in requirements_nvidia.txt"
|
||||
)
|
||||
|
||||
|
||||
def create_directory_if_not_exists(directory):
|
||||
if not directory.exists():
|
||||
directory.mkdir(parents=True)
|
||||
|
||||
|
||||
def download_file(url, destination):
|
||||
response = requests.get(url, stream=True)
|
||||
total_size_in_bytes = int(response.headers.get("content-length", 0))
|
||||
block_size = 1024 # 1 Kibibyte
|
||||
|
||||
progress_bar = tqdm(total=total_size_in_bytes, unit="iB", unit_scale=True)
|
||||
|
||||
with open(destination, "wb") as file:
|
||||
for data in response.iter_content(block_size):
|
||||
progress_bar.update(len(data))
|
||||
file.write(data)
|
||||
|
||||
progress_bar.close()
|
||||
|
||||
|
||||
def check_tts_version():
|
||||
try:
|
||||
tts_version = metadata.version("tts")
|
||||
print(
|
||||
f"[{params['branding']}Startup] TTS version installed: \033[93m"
|
||||
+ tts_version
|
||||
+ "\033[0m"
|
||||
)
|
||||
|
||||
if version.parse(tts_version) < version.parse(tts_version_required):
|
||||
print(
|
||||
f"[{params['branding']}Startup] \033[91mWarning\033[0m TTS version is too old. Please upgrade to version \033[93m"
|
||||
+ tts_version_required
|
||||
+ "\033[0m or later.\033[0m"
|
||||
)
|
||||
print(
|
||||
f"[{params['branding']}Startup] \033[91mWarning\033[0m At your terminal/command prompt \033[94mpip install --upgrade tts\033[0m"
|
||||
)
|
||||
else:
|
||||
print(f"[{params['branding']}Startup] TTS version is up to date.")
|
||||
except metadata.PackageNotFoundError:
|
||||
print(
|
||||
f"[{params['branding']}Startup] \033[91mWarning\033[0m TTS is not installed."
|
||||
)
|
||||
|
||||
|
||||
# Check and create directories
|
||||
if str(base_path) == "models":
|
||||
create_directory_if_not_exists(this_dir / base_path / model_path)
|
||||
else:
|
||||
create_directory_if_not_exists(base_path / model_path)
|
||||
print(
|
||||
f"[{params['branding']}Startup] \033[94mInfo\033[0m Custom path set in \033[93mmodeldownload.json\033[0m. Using the following settings:"
|
||||
)
|
||||
print(
|
||||
f"[{params['branding']}Startup] \033[94mInfo\033[0m Base folder Path:\033[93m",
|
||||
base_path,
|
||||
"\033[0m",
|
||||
)
|
||||
print(
|
||||
f"[{params['branding']}Startup] \033[94mInfo\033[0m Model folder Path:\033[93m",
|
||||
model_path,
|
||||
"\033[0m",
|
||||
)
|
||||
print(
|
||||
f"[{params['branding']}Startup] \033[94mInfo\033[0m Full Path:\033[93m",
|
||||
base_path / model_path,
|
||||
"\033[0m",
|
||||
)
|
||||
|
||||
# Download files if they don't exist
|
||||
print("[" + params["branding"] + "Startup] Checking Model is Downloaded.")
|
||||
for filename, url in files_to_download.items():
|
||||
if str(base_path) == "models":
|
||||
destination = this_dir / base_path / model_path / filename
|
||||
else:
|
||||
destination = Path(base_path) / model_path / filename
|
||||
|
||||
if not destination.exists():
|
||||
print(f"[{params['branding']}Startup] Downloading " + filename + "...")
|
||||
download_file(url, destination)
|
||||
|
||||
check_tts_version()
|
||||
@@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
pythonpath = .
|
||||
@@ -1,7 +0,0 @@
|
||||
gradio>=3.50.2
|
||||
torch>=2.1.0+cu118
|
||||
torchaudio>=2.1.0+cu118
|
||||
TTS==0.21.3
|
||||
faster-whisper>=0.10.0
|
||||
tqdm>=4.66.0
|
||||
pandas>=1.5.0
|
||||
@@ -1,15 +0,0 @@
|
||||
numpy>=1.22.0
|
||||
soundfile>=0.12.1
|
||||
uvicorn>=0.24.0.post1
|
||||
TTS>=0.21.3
|
||||
torch>=2.1.0+cu118
|
||||
torchaudio>=2.1.0+cu118
|
||||
fastapi>=0.104.1
|
||||
Jinja2>=3.1.2
|
||||
requests>=2.31.0
|
||||
tqdm>=4.66.1
|
||||
importlib-metadata>=4.8.1
|
||||
packaging>=23.2
|
||||
pydantic>=1.10.13
|
||||
sounddevice>=0.4.6
|
||||
python-multipart>=0.0.6
|
||||
@@ -1,15 +0,0 @@
|
||||
numpy>=1.22.0
|
||||
soundfile>=0.12.1
|
||||
uvicorn>=0.24.0.post1
|
||||
TTS>=0.21.3
|
||||
torch>=2.1.0
|
||||
torchaudio>=2.1.0
|
||||
fastapi>=0.104.1
|
||||
Jinja2>=3.1.2
|
||||
requests>=2.31.0
|
||||
tqdm>=4.66.1
|
||||
importlib-metadata>=4.8.1
|
||||
packaging>=23.2
|
||||
pydantic>=1.10.13
|
||||
sounddevice>=0.4.6
|
||||
python-multipart>=0.0.6
|
||||
@@ -0,0 +1,20 @@
|
||||
BASIC SETUP
|
||||
-----------
|
||||
Place the `alltalk.js` in your `\SillyTavern\public\scripts\extensions\tts\` folder, replacing the existing file.
|
||||
|
||||
CHANGING THE DEFAULT IP ADDRESS
|
||||
-------------------------------
|
||||
If you change the default IP/URL in the SillyTavern interface, you need to press Enter for it to save the setting. Just typing in an address/URL will not change the setting.
|
||||
|
||||
SIMPLE TROUBLESHOOTING
|
||||
----------------------
|
||||
The settings for AllTalk are stored, saved and managed by SillyTavern and not the AllTalk extension. You can manually edit OR delete these settings if necessary by editing the `settings.json` file that SillyTavern stores. I would suggest taking a copy of the file before making any changes.
|
||||
|
||||
Older versions of SillyTavern store `settings.json` file in `\SillyTavern\public\`
|
||||
|
||||
Newer version of SillyTavern store `settings.json` file in `\SillyTavern\data\default-user`
|
||||
|
||||
Within the `settings.json` file you will find a section called `"AllTalk": {` and it is closed out a few lines down with `},`
|
||||
|
||||
You can either manually edit any of the settings in there OR you can delete the entire section from the start of `"AllTalk":` to the end of `},` and when you refresh the SillyTavern web page OR restart SillyTavern, new settings for AllTalk will be created/saved.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Place the `alltalk.js` in your `\SillyTavern\public\scripts\extensions\tts\` folder, replacing the existing file.
|
||||
@@ -0,0 +1,834 @@
|
||||
import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js';
|
||||
import { saveTtsProviderSettings } from './index.js';
|
||||
|
||||
export { AllTalkTtsProvider };
|
||||
|
||||
class AllTalkTtsProvider {
|
||||
//########//
|
||||
// Config //
|
||||
//########//
|
||||
|
||||
settings = {};
|
||||
constructor() {
|
||||
// Initialize with default settings if they are not already set
|
||||
this.settings = {
|
||||
provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851',
|
||||
language: this.settings.language || 'en',
|
||||
voiceMap: this.settings.voiceMap || {},
|
||||
at_generation_method: this.settings.at_generation_method || 'standard_generation',
|
||||
narrator_enabled: this.settings.narrator_enabled || 'false',
|
||||
at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator',
|
||||
narrator_voice_gen: this.settings.narrator_voice_gen || 'female_01.wav',
|
||||
finetuned_model: this.settings.finetuned_model || 'false'
|
||||
};
|
||||
// Separate property for dynamically updated settings from the server
|
||||
this.dynamicSettings = {
|
||||
modelsAvailable: [],
|
||||
currentModel: '',
|
||||
deepspeed_available: false,
|
||||
deepSpeedEnabled: false,
|
||||
lowVramEnabled: false,
|
||||
};
|
||||
}
|
||||
ready = false;
|
||||
voices = [];
|
||||
separator = '. ';
|
||||
audioElement = document.createElement('audio');
|
||||
|
||||
languageLabels = {
|
||||
'Arabic': 'ar',
|
||||
'Brazilian Portuguese': 'pt',
|
||||
'Chinese': 'zh-cn',
|
||||
'Czech': 'cs',
|
||||
'Dutch': 'nl',
|
||||
'English': 'en',
|
||||
'French': 'fr',
|
||||
'German': 'de',
|
||||
'Italian': 'it',
|
||||
'Polish': 'pl',
|
||||
'Russian': 'ru',
|
||||
'Spanish': 'es',
|
||||
'Turkish': 'tr',
|
||||
'Japanese': 'ja',
|
||||
'Korean': 'ko',
|
||||
'Hungarian': 'hu',
|
||||
'Hindi': 'hi',
|
||||
};
|
||||
|
||||
get settingsHtml() {
|
||||
let html = `<div class="at-settings-separator">AllTalk Settings</div>`;
|
||||
|
||||
html += `<div class="at-settings-row">
|
||||
|
||||
<div class="at-settings-option">
|
||||
<label for="at_generation_method">AllTalk TTS Generation Method</label>
|
||||
<select id="at_generation_method">
|
||||
<option value="standard_generation">Standard Audio Generation (AT Narrator - Optional)</option>
|
||||
<option value="streaming_enabled">Streaming Audio Generation (AT Narrator - Disabled)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
html += `<div class="at-settings-row">
|
||||
|
||||
<div class="at-settings-option">
|
||||
<label for="at_narrator_enabled">AT Narrator</label>
|
||||
<select id="at_narrator_enabled">
|
||||
<option value="true">Enabled</option>
|
||||
<option value="false">Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="at-settings-option">
|
||||
<label for="at_narrator_text_not_inside">Text Not Inside * or " is</label>
|
||||
<select id="at_narrator_text_not_inside">
|
||||
<option value="narrator">Narrator</option>
|
||||
<option value="character">Character</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>`;
|
||||
|
||||
html += `<div class="at-settings-row">
|
||||
<div class="at-settings-option">
|
||||
<label for="narrator_voice">Narrator Voice</label>
|
||||
<select id="narrator_voice">`;
|
||||
if (this.voices) {
|
||||
for (let voice of this.voices) {
|
||||
html += `<option value="${voice.voice_id}">${voice.name}</option>`;
|
||||
}
|
||||
}
|
||||
html += `</select>
|
||||
</div>
|
||||
<div class="at-settings-option">
|
||||
<label for="language_options">Language</label>
|
||||
<select id="language_options">`;
|
||||
for (let language in this.languageLabels) {
|
||||
html += `<option value="${this.languageLabels[language]}" ${this.languageLabels[language] === this.settings?.language ? 'selected="selected"' : ''}>${language}</option>`;
|
||||
}
|
||||
html += `</select>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
||||
html += `<div class="at-model-endpoint-row">
|
||||
<div class="at-model-option">
|
||||
<label for="switch_model">Switch Model</label>
|
||||
<select id="switch_model">
|
||||
<option value="api_tts">API TTS</option>
|
||||
<option value="api_local">API Local</option>
|
||||
<option value="xttsv2_local">XTTSv2 Local</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="at-endpoint-option">
|
||||
<label for="at_server">AllTalk Endpoint:</label>
|
||||
<input id="at_server" type="text" class="text_pole" maxlength="80" value="${this.settings.provider_endpoint}"/>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
||||
html += `<div class="at-model-endpoint-row">
|
||||
<div class="at-settings-option">
|
||||
<label for="low_vram">Low VRAM</label>
|
||||
<input id="low_vram" type="checkbox"/>
|
||||
</div>
|
||||
<div class="at-settings-option">
|
||||
<label for="deepspeed">DeepSpeed</label>
|
||||
<input id="deepspeed" type="checkbox"/>
|
||||
</div>
|
||||
<div class="at-settings-option status-option">
|
||||
<span>Status: <span id="status_info">Ready</span></span>
|
||||
</div>
|
||||
<div class="at-settings-option empty-option">
|
||||
<!-- This div remains empty for spacing -->
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
||||
html += `<div class="at-website-row">
|
||||
<div class="at-website-option">
|
||||
<span>AllTalk <a target="_blank" href="${this.settings.provider_endpoint}">Config & Docs</a>.</span>
|
||||
</div>
|
||||
|
||||
<div class="at-website-option">
|
||||
<span>AllTalk <a target="_blank" href="https://github.com/erew123/alltalk_tts/">Website</a>.</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
html += `<div class="at-website-row">
|
||||
<div class="at-website-option">
|
||||
<span><strong>Text-generation-webui</strong> users - Uncheck <strong>Enable TTS</strong> in Text-generation-webui.</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
//#################//
|
||||
// Startup ST & AT //
|
||||
//#################//
|
||||
|
||||
async loadSettings(settings) {
|
||||
updateStatus('Offline');
|
||||
|
||||
if (Object.keys(settings).length === 0) {
|
||||
console.info('Using default AllTalk TTS Provider settings');
|
||||
} else {
|
||||
// Populate settings with provided values, ignoring server-provided settings
|
||||
for (const key in settings) {
|
||||
if (key in this.settings) {
|
||||
this.settings[key] = settings[key];
|
||||
} else {
|
||||
console.debug(`Ignoring non-user-configurable setting: ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI elements to reflect the loaded settings
|
||||
$('#at_server').val(this.settings.provider_endpoint);
|
||||
$('#language_options').val(this.settings.language);
|
||||
//$('#voicemap').val(this.settings.voiceMap);
|
||||
$('#at_generation_method').val(this.settings.at_generation_method);
|
||||
$('#at_narrator_enabled').val(this.settings.narrator_enabled);
|
||||
$('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside);
|
||||
$('#narrator_voice').val(this.settings.narrator_voice_gen);
|
||||
|
||||
console.debug('AllTalkTTS: Settings loaded');
|
||||
try {
|
||||
// Check if TTS provider is ready
|
||||
await this.checkReady();
|
||||
await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server
|
||||
await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready
|
||||
this.updateNarratorVoicesDropdown();
|
||||
this.updateLanguageDropdown();
|
||||
this.setupEventListeners();
|
||||
this.applySettingsToHTML();
|
||||
updateStatus('Ready');
|
||||
} catch (error) {
|
||||
console.error("Error loading settings:", error);
|
||||
updateStatus('Offline');
|
||||
}
|
||||
}
|
||||
|
||||
applySettingsToHTML() {
|
||||
// Apply loaded settings or use defaults
|
||||
const narratorVoiceSelect = document.getElementById('narrator_voice');
|
||||
const atNarratorSelect = document.getElementById('at_narrator_enabled');
|
||||
const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
|
||||
const generationMethodSelect = document.getElementById('at_generation_method');
|
||||
this.settings.narrator_voice = this.settings.narrator_voice_gen;
|
||||
// Apply settings to Narrator Voice dropdown
|
||||
if (narratorVoiceSelect && this.settings.narrator_voice) {
|
||||
narratorVoiceSelect.value = this.settings.narrator_voice.replace('.wav', '');
|
||||
}
|
||||
// Apply settings to AT Narrator Enabled dropdown
|
||||
if (atNarratorSelect) {
|
||||
// Sync the state with the checkbox in index.js
|
||||
const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js
|
||||
const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js
|
||||
const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js
|
||||
// Sync the state with the checkbox in index.js
|
||||
if (this.settings.narrator_enabled) {
|
||||
ttsPassAsterisksCheckbox.checked = false;
|
||||
$('#tts_pass_asterisks').click(); // Simulate a click event
|
||||
$('#tts_pass_asterisks').trigger('change');
|
||||
}
|
||||
if (!this.settings.narrator_enabled) {
|
||||
ttsPassAsterisksCheckbox.checked = true;
|
||||
$('#tts_pass_asterisks').click(); // Simulate a click event
|
||||
$('#tts_pass_asterisks').trigger('change');
|
||||
}
|
||||
// Uncheck and set tts_narrate_quoted to false if narrator is enabled
|
||||
if (this.settings.narrator_enabledd) {
|
||||
ttsNarrateQuotedCheckbox.checked = true;
|
||||
ttsNarrateDialoguesCheckbox.checked = true;
|
||||
// Trigger click events instead of change events
|
||||
$('#tts_narrate_quoted').click();
|
||||
$('#tts_narrate_quoted').trigger('change');
|
||||
$('#tts_narrate_dialogues').click();
|
||||
$('#tts_narrate_dialogues').trigger('change');
|
||||
}
|
||||
atNarratorSelect.value = this.settings.narrator_enabled.toString();
|
||||
this.settings.narrator_enabled = this.settings.narrator_enabled.toString();
|
||||
}
|
||||
// Apply settings to the Language dropdown
|
||||
const languageSelect = document.getElementById('language_options');
|
||||
if (languageSelect && this.settings.language) {
|
||||
languageSelect.value = this.settings.language;
|
||||
}
|
||||
// Apply settings to Text Not Inside dropdown
|
||||
if (textNotInsideSelect && this.settings.text_not_inside) {
|
||||
textNotInsideSelect.value = this.settings.text_not_inside;
|
||||
this.settings.at_narrator_text_not_inside = this.settings.text_not_inside;
|
||||
}
|
||||
// Apply settings to Generation Method dropdown
|
||||
if (generationMethodSelect && this.settings.at_generation_method) {
|
||||
generationMethodSelect.value = this.settings.at_generation_method;
|
||||
}
|
||||
// Additional logic to disable/enable dropdowns based on the selected generation method
|
||||
const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled';
|
||||
if (isStreamingEnabled) {
|
||||
// Disable certain dropdowns when streaming is enabled
|
||||
if (atNarratorSelect) atNarratorSelect.disabled = true;
|
||||
if (textNotInsideSelect) textNotInsideSelect.disabled = true;
|
||||
if (narratorVoiceSelect) narratorVoiceSelect.disabled = true;
|
||||
} else {
|
||||
// Enable dropdowns for standard generation
|
||||
if (atNarratorSelect) atNarratorSelect.disabled = false;
|
||||
if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled;
|
||||
if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled;
|
||||
}
|
||||
const modelSelect = document.getElementById('switch_model');
|
||||
if (this.settings.finetuned_model === 'true') {
|
||||
const ftOption = document.createElement('option');
|
||||
ftOption.value = 'XTTSv2 FT';
|
||||
ftOption.textContent = 'XTTSv2 FT';
|
||||
modelSelect.appendChild(ftOption);
|
||||
}
|
||||
}
|
||||
|
||||
//##############################//
|
||||
// Check AT Server is Available //
|
||||
//##############################//
|
||||
|
||||
async checkReady() {
|
||||
try {
|
||||
const response = await fetch(`${this.settings.provider_endpoint}/api/ready`);
|
||||
// Check if the HTTP request was successful
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const statusText = await response.text();
|
||||
// Check if the response is 'Ready'
|
||||
if (statusText === 'Ready') {
|
||||
this.ready = true; // Set the ready flag to true
|
||||
console.log('TTS service is ready.');
|
||||
} else {
|
||||
this.ready = false;
|
||||
console.log('TTS service is not ready.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking TTS service readiness:', error);
|
||||
this.ready = false; // Ensure ready flag is set to false in case of error
|
||||
}
|
||||
}
|
||||
|
||||
//######################//
|
||||
// Get Available Voices //
|
||||
//######################//
|
||||
|
||||
async fetchTtsVoiceObjects() {
|
||||
const response = await fetch(`${this.settings.provider_endpoint}/api/voices`);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
const voices = data.voices.map(filename => {
|
||||
const voiceName = filename.replace('.wav', '');
|
||||
return {
|
||||
name: voiceName,
|
||||
voice_id: voiceName,
|
||||
preview_url: null, // Preview URL will be dynamically generated
|
||||
lang: 'en' // Default language
|
||||
};
|
||||
});
|
||||
this.voices = voices; // Assign to the class property
|
||||
return voices; // Also return this list
|
||||
}
|
||||
|
||||
//##########################################//
|
||||
// Get Current AT Server Config & Update ST //
|
||||
//##########################################//
|
||||
|
||||
async updateSettingsFromServer() {
|
||||
try {
|
||||
// Fetch current settings
|
||||
const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch current settings: ${response.statusText}`);
|
||||
}
|
||||
const currentSettings = await response.json();
|
||||
// Update internal settings
|
||||
this.settings.modelsAvailable = currentSettings.models_available;
|
||||
this.settings.currentModel = currentSettings.current_model_loaded;
|
||||
this.settings.deepspeed_available = currentSettings.deepspeed_available;
|
||||
this.settings.deepSpeedEnabled = currentSettings.deepspeed_status;
|
||||
this.settings.lowVramEnabled = currentSettings.low_vram_status;
|
||||
this.settings.finetuned_model = currentSettings.finetuned_model
|
||||
// Update HTML elements
|
||||
this.updateModelDropdown();
|
||||
this.updateCheckboxes();
|
||||
} catch (error) {
|
||||
console.error(`Error updating settings from server: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
//###################################################//
|
||||
// Get Current AT Server Config & Update ST (Models) //
|
||||
//###################################################//
|
||||
|
||||
updateModelDropdown() {
|
||||
const modelSelect = document.getElementById('switch_model');
|
||||
if (modelSelect) {
|
||||
modelSelect.innerHTML = ''; // Clear existing options
|
||||
this.settings.modelsAvailable.forEach(model => {
|
||||
const option = document.createElement('option');
|
||||
option.value = model.model_name;
|
||||
option.textContent = model.model_name; // Use model_name instead of name
|
||||
option.selected = model.model_name === this.settings.currentModel;
|
||||
modelSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//#######################################################//
|
||||
// Get Current AT Server Config & Update ST (DS and LVR) //
|
||||
//#######################################################//
|
||||
|
||||
updateCheckboxes() {
|
||||
const deepspeedCheckbox = document.getElementById('deepspeed');
|
||||
const lowVramCheckbox = document.getElementById('low_vram');
|
||||
if (lowVramCheckbox) lowVramCheckbox.checked = this.settings.lowVramEnabled;
|
||||
if (deepspeedCheckbox) {
|
||||
deepspeedCheckbox.checked = this.settings.deepSpeedEnabled;
|
||||
deepspeedCheckbox.disabled = !this.settings.deepspeed_available; // Disable checkbox if deepspeed is not available
|
||||
}
|
||||
}
|
||||
|
||||
//###############################################################//
|
||||
// Get Current AT Server Config & Update ST (AT Narrator Voices) //
|
||||
//###############################################################//
|
||||
|
||||
updateNarratorVoicesDropdown() {
|
||||
const narratorVoiceSelect = document.getElementById('narrator_voice');
|
||||
if (narratorVoiceSelect && this.voices) {
|
||||
// Clear existing options
|
||||
narratorVoiceSelect.innerHTML = '';
|
||||
// Add new options
|
||||
for (let voice of this.voices) {
|
||||
const option = document.createElement('option');
|
||||
option.value = voice.voice_id;
|
||||
option.textContent = voice.name;
|
||||
narratorVoiceSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//######################################################//
|
||||
// Get Current AT Server Config & Update ST (Languages) //
|
||||
//######################################################//
|
||||
|
||||
updateLanguageDropdown() {
|
||||
const languageSelect = document.getElementById('language_options');
|
||||
if (languageSelect) {
|
||||
// Ensure default language is set
|
||||
this.settings.language = this.settings.language;
|
||||
|
||||
languageSelect.innerHTML = '';
|
||||
for (let language in this.languageLabels) {
|
||||
const option = document.createElement('option');
|
||||
option.value = this.languageLabels[language];
|
||||
option.textContent = language;
|
||||
if (this.languageLabels[language] === this.settings.language) {
|
||||
option.selected = true;
|
||||
}
|
||||
languageSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//########################################//
|
||||
// Start AT TTS extenstion page listeners //
|
||||
//########################################//
|
||||
|
||||
setupEventListeners() {
|
||||
|
||||
let debounceTimeout;
|
||||
const debounceDelay = 500; // Milliseconds
|
||||
|
||||
// Define the event handler function
|
||||
const onModelSelectChange = async (event) => {
|
||||
console.log("Model select change event triggered"); // Debugging statement
|
||||
const selectedModel = event.target.value;
|
||||
console.log(`Selected model: ${selectedModel}`); // Debugging statement
|
||||
// Set status to Processing
|
||||
updateStatus('Processing');
|
||||
try {
|
||||
const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP Error: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log("POST response data:", data); // Debugging statement
|
||||
// Set status to Ready if successful
|
||||
updateStatus('Ready');
|
||||
} catch (error) {
|
||||
console.error("POST request error:", error); // Debugging statement
|
||||
// Set status to Error in case of failure
|
||||
updateStatus('Error');
|
||||
}
|
||||
|
||||
// Handle response or error
|
||||
};
|
||||
|
||||
const debouncedModelSelectChange = (event) => {
|
||||
clearTimeout(debounceTimeout);
|
||||
debounceTimeout = setTimeout(() => {
|
||||
onModelSelectChange(event);
|
||||
}, debounceDelay);
|
||||
};
|
||||
|
||||
// Switch Model Listener
|
||||
const modelSelect = document.getElementById('switch_model');
|
||||
if (modelSelect) {
|
||||
// Remove the event listener if it was previously added
|
||||
modelSelect.removeEventListener('change', debouncedModelSelectChange);
|
||||
// Add the debounced event listener
|
||||
modelSelect.addEventListener('change', debouncedModelSelectChange);
|
||||
}
|
||||
|
||||
// DeepSpeed Listener
|
||||
const deepspeedCheckbox = document.getElementById('deepspeed');
|
||||
if (deepspeedCheckbox) {
|
||||
deepspeedCheckbox.addEventListener('change', async (event) => {
|
||||
const deepSpeedValue = event.target.checked ? 'True' : 'False';
|
||||
// Set status to Processing
|
||||
updateStatus('Processing');
|
||||
try {
|
||||
const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP Error: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log("POST response data:", data); // Debugging statement
|
||||
// Set status to Ready if successful
|
||||
updateStatus('Ready');
|
||||
} catch (error) {
|
||||
console.error("POST request error:", error); // Debugging statement
|
||||
// Set status to Error in case of failure
|
||||
updateStatus('Error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Low VRAM Listener
|
||||
const lowVramCheckbox = document.getElementById('low_vram');
|
||||
if (lowVramCheckbox) {
|
||||
lowVramCheckbox.addEventListener('change', async (event) => {
|
||||
const lowVramValue = event.target.checked ? 'True' : 'False';
|
||||
// Set status to Processing
|
||||
updateStatus('Processing');
|
||||
try {
|
||||
const response = await fetch(`${this.settings.provider_endpoint}/api/lowvramsetting?new_low_vram_value=${lowVramValue}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP Error: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log("POST response data:", data); // Debugging statement
|
||||
// Set status to Ready if successful
|
||||
updateStatus('Ready');
|
||||
} catch (error) {
|
||||
console.error("POST request error:", error); // Debugging statement
|
||||
// Set status to Error in case of failure
|
||||
updateStatus('Error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Narrator Voice Dropdown Listener
|
||||
const narratorVoiceSelect = document.getElementById('narrator_voice');
|
||||
if (narratorVoiceSelect) {
|
||||
narratorVoiceSelect.addEventListener('change', (event) => {
|
||||
this.settings.narrator_voice_gen = `${event.target.value}.wav`;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
}
|
||||
|
||||
const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
|
||||
if (textNotInsideSelect) {
|
||||
textNotInsideSelect.addEventListener('change', (event) => {
|
||||
this.settings.text_not_inside = event.target.value;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
}
|
||||
|
||||
// AT Narrator Dropdown Listener
|
||||
const atNarratorSelect = document.getElementById('at_narrator_enabled');
|
||||
const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js
|
||||
const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js
|
||||
const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js
|
||||
|
||||
if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) {
|
||||
atNarratorSelect.addEventListener('change', (event) => {
|
||||
const isNarratorEnabled = event.target.value === 'true';
|
||||
this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here
|
||||
textNotInsideSelect.disabled = !isNarratorEnabled;
|
||||
narratorVoiceSelect.disabled = !isNarratorEnabled;
|
||||
|
||||
// Sync the state with the checkbox in index.js
|
||||
if (isNarratorEnabled) {
|
||||
ttsPassAsterisksCheckbox.checked = false;
|
||||
$('#tts_pass_asterisks').click(); // Simulate a click event
|
||||
$('#tts_pass_asterisks').trigger('change');
|
||||
}
|
||||
if (!isNarratorEnabled) {
|
||||
ttsPassAsterisksCheckbox.checked = true;
|
||||
$('#tts_pass_asterisks').click(); // Simulate a click event
|
||||
$('#tts_pass_asterisks').trigger('change');
|
||||
}
|
||||
// Uncheck and set tts_narrate_quoted to false if narrator is enabled
|
||||
if (isNarratorEnabled) {
|
||||
ttsNarrateQuotedCheckbox.checked = true;
|
||||
ttsNarrateDialoguesCheckbox.checked = true;
|
||||
// Trigger click events instead of change events
|
||||
$('#tts_narrate_quoted').click();
|
||||
$('#tts_narrate_quoted').trigger('change');
|
||||
$('#tts_narrate_dialogues').click();
|
||||
$('#tts_narrate_dialogues').trigger('change');
|
||||
}
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Event Listener for AT Generation Method Dropdown
|
||||
const atGenerationMethodSelect = document.getElementById('at_generation_method');
|
||||
const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled');
|
||||
if (atGenerationMethodSelect) {
|
||||
atGenerationMethodSelect.addEventListener('change', (event) => {
|
||||
const selectedMethod = event.target.value;
|
||||
|
||||
if (selectedMethod === 'streaming_enabled') {
|
||||
// Disable and unselect AT Narrator
|
||||
atNarratorEnabledSelect.disabled = true;
|
||||
atNarratorEnabledSelect.value = 'false';
|
||||
textNotInsideSelect.disabled = true;
|
||||
narratorVoiceSelect.disabled = true;
|
||||
} else if (selectedMethod === 'standard_generation') {
|
||||
// Enable AT Narrator
|
||||
atNarratorEnabledSelect.disabled = false;
|
||||
}
|
||||
this.settings.at_generation_method = selectedMethod; // Update the setting here
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
}
|
||||
|
||||
// Listener for Language Dropdown
|
||||
const languageSelect = document.getElementById('language_options');
|
||||
if (languageSelect) {
|
||||
languageSelect.addEventListener('change', (event) => {
|
||||
this.settings.language = event.target.value;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
}
|
||||
|
||||
// Listener for AllTalk Endpoint Input
|
||||
const atServerInput = document.getElementById('at_server');
|
||||
if (atServerInput) {
|
||||
atServerInput.addEventListener('input', (event) => {
|
||||
this.settings.provider_endpoint = event.target.value;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#############################//
|
||||
// Store ST interface settings //
|
||||
//#############################//
|
||||
|
||||
onSettingsChange() {
|
||||
// Update settings based on the UI elements
|
||||
//this.settings.provider_endpoint = $('#at_server').val();
|
||||
this.settings.language = $('#language_options').val();
|
||||
//this.settings.voiceMap = $('#voicemap').val();
|
||||
this.settings.at_generation_method = $('#at_generation_method').val();
|
||||
this.settings.narrator_enabled = $('#at_narrator_enabled').val();
|
||||
this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val();
|
||||
this.settings.narrator_voice_gen = $('#narrator_voice').val();
|
||||
// Save the updated settings
|
||||
saveTtsProviderSettings();
|
||||
}
|
||||
|
||||
//#########################//
|
||||
// ST Handle Reload button //
|
||||
//#########################//
|
||||
|
||||
async onRefreshClick() {
|
||||
await this.checkReady(); // Check if the TTS provider is ready
|
||||
await this.loadSettings(this.settings); // Reload the settings
|
||||
// Additional actions as needed
|
||||
}
|
||||
|
||||
//##################//
|
||||
// Preview AT Voice //
|
||||
//##################//
|
||||
|
||||
async previewTtsVoice(voiceName) {
|
||||
try {
|
||||
// Prepare data for POST request
|
||||
const postData = new URLSearchParams();
|
||||
postData.append("voice", `${voiceName}.wav`);
|
||||
// Making the POST request
|
||||
const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: postData,
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error(`[previewTtsVoice] Error Response Text:`, errorText);
|
||||
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
||||
}
|
||||
// Assuming the server returns a URL to the .wav file
|
||||
const data = await response.json();
|
||||
if (data.output_file_url) {
|
||||
// Use an audio element to play the .wav file
|
||||
const audioElement = new Audio(`${this.settings.provider_endpoint}${data.output_file_url}`);
|
||||
audioElement.play().catch(e => console.error("Error playing audio:", e));
|
||||
} else {
|
||||
console.warn("[previewTtsVoice] No output file URL received in the response");
|
||||
throw new Error("No output file URL received in the response");
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("[previewTtsVoice] Exception caught during preview generation:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
//#####################//
|
||||
// Populate ST voices //
|
||||
//#####################//
|
||||
|
||||
async getVoice(voiceName, generatePreview = false) {
|
||||
// Ensure this.voices is populated
|
||||
if (this.voices.length === 0) {
|
||||
// Fetch voice objects logic
|
||||
}
|
||||
// Find the object where the name matches voiceName
|
||||
const match = this.voices.find(voice => voice.name === voiceName);
|
||||
if (!match) {
|
||||
// Error handling
|
||||
}
|
||||
// Generate preview URL only if requested
|
||||
if (!match.preview_url && generatePreview) {
|
||||
// Generate preview logic
|
||||
}
|
||||
return match; // Return the found voice object
|
||||
}
|
||||
|
||||
//##########################################//
|
||||
// Generate TTS Streaming or call Standard //
|
||||
//##########################################//
|
||||
|
||||
async generateTts(inputText, voiceId) {
|
||||
try {
|
||||
if (this.settings.at_generation_method === 'streaming_enabled') {
|
||||
// Construct the streaming URL
|
||||
const streamingUrl = `${this.settings.provider_endpoint}/api/tts-generate-streaming?text=${encodeURIComponent(inputText)}&voice=${encodeURIComponent(voiceId)}.wav&language=${encodeURIComponent(this.settings.language)}&output_file=stream_output.wav`;
|
||||
console.log("Streaming URL:", streamingUrl);
|
||||
|
||||
// Return the streaming URL directly
|
||||
return streamingUrl;
|
||||
} else {
|
||||
// For standard method
|
||||
const outputUrl = await this.fetchTtsGeneration(inputText, voiceId);
|
||||
const audioResponse = await fetch(outputUrl);
|
||||
if (!audioResponse.ok) {
|
||||
throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`);
|
||||
}
|
||||
return audioResponse; // Return the fetch response directly
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in generateTts:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//####################//
|
||||
// Generate Standard //
|
||||
//####################//
|
||||
|
||||
async fetchTtsGeneration(inputText, voiceId) {
|
||||
// Prepare the request payload
|
||||
const requestBody = new URLSearchParams({
|
||||
'text_input': inputText,
|
||||
'text_filtering': "standard",
|
||||
'character_voice_gen': voiceId + ".wav",
|
||||
'narrator_enabled': this.settings.narrator_enabled,
|
||||
'narrator_voice_gen': this.settings.narrator_voice_gen + ".wav",
|
||||
'text_not_inside': this.settings.at_narrator_text_not_inside,
|
||||
'language': this.settings.language,
|
||||
'output_file_name': "st_output",
|
||||
'output_file_timestamp': "true",
|
||||
'autoplay': "false",
|
||||
'autoplay_volume': "0.8"
|
||||
}).toString();
|
||||
|
||||
try {
|
||||
const response = await doExtrasFetch(
|
||||
`${this.settings.provider_endpoint}/api/tts-generate`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
body: requestBody
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error(`[fetchTtsGeneration] Error Response Text:`, errorText);
|
||||
// toastr.error(response.statusText, 'TTS Generation Failed');
|
||||
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
//const outputUrl = data.output_file_url;
|
||||
const outputUrl = `${this.settings.provider_endpoint}${data.output_file_url}`;
|
||||
return outputUrl; // Return only the output_file_url
|
||||
} catch (error) {
|
||||
console.error("[fetchTtsGeneration] Exception caught:", error);
|
||||
throw error; // Rethrow the error for further handling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#########################//
|
||||
// Update Status Messages //
|
||||
//#########################//
|
||||
|
||||
function updateStatus(message) {
|
||||
const statusElement = document.getElementById('status_info');
|
||||
if (statusElement) {
|
||||
statusElement.textContent = message;
|
||||
switch (message) {
|
||||
case 'Offline':
|
||||
statusElement.style.color = 'red';
|
||||
break;
|
||||
case 'Ready':
|
||||
statusElement.style.color = 'lightgreen';
|
||||
break;
|
||||
case 'Processing':
|
||||
statusElement.style.color = 'blue';
|
||||
break;
|
||||
case 'Error':
|
||||
statusElement.style.color = 'red';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
Text-generation-webui remote extension for AllTalk!
|
||||
|
||||
This folder contains everything you need to set up a Text-generation-webui extension that can communicate with an AllTalk server remotely.
|
||||
|
||||
To get started, follow these simple steps:
|
||||
|
||||
1) In your Text-generation-webui's extensions folder, create a new folder and name it "alltalk_tts".
|
||||
2) Take all the files from this current folder and copy them into the newly created "alltalk_tts" folder.
|
||||
3) Start & Open Text-generation-webui and look for "alltalk_tts" in the list of extensions. Select alltalk_tts, save your settings and restart Text-generation-webui.
|
||||
4) Text-generation-webui will restart and load the AllTalk extension. You may see an error/warning as the extension will not yet have the IP address details of your remote AllTalk server.
|
||||
5) In the extension settings, enter the correct IP address and port of your AllTalk server. If your AllTalk server is running on a remote machine accessed through a secure tunnel, make sure to use "https" instead of "http".
|
||||
6) After entering the server details, click on the "Refresh settings & voices" button. If the settings are correct, the extension will download all the available settings from the AllTalk server.
|
||||
7) The first time you successfully generate speech using the extension, the settings will be automatically saved to a local configuration file.
|
||||
|
||||
Please note that if the AllTalk server you're connected to doesn't support certain settings/features, those options will be greyed out and unavailable in the extension.
|
||||
|
||||
Please use the "Refresh settings & voices" button to make sure the extension is in sync with your AllTalk server.
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"branding": "AllTalk ",
|
||||
"tgwui": {
|
||||
"tgwui_activate_tts": true,
|
||||
"tgwui_autoplay_tts": true,
|
||||
"tgwui_narrator_enabled": "false",
|
||||
"tgwui_non_quoted_text_is": "silent",
|
||||
"tgwui_deepspeed_enabled": false,
|
||||
"tgwui_language": "English",
|
||||
"tgwui_lowvram_enabled": false,
|
||||
"tgwui_pitch_set": 0,
|
||||
"tgwui_temperature_set": 0.75,
|
||||
"tgwui_repetitionpenalty_set": 10,
|
||||
"tgwui_generationspeed_set": 1,
|
||||
"tgwui_narrator_voice": "en_US-ljspeech-high.onnx",
|
||||
"tgwui_show_text": true,
|
||||
"tgwui_character_voice": "en_US-ljspeech-high.onnx",
|
||||
"tgwui_rvc_char_voice": "Disabled",
|
||||
"tgwui_rvc_char_pitch": 0,
|
||||
"tgwui_rvc_narr_voice": "Disabled",
|
||||
"tgwui_rvc_narr_pitch": 0
|
||||
},
|
||||
"remote_connection": {
|
||||
"server_protocol": "http://",
|
||||
"server_address": "127.0.0.1:7851",
|
||||
"connection_timeout": 5,
|
||||
"use_legacy_api": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,865 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<link rel="icon" href="./static/favicon.ico" type="image/x-icon">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AllTalk TTS</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 40px auto;
|
||||
padding: 20px;
|
||||
background-color: #f4f4f4;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
color: black;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
min-width: 220px;
|
||||
/* Ensure a minimum width for the form groups */
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.form-group select,
|
||||
.form-group input[type="text"],
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
/* Include padding and border in the element's total width and height */
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-basis: 100%;
|
||||
/* Ensure button groups take up the full width */
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
/* Align buttons to the start */
|
||||
align-items: center;
|
||||
/* Adjusted to center align buttons */
|
||||
gap: 10px;
|
||||
/* Add space between buttons */
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
transition: background-color 0.3s;
|
||||
flex: 1;
|
||||
/* Allow buttons to take up equal space */
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
#refreshSettingsBtn {
|
||||
background-color: #3498db;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#submit {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
/* Make the submit button take the full width */
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#outputFile,
|
||||
#modelSelect {
|
||||
width: 100%;
|
||||
/* Set to full width */
|
||||
box-sizing: border-box;
|
||||
/* Include padding and border in the element's total width and height */
|
||||
}
|
||||
|
||||
#outputFile {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
p,
|
||||
span {
|
||||
color: #555;
|
||||
font-size: 16px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #e9e9e9;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
color: #3366ff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
overflow-x: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
ul {
|
||||
color: #555;
|
||||
list-style-type: square;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0077cc;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#container {
|
||||
max-width: 1000px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
#outputFileContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
textarea,
|
||||
input,
|
||||
select,
|
||||
button {
|
||||
padding: 4px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#outputFilePath {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#audioSource {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#outputFilePath {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Responsive styling */
|
||||
@media (max-width: 768px) {
|
||||
.form-group {
|
||||
flex: 1 1 100%;
|
||||
/* Make each form group take full width on small screens */
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
/* Stack buttons vertically on small screens */
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Declare variables in the global scope
|
||||
let protocol, host, port, cleanedBaseUrl, currentUrl;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Use URL constructor for robust parsing
|
||||
currentUrl = new URL(window.location.href);
|
||||
console.log('CurrentURL', currentUrl.href);
|
||||
|
||||
protocol = currentUrl.protocol; // "http:" or "https:"
|
||||
host = currentUrl.hostname; // "127.0.0.1"
|
||||
port = currentUrl.port || (protocol === 'https:' ? '443' : '80'); // "7851" or default ports
|
||||
const basePath = currentUrl.pathname.split('/')[1] ? `/${currentUrl.pathname.split('/')[1]}` : '';
|
||||
|
||||
console.log('Protocol:', protocol);
|
||||
console.log('Host:', host);
|
||||
console.log('Port:', port);
|
||||
console.log('Base Path:', basePath);
|
||||
|
||||
cleanedBaseUrl = `${protocol}//${host}:${port}${basePath}`;
|
||||
console.log('CleanedBaseURL', cleanedBaseUrl);
|
||||
|
||||
// Fetch settings and populate the form, then set the URLs
|
||||
fetchSettings().then(() => {
|
||||
setUrls();
|
||||
}).catch(console.error);
|
||||
});
|
||||
|
||||
async function fetchSettings() {
|
||||
try {
|
||||
const response = await fetch('/settings-json');
|
||||
console.log("Response is:", response);
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok ' + response.statusText);
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log("Data is:", data);
|
||||
populateForm(data);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch settings:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function populateForm(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const deleteOutputWavsElem = document.getElementById('delete_output_wavs');
|
||||
const gradioInterfaceElem = document.getElementById('gradio_interface');
|
||||
const gradioPortNumberElem = document.getElementById('gradio_port_number');
|
||||
const apiPortNumberElem = document.getElementById('api_port_number');
|
||||
if (deleteOutputWavsElem) {
|
||||
deleteOutputWavsElem.value = data.delete_output_wavs;
|
||||
}
|
||||
if (gradioInterfaceElem) {
|
||||
gradioInterfaceElem.value = data.gradio_interface ? 'true' : 'false';
|
||||
}
|
||||
if (gradioPortNumberElem) {
|
||||
gradioPortNumberElem.value = data.gradio_port_number;
|
||||
console.log("Populated gradioPortNumberElem.value:", gradioPortNumberElem.value); // Add logging here
|
||||
}
|
||||
if (apiPortNumberElem) {
|
||||
apiPortNumberElem.value = data.api_def.api_port_number;
|
||||
}
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to set the URLs
|
||||
function setUrls() {
|
||||
const ttsGeneratorLink = document.getElementById('ttsGeneratorLink');
|
||||
ttsGeneratorLink.href = `${cleanedBaseUrl}/static/tts_generator/tts_generator.html`;
|
||||
ttsGeneratorLink.textContent = 'TTS Generator';
|
||||
const GradioLink = document.getElementById('GradioLink');
|
||||
const GradioLinkDark = document.getElementById('GradioLinkDark');
|
||||
const gradioPortNumberElem = document.getElementById('gradio_port_number');
|
||||
if (gradioPortNumberElem && gradioPortNumberElem.value) {
|
||||
GradioLink.href = `${protocol}//${host}:${gradioPortNumberElem.value}`;
|
||||
GradioLinkDark.href = `${protocol}//${host}:${gradioPortNumberElem.value}?__theme=dark`;
|
||||
} else {
|
||||
GradioLink.href = `${protocol}//${host}:7852/gradio`;
|
||||
GradioLinkDark.href = `${protocol}//${host}:7852/gradio?__theme=dark`;
|
||||
console.error('Gradio port number element not found or empty.');
|
||||
}
|
||||
GradioLink.textContent = 'Gradio (Light theme)';
|
||||
GradioLinkDark.textContent = 'Gradio (Dark theme)';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 id="toc">AllTalk TTS</h1>
|
||||
<hr>
|
||||
<div style="display: flex; justify-content: flex-start; align-items: flex-start;">
|
||||
<div style="flex: 1; padding-left: 20px;">
|
||||
<h3>Useful links</h3>
|
||||
<ul>
|
||||
<li><a href="" id="ttsGeneratorLink" target="_blank" rel="noopener">TTS Generator</a></li>
|
||||
<li><a href="" id="GradioLink" target="_blank" rel="noopener">Gradio (Light theme)</a></li>
|
||||
<li><a href="" id="GradioLinkDark" target="_blank" rel="noopener">Gradio (Dark theme)</a></li>
|
||||
<li>Documentation is inside the Gradio interface</li>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="flex: 1; padding-right: 20px;">
|
||||
<h3 id="index">Support Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/erew123/alltalk_tts" target="_blank" rel="noopener">Github</a>
|
||||
</li>
|
||||
<li><a href="https://github.com/erew123/alltalk_tts/issues/25" target="_blank"
|
||||
rel="noopener">Changelog</a></li>
|
||||
<li><a href="https://github.com/erew123/alltalk_tts/issues/74" target="_blank" rel="noopener">Feature
|
||||
Requests</a></li>
|
||||
<li><a href="https://github.com/erew123/alltalk_tts?#-help-with-problems" target="_blank"
|
||||
rel="noopener">Help Documents</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
|
||||
<div id="container">
|
||||
<form method="post" action="/tts-demo-request" id="ttsForm">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="text">Text:</label>
|
||||
<textarea id="text" name="text" rows="4" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="voice">Voice:</label>
|
||||
<select id="voice" name="voice" required></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="rvcvoice">RVC Voice:</label>
|
||||
<select id="rvcvoice" name="rvccharacter_voice_gen" required></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="language">Language:</label>
|
||||
<select id="language" name="language" value="English" required>
|
||||
<option value="en" selected>English</option>
|
||||
<option value="ar">Arabic</option>
|
||||
<option value="zh-cn">Chinese</option>
|
||||
<option value="cs">Czech</option>
|
||||
<option value="nl">Dutch</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="de">German</option>
|
||||
<option value="hu">Hungarian</option>
|
||||
<option value="it">Italian</option>
|
||||
<option value="ja">Japanese</option>
|
||||
<option value="ko">Korean</option>
|
||||
<option value="pl">Polish</option>
|
||||
<option value="pt">Portuguese</option>
|
||||
<option value="ru">Russian</option>
|
||||
<option value="es">Spanish</option>
|
||||
<option value="tr">Turkish</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="streaming">Streaming:</label>
|
||||
<select id="streaming" name="streaming" required>
|
||||
<option value="false" selected>No</option>
|
||||
<option value="true">Yes</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="outputFile">Output Filename:</label>
|
||||
<input type="text" id="outputFile" name="output_file" value="demo_output" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="engineSelect">Available Engines:</label>
|
||||
<select id="engineSelect" name="engineSelect" required></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="modelSelect">Available Models:</label>
|
||||
<select id="modelSelect" name="modelSelect" required></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row button-group">
|
||||
<button id="refreshSettingsBtn" type="button">Refresh Settings</button>
|
||||
<button id="changeEngineBtn" type="button">Change Engine</button>
|
||||
<button id="changeModelBtn" type="button">Change Model</button>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<button id="submit" type="submit">Generate TTS</button>
|
||||
</div>
|
||||
<audio controls autoplay id="audioSource">
|
||||
<source type="audio/wav" />
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
<span id="outputFilePath">{{ output_file_path }}</span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="container">
|
||||
<form method="post" action="/update-settings" id="settingsForm" enctype="application/x-www-form-urlencoded">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="delete_output_wavs"">Del WAV's older than:</label>
|
||||
<select id=" delete_output_wavs" name="delete_output_wavs" required>
|
||||
<option value="Disabled">Disabled</option>
|
||||
<option value="1 Day">1 Day</option>
|
||||
<option value="2 Days">2 Days</option>
|
||||
<option value="3 Days">3 Days</option>
|
||||
<option value="4 Days">4 Days</option>
|
||||
<option value="5 Days">5 Days</option>
|
||||
<option value="6 Days">6 Days</option>
|
||||
<option value="7 Days">7 Days</option>
|
||||
<option value="14 Days">14 Days</option>
|
||||
<option value="21 Days">21 Days</option>
|
||||
<option value="28 Days">28 Days</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="api_port_number">API Port Number:</label>
|
||||
<input type="number" id="api_port_number" name="api_port_number" required
|
||||
style="width: 228px; height: 20px;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="gradio_interface">Gradio Interface:</label>
|
||||
<select id="gradio_interface" name="gradio_interface" required>
|
||||
<option value="true">Enabled</option>
|
||||
<option value="false">Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="gradio_port_number">Gradio Port Number:</label>
|
||||
<input type="number" id="gradio_port_number" name="gradio_port_number" required
|
||||
style="width: 228px; height: 20px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<button id="submit" type="submit">Update Settings</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<hr>
|
||||
<h3 id="🛠️-about-this-project">🛠️ <strong>About this project</strong></h3>
|
||||
<p>AllTalk is a labour of love, developed and supported in my personal free time. As such, my ability to respond to
|
||||
support requests is limited. I prioritize issues based on their impact and the number of users affected. I
|
||||
appreciate your understanding and patience. If your inquiry isn't covered by the documentation or existing
|
||||
discussions, and it's not related to a bug or feature request, I'll do my best to assist as time allows.
|
||||
</p>
|
||||
<hr>
|
||||
<h3 id="💖-showing-your-support">💖 Showing Your Support</h3>
|
||||
<p>If AllTalk has been helpful to you, consider showing your support through a donation on my <a
|
||||
href="https://ko-fi.com/erew123">Ko-fi page</a>. Your support is greatly appreciated and helps ensure the
|
||||
continued development and improvement of AllTalk.</p>
|
||||
<hr>
|
||||
<h3 id="⚠️-showing-your-support">⚠️ Changing Available Engine</h3>
|
||||
<div>
|
||||
<p>Changing the <b>Available Engine</b> on this web page will cause the Gradio interface to become
|
||||
unsynchronized with the TTS Engine process <b>until</b> AllTalk is restarted. This may result in issues with
|
||||
the Gradio interface, including warnings such as <b><i>[Errno 10048] error while attempting to bind on
|
||||
address</i></b> or <b><i>runner</i></b> errors when exiting AllTalk. For best results, please use
|
||||
the Gradio interface to change and manage your <b>Available Engine</b>"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Use URL constructor for robust parsing
|
||||
const currentUrl = new URL(window.location.href);
|
||||
console.log('CurrentURL', currentUrl.href);
|
||||
|
||||
protocol = currentUrl.protocol; // "http:" or "https:"
|
||||
host = currentUrl.hostname; // "127.0.0.1"
|
||||
port = currentUrl.port || (protocol === 'https:' ? '443' : '80'); // "7851" or default ports
|
||||
|
||||
// Determine the base path by taking only the first part of the path
|
||||
const pathParts = currentUrl.pathname.split('/');
|
||||
const basePath = pathParts.length > 1 && pathParts[1] ? `/${pathParts[1]}` : '';
|
||||
|
||||
console.log('Protocol:', protocol);
|
||||
console.log('Host:', host);
|
||||
console.log('Port:', port);
|
||||
console.log('Base Path:', basePath);
|
||||
|
||||
cleanedBaseUrl = `${protocol}//${host}:${port}`;
|
||||
console.log('CleanedBaseURL', cleanedBaseUrl);
|
||||
|
||||
setUrls();
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const engineSelect = document.getElementById('engineSelect');
|
||||
const modelSelect = document.getElementById('modelSelect');
|
||||
const changeEngineBtn = document.getElementById('changeEngineBtn');
|
||||
const changeModelBtn = document.getElementById('changeModelBtn');
|
||||
const refreshSettingsBtn = document.getElementById('refreshSettingsBtn');
|
||||
console.log('Protocol:', protocol);
|
||||
console.log('Host:', host);
|
||||
console.log('Port:', port);
|
||||
|
||||
// Function to dynamically construct the URL for fetching voices
|
||||
function getVoicesUrl() {
|
||||
return `${cleanedBaseUrl}/api/voices`;
|
||||
}
|
||||
|
||||
// Function to dynamically construct the URL for fetching RVC voices
|
||||
function getrvcVoicesUrl() {
|
||||
return `${cleanedBaseUrl}/api/rvcvoices`;
|
||||
}
|
||||
|
||||
// Function to fetch current settings and populate the dropdown
|
||||
function fetchAndPopulateSettings() {
|
||||
fetch(`${cleanedBaseUrl}/api/currentsettings`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Clear existing options
|
||||
engineSelect.innerHTML = '';
|
||||
modelSelect.innerHTML = '';
|
||||
|
||||
// Populate the dropdown with available engines
|
||||
data.engines_available.forEach(engine => {
|
||||
const option = document.createElement('option');
|
||||
option.value = engine;
|
||||
option.textContent = engine;
|
||||
engineSelect.appendChild(option);
|
||||
});
|
||||
|
||||
// Populate the dropdown with available models
|
||||
data.models_available.forEach(model => {
|
||||
const option = document.createElement('option');
|
||||
option.value = model.name;
|
||||
option.textContent = model.name;
|
||||
modelSelect.appendChild(option);
|
||||
});
|
||||
|
||||
// Set the current loaded engine and model as selected
|
||||
engineSelect.value = data.current_engine_loaded;
|
||||
modelSelect.value = data.current_model_loaded;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching current settings:', error);
|
||||
// Retry fetching settings after 5 seconds on error
|
||||
setTimeout(fetchAndPopulateSettings, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to reload voices
|
||||
function reloadVoices() {
|
||||
// Clear existing options
|
||||
document.getElementById('voice').innerHTML = '';
|
||||
document.getElementById('rvcvoice').innerHTML = '';
|
||||
|
||||
// Fetch voices and populate the dropdown
|
||||
fetch(getVoicesUrl())
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const voiceSelect = document.getElementById('voice');
|
||||
data.voices.forEach(voice => {
|
||||
const option = document.createElement('option');
|
||||
option.value = voice;
|
||||
option.textContent = voice;
|
||||
voiceSelect.appendChild(option);
|
||||
});
|
||||
resetChangeEngineBtn();
|
||||
})
|
||||
.catch(error => console.error('Error fetching voices:', error));
|
||||
|
||||
// Fetch RVC voices and populate the dropdown
|
||||
fetch(getrvcVoicesUrl())
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const rvcVoiceSelect = document.getElementById('rvcvoice');
|
||||
data.rvcvoices.forEach(rvcvoice => {
|
||||
const option = document.createElement('option');
|
||||
option.value = rvcvoice;
|
||||
option.textContent = rvcvoice;
|
||||
rvcVoiceSelect.appendChild(option);
|
||||
});
|
||||
resetChangeEngineBtn();
|
||||
})
|
||||
.catch(error => console.error('Error fetching RVC voices:', error));
|
||||
}
|
||||
|
||||
// Function to check if engine is ready
|
||||
function checkEngineReady() {
|
||||
fetch(`${cleanedBaseUrl}/api/ready`, {
|
||||
method: 'GET'
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if (data === 'Ready') {
|
||||
console.log('Engine is ready');
|
||||
reloadVoices(); // Call the reloadVoices function
|
||||
fetchAndPopulateSettings(); // Refresh settings when engine is ready
|
||||
} else {
|
||||
console.log('Engine not ready yet, rechecking...');
|
||||
setTimeout(() => checkEngineReady(), 1000); // Recheck after 1 second
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error checking engine readiness:', error);
|
||||
setTimeout(() => checkEngineReady(), 1000); // Recheck after 1 second even on error
|
||||
});
|
||||
}
|
||||
|
||||
// Function to change the engine button to "Changing Engine" state
|
||||
function setChangeEngineBtnChanging() {
|
||||
changeEngineBtn.style.backgroundColor = 'gray';
|
||||
changeEngineBtn.textContent = 'Changing Engine';
|
||||
changeEngineBtn.disabled = true;
|
||||
}
|
||||
|
||||
// Function to reset the engine button to its original state
|
||||
function resetChangeEngineBtn() {
|
||||
changeEngineBtn.style.backgroundColor = '#4caf50';
|
||||
changeEngineBtn.textContent = 'Change Engine';
|
||||
changeEngineBtn.disabled = false;
|
||||
}
|
||||
|
||||
// Function to change the model button to "Changing Model" state
|
||||
function setChangeModelBtnChanging() {
|
||||
changeModelBtn.style.backgroundColor = 'gray';
|
||||
changeModelBtn.textContent = 'Changing Model';
|
||||
changeModelBtn.disabled = true;
|
||||
}
|
||||
|
||||
// Function to reset the model button to its original state
|
||||
function resetChangeModelBtn() {
|
||||
changeModelBtn.style.backgroundColor = '#4caf50';
|
||||
changeModelBtn.textContent = 'Change Model';
|
||||
changeModelBtn.disabled = false;
|
||||
}
|
||||
|
||||
// Event listener for the "Change Engine" button
|
||||
changeEngineBtn.addEventListener('click', function () {
|
||||
const selectedEngine = engineSelect.value;
|
||||
|
||||
setChangeEngineBtnChanging(); // Set button to "Changing Engine" state
|
||||
|
||||
fetch(`${cleanedBaseUrl}/api/enginereload?engine=${encodeURIComponent(selectedEngine)}`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.catch(error => console.error('Error sending engine reload request:', error));
|
||||
|
||||
console.log('Engine reload request sent, waiting 15 seconds before checking readiness...');
|
||||
setTimeout(() => {
|
||||
checkEngineReady();
|
||||
}, 15000); // Wait 15 seconds before starting to check
|
||||
});
|
||||
|
||||
// Event listener for the "Change Model" button
|
||||
changeModelBtn.addEventListener('click', function () {
|
||||
const selectedModel = modelSelect.value;
|
||||
|
||||
setChangeModelBtnChanging(); // Set button to "Changing Model" state
|
||||
fetch(`${cleanedBaseUrl}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'model-success') {
|
||||
console.log('Model changed successfully');
|
||||
setTimeout(() => {
|
||||
reloadVoices(); // Call the reloadVoices function after a delay
|
||||
fetchAndPopulateSettings(); // Refresh settings
|
||||
}, 12000); // Wait 12 second before refreshing settings and voices
|
||||
} else {
|
||||
console.error('Failed to change model');
|
||||
resetChangeModelBtn(); // Reset button to original state
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error changing model:', error);
|
||||
resetChangeModelBtn(); // Reset button to original state
|
||||
});
|
||||
});
|
||||
|
||||
// Event listener for the "Refresh Settings" button
|
||||
refreshSettingsBtn.addEventListener('click', function () {
|
||||
fetchAndPopulateSettings();
|
||||
reloadVoices();
|
||||
resetChangeEngineBtn(); // Reset button to original state
|
||||
resetChangeModelBtn(); // Reset button to original state
|
||||
});
|
||||
|
||||
// Fetch settings and populate the dropdown on page load
|
||||
fetchAndPopulateSettings();
|
||||
reloadVoices();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Audio player
|
||||
const audioPlayer = document.getElementById('audioSource');
|
||||
function enableLoader(enable) {
|
||||
// Change the submit button text and disable it while loading
|
||||
const submit = document.getElementById('submit');
|
||||
submit.disabled = enable ? true : false;
|
||||
submit.style.opacity = enable ? 0.7 : 1.0;
|
||||
submit.innerText = enable ? 'Generating...' : 'Generate TTS';
|
||||
// Disable the audio player while loading
|
||||
audioPlayer.disabled = enable ? true : false;
|
||||
audioPlayer.style.opacity = enable ? 0.7 : 1.0;
|
||||
audioPlayer.style.pointerEvents = enable ? 'none' : 'auto';
|
||||
}
|
||||
audioPlayer.addEventListener('canplay', () => {
|
||||
enableLoader(false);
|
||||
audioPlayer.play();
|
||||
});
|
||||
audioPlayer.addEventListener('abort', () => {
|
||||
enableLoader(false);
|
||||
});
|
||||
audioPlayer.addEventListener('error', (e) => {
|
||||
enableLoader(false);
|
||||
});
|
||||
|
||||
// Streaming selector
|
||||
const streaming = document.getElementById('streaming');
|
||||
streaming.addEventListener('change', (event) => {
|
||||
const outputFileContainer = document.getElementById('outputFileContainer');
|
||||
if (event.target.value === 'true') {
|
||||
outputFileContainer.style.display = 'none';
|
||||
} else {
|
||||
outputFileContainer.style.display = 'flex';
|
||||
}
|
||||
});
|
||||
|
||||
// Form submit event listener
|
||||
const form = document.getElementById('ttsForm');
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
enableLoader(true);
|
||||
audioPlayer.pause();
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
// Get and clean the text input from the textarea
|
||||
let textInput = formData.get('text');
|
||||
let cleanedText = textInput.replace(/ \- | \– /g, ' '); // Replace hyphens
|
||||
cleanedText = cleanedText.replace(/%/g, " percent");
|
||||
//cleanedText = cleanedText.replace(/([!?.])\1+/g, '$1');
|
||||
//cleanedText = cleanedText.replace(/[^a-zA-Z0-9\s\.,;:!?\-\'"À-ÿ\u0400-\u04FF]/g, '');
|
||||
//cleanedText = cleanedText.replace(/[^a-zA-Z0-9\s\.,;:!?\-\'"À-ÿ\u0400-\u04FF]\$/g, '')
|
||||
cleanedText = cleanedText.replace(/[^a-zA-Z0-9\s.,;:!?\-\'"$\u0400-\u04FF\u00C0-\u00FF\u0150\u0151\u0170\u0171\u011E\u011F\u0130\u0131\u015E\u015F\u00E7\u00C7\u00F6\u00D6\u0900-\u097F\u2018\u2019\u201C\u201D\u3001\u3002\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF\uAC00-\uD7A3\u1100-\u11FF\u3130-\u318F\uFF01\uFF0c\uFF1A\uFF1B\uFF1F]/g, '')
|
||||
cleanedText = cleanedText.replace(/\n+/g, ' ');
|
||||
cleanedText = cleanedText.replace(/\.\s'/g, ".'"); // Remove space between period and single quote
|
||||
//cleanedText = cleanedText.replace(/'/g, ""); // Remove all single quotes
|
||||
|
||||
// Set the cleaned text back to the form data
|
||||
formData.set('text', cleanedText);
|
||||
|
||||
// Check if streaming or not and handle accordingly
|
||||
if (formData.get('streaming') === 'true') {
|
||||
// For streaming, update the audio player src
|
||||
const searchParams = new URLSearchParams(formData).toString();
|
||||
audioPlayer.src = `${cleanedBaseUrl}/api/tts-generate-streaming?${searchParams}`;
|
||||
audioPlayer.load(); // Reload the audio player
|
||||
} else {
|
||||
// Add hard-coded settings to the formData object
|
||||
formData.append('text_filtering', 'standard');
|
||||
formData.append('narrator_enabled', false);
|
||||
formData.append('autoplay', false);
|
||||
formData.append('autoplay_volume', '0.1');
|
||||
// Change the field name from "voice" to "character_voice_gen"
|
||||
const voice = formData.get('voice');
|
||||
formData.delete('voice');
|
||||
formData.append('character_voice_gen', voice);
|
||||
// Update the field name from "output_file" to "output_file_name"
|
||||
const outputFile = formData.get('output_file');
|
||||
formData.delete('output_file');
|
||||
formData.append('output_file_name', outputFile);
|
||||
formData.delete('text');
|
||||
formData.set('text_input', cleanedText);
|
||||
// Convert "output_file_timestamp" value to boolean
|
||||
formData.append('output_file_timestamp', false);
|
||||
|
||||
console.log('Form Data:');
|
||||
for (const [key, value] of formData.entries()) {
|
||||
console.log(`${key}: ${value}`);
|
||||
}
|
||||
|
||||
// Send the POST request to the API endpoint
|
||||
try {
|
||||
const response = await fetch(`${cleanedBaseUrl}/api/tts-generate`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
cache: "no-store"
|
||||
});
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// Process the response
|
||||
const result = await response.json();
|
||||
console.log('Response:', result);
|
||||
|
||||
// Update output file path and audio player source using the response data
|
||||
const outputFilePath = document.getElementById('outputFilePath');
|
||||
outputFilePath.textContent = result.output_file_path;
|
||||
audioPlayer.src = cleanedBaseUrl + result.output_file_url;
|
||||
audioPlayer.load();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
// Handle the error (e.g., display an error message to the user)
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<hr>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -1,260 +1,248 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AllTalk Startup Settings</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
text-decoration: underline;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 975px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 5px;
|
||||
/* Adjusted the gap to create more space */
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
grid-column: span 2;
|
||||
/* Span both columns */
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
select,
|
||||
input,
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 3px;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 3px;
|
||||
box-sizing: border-box;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
grid-column: span 4;
|
||||
}
|
||||
|
||||
.warning {
|
||||
grid-column: span 4;
|
||||
/* Span all 4 columns */
|
||||
text-align: center;
|
||||
/* Center-align the text */
|
||||
color: red;
|
||||
/* Adjusted color for emphasis */
|
||||
font-weight: bold;
|
||||
/* Make the text bold */
|
||||
margin-top: 10px;
|
||||
/* Add some space between the button and the warning */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h3>AllTalk Startup Settings</h3><br>
|
||||
<form action="/update-settings" method="post">
|
||||
|
||||
<label for="activate" style="color: #33cc33;">Activate TTS:</label>
|
||||
<select id="activate" name="activate" required="">
|
||||
<option {% if data.activate %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.activate %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="autoplay" style="color: #33cc33;"> Autoplay TTS output:</label>
|
||||
<select id="autoplay" name="autoplay" required="">
|
||||
<option {% if data.autoplay %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.autoplay %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="show_text" style="color: #33cc33;"> Show Text on generation:</label>
|
||||
<select id="show_text" name="show_text" required="">
|
||||
<option {% if data.show_text %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.show_text %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="voice" style="color: #33cc33;"> Default voice:</label>
|
||||
<select id="voice" name="voice" required="">
|
||||
{% for file in wav_files %}
|
||||
<option value="{{ file }}" {% if data.voice==file %}selected{% endif %}>{{ file }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
|
||||
<label for="language" style="color: #33cc33;">Default Language:</label>
|
||||
<select id="language" name="language" required="">
|
||||
<option value="Arabic" {% if data.language=='Arabic' %}selected{% endif %}>Arabic</option>
|
||||
<option value="Chinese" {% if data.language=='Chinese' %}selected{% endif %}>Chinese</option>
|
||||
<option value="Czech" {% if data.language=='Czech' %}selected{% endif %}>Czech</option>
|
||||
<option value="Dutch" {% if data.language=='Dutch' %}selected{% endif %}>Dutch</option>
|
||||
<option value="English" {% if data.language=='English' %}selected{% endif %}>English</option>
|
||||
<option value="French" {% if data.language=='French' %}selected{% endif %}>French</option>
|
||||
<option value="German" {% if data.language=='German' %}selected{% endif %}>German</option>
|
||||
<option value="Hungarian" {% if data.language=='Hungarian' %}selected{% endif %}>Hungarian</option>
|
||||
<option value="Italian" {% if data.language=='Italian' %}selected{% endif %}>Italian</option>
|
||||
<option value="Japanese" {% if data.language=='Japanese' %}selected{% endif %}>Japanese</option>
|
||||
<option value="Korean" {% if data.language=='Korean' %}selected{% endif %}>Korean</option>
|
||||
<option value="Polish" {% if data.language=='Polish' %}selected{% endif %}>Polish</option>
|
||||
<option value="Portuguese" {% if data.language=='Portuguese' %}selected{% endif %}>Portuguese</option>
|
||||
<option value="Russian" {% if data.language=='Russian' %}selected{% endif %}>Russian</option>
|
||||
<option value="Spanish" {% if data.language=='Spanish' %}selected{% endif %}>Spanish</option>
|
||||
<option value="Turkish" {% if data.language=='Turkish' %}selected{% endif %}>Turkish</option>
|
||||
</select>
|
||||
|
||||
<label for="narrator_voice" style="color: #33cc33;"> Narrator voice:</label>
|
||||
<select id="narrator_voice" name="narrator_voice" required="">
|
||||
{% for file in wav_files %}
|
||||
<option value="{{ file }}" {% if data.narrator_voice==file %}selected{% endif %}>{{ file }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label for="remove_trailing_dots" style="color: #33cc33;"> Remove Trailing Dots:</label>
|
||||
<select id="remove_trailing_dots" name="remove_trailing_dots" required="">
|
||||
<option {% if data.remove_trailing_dots %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.remove_trailing_dots %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="narrator_enabled" style="color: #33cc33;"> Narrator enabled:</label>
|
||||
<select id="narrator_enabled" name="narrator_enabled" required="">
|
||||
<option {% if data.narrator_enabled %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.narrator_enabled %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="delete_output_wavs" style="color: #33cc33;">Del WAV's older than:</label>
|
||||
<select id="delete_output_wavs" name="delete_output_wavs" required="">
|
||||
<option value="Disabled" {% if data.delete_output_wavs=='Disabled' %}selected{% endif %}>Disabled</option>
|
||||
<option value="1 Day" {% if data.delete_output_wavs=='1 Day' %}selected{% endif %}>1 Day</option>
|
||||
<option value="2 Days" {% if data.delete_output_wavs=='2 Days' %}selected{% endif %}>2 Days</option>
|
||||
<option value="3 Days" {% if data.delete_output_wavs=='3 Days' %}selected{% endif %}>3 Days</option>
|
||||
<option value="4 Day" {% if data.delete_output_wavs=='4 Days' %}selected{% endif %}>4 Days</option>
|
||||
<option value="5 Day" {% if data.delete_output_wavs=='5 Days' %}selected{% endif %}>5 Days</option>
|
||||
<option value="6 Day" {% if data.delete_output_wavs=='6 Days' %}selected{% endif %}>6 Days</option>
|
||||
<option value="7 Days" {% if data.delete_output_wavs=='7 Days' %}selected{% endif %}>7 Days</option>
|
||||
<option value="14 Days" {% if data.delete_output_wavs=='14 Days' %}selected{% endif %}>14 Days</option>
|
||||
<option value="21 Days" {% if data.delete_output_wavs=='21 Days' %}selected{% endif %}>21 Days</option>
|
||||
<option value="28 Days" {% if data.delete_output_wavs=='28 Days' %}selected{% endif %}>28 Days</option>
|
||||
</select>
|
||||
|
||||
|
||||
<label for="low_vram" style="color: #33cc33;"> Low VRAM:</label>
|
||||
<select id="low_vram" name="low_vram" required="">
|
||||
<option {% if data.low_vram %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.low_vram %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="local_temperature" style="color: red;">Local Model temperature:</label>
|
||||
<select id="local_temperature" name="local_temperature" required="">
|
||||
{% for temp in range(0, 101, 5) %}
|
||||
<option value="{{ temp/100 }}" {% if data.local_temperature|float == temp/100 %}selected{% endif %}>{{ "{:.2f}".format(temp/100) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label for="local_repetition_penalty" style="color: red;"> Local Model repetition penalty:</label>
|
||||
<select id="local_repetition_penalty" name="local_repetition_penalty" required="">
|
||||
{% for repetition in range(10, 210, 10) %}
|
||||
<option value="{{ repetition/10 }}" {% if data.local_repetition_penalty|float == repetition/10 %}selected{% endif %}>{{ repetition/10 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label for="deepspeed_activate" style="color: red;">DeepSpeed Activate:</label>
|
||||
<select id="deepspeed_activate" name="deepspeed_activate" required="">
|
||||
<option {% if data.deepspeed_activate %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.deepspeed_activate %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="output_folder_wav" style="color: red;"> WAV Output Folder:</label>
|
||||
<input id="output_folder_wav" name="output_folder_wav" required="" type="text"
|
||||
value="{{ data.output_folder_wav }}" />
|
||||
|
||||
|
||||
<input type="hidden" id="tts_model_loaded" name="tts_model_loaded" value="{{ data.tts_model_loaded }}" />
|
||||
|
||||
<!-- Continue updating other dropdowns and inputs in a similar manner -->
|
||||
|
||||
<label for="tts_model_name" style="color: red;"> API TTS Model Name:</label>
|
||||
<input id="tts_model_name" name="tts_model_name" required="" type="text" value="{{ data.tts_model_name }}" />
|
||||
|
||||
<label for="model_version"
|
||||
style="color: red; font-size: 16px; line-height: 1; vertical-align: bottom; margin-bottom: 0;">
|
||||
API Local Model Folder:</label>
|
||||
<span style="font-size: 14px; vertical-align: bottom; margin-bottom: 0; margin-top: 2px;">{{
|
||||
modeldownload_model_path }}</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<label for="ip_address" style="color: red;"> IP Address:</label>
|
||||
<input id="ip_address" name="ip_address" required="" type="text" value="{{ data.ip_address }}" />
|
||||
|
||||
<label for="port_number" style="color: red;"> Port Number:</label>
|
||||
<input id="port_number" name="port_number" required="" type="text" value="{{ data.port_number }}" />
|
||||
|
||||
<div class="radio-group">
|
||||
<label for="tts_method_api_local" style="color: #33cc33;">Model: <span
|
||||
style="color: black; font-weight: normal;">API Local</span>:</label>
|
||||
<input id="tts_method_api_local_true" {% if data.tts_method_api_local %}checked{% endif %} name="tts_method"
|
||||
type="radio" value="api_local" />
|
||||
</div>
|
||||
|
||||
<div class="radio-group">
|
||||
<label for="tts_method_api_tts" style="color: #33cc33;"> Model: <span
|
||||
style="color: black; font-weight: normal;">API TTS</span>:</label>
|
||||
<input id="tts_method_api_tts_true" {% if data.tts_method_api_tts %}checked{% endif %} name="tts_method"
|
||||
type="radio" value="api_tts" />
|
||||
</div>
|
||||
|
||||
<div class="radio-group">
|
||||
<label for="tts_method_xtts_local" style="color: #33cc33;">Model: <span
|
||||
style="color: black; font-weight: normal;">XTTS Local</span>:</label>
|
||||
<input id="tts_method_xtts_local_true" {% if data.tts_method_xtts_local %}checked{% endif %}
|
||||
name="tts_method" type="radio" value="xtts_local" />
|
||||
</div>
|
||||
<button type="submit">Update Settings</button>
|
||||
</form>
|
||||
<p class="warning">WARNING: Changing RED items or setting DeepSpeed to True on a system without DeepSpeed installed,
|
||||
may cause errors.</p>
|
||||
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AllTalk Startup Settings</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
text-decoration: underline;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 975px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 5px;
|
||||
/* Adjusted the gap to create more space */
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
grid-column: span 2;
|
||||
/* Span both columns */
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
select,
|
||||
input,
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 3px;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 3px;
|
||||
box-sizing: border-box;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
grid-column: span 4;
|
||||
}
|
||||
|
||||
.warning {
|
||||
grid-column: span 4;
|
||||
/* Span all 4 columns */
|
||||
text-align: center;
|
||||
/* Center-align the text */
|
||||
color: red;
|
||||
/* Adjusted color for emphasis */
|
||||
font-weight: bold;
|
||||
/* Make the text bold */
|
||||
margin-top: 10px;
|
||||
/* Add some space between the button and the warning */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h3>AllTalk Startup Settings</h3><br>
|
||||
<form action="/update-settings" method="post">
|
||||
|
||||
<label for="activate" style="color: #b273fa;">TG-webui Activate TTS:</label>
|
||||
<select id="activate" name="activate" required="">
|
||||
<option {% if data.activate %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.activate %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="autoplay" style="color: #b273fa;"> TG-webui Autoplay TTS:</label>
|
||||
<select id="autoplay" name="autoplay" required="">
|
||||
<option {% if data.autoplay %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.autoplay %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="show_text" style="color: #b273fa;">TG-webui Show Text:</label>
|
||||
<select id="show_text" name="show_text" required="">
|
||||
<option {% if data.show_text %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.show_text %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="voice" style="color: #b273fa;"> TG-webui Default voice:</label>
|
||||
<select id="voice" name="voice" required="">
|
||||
{% for file in wav_files %}
|
||||
<option value="{{ file }}" {% if data.voice==file %}selected{% endif %}>{{ file }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
|
||||
<label for="language" style="color: #b273fa;">TG-webui Default Language:</label>
|
||||
<select id="language" name="language" required="">
|
||||
<option value="Arabic" {% if data.language=='Arabic' %}selected{% endif %}>Arabic</option>
|
||||
<option value="Chinese" {% if data.language=='Chinese' %}selected{% endif %}>Chinese</option>
|
||||
<option value="Czech" {% if data.language=='Czech' %}selected{% endif %}>Czech</option>
|
||||
<option value="Dutch" {% if data.language=='Dutch' %}selected{% endif %}>Dutch</option>
|
||||
<option value="English" {% if data.language=='English' %}selected{% endif %}>English</option>
|
||||
<option value="French" {% if data.language=='French' %}selected{% endif %}>French</option>
|
||||
<option value="German" {% if data.language=='German' %}selected{% endif %}>German</option>
|
||||
<option value="Hungarian" {% if data.language=='Hungarian' %}selected{% endif %}>Hungarian</option>
|
||||
<option value="Italian" {% if data.language=='Italian' %}selected{% endif %}>Italian</option>
|
||||
<option value="Japanese" {% if data.language=='Japanese' %}selected{% endif %}>Japanese</option>
|
||||
<option value="Korean" {% if data.language=='Korean' %}selected{% endif %}>Korean</option>
|
||||
<option value="Polish" {% if data.language=='Polish' %}selected{% endif %}>Polish</option>
|
||||
<option value="Portuguese" {% if data.language=='Portuguese' %}selected{% endif %}>Portuguese</option>
|
||||
<option value="Russian" {% if data.language=='Russian' %}selected{% endif %}>Russian</option>
|
||||
<option value="Spanish" {% if data.language=='Spanish' %}selected{% endif %}>Spanish</option>
|
||||
<option value="Turkish" {% if data.language=='Turkish' %}selected{% endif %}>Turkish</option>
|
||||
</select>
|
||||
|
||||
<label for="narrator_voice" style="color: #b273fa;"> TG-webui Narrator voice:</label>
|
||||
<select id="narrator_voice" name="narrator_voice" required="">
|
||||
{% for file in wav_files %}
|
||||
<option value="{{ file }}" {% if data.narrator_voice==file %}selected{% endif %}>{{ file }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label for="remove_trailing_dots" style="color: #b273fa;">TG-webui Remove Trailing Dots:</label>
|
||||
<select id="remove_trailing_dots" name="remove_trailing_dots" required="">
|
||||
<option {% if data.remove_trailing_dots %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.remove_trailing_dots %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="narrator_enabled" style="color: #b273fa;"> TG-webui Narrator enabled:</label>
|
||||
<select id="narrator_enabled" name="narrator_enabled" required="">
|
||||
<option {% if data.narrator_enabled %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.narrator_enabled %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="delete_output_wavs" style="color: #33cc33;">Del WAV's older than:</label>
|
||||
<select id="delete_output_wavs" name="delete_output_wavs" required="">
|
||||
<option value="Disabled" {% if data.delete_output_wavs=='Disabled' %}selected{% endif %}>Disabled</option>
|
||||
<option value="1 Day" {% if data.delete_output_wavs=='1 Day' %}selected{% endif %}>1 Day</option>
|
||||
<option value="2 Days" {% if data.delete_output_wavs=='2 Days' %}selected{% endif %}>2 Days</option>
|
||||
<option value="3 Days" {% if data.delete_output_wavs=='3 Days' %}selected{% endif %}>3 Days</option>
|
||||
<option value="4 Day" {% if data.delete_output_wavs=='4 Days' %}selected{% endif %}>4 Days</option>
|
||||
<option value="5 Day" {% if data.delete_output_wavs=='5 Days' %}selected{% endif %}>5 Days</option>
|
||||
<option value="6 Day" {% if data.delete_output_wavs=='6 Days' %}selected{% endif %}>6 Days</option>
|
||||
<option value="7 Days" {% if data.delete_output_wavs=='7 Days' %}selected{% endif %}>7 Days</option>
|
||||
<option value="14 Days" {% if data.delete_output_wavs=='14 Days' %}selected{% endif %}>14 Days</option>
|
||||
<option value="21 Days" {% if data.delete_output_wavs=='21 Days' %}selected{% endif %}>21 Days</option>
|
||||
<option value="28 Days" {% if data.delete_output_wavs=='28 Days' %}selected{% endif %}>28 Days</option>
|
||||
</select>
|
||||
|
||||
|
||||
<label for="low_vram" style="color: #33cc33;"> Low VRAM:</label>
|
||||
<select id="low_vram" name="low_vram" required="">
|
||||
<option {% if data.low_vram %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.low_vram %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="local_temperature" style="color: red;">Local Model temperature:</label>
|
||||
<select id="local_temperature" name="local_temperature" required="">
|
||||
{% for temp in range(0, 101, 5) %}
|
||||
<option value="{{ temp/100 }}" {% if data.local_temperature|float == temp/100 %}selected{% endif %}>{{ "{:.2f}".format(temp/100) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label for="local_repetition_penalty" style="color: red;"> Local Model repetition penalty:</label>
|
||||
<select id="local_repetition_penalty" name="local_repetition_penalty" required="">
|
||||
{% for repetition in range(10, 210, 10) %}
|
||||
<option value="{{ repetition/10 }}" {% if data.local_repetition_penalty|float == repetition/10 %}selected{% endif %}>{{ repetition/10 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label for="deepspeed_activate" style="color: red;">DeepSpeed Activate:</label>
|
||||
<select id="deepspeed_activate" name="deepspeed_activate" required="">
|
||||
<option {% if data.deepspeed_activate %}selected{% endif %} value="true">True</option>
|
||||
<option {% if not data.deepspeed_activate %}selected{% endif %} value="false">False</option>
|
||||
</select>
|
||||
|
||||
<label for="output_folder_wav" style="color: red;"> WAV Output Folder:</label>
|
||||
<input id="output_folder_wav" name="output_folder_wav" required="" type="text"
|
||||
value="{{ data.output_folder_wav }}" />
|
||||
|
||||
|
||||
<input type="hidden" id="tts_model_loaded" name="tts_model_loaded" value="{{ data.tts_model_loaded }}" />
|
||||
|
||||
<!-- Continue updating other dropdowns and inputs in a similar manner -->
|
||||
|
||||
<label for="tts_model_name" style="color: red;"> API TTS Model Name:</label>
|
||||
<input id="tts_model_name" name="tts_model_name" required="" type="text" value="{{ data.tts_model_name }}" />
|
||||
|
||||
<label for="model_version"
|
||||
style="color: red; font-size: 16px; line-height: 1; vertical-align: bottom; margin-bottom: 0;">
|
||||
API Local Model Folder:</label>
|
||||
<span style="font-size: 14px; vertical-align: bottom; margin-bottom: 0; margin-top: 2px;">{{
|
||||
modeldownload_model_path }}</span>
|
||||
|
||||
|
||||
<label for="port_number" style="color: red;"> Port Number:</label>
|
||||
<input id="port_number" name="port_number" required="" type="text" value="{{ data.port_number }}" /><br>
|
||||
|
||||
<div class="radio-group">
|
||||
<label for="tts_method_api_local" style="color: #33cc33;">Model: <span
|
||||
style="color: black; font-weight: normal;">API Local</span>:</label>
|
||||
<input id="tts_method_api_local_true" {% if data.tts_method_api_local %}checked{% endif %} name="tts_method"
|
||||
type="radio" value="api_local" />
|
||||
</div>
|
||||
|
||||
<div class="radio-group">
|
||||
<label for="tts_method_api_tts" style="color: #33cc33;"> Model: <span
|
||||
style="color: black; font-weight: normal;">API TTS</span>:</label>
|
||||
<input id="tts_method_api_tts_true" {% if data.tts_method_api_tts %}checked{% endif %} name="tts_method"
|
||||
type="radio" value="api_tts" />
|
||||
</div>
|
||||
|
||||
<div class="radio-group">
|
||||
<label for="tts_method_xtts_local" style="color: #33cc33;">Model: <span
|
||||
style="color: black; font-weight: normal;">XTTS Local</span>:</label>
|
||||
<input id="tts_method_xtts_local_true" {% if data.tts_method_xtts_local %}checked{% endif %}
|
||||
name="tts_method" type="radio" value="xtts_local" />
|
||||
</div>
|
||||
<button type="submit">Update Settings</button>
|
||||
</form>
|
||||
<p class="warning">WARNING: Changing RED items or setting DeepSpeed to True on a system without DeepSpeed installed,
|
||||
may cause errors.</p>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,296 @@
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
import json
|
||||
import warnings
|
||||
import pyperclip
|
||||
import requests
|
||||
from PyQt5.QtWidgets import (QApplication, QSystemTrayIcon, QMenu, QWidget, QVBoxLayout, QLabel,
|
||||
QLineEdit, QPushButton, QMessageBox, QComboBox, QSlider)
|
||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
|
||||
from PyQt5.QtGui import QIcon
|
||||
|
||||
# Suppress the specific warning
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module="charset_normalizer")
|
||||
|
||||
SETTINGS_FILE = "tts_settings.json"
|
||||
TTS_SERVER_URL = "http://127.0.0.1:7851"
|
||||
|
||||
LANGUAGES = {
|
||||
"ar": "Arabic", "zh-cn": "Chinese (Simplified)", "cs": "Czech", "nl": "Dutch",
|
||||
"en": "English", "fr": "French", "de": "German", "hi": "Hindi (limited support)",
|
||||
"hu": "Hungarian", "it": "Italian", "ja": "Japanese", "ko": "Korean",
|
||||
"pl": "Polish", "pt": "Portuguese", "ru": "Russian", "es": "Spanish", "tr": "Turkish"
|
||||
}
|
||||
|
||||
class ClipboardMonitor(QThread):
|
||||
text_changed = pyqtSignal(str)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.last_text = pyperclip.paste()
|
||||
self.running = True
|
||||
|
||||
def run(self):
|
||||
while self.running:
|
||||
current_text = pyperclip.paste()
|
||||
if current_text != self.last_text:
|
||||
self.text_changed.emit(current_text)
|
||||
self.last_text = current_text
|
||||
time.sleep(0.5)
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
class TTSWorker(QThread):
|
||||
error_occurred = pyqtSignal(str)
|
||||
|
||||
def __init__(self, api_url, params):
|
||||
super().__init__()
|
||||
self.api_url = api_url
|
||||
self.params = params
|
||||
self.text_queue = []
|
||||
|
||||
def add_text(self, text):
|
||||
self.text_queue.append(text)
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self.text_queue:
|
||||
text = self.text_queue.pop(0)
|
||||
self.send_to_tts(text)
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
|
||||
def send_to_tts(self, text):
|
||||
params = self.params.copy()
|
||||
params["text_input"] = text
|
||||
params["language"] = params["language"][:2] # Ensure we only send the 2-letter code
|
||||
try:
|
||||
response = requests.post(f"{self.api_url}/api/tts-generate", data=params)
|
||||
if response.status_code != 200:
|
||||
error_message = f"TTS generation failed with status code: {response.status_code}. Response: {response.text}"
|
||||
print(error_message)
|
||||
self.error_occurred.emit(error_message)
|
||||
except requests.RequestException as e:
|
||||
error_message = f"Error sending request to TTS API: {e}"
|
||||
print(error_message)
|
||||
self.error_occurred.emit(error_message)
|
||||
|
||||
class SettingsWindow(QWidget):
|
||||
settings_updated = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, tts_worker):
|
||||
super().__init__()
|
||||
self.tts_worker = tts_worker
|
||||
self.voices = []
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
layout.addWidget(QLabel("AllTalk API URL:"))
|
||||
self.api_url_input = QLineEdit(self.tts_worker.api_url)
|
||||
layout.addWidget(self.api_url_input)
|
||||
|
||||
layout.addWidget(QLabel("Voice:"))
|
||||
self.voice_combo = QComboBox()
|
||||
layout.addWidget(self.voice_combo)
|
||||
|
||||
layout.addWidget(QLabel("Language:"))
|
||||
self.language_combo = QComboBox()
|
||||
for code, name in LANGUAGES.items():
|
||||
self.language_combo.addItem(f"{name} ({code})", code)
|
||||
current_lang = self.tts_worker.params.get("language", "en")
|
||||
index = self.language_combo.findData(current_lang)
|
||||
if index >= 0:
|
||||
self.language_combo.setCurrentIndex(index)
|
||||
layout.addWidget(self.language_combo)
|
||||
|
||||
layout.addWidget(QLabel("Text Filtering:"))
|
||||
self.text_filtering_input = QLineEdit(self.tts_worker.params["text_filtering"])
|
||||
layout.addWidget(self.text_filtering_input)
|
||||
|
||||
layout.addWidget(QLabel("Playback Volume:"))
|
||||
self.volume_slider = QSlider(Qt.Horizontal)
|
||||
self.volume_slider.setRange(1, 10)
|
||||
self.volume_slider.setValue(int(float(self.tts_worker.params["autoplay_volume"]) * 10))
|
||||
layout.addWidget(self.volume_slider)
|
||||
|
||||
save_button = QPushButton("Save")
|
||||
save_button.clicked.connect(self.save_settings)
|
||||
layout.addWidget(save_button)
|
||||
|
||||
refresh_button = QPushButton("Refresh Voices")
|
||||
refresh_button.clicked.connect(self.refresh_voices)
|
||||
layout.addWidget(refresh_button)
|
||||
|
||||
self.setWindowTitle("AllTalk Clipboard TTS")
|
||||
self.setGeometry(300, 300, 300, 400)
|
||||
|
||||
self.refresh_voices()
|
||||
|
||||
def refresh_voices(self):
|
||||
try:
|
||||
response = requests.get(f"{self.tts_worker.api_url}/api/voices")
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if data["status"] == "success" and "voices" in data:
|
||||
self.voices = data["voices"]
|
||||
self.voice_combo.clear()
|
||||
for voice in self.voices:
|
||||
self.voice_combo.addItem(voice)
|
||||
if self.voices:
|
||||
current_voice = self.tts_worker.params["character_voice_gen"]
|
||||
if current_voice in self.voices:
|
||||
self.voice_combo.setCurrentText(current_voice)
|
||||
else:
|
||||
self.voice_combo.setCurrentIndex(0)
|
||||
else:
|
||||
QMessageBox.warning(self, "Error", "Failed to fetch voices: Unexpected response format")
|
||||
else:
|
||||
QMessageBox.warning(self, "Error", f"Failed to fetch voices. Status code: {response.status_code}")
|
||||
except requests.RequestException as e:
|
||||
QMessageBox.warning(self, "Error", f"Failed to fetch voices: {str(e)}")
|
||||
|
||||
def save_settings(self):
|
||||
new_settings = {
|
||||
"api_url": self.api_url_input.text(),
|
||||
"params": {
|
||||
"character_voice_gen": self.voice_combo.currentText(),
|
||||
"language": self.language_combo.currentData(),
|
||||
"text_filtering": self.text_filtering_input.text(),
|
||||
"output_file_name": "clipboard_tts",
|
||||
"output_file_timestamp": "true",
|
||||
"autoplay": "true",
|
||||
"autoplay_volume": str(self.volume_slider.value() / 10.0)
|
||||
}
|
||||
}
|
||||
self.settings_updated.emit(new_settings)
|
||||
self.hide()
|
||||
|
||||
class TTSApp(QSystemTrayIcon):
|
||||
def __init__(self, app):
|
||||
super().__init__(parent=app)
|
||||
self.app = app
|
||||
self.load_settings()
|
||||
self.init_ui()
|
||||
|
||||
def load_settings(self):
|
||||
try:
|
||||
with open(SETTINGS_FILE, 'r') as f:
|
||||
self.settings = json.load(f)
|
||||
except FileNotFoundError:
|
||||
self.settings = {
|
||||
"api_url": TTS_SERVER_URL,
|
||||
"params": {
|
||||
"text_filtering": "standard",
|
||||
"character_voice_gen": "",
|
||||
"language": "en",
|
||||
"output_file_name": "clipboard_tts",
|
||||
"output_file_timestamp": "true",
|
||||
"autoplay": "true",
|
||||
"autoplay_volume": "0.8"
|
||||
}
|
||||
}
|
||||
|
||||
def save_settings(self):
|
||||
with open(SETTINGS_FILE, 'w') as f:
|
||||
json.dump(self.settings, f)
|
||||
|
||||
def init_ui(self):
|
||||
self.tts_worker = TTSWorker(self.settings["api_url"], self.settings["params"])
|
||||
self.tts_worker.error_occurred.connect(self.show_error_message)
|
||||
self.tts_worker.start()
|
||||
|
||||
self.clipboard_monitor = ClipboardMonitor()
|
||||
self.clipboard_monitor.text_changed.connect(self.on_text_changed)
|
||||
|
||||
self.setIcon(QIcon("icon.png"))
|
||||
self.setVisible(True)
|
||||
|
||||
menu = QMenu()
|
||||
start_action = menu.addAction("Start Monitoring")
|
||||
start_action.triggered.connect(self.start_monitoring)
|
||||
stop_action = menu.addAction("Stop Monitoring")
|
||||
stop_action.triggered.connect(self.stop_monitoring)
|
||||
settings_action = menu.addAction("Settings")
|
||||
settings_action.triggered.connect(self.show_settings)
|
||||
exit_action = menu.addAction("Exit")
|
||||
exit_action.triggered.connect(self.exit_app)
|
||||
self.setContextMenu(menu)
|
||||
|
||||
self.settings_window = SettingsWindow(self.tts_worker)
|
||||
self.settings_window.settings_updated.connect(self.update_settings)
|
||||
|
||||
def on_text_changed(self, text):
|
||||
self.tts_worker.add_text(text)
|
||||
|
||||
def start_monitoring(self):
|
||||
self.clipboard_monitor.start()
|
||||
self.showMessage("TTS App", "Clipboard monitoring started")
|
||||
|
||||
def stop_monitoring(self):
|
||||
self.clipboard_monitor.stop()
|
||||
self.showMessage("TTS App", "Clipboard monitoring stopped")
|
||||
|
||||
def show_settings(self):
|
||||
self.settings_window.show()
|
||||
|
||||
def update_settings(self, new_settings):
|
||||
self.settings = new_settings
|
||||
self.tts_worker.api_url = new_settings["api_url"]
|
||||
self.tts_worker.params = new_settings["params"]
|
||||
self.save_settings()
|
||||
self.showMessage("TTS App", "Settings updated successfully")
|
||||
|
||||
def show_error_message(self, message):
|
||||
QMessageBox.critical(None, "Error", message)
|
||||
|
||||
def exit_app(self):
|
||||
self.clipboard_monitor.stop()
|
||||
self.tts_worker.quit()
|
||||
QApplication.instance().quit()
|
||||
|
||||
def check_server_ready(url, timeout=10):
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
response = requests.get(f"{url}/api/ready")
|
||||
if response.status_code == 200 and response.text == "Ready":
|
||||
return True
|
||||
except requests.RequestException:
|
||||
pass
|
||||
time.sleep(1)
|
||||
return False
|
||||
|
||||
def signal_handler(signal, frame):
|
||||
print("[Clipboard to TTS] Exiting...")
|
||||
QApplication.instance().quit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("[Clipboard to TTS] AllTalk Clipboard to TTS is starting...")
|
||||
|
||||
if not check_server_ready(TTS_SERVER_URL):
|
||||
print("[Clipboard to TTS] Error: AllTalk TTS server is not ready. Please start the AllTalk server and try again.")
|
||||
sys.exit(1)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
if not QSystemTrayIcon.isSystemTrayAvailable():
|
||||
print("[Clipboard to TTS] System tray is not available on this system.")
|
||||
sys.exit(1)
|
||||
|
||||
tts_app = TTSApp(app)
|
||||
|
||||
# This allows for Ctrl+C to work
|
||||
timer = QTimer()
|
||||
timer.start(500)
|
||||
timer.timeout.connect(lambda: None)
|
||||
|
||||
print("[Clipboard to TTS] AllTalk Clipboard to TTS is running... Check your system tray for the icon.")
|
||||
print("[Clipboard to TTS] Press Ctrl+C to exit")
|
||||
sys.exit(app.exec_())
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,13 @@
|
||||
This is a Windows only clipboard to TTS extension. Its a very rough bit of code with no logic or error handling built in.
|
||||
|
||||
When run it will create a system tray icon that you can Start/Stop clipboard monitoring for the text stored in the clipboard changing. When the text changes, it will be sent to AllTalk for TTS generation. You can specify the voice to use in the System Tray icon Settings area.
|
||||
|
||||
In the main folder, run "start_environment.bat" to start the AllTalk Python environment.
|
||||
|
||||
You will need to "pip install pyqt5 pyperclip requests" for to install the requirements into the AllTalk Python environment (you will only need to do this the once, but always start the Python environment).
|
||||
|
||||
Go to the "cliptotts" folder and "python clipboard-to-tts.py" which should start the application.
|
||||
|
||||
Start monitoring of the clipboard on the icon in the system tray.
|
||||
|
||||
When you Ctrl+C text to the clipboard, it will be sent over for TTS generation and it will play back at the AllTalk console.
|
||||
@@ -0,0 +1,564 @@
|
||||
======================================
|
||||
AllTalk Base Diagnostic log file for Standalone Installations
|
||||
======================================
|
||||
Diagnostic log created at 2024-11-06 01:25:33.123679
|
||||
======================================
|
||||
|
||||
OPERATING SYSTEM:
|
||||
OS Version: Windows 10.0.22631
|
||||
Note: Windows 11 will list as build is 10.x.22xxx
|
||||
|
||||
HARDWARE ENVIRONMENT:
|
||||
CPU: Physical Cores: 8, Total Cores: 16, Max Frequency: 3800.0 MHz
|
||||
System RAM: 19.46 GB available out of 31.34 GB total
|
||||
|
||||
GPU INFORMATION:
|
||||
Fri Sep 27 20:50:15 2024
|
||||
+-----------------------------------------------------------------------------------------+
|
||||
| NVIDIA-SMI 560.94 Driver Version: 560.94 CUDA Version: 12.6 |
|
||||
|-----------------------------------------+------------------------+----------------------+
|
||||
| GPU Name Driver-Model | Bus-Id Disp.A | Volatile Uncorr. ECC |
|
||||
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
|
||||
| | | MIG M. |
|
||||
|=========================================+========================+======================|
|
||||
| 0 NVIDIA GeForce RTX 4070 WDDM | 00000000:01:00.0 On | N/A |
|
||||
| 0% 39C P8 13W / 200W | 1071MiB / 12282MiB | 1% Default |
|
||||
| | | N/A |
|
||||
+-----------------------------------------+------------------------+----------------------+
|
||||
|
||||
+-----------------------------------------------------------------------------------------+
|
||||
| Processes: |
|
||||
| GPU GI CI PID Type Process name GPU Memory |
|
||||
| ID ID Usage |
|
||||
|=========================================================================================|
|
||||
| 0 N/A N/A 1508 C+G ...nt.CBS_cw5n1h2txyewy\SearchHost.exe N/A |
|
||||
| 0 N/A N/A 2756 C+G ...\cef\cef.win7x64\steamwebhelper.exe N/A |
|
||||
| 0 N/A N/A 5488 C+G ...2txyewy\StartMenuExperienceHost.exe N/A |
|
||||
| 0 N/A N/A 6504 C+G ...ekyb3d8bbwe\PhoneExperienceHost.exe N/A |
|
||||
| 0 N/A N/A 7212 C+G ...t.LockApp_cw5n1h2txyewy\LockApp.exe N/A |
|
||||
| 0 N/A N/A 8604 C+G C:\Windows\explorer.exe N/A |
|
||||
| 0 N/A N/A 8852 C+G ...ft Office\root\Office16\OUTLOOK.EXE N/A |
|
||||
| 0 N/A N/A 11964 C+G ...5.0_x64__cv1g1gvanyjgm\WhatsApp.exe N/A |
|
||||
| 0 N/A N/A 12516 C+G ...CBS_cw5n1h2txyewy\TextInputHost.exe N/A |
|
||||
| 0 N/A N/A 13376 C+G ...\cef\cef.win7x64\steamwebhelper.exe N/A |
|
||||
| 0 N/A N/A 17632 C+G ...nr4m\radeonsoftware\AMDRSSrcExt.exe N/A |
|
||||
| 0 N/A N/A 18316 C+G ...m\radeonsoftware\RadeonSoftware.exe N/A |
|
||||
| 0 N/A N/A 18908 C+G ...siveControlPanel\SystemSettings.exe N/A |
|
||||
| 0 N/A N/A 19032 C+G ...am Files\Microsoft VS Code\Code.exe N/A |
|
||||
| 0 N/A N/A 21216 C+G ...bytes\Anti-Malware\Malwarebytes.exe N/A |
|
||||
| 0 N/A N/A 21476 C+G ...crosoft\Edge\Application\msedge.exe N/A |
|
||||
| 0 N/A N/A 21604 C+G ...ta\Local\WeMod\app-9.10.3\WeMod.exe N/A |
|
||||
| 0 N/A N/A 22344 C+G ...__8wekyb3d8bbwe\WindowsTerminal.exe N/A |
|
||||
| 0 N/A N/A 25292 C+G ...5n1h2txyewy\ShellExperienceHost.exe N/A |
|
||||
| 0 N/A N/A 26220 C ...\alltalk_environment\env\python.exe N/A |
|
||||
| 0 N/A N/A 27960 C+G ...Brave-Browser\Application\brave.exe N/A |
|
||||
+-----------------------------------------------------------------------------------------+
|
||||
|
||||
CUDA:
|
||||
CUDA Working: Success - CUDA is available and working.
|
||||
CUDA_HOME : N/A
|
||||
CUDA Device : NVIDIA GeForce RTX 4070
|
||||
CUDA Memory : 11.99 GB
|
||||
CUDA Version: 12.1
|
||||
|
||||
DISK INFORMATION:
|
||||
Drive: C:\ | Total: 1000.95 GB | Used: 852.08 GB | Free: 148.87 GB | Type: NTFS
|
||||
|
||||
NETWORK PORT:
|
||||
Port Status : Port 7851 is available.
|
||||
Cublas64_11 Path: C:\myfiles\alltalk_tts\alltalk_environment\env\Lib\site-packages\nvidia/cublas\bin\cublas64_11.dll
|
||||
|
||||
Windows C++ Build tools & Windows SDK:
|
||||
Windows Version: Windows 11
|
||||
Visual C++ Build Tools and/or Visual Studio found:
|
||||
Visual Studio 2019 Build Tools (Path: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools)
|
||||
|
||||
Windows SDK(s) found:
|
||||
SDK Version: 10.0.22000.0
|
||||
Path: C:\Program Files (x86)\Windows Kits\10
|
||||
Python setuptools version: 75.1.0
|
||||
|
||||
Windows Espeak-ng:
|
||||
Espeak-ng version: eSpeak NG text-to-speech: 1.52-dev Data at: C:\Program Files\eSpeak NG\/espeak-ng-data
|
||||
|
||||
PYTHON & PYTORCH:
|
||||
Torch Version: 2.2.1
|
||||
Python Version: 3.11.10
|
||||
Python Version Info: sys.version_info(major=3, minor=11, micro=10, releaselevel='final', serial=0)
|
||||
Python Executable: C:\myfiles\alltalk_tts\alltalk_environment\env\python.exe
|
||||
Python Virtual Environment: N/A (Should be N/A when in Text-generation-webui Conda Python environment)
|
||||
Conda Environment: C:\myfiles\alltalk_tts\alltalk_environment\env
|
||||
|
||||
Python Search Path:
|
||||
C:\myfiles\alltalk_tts
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\python311.zip
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\DLLs
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\Lib
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\Lib\site-packages
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\Lib\site-packages\win32
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\Lib\site-packages\win32\lib
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\Lib\site-packages\Pythonwin
|
||||
|
||||
OS SEARCHPATH ENVIRONMENT:
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\Lib\site-packages\PyQt6\Qt6\bin
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\Library\mingw-w64\bin
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\Library\usr\bin
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\Library\bin
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\Scripts
|
||||
C:\myfiles\alltalk_tts\alltalk_environment\env\bin
|
||||
C:\Program Files (x86)\Common Files\Oracle\Java\javapath
|
||||
C:\Windows\system32
|
||||
C:\Windows
|
||||
C:\Windows\System32\Wbem
|
||||
C:\Windows\System32\WindowsPowerShell\v1.0
|
||||
C:\Program Files\dotnet
|
||||
C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR
|
||||
C:\Program Files\Git\cmd
|
||||
C:\ProgramData\chocolatey\bin
|
||||
C:
|
||||
C:\Program Files\nodejs
|
||||
C:\Program Files (x86)\Windows Kits\10\App Certification Kit
|
||||
C:\Program Files\Microsoft VS Code\bin
|
||||
C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common
|
||||
C:\Program Files\NVIDIA Corporation\Nsight Compute 2023.1.0
|
||||
C:\Program Files\eSpeak NG
|
||||
C:\Program Files (x86)\eSpeak NG
|
||||
|
||||
CONDA INFORMATION:
|
||||
Conda Executable: C:\myfiles\alltalk_tts\alltalk_environment\conda\condabin\..\Scripts\conda.exe
|
||||
Conda Version: 24.4.0
|
||||
Current Environment: C:\myfiles\alltalk_tts\alltalk_environment\env
|
||||
Conda Environments:
|
||||
- C:\myfiles\alltalk_tts\alltalk_environment\conda
|
||||
- C:\myfiles\alltalk_tts\alltalk_environment\env
|
||||
|
||||
CONDA PACKAGES IN CURRENT ENVIRONMENT:
|
||||
|
||||
CONDA-FORGE PACKAGES:
|
||||
aom Version: 3.9.1 Channel: conda-forge
|
||||
cairo Version: 1.18.0 Channel: conda-forge
|
||||
ffmpeg Version: 4.3.1 Channel: conda-forge
|
||||
fontconfig Version: 2.15.0 Channel: conda-forge
|
||||
freetype Version: 2.12.1 Channel: conda-forge
|
||||
harfbuzz Version: 9.0.0 Channel: conda-forge
|
||||
icu Version: 75.1 Channel: conda-forge
|
||||
lcms2 Version: 2.15 Channel: conda-forge
|
||||
libexpat Version: 2.6.4 Channel: conda-forge
|
||||
libglib Version: 2.82.2 Channel: conda-forge
|
||||
libiconv Version: 1.17 Channel: conda-forge
|
||||
libintl Version: 0.22.5 Channel: conda-forge
|
||||
libopus Version: 1.3.1 Channel: conda-forge
|
||||
libpng Version: 1.6.44 Channel: conda-forge
|
||||
libsqlite Version: 3.47.0 Channel: conda-forge
|
||||
libxcb Version: 1.13 Channel: conda-forge
|
||||
libxml2 Version: 2.13.5 Channel: conda-forge
|
||||
libzlib Version: 1.3.1 Channel: conda-forge
|
||||
openh264 Version: 2.4.1 Channel: conda-forge
|
||||
openssl Version: 3.4.0 Channel: conda-forge
|
||||
pcre2 Version: 10.44 Channel: conda-forge
|
||||
pixman Version: 0.43.4 Channel: conda-forge
|
||||
python Version: 3.11.10 Channel: conda-forge
|
||||
python_abi Version: 3.11 Channel: conda-forge
|
||||
svt-av1 Version: 2.2.1 Channel: conda-forge
|
||||
tk Version: 8.6.13 Channel: conda-forge
|
||||
vc14_runtime Version: 14.42.34433 Channel: conda-forge
|
||||
vs2015_runtime Version: 14.42.34433 Channel: conda-forge
|
||||
x264 Version: 1!164.3095 Channel: conda-forge
|
||||
x265 Version: 3.5 Channel: conda-forge
|
||||
xorg-libxau Version: 1.0.11 Channel: conda-forge
|
||||
xorg-libxdmcp Version: 1.1.3 Channel: conda-forge
|
||||
zlib Version: 1.3.1 Channel: conda-forge
|
||||
zstd Version: 1.5.6 Channel: conda-forge
|
||||
|
||||
PKGS/MAIN PACKAGES:
|
||||
blas Version: 1.0 Channel: pkgs/main
|
||||
brotli-python Version: 1.0.9 Channel: pkgs/main
|
||||
bzip2 Version: 1.0.8 Channel: pkgs/main
|
||||
ca-certificates Version: 2024.9.24 Channel: pkgs/main
|
||||
certifi Version: 2024.8.30 Channel: pkgs/main
|
||||
charset-normalizer Version: 3.3.2 Channel: pkgs/main
|
||||
dav1d Version: 1.2.1 Channel: pkgs/main
|
||||
font-ttf-dejavu-sans-mono Version: 2.37 Channel: pkgs/main
|
||||
font-ttf-inconsolata Version: 2.001 Channel: pkgs/main
|
||||
font-ttf-source-code-pro Version: 2.030 Channel: pkgs/main
|
||||
font-ttf-ubuntu Version: 0.83 Channel: pkgs/main
|
||||
fonts-anaconda Version: 1 Channel: pkgs/main
|
||||
fonts-conda-ecosystem Version: 1 Channel: pkgs/main
|
||||
gmpy2 Version: 2.1.2 Channel: pkgs/main
|
||||
graphite2 Version: 1.3.14 Channel: pkgs/main
|
||||
idna Version: 3.7 Channel: pkgs/main
|
||||
intel-openmp Version: 2023.1.0 Channel: pkgs/main
|
||||
jinja2 Version: 3.1.4 Channel: pkgs/main
|
||||
jpeg Version: 9e Channel: pkgs/main
|
||||
lerc Version: 3.0 Channel: pkgs/main
|
||||
libdeflate Version: 1.17 Channel: pkgs/main
|
||||
libffi Version: 3.4.4 Channel: pkgs/main
|
||||
libjpeg-turbo Version: 2.0.0 Channel: pkgs/main
|
||||
libtiff Version: 4.5.0 Channel: pkgs/main
|
||||
libuv Version: 1.48.0 Channel: pkgs/main
|
||||
libwebp-base Version: 1.3.2 Channel: pkgs/main
|
||||
lz4-c Version: 1.9.4 Channel: pkgs/main
|
||||
markupsafe Version: 2.1.3 Channel: pkgs/main
|
||||
mkl Version: 2023.1.0 Channel: pkgs/main
|
||||
mkl-service Version: 2.4.0 Channel: pkgs/main
|
||||
mkl_fft Version: 1.3.11 Channel: pkgs/main
|
||||
mkl_random Version: 1.2.8 Channel: pkgs/main
|
||||
mpc Version: 1.1.0 Channel: pkgs/main
|
||||
mpfr Version: 4.0.2 Channel: pkgs/main
|
||||
mpir Version: 3.0.0 Channel: pkgs/main
|
||||
mpmath Version: 1.3.0 Channel: pkgs/main
|
||||
networkx Version: 3.2.1 Channel: pkgs/main
|
||||
numpy Version: 1.26.4 Channel: pkgs/main
|
||||
numpy-base Version: 1.26.4 Channel: pkgs/main
|
||||
openjpeg Version: 2.5.2 Channel: pkgs/main
|
||||
packaging Version: 24.1 Channel: pkgs/main
|
||||
pip Version: 24.2 Channel: pkgs/main
|
||||
pthread-stubs Version: 0.3 Channel: pkgs/main
|
||||
pysocks Version: 1.7.1 Channel: pkgs/main
|
||||
pyyaml Version: 6.0.2 Channel: pkgs/main
|
||||
requests Version: 2.32.3 Channel: pkgs/main
|
||||
setuptools Version: 75.1.0 Channel: pkgs/main
|
||||
sqlite Version: 3.45.3 Channel: pkgs/main
|
||||
sympy Version: 1.13.2 Channel: pkgs/main
|
||||
tbb Version: 2021.8.0 Channel: pkgs/main
|
||||
ucrt Version: 10.0.20348.0 Channel: pkgs/main
|
||||
urllib3 Version: 2.2.3 Channel: pkgs/main
|
||||
vc Version: 14.40 Channel: pkgs/main
|
||||
wheel Version: 0.44.0 Channel: pkgs/main
|
||||
win_inet_pton Version: 1.1.0 Channel: pkgs/main
|
||||
xz Version: 5.4.6 Channel: pkgs/main
|
||||
yaml Version: 0.2.5 Channel: pkgs/main
|
||||
|
||||
PKGS/MSYS2 PACKAGES:
|
||||
m2w64-gcc-libgfortran Version: 5.3.0 Channel: pkgs/msys2
|
||||
m2w64-gcc-libs Version: 5.3.0 Channel: pkgs/msys2
|
||||
m2w64-gcc-libs-core Version: 5.3.0 Channel: pkgs/msys2
|
||||
m2w64-gmp Version: 6.1.0 Channel: pkgs/msys2
|
||||
m2w64-libwinpthread-git Version: 5.0.0.4634.697f757 Channel: pkgs/msys2
|
||||
msys2-conda-epoch Version: 20160418 Channel: pkgs/msys2
|
||||
|
||||
OTHER PACKAGES:
|
||||
faiss-cpu Version: 1.9.0 Channel: pytorch
|
||||
libfaiss Version: 1.9.0 Channel: pytorch
|
||||
pytorch Version: 2.2.1 Channel: pytorch
|
||||
pytorch-cuda Version: 12.1 Channel: pytorch
|
||||
pytorch-mutex Version: 1.0 Channel: pytorch
|
||||
|
||||
PACKAGE VERSIONS vs REQUIREMENTS FILE:
|
||||
coqui-tts Required: >= 0.24.3 Installed: 0.24.3
|
||||
filelock Required: >= 3.16.1 Installed: 3.13.4
|
||||
fuzzywuzzy Required: >= 0.18.0 Installed: 0.18.0
|
||||
ffmpeg-python Required: >= 0.2.0 Installed: 0.2.0
|
||||
gradio Required: == 4.44.1 Installed: 4.44.1
|
||||
importlib_metadata Required: >= 7.2.1 Installed: 8.5.0
|
||||
inputimeout Required: >= 1.0.4 Installed: 1.0.4
|
||||
Jinja2 Required: >= 3.1.4 Installed: 3.1.4
|
||||
librosa Required: >= 0.10.2.post1 Installed: 0.10.2.post1
|
||||
onnxruntime-gpu Required: >= 1.18.1 Installed: 1.20.1
|
||||
pydantic Required: >= 2.8.2 Installed: 2.10.1
|
||||
python-Levenshtein Required: >= 0.25.1 Installed: 0.26.1
|
||||
praat-parselmouth Required: >= 0.4.4 Installed: 0.4.5
|
||||
pyworld Required: >= 0.3.4 Installed: 0.3.4
|
||||
sounddevice Required: >= 0.4.7 Installed: 0.5.1
|
||||
soundfile Required: >= 0.12.1 Installed: 0.12.1
|
||||
spacy Required: >= 3.7.1 Installed: 3.7.5
|
||||
torchcrepe Required: >= 0.0.2 Installed: 0.0.23
|
||||
tqdm Required: >= 4.66.5 Installed: 4.67.1
|
||||
unidic-lite Required: >= 1.0.8 Installed: 1.0.8
|
||||
uvicorn Required: >= 0.29.0 Installed: 0.32.1
|
||||
pillow Required: == 10.3.0 Installed: 10.3.0
|
||||
pypinyin Required: >= 0.52.0 Installed: 0.53.0
|
||||
word2number Required: >= 1.1 Installed: 1.1
|
||||
cutlet Required: == 0.4.0 Installed: 0.4.0
|
||||
fugashi Required: == 1.3.1 Installed: 1.3.1
|
||||
fastapi Required: == 0.112.2 Installed: 0.112.2
|
||||
plotly Required: == 5.24.1 Installed: 5.24.1
|
||||
scipy Required: == 1.14.1 Installed: 1.14.1
|
||||
|
||||
PYTHON PACKAGES:
|
||||
absl-py = 2.1.0
|
||||
accelerate = 1.1.1
|
||||
aiofiles = 23.2.1
|
||||
aiohappyeyeballs = 2.4.3
|
||||
aiohttp = 3.11.7
|
||||
aiosignal = 1.3.1
|
||||
annotated-types = 0.7.0
|
||||
antlr4-python3-runtime = 4.9.3
|
||||
anyascii = 0.3.2
|
||||
anyio = 4.6.2.post1
|
||||
argbind = 0.3.9
|
||||
asttokens = 2.4.1
|
||||
attrs = 24.2.0
|
||||
audioread = 3.0.1
|
||||
babel = 2.16.0
|
||||
bitarray = 3.0.0
|
||||
bitsandbytes = 0.44.1
|
||||
blis = 0.7.11
|
||||
boto3 = 1.35.69
|
||||
botocore = 1.35.69
|
||||
Brotli = 1.0.9
|
||||
cached_path = 1.6.4
|
||||
cachetools = 5.5.0
|
||||
catalogue = 2.0.10
|
||||
certifi = 2024.8.30
|
||||
cffi = 1.17.1
|
||||
charset-normalizer = 3.3.2
|
||||
click = 8.1.7
|
||||
cloudpathlib = 0.20.0
|
||||
colorama = 0.4.6
|
||||
coloredlogs = 15.0.1
|
||||
confection = 0.1.5
|
||||
contourpy = 1.3.1
|
||||
coqpit = 0.0.17
|
||||
coqui-tts = 0.24.3
|
||||
coqui-tts-trainer = 0.1.7
|
||||
cutlet = 0.4.0
|
||||
cycler = 0.12.1
|
||||
cymem = 2.0.10
|
||||
Cython = 3.0.11
|
||||
datasets = 3.1.0
|
||||
dateparser = 1.1.8
|
||||
decorator = 5.1.1
|
||||
deepspeed = 0.14.0+ce78a63
|
||||
descript-audiotools = 0.7.4
|
||||
descript-audio-codec = 1.0.0
|
||||
dill = 0.3.8
|
||||
docker-pycreds = 0.4.0
|
||||
docopt = 0.6.2
|
||||
docstring_parser = 0.16
|
||||
einops = 0.8.0
|
||||
einx = 0.3.0
|
||||
ema-pytorch = 0.7.6
|
||||
encodec = 0.1.1
|
||||
executing = 2.1.0
|
||||
fairseq = 0.12.4
|
||||
faiss = 1.9.0
|
||||
fastapi = 0.112.2
|
||||
ffmpeg-python = 0.2.0
|
||||
ffmpy = 0.4.0
|
||||
filelock = 3.13.4
|
||||
fire = 0.7.0
|
||||
flatbuffers = 24.3.25
|
||||
flatten-dict = 0.4.2
|
||||
fonttools = 4.55.0
|
||||
frozendict = 2.4.6
|
||||
frozenlist = 1.5.0
|
||||
fsspec = 2024.9.0
|
||||
fugashi = 1.3.1
|
||||
future = 1.0.0
|
||||
fuzzywuzzy = 0.18.0
|
||||
gitdb = 4.0.11
|
||||
GitPython = 3.1.43
|
||||
gmpy2 = 2.1.2
|
||||
googleapis-common-protos = 1.66.0
|
||||
google-api-core = 2.23.0
|
||||
google-auth = 2.36.0
|
||||
google-cloud-core = 2.4.1
|
||||
google-cloud-storage = 2.18.2
|
||||
google-crc32c = 1.6.0
|
||||
google-resumable-media = 2.7.2
|
||||
gradio = 4.44.1
|
||||
gradio_client = 1.3.0
|
||||
grpcio = 1.68.0
|
||||
gruut = 2.4.0
|
||||
gruut-ipa = 0.13.0
|
||||
gruut_lang_de = 2.0.1
|
||||
gruut_lang_en = 2.0.1
|
||||
gruut_lang_es = 2.0.1
|
||||
gruut_lang_fr = 2.0.2
|
||||
h11 = 0.14.0
|
||||
hangul-romanize = 0.1.0
|
||||
hjson = 3.1.0
|
||||
httpcore = 1.0.7
|
||||
httpx = 0.27.2
|
||||
huggingface-hub = 0.26.2
|
||||
humanfriendly = 10.0
|
||||
hydra-core = 1.3.2
|
||||
idna = 3.7
|
||||
importlib_metadata = 8.5.0
|
||||
importlib_resources = 6.4.5
|
||||
inflect = 7.4.0
|
||||
inputimeout = 1.0.4
|
||||
ipython = 8.29.0
|
||||
jaconv = 0.4.0
|
||||
jedi = 0.19.2
|
||||
jieba = 0.42.1
|
||||
Jinja2 = 3.1.4
|
||||
jmespath = 1.0.1
|
||||
joblib = 1.4.2
|
||||
jsonlines = 1.2.0
|
||||
julius = 0.2.7
|
||||
kiwisolver = 1.4.7
|
||||
langcodes = 3.5.0
|
||||
language_data = 1.3.0
|
||||
lazy_loader = 0.4
|
||||
Levenshtein = 0.26.1
|
||||
librosa = 0.10.2.post1
|
||||
llvmlite = 0.43.0
|
||||
local-attention = 1.9.15
|
||||
lxml = 5.3.0
|
||||
marisa-trie = 1.2.1
|
||||
Markdown = 3.7
|
||||
markdown2 = 2.5.1
|
||||
markdown-it-py = 3.0.0
|
||||
MarkupSafe = 2.1.3
|
||||
matplotlib = 3.9.2
|
||||
matplotlib-inline = 0.1.7
|
||||
mdurl = 0.1.2
|
||||
mkl_fft = 1.3.11
|
||||
mkl_random = 1.2.8
|
||||
mkl-service = 2.4.0
|
||||
mojimoji = 0.0.13
|
||||
more-itertools = 10.5.0
|
||||
mpmath = 1.3.0
|
||||
msgpack = 1.1.0
|
||||
multidict = 6.1.0
|
||||
multiprocess = 0.70.16
|
||||
murmurhash = 1.0.11
|
||||
networkx = 3.2.1
|
||||
ninja = 1.11.1.2
|
||||
num2words = 0.5.13
|
||||
numba = 0.60.0
|
||||
numpy = 1.26.4
|
||||
omegaconf = 2.3.0
|
||||
onnxruntime-gpu = 1.20.1
|
||||
openai-whisper = 20240930
|
||||
orjson = 3.10.12
|
||||
packaging = 24.1
|
||||
pandas = 2.2.3
|
||||
parler_tts = 0.2.1
|
||||
parso = 0.8.4
|
||||
pillow = 10.3.0
|
||||
pip = 24.2
|
||||
platformdirs = 4.3.6
|
||||
plotly = 5.24.1
|
||||
pooch = 1.8.2
|
||||
portalocker = 3.0.0
|
||||
praat-parselmouth = 0.4.5
|
||||
preshed = 3.0.9
|
||||
prompt_toolkit = 3.0.48
|
||||
propcache = 0.2.0
|
||||
protobuf = 4.25.5
|
||||
proto-plus = 1.25.0
|
||||
psutil = 6.1.0
|
||||
pure_eval = 0.2.3
|
||||
pyarrow = 18.1.0
|
||||
pyasn1 = 0.6.1
|
||||
pyasn1_modules = 0.4.1
|
||||
pycparser = 2.22
|
||||
pydantic = 2.10.1
|
||||
pydantic_core = 2.27.1
|
||||
pydub = 0.25.1
|
||||
Pygments = 2.18.0
|
||||
pyloudnorm = 0.1.1
|
||||
pynvml = 11.5.3
|
||||
pyparsing = 3.2.0
|
||||
pypinyin = 0.53.0
|
||||
PyQt6 = 6.7.1
|
||||
PyQt6-Qt6 = 6.7.3
|
||||
PyQt6_sip = 13.8.0
|
||||
pyreadline3 = 3.5.4
|
||||
pysbd = 0.3.4
|
||||
PySocks = 1.7.1
|
||||
pystoi = 0.4.1
|
||||
python-crfsuite = 0.9.11
|
||||
python-dateutil = 2.9.0.post0
|
||||
python-Levenshtein = 0.26.1
|
||||
python-multipart = 0.0.17
|
||||
pytz = 2024.2
|
||||
pywin32 = 308
|
||||
pyworld = 0.3.4
|
||||
PyYAML = 6.0.2
|
||||
py-cpuinfo = 9.0.0
|
||||
randomname = 0.2.1
|
||||
RapidFuzz = 3.10.1
|
||||
regex = 2024.11.6
|
||||
requests = 2.32.3
|
||||
resampy = 0.4.3
|
||||
rich = 13.9.4
|
||||
rotary-embedding-torch = 0.8.5
|
||||
rsa = 4.9
|
||||
ruff = 0.8.0
|
||||
s3transfer = 0.10.4
|
||||
sacrebleu = 2.4.3
|
||||
safetensors = 0.4.5
|
||||
scikit-learn = 1.5.2
|
||||
scipy = 1.14.1
|
||||
semantic-version = 2.10.0
|
||||
sentencepiece = 0.2.0
|
||||
sentry-sdk = 2.19.0
|
||||
setproctitle = 1.3.4
|
||||
setuptools = 75.1.0
|
||||
shellingham = 1.5.4
|
||||
six = 1.16.0
|
||||
smart-open = 7.0.5
|
||||
smmap = 5.0.1
|
||||
sniffio = 1.3.1
|
||||
sounddevice = 0.5.1
|
||||
soundfile = 0.12.1
|
||||
soxr = 0.5.0.post1
|
||||
spacy = 3.7.5
|
||||
spacy-legacy = 3.0.12
|
||||
spacy-loggers = 1.0.5
|
||||
srsly = 2.4.8
|
||||
stack-data = 0.6.3
|
||||
starlette = 0.38.6
|
||||
SudachiDict-core = 20241021
|
||||
SudachiPy = 0.6.9
|
||||
sympy = 1.13.2
|
||||
tabulate = 0.9.0
|
||||
tenacity = 9.0.0
|
||||
tensorboard = 2.18.0
|
||||
tensorboard-data-server = 0.7.2
|
||||
termcolor = 2.5.0
|
||||
thinc = 8.2.5
|
||||
threadpoolctl = 3.5.0
|
||||
tiktoken = 0.8.0
|
||||
tokenizers = 0.20.3
|
||||
tomli = 2.1.0
|
||||
tomlkit = 0.12.0
|
||||
torch = 2.2.1
|
||||
torchaudio = 2.2.1
|
||||
torchcrepe = 0.0.23
|
||||
torchdiffeq = 0.2.5
|
||||
torchvision = 0.17.1
|
||||
torch-stoi = 0.2.3
|
||||
tqdm = 4.67.1
|
||||
traitlets = 5.14.3
|
||||
transformers = 4.46.1
|
||||
transformers-stream-generator = 0.0.5
|
||||
typeguard = 4.4.1
|
||||
typer = 0.13.1
|
||||
typing_extensions = 4.12.2
|
||||
tzdata = 2024.2
|
||||
tzlocal = 5.2
|
||||
unidic-lite = 1.0.8
|
||||
urllib3 = 2.2.3
|
||||
uvicorn = 0.32.1
|
||||
vocos = 0.1.0
|
||||
wandb = 0.18.7
|
||||
wasabi = 1.1.3
|
||||
wcwidth = 0.2.13
|
||||
weasel = 0.4.1
|
||||
websockets = 12.0
|
||||
Werkzeug = 3.1.3
|
||||
wheel = 0.44.0
|
||||
win-inet-pton = 1.1.0
|
||||
word2number = 1.1
|
||||
wrapt = 1.17.0
|
||||
xxhash = 3.5.0
|
||||
x-transformers = 1.42.17
|
||||
yarl = 1.18.0
|
||||
zipp = 3.21.0
|
||||
|
||||
DeepSpeed Installation Requirements:
|
||||
OS : Windows
|
||||
Python : 3.11.10
|
||||
PyTorch: 2.2.1
|
||||
CUDA : 12.1
|
||||
|
||||
To install DeepSpeed, run the following command based on your environment:
|
||||
pip install deepspeed==<version> --no-cache-dir
|
||||
Ensure to choose the correct DeepSpeed version matching your environment.
|
||||
@@ -0,0 +1,369 @@
|
||||
"""
|
||||
firstrun.py
|
||||
|
||||
This script handles the first-time setup for the Alltalk TTS system. It allows the user to download and configure
|
||||
the initial Text-to-Speech (TTS) engine and associated models. It provides automatic detection for Docker or Google
|
||||
Colab environments to simplify the setup process, as well as an interactive menu or command-line arguments for manual configuration.
|
||||
|
||||
Features:
|
||||
---------
|
||||
1. **Automatic Environment Detection**:
|
||||
- Detects if the script is running in a Docker container or Google Colab environment.
|
||||
- Automatically sets up the Piper TTS engine in these cases.
|
||||
|
||||
2. **Command-Line Arguments**:
|
||||
- Accepts a `--tts_model` argument to bypass the interactive menu and directly set up a specific TTS engine:
|
||||
- `piper`: Sets up the Piper TTS engine.
|
||||
- `vits`: Sets up the VITS TTS engine.
|
||||
- `xtts`: Sets up the XTTS TTS engine.
|
||||
- `none`: Skips model setup entirely.
|
||||
|
||||
3. **Interactive Menu**:
|
||||
- If no command-line argument is provided and the environment is not Docker/Colab, it presents an interactive
|
||||
menu for the user to select a TTS model.
|
||||
|
||||
4. **Configuration Management**:
|
||||
- Updates the Alltalk TTS configuration files to reflect the selected TTS engine and marks the initial setup as complete.
|
||||
|
||||
Usage:
|
||||
------
|
||||
Command-Line:
|
||||
python firstrun.py --tts_model [piper|vits|xtts|none]
|
||||
|
||||
Interactive Mode:
|
||||
python firstrun.py
|
||||
|
||||
In Docker/Colab:
|
||||
The script automatically selects and sets up the Piper TTS engine without user interaction.
|
||||
|
||||
Functions:
|
||||
----------
|
||||
- **is_running_in_colab**: Detects if the script is running in Google Colab.
|
||||
- **is_running_in_docker**: Detects if the script is running in a Docker container.
|
||||
- **download_file**: Downloads a file with a progress bar and retry logic.
|
||||
- **setup_tts**: Sets up the TTS engine passed as argument.
|
||||
- **download_tts_model**: Downloads the TTS engine's associated files.
|
||||
- **update_tts_engines**: Updates the configuration to reflect the selected TTS engine.
|
||||
- **set_firstrun_model_false**: Marks the initial setup as complete in the configuration.
|
||||
- **warning_message**: Displays post-setup warnings or tips for the user.
|
||||
|
||||
Notes:
|
||||
------
|
||||
- Ensure network access is available for downloading models during setup.
|
||||
- Configuration files are automatically updated and backed up if needed.
|
||||
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
import sys
|
||||
import zipfile
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
from inputimeout import inputimeout, TimeoutOccurred
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
# Setting the module search path:
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent.resolve()))
|
||||
from config import AlltalkTTSEnginesConfig, AlltalkConfig #pylint: disable=wrong-import-position
|
||||
|
||||
this_dir = Path(__file__).parent.resolve().parent.resolve().parent.resolve()
|
||||
|
||||
def is_running_in_colab():
|
||||
"""Test for google colab"""
|
||||
try:
|
||||
import google.colab
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def is_running_in_docker():
|
||||
"""Test for google colab"""
|
||||
path = '/proc/self/cgroup'
|
||||
return os.path.exists('/.dockerenv') or (
|
||||
os.path.exists(path) and any('docker' in line for line in open(path, encoding='utf-8'))
|
||||
)
|
||||
|
||||
def download_file(url: str, dest_path: str, timeout: int = 30, retries: int = 3) -> bool:
|
||||
"""
|
||||
Download a file from a URL to a specified destination path with progress bar.
|
||||
|
||||
Args:
|
||||
url (str): URL of the file to download.
|
||||
dest_path (str): Destination path where the file will be saved.
|
||||
timeout (int, optional): Connection and read timeout in seconds. Defaults to 30.
|
||||
retries (int, optional): Number of retry attempts for failed downloads. Defaults to 3.
|
||||
|
||||
Returns:
|
||||
bool: True if the file was successfully downloaded, False otherwise.
|
||||
|
||||
Raises:
|
||||
ValueError: If the downloaded file size does not match the expected size.
|
||||
"""
|
||||
# Don't re-download if file already exists (which is the case when using Docker):
|
||||
if Path(dest_path).exists():
|
||||
print(f"File already exists at: {dest_path}. Skipping download.")
|
||||
return True
|
||||
|
||||
progress_bar = None
|
||||
for attempt in range(1, retries + 1):
|
||||
try:
|
||||
print(f"Attempting to download: {url} (Attempt {attempt}/{retries})")
|
||||
response = requests.get(url, stream=True, timeout=(timeout, timeout))
|
||||
response.raise_for_status()
|
||||
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
block_size = 1024 # 1 Kibibyte
|
||||
|
||||
# Initialize progress bar
|
||||
progress_bar = tqdm(total=total_size, unit='iB', unit_scale=True,
|
||||
desc=f"Downloading {Path(dest_path).name} (Attempt {attempt}/{retries})")
|
||||
Path(dest_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(dest_path, 'wb') as dwn_file:
|
||||
for data in response.iter_content(block_size):
|
||||
if data:
|
||||
dwn_file.write(data)
|
||||
progress_bar.update(len(data))
|
||||
|
||||
if progress_bar:
|
||||
progress_bar.close()
|
||||
|
||||
# Verify file size
|
||||
if progress_bar.n != total_size and total_size > 0:
|
||||
raise ValueError(
|
||||
f"Downloaded size ({progress_bar.n} bytes) does not match expected size ({total_size} bytes)."
|
||||
)
|
||||
|
||||
print(f"Download completed: {dest_path}")
|
||||
return True
|
||||
|
||||
except requests.Timeout:
|
||||
print(f"Timeout error during download: {url}")
|
||||
except requests.ConnectionError:
|
||||
print(f"Connection error during download: {url}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"HTTP error during download: {url}. Details: {str(e)}")
|
||||
except ValueError as e:
|
||||
print(f"File validation error: {str(e)}")
|
||||
if Path(dest_path).exists():
|
||||
Path(dest_path).unlink() # Remove incomplete file
|
||||
break # Stop retrying on file validation errors
|
||||
except Exception as e:
|
||||
print(f"Unexpected error during download: {str(e)}")
|
||||
break # Stop retrying on unexpected errors
|
||||
finally:
|
||||
if progress_bar:
|
||||
progress_bar.close()
|
||||
|
||||
if Path(dest_path).exists():
|
||||
Path(dest_path).unlink() # Remove incomplete file
|
||||
|
||||
if attempt < retries:
|
||||
print(f"Retrying in 5 seconds... (Attempt {attempt + 1}/{retries})")
|
||||
time.sleep(5)
|
||||
else:
|
||||
print(f"Exhausted all retries for {url}.")
|
||||
|
||||
# Return False if all attempts fail
|
||||
return False
|
||||
|
||||
def unzip_compressed_files(downloaded_files: []):
|
||||
for file in downloaded_files:
|
||||
_, file_extension = os.path.splitext(file)
|
||||
if file_extension == '.zip':
|
||||
with zipfile.ZipFile(file, 'r') as zip_ref:
|
||||
zip_ref.extractall(file.parent)
|
||||
os.remove(file)
|
||||
|
||||
def setup_tts(tts_model: str, on_download_completed = unzip_compressed_files):
|
||||
download_tts_model(tts_model, on_download_completed)
|
||||
update_tts_engines(tts_model)
|
||||
set_firstrun_model_false()
|
||||
|
||||
def download_tts_model(tts_model: str, on_download_completed = None):
|
||||
with open(os.path.join(this_dir, "system", "tts_engines", tts_model, "available_models.json")) as f:
|
||||
available_models = json.load(f)
|
||||
first_start_model = available_models["first_start_model"]
|
||||
model = next(m for m in available_models["models"] if m["model_name"] == first_start_model)
|
||||
|
||||
# folder_path: Special case for vits: firstrun.py expects the ZIP download directly in the model folder.
|
||||
# When firstrun.py unzips the file, the correct folder will be created automatically.
|
||||
folder_path = "" if tts_model == "vits" else model.get("folder_path", "")
|
||||
files_to_download = model.get("files_to_download", []) or [model.get("github_rls_url", None)]
|
||||
target_dir = this_dir / f"models/{tts_model}/{folder_path}"
|
||||
|
||||
if type(files_to_download) == dict:
|
||||
files_to_download = list(files_to_download.values())
|
||||
|
||||
downloaded_files = []
|
||||
for file_download in files_to_download:
|
||||
urlpath = urlsplit(file_download).path
|
||||
file_name = os.path.basename(urlpath)
|
||||
target_file = target_dir / file_name
|
||||
download_file(file_download, target_file)
|
||||
downloaded_files.append(target_file)
|
||||
|
||||
if on_download_completed:
|
||||
on_download_completed(downloaded_files)
|
||||
|
||||
def update_tts_engines(engine):
|
||||
"""Set engine to users choice that matches the download"""
|
||||
try:
|
||||
config_instance = AlltalkTTSEnginesConfig.get_instance()
|
||||
config_instance.change_engine(engine)
|
||||
config_instance.save()
|
||||
except Exception as e:
|
||||
print(f"[{branding}TTS] Error updating TTS engine configuration: {str(e)}")
|
||||
|
||||
def set_firstrun_model_false():
|
||||
"""
|
||||
Marks the first-run setup as complete in the Alltalk configuration.
|
||||
|
||||
This function updates the `firstrun_model` flag in the configuration file to `False`,
|
||||
indicating that the initial setup has been completed. It attempts to save the updated
|
||||
configuration and verifies the changes, retrying with a direct file write if necessary.
|
||||
"""
|
||||
try:
|
||||
# Get a fresh instance of the config
|
||||
config_fr = AlltalkConfig.get_instance()
|
||||
config_fr.firstrun_model = False
|
||||
|
||||
# Force a flush to disk immediately
|
||||
config_fr.save()
|
||||
|
||||
# Verify the save worked
|
||||
config_path = AlltalkConfig.default_config_path()
|
||||
with open(config_path, 'r', encoding="utf-8") as f:
|
||||
saved_config = json.load(f)
|
||||
if saved_config.get('firstrun_model', True):
|
||||
print(f"[{branding}TTS] Warning: Failed to save firstrun_model=False")
|
||||
# Try one more time with direct file write
|
||||
saved_config['firstrun_model'] = False
|
||||
with open(config_path, 'w', encoding="utf-8") as f:
|
||||
json.dump(saved_config, f, indent=2)
|
||||
except Exception as e:
|
||||
print(f"[{branding}TTS] Error saving firstrun configuration: {str(e)}")
|
||||
|
||||
def warning_message():
|
||||
"""AllTalk V1 Warning message re updating"""
|
||||
print(f"[{branding}TTS]")
|
||||
print(f"[{branding}TTS] \033[93mIf you have you have UPGRADED from v1 ensure you have re-installed\033[0m")
|
||||
print(f"[{branding}TTS] \033[93mthe requirements. Otherwise you will get failures and errors!\033[0m")
|
||||
print(f"[{branding}TTS] \033[93mOn Linux ignore the \033[0m'sparse_attn requires a torch version' \033[93mand\033[0m")
|
||||
print(f"[{branding}TTS] \033[0m'using untested triton version' \033[93mmessages.\033[0m")
|
||||
print(f"[{branding}TTS]")
|
||||
|
||||
# Load confignew.json (might be version 1 or 2):
|
||||
with open(AlltalkConfig.default_config_path(), 'r', encoding="utf-8") as file:
|
||||
config_json = json.load(file)
|
||||
|
||||
branding = config_json['branding']
|
||||
|
||||
# Check if the config file is version 1
|
||||
if "ip_address" in config_json:
|
||||
print(f"[{branding}TTS] Detected version 1 configuration file.")
|
||||
print(f"[{branding}TTS] Upgrading to version 2 configuration file...")
|
||||
|
||||
# Backup the old configuration file
|
||||
backup_path = AlltalkConfig.default_config_path() + ".backup"
|
||||
shutil.copyfile(AlltalkConfig.default_config_path(), backup_path)
|
||||
|
||||
# Copy the new configuration template to replace the old config file
|
||||
new_config_template_path = this_dir / 'system/config/confignew.json'
|
||||
shutil.copyfile(new_config_template_path, AlltalkConfig.default_config_path())
|
||||
|
||||
print(f"[{branding}TTS] Configuration file upgraded successfully.")
|
||||
|
||||
config = AlltalkConfig.get_instance()
|
||||
branding = config.branding
|
||||
# Argument parser setup
|
||||
parser = argparse.ArgumentParser(description="TTS Setup Script")
|
||||
parser.add_argument('--tts_model', type=str, choices=['piper', 'vits', 'xtts', 'none'],
|
||||
help="Specify TTS model to set up (piper, vits, xtts, or none)")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check if firstrun_model is true
|
||||
if config.firstrun_model:
|
||||
# Handle command-line argument for tts_model
|
||||
if args.tts_model:
|
||||
if args.tts_model in {'piper', 'vits', 'xtts'}:
|
||||
print(f"[{branding}TTS] Setting up model '{args.tts_model}'.")
|
||||
setup_tts(args.tts_model)
|
||||
elif args.tts_model == 'none':
|
||||
print(f"[{branding}TTS] No TTS model setup requested.")
|
||||
print(f"[{branding}TTS] Setup completed for {args.tts_model}. Exiting.")
|
||||
sys.exit()
|
||||
|
||||
# Check for Colab or Docker environment first
|
||||
if is_running_in_colab() or is_running_in_docker():
|
||||
print(f"[{branding}TTS] Detected Colab/Docker environment - automatically selecting Piper")
|
||||
setup_tts('piper')
|
||||
warning_message()
|
||||
sys.exit()
|
||||
|
||||
# Present the menu if no argument is passed and not in Colab/Docker
|
||||
print(f"[{branding}TTS]")
|
||||
print(f"[{branding}TTS] \033[92mThis is the first-time startup.. Please download a start TTS model. Other TTS engines\033[0m")
|
||||
print(f"[{branding}TTS] \033[92mand TTS models can be downloaded/managed in the Gradio Interface `TTS Engines Settings`\033[0m")
|
||||
print(f"[{branding}TTS] \033[92mtab after initial setup.\033[0m")
|
||||
print(f"[{branding}TTS]")
|
||||
|
||||
# List of available models
|
||||
models = [
|
||||
{"name": "piper", "model": "piper"},
|
||||
{"name": "vits", "model": "tts_models--en--vctk--vits"},
|
||||
{"name": "xtts", "model": "xttsv2_2.0.3"},
|
||||
]
|
||||
|
||||
# Display models to the user
|
||||
print(f"[{branding}TTS] \033[94mAvailable First Time Start-up models:\033[0m")
|
||||
print(f"[{branding}TTS]")
|
||||
for idx, model in enumerate(models):
|
||||
print(f"[{branding}TTS] \033[93m{idx + 1}. \033[94m{model['name']} - {model['model']}\033[0m")
|
||||
print(f"[{branding}TTS] \033[93m{len(models) + 1}.\033[94m I have my own models already\033[0m")
|
||||
print(f"[{branding}TTS]")
|
||||
print(f"[{branding}TTS] \033[94mIn \033[91m60 seconds\033[0m \033[94ma Piper model will be \033[91mdownloaded automatically.\033[0m")
|
||||
print(f"[{branding}TTS]")
|
||||
|
||||
# Auto-select model after 60 seconds
|
||||
selected_model = models[0] # Default to the first model (piper)
|
||||
try:
|
||||
user_choice = inputimeout(prompt=f"[{branding}TTS] \033[92mEnter your choice 1-4: \033[0m ", timeout=60)
|
||||
except TimeoutOccurred:
|
||||
user_choice = None
|
||||
|
||||
if user_choice is None:
|
||||
print(f"[{branding}TTS]")
|
||||
print(f"[{branding}TTS] No input received. Proceeding with the default model (piper).")
|
||||
elif user_choice.lower() == 'own' or (user_choice.isdigit() and int(user_choice) == len(models) + 1):
|
||||
print(f"[{branding}TTS]")
|
||||
print(f"[{branding}TTS] Please use the Gradio interface TTS Engine Settings > [Engine Name] > Model Download")
|
||||
print(f"[{branding}TTS] To download models for your selected TTS Engine. Or use the [Engine Name] help sections")
|
||||
print(f"[{branding}TTS] for instructions on using your own TTS models.")
|
||||
print(f"[{branding}TTS]")
|
||||
os.makedirs('models/piper', exist_ok=True)
|
||||
os.makedirs('models/vits', exist_ok=True)
|
||||
os.makedirs('models/xtts/', exist_ok=True)
|
||||
os.makedirs('models/f5-tts/', exist_ok=True)
|
||||
os.makedirs('models/rvc_voices/', exist_ok=True)
|
||||
update_tts_engines('piper')
|
||||
set_firstrun_model_false()
|
||||
warning_message()
|
||||
exit()
|
||||
|
||||
if user_choice is not None and user_choice.isdigit() and 1 <= int(user_choice) <= len(models):
|
||||
selected_model = models[int(user_choice) - 1]
|
||||
warning_message()
|
||||
|
||||
if user_choice is None or user_choice.lower() != 'own':
|
||||
if selected_model['name'] in {'piper', 'vits', 'xtts'}:
|
||||
setup_tts(selected_model['name'])
|
||||
print(f"[{branding}TTS] {selected_model['name']} model downloaded and configuration updated successfully.")
|
||||
warning_message()
|
||||
sys.exit()
|
||||
else:
|
||||
sys.exit()
|
||||
@@ -0,0 +1,287 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
import zipfile
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
from inputimeout import inputimeout, TimeoutOccurred
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, List, Any
|
||||
|
||||
# Setting the module search path:
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent.resolve()))
|
||||
|
||||
from config import AlltalkTTSEnginesConfig, AlltalkConfig
|
||||
|
||||
this_dir = Path(__file__).parent.resolve().parent.resolve().parent.resolve()
|
||||
|
||||
def is_running_in_colab() -> bool:
|
||||
"""Check if the script is running in Google Colab."""
|
||||
try:
|
||||
import google.colab
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def is_running_in_docker() -> bool:
|
||||
"""Check if the script is running in a Docker container."""
|
||||
return os.path.exists('/.dockerenv') or (
|
||||
os.path.exists('/proc/self/cgroup') and
|
||||
any('docker' in line for line in open('/proc/self/cgroup'))
|
||||
)
|
||||
|
||||
def download_file(url: str, dest_path: str, timeout: int = 30) -> bool:
|
||||
"""
|
||||
Download a file from a URL with progress bar and error handling.
|
||||
|
||||
Args:
|
||||
url: URL to download from
|
||||
dest_path: Where to save the file
|
||||
timeout: Connection timeout in seconds
|
||||
|
||||
Returns:
|
||||
bool: True if download was successful
|
||||
"""
|
||||
progress_bar = None
|
||||
try:
|
||||
response = requests.get(url, stream=True, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
block_size = 1024
|
||||
|
||||
progress_bar = tqdm(total=total_size, unit='iB', unit_scale=True,
|
||||
desc=f"Downloading {Path(dest_path).name}")
|
||||
|
||||
Path(dest_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(dest_path, 'wb') as file:
|
||||
for data in response.iter_content(block_size):
|
||||
if data:
|
||||
file.write(data)
|
||||
progress_bar.update(len(data))
|
||||
|
||||
if progress_bar.n != total_size and total_size > 0:
|
||||
raise requests.exceptions.RequestException(
|
||||
f"Incomplete download: {progress_bar.n}/{total_size} bytes"
|
||||
)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error downloading {url}: {str(e)}")
|
||||
if Path(dest_path).exists():
|
||||
Path(dest_path).unlink()
|
||||
return False
|
||||
|
||||
finally:
|
||||
if progress_bar:
|
||||
progress_bar.close()
|
||||
|
||||
def setup_model(model_name: str) -> bool:
|
||||
"""
|
||||
Set up a specific TTS model.
|
||||
|
||||
Args:
|
||||
model_name: Name of the model to setup ('piper', 'vits', or 'xtts')
|
||||
|
||||
Returns:
|
||||
bool: True if setup was successful
|
||||
"""
|
||||
try:
|
||||
if model_name == "piper":
|
||||
os.makedirs(this_dir / 'models/piper', exist_ok=True)
|
||||
os.makedirs(this_dir / 'models/xtts', exist_ok=True)
|
||||
return all([
|
||||
download_file(
|
||||
"https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx?download=true",
|
||||
this_dir / "models/piper/en_US-ljspeech-high.onnx"
|
||||
),
|
||||
download_file(
|
||||
"https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/ljspeech/high/en_US-ljspeech-high.onnx.json?download=true.json",
|
||||
this_dir / "models/piper/en_US-ljspeech-high.onnx.json"
|
||||
)
|
||||
])
|
||||
|
||||
elif model_name == "vits":
|
||||
os.makedirs(this_dir / 'models/vits', exist_ok=True)
|
||||
os.makedirs(this_dir / 'models/xtts', exist_ok=True)
|
||||
zip_path = this_dir / 'models/vits/vits.zip'
|
||||
|
||||
if not download_file(
|
||||
"https://coqui.gateway.scarf.sh/v0.6.1_models/tts_models--en--vctk--vits.zip",
|
||||
zip_path
|
||||
):
|
||||
return False
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(this_dir / 'models/vits')
|
||||
os.remove(zip_path)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error extracting VITS model: {str(e)}")
|
||||
return False
|
||||
|
||||
elif model_name == "xtts":
|
||||
os.makedirs(this_dir / 'models/xtts/xttsv2_2.0.3', exist_ok=True)
|
||||
base_url = "https://huggingface.co/coqui/XTTS-v2/resolve/v2.0.3/"
|
||||
files = [
|
||||
"LICENSE.txt", "README.md", "config.json", "model.pth",
|
||||
"dvae.pth", "mel_stats.pth", "speakers_xtts.pth", "vocab.json"
|
||||
]
|
||||
return all(
|
||||
download_file(
|
||||
base_url + file + "?download=true",
|
||||
this_dir / f"models/xtts/xttsv2_2.0.3/{file}"
|
||||
) for file in files
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error setting up {model_name}: {str(e)}")
|
||||
return False
|
||||
|
||||
def update_tts_engines(engine: str) -> bool:
|
||||
"""Update TTS engine configuration with error handling."""
|
||||
try:
|
||||
AlltalkTTSEnginesConfig.get_instance().change_engine(engine).save()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error updating TTS engine configuration: {str(e)}")
|
||||
return False
|
||||
|
||||
def set_firstrun_model_false() -> bool:
|
||||
"""Set firstrun_model to false with verification."""
|
||||
try:
|
||||
# Get fresh config instance and update
|
||||
config = AlltalkConfig.get_instance()
|
||||
config.firstrun_model = False
|
||||
config.save()
|
||||
|
||||
# Verify the save worked
|
||||
config_path = AlltalkConfig.default_config_path()
|
||||
with open(config_path, 'r') as f:
|
||||
saved_config = json.load(f)
|
||||
if saved_config.get('firstrun_model', True):
|
||||
print(f"Warning: Failed to save firstrun_model=False")
|
||||
# Try direct file write as fallback
|
||||
saved_config['firstrun_model'] = False
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(saved_config, f, indent=2)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error saving firstrun configuration: {str(e)}")
|
||||
return False
|
||||
|
||||
def warning_message(branding: str):
|
||||
"""Display warning messages about installation and compatibility."""
|
||||
print(f"[{branding}TTS]")
|
||||
print(f"[{branding}TTS] \033[93mIf you have UPGRADED from v1 ensure you have re-installed\033[0m")
|
||||
print(f"[{branding}TTS] \033[93mthe requirements. Otherwise you will get failures and errors!\033[0m")
|
||||
print(f"[{branding}TTS] \033[93mOn Linux ignore the \033[0m'sparse_attn requires a torch version' \033[93mand\033[0m")
|
||||
print(f"[{branding}TTS] \033[0m'using untested triton version' \033[93mmessages.\033[0m")
|
||||
print(f"[{branding}TTS]")
|
||||
|
||||
def main():
|
||||
"""Main execution function."""
|
||||
try:
|
||||
# Load configuration
|
||||
with open(AlltalkConfig.default_config_path(), 'r') as file:
|
||||
config_json = json.load(file)
|
||||
|
||||
branding = config_json['branding']
|
||||
|
||||
# Handle version 1 config upgrade
|
||||
if "ip_address" in config_json:
|
||||
print(f"[{branding}TTS] Detected version 1 configuration file.")
|
||||
print(f"[{branding}TTS] Upgrading to version 2 configuration file...")
|
||||
|
||||
backup_path = AlltalkConfig.default_config_path() + ".backup"
|
||||
shutil.copyfile(AlltalkConfig.default_config_path(), backup_path)
|
||||
|
||||
new_config_template_path = this_dir / 'system/config/confignew.json'
|
||||
shutil.copyfile(new_config_template_path, AlltalkConfig.default_config_path())
|
||||
|
||||
print(f"[{branding}TTS] Configuration file upgraded successfully.")
|
||||
|
||||
config = AlltalkConfig.get_instance()
|
||||
branding = config.branding
|
||||
|
||||
if not config.firstrun_model:
|
||||
return
|
||||
|
||||
# Auto-select Piper for Colab/Docker
|
||||
if is_running_in_colab() or is_running_in_docker():
|
||||
print(f"[{branding}TTS] Detected Colab/Docker environment - automatically selecting Piper")
|
||||
if setup_model('piper'):
|
||||
update_tts_engines('piper')
|
||||
set_firstrun_model_false()
|
||||
warning_message(branding)
|
||||
return
|
||||
|
||||
# Normal interactive setup
|
||||
models = [
|
||||
{"name": "piper", "model": "piper"},
|
||||
{"name": "vits", "model": "tts_models--en--vctk--vits"},
|
||||
{"name": "xtts", "model": "xttsv2_2.0.3"},
|
||||
]
|
||||
|
||||
print(f"[{branding}TTS]")
|
||||
print(f"[{branding}TTS] \033[92mThis is the first time startup. Please download a start TTS model.\033[0m")
|
||||
print(f"[{branding}TTS]")
|
||||
print(f"[{branding}TTS] \033[94mAvailable First Time Start-up models:\033[0m")
|
||||
print(f"[{branding}TTS]")
|
||||
|
||||
for idx, model in enumerate(models):
|
||||
print(f"[{branding}TTS] \033[93m{idx + 1}. \033[94m{model['name']} - {model['model']}\033[0m")
|
||||
print(f"[{branding}TTS] \033[93m{len(models) + 1}.\033[94m I have my own models already\033[0m")
|
||||
print(f"[{branding}TTS]")
|
||||
print(f"[{branding}TTS] \033[94mIn \033[91m60 seconds\033[0m \033[94ma Piper model will be \033[91mdownloaded automatically.\033[0m")
|
||||
print(f"[{branding}TTS]")
|
||||
|
||||
try:
|
||||
user_choice = inputimeout(
|
||||
prompt=f"[{branding}TTS] \033[92mEnter your choice 1-4: \033[0m ",
|
||||
timeout=60
|
||||
)
|
||||
except TimeoutOccurred:
|
||||
user_choice = None
|
||||
|
||||
if user_choice is None:
|
||||
print(f"[{branding}TTS] No input received. Proceeding with the default model (piper).")
|
||||
selected_model = models[0]
|
||||
elif user_choice.lower() == 'own' or (user_choice.isdigit() and int(user_choice) == len(models) + 1):
|
||||
print(f"[{branding}TTS] If you have your own XTTS models, please move them into the")
|
||||
print(f"[{branding}TTS] \033[93m/models/xtts/\033[0m folder")
|
||||
|
||||
os.makedirs('models/piper', exist_ok=True)
|
||||
os.makedirs('models/vits', exist_ok=True)
|
||||
os.makedirs('models/xtts/', exist_ok=True)
|
||||
|
||||
update_tts_engines('piper')
|
||||
set_firstrun_model_false()
|
||||
warning_message(branding)
|
||||
return
|
||||
elif user_choice.isdigit() and 1 <= int(user_choice) <= len(models):
|
||||
selected_model = models[int(user_choice) - 1]
|
||||
warning_message(branding)
|
||||
else:
|
||||
print(f"[{branding}TTS] Invalid choice. Proceeding with the default model (piper).")
|
||||
selected_model = models[0]
|
||||
|
||||
if setup_model(selected_model['name']):
|
||||
update_tts_engines(selected_model['name'])
|
||||
set_firstrun_model_false()
|
||||
print(f"[{branding}TTS] {selected_model['name']} model downloaded and configuration updated successfully.")
|
||||
warning_message(branding)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Get the conda environment path
|
||||
env_path="${CONDA_DEFAULT_ENV}/lib"
|
||||
|
||||
# List of broken symlinks
|
||||
declare -A symlinks=(
|
||||
["libcufile.so"]="libcufile.so"
|
||||
["libcufile_rdma.so"]="libcufile_rdma.so"
|
||||
["libcudart.so"]="libcudart.so"
|
||||
["libnvjpeg.so"]="libnvjpeg.so"
|
||||
["libcurand.so"]="libcurand.so"
|
||||
["libnvJitLink.so"]="libnvJitLink.so"
|
||||
["libnvrtc-builtins.so"]="libnvrtc-builtins.so"
|
||||
["libnvrtc.so"]="libnvrtc.so"
|
||||
)
|
||||
|
||||
# Function to test for broken symlinks
|
||||
test_broken_symlinks() {
|
||||
echo "Testing for broken symlinks..."
|
||||
broken_found=false
|
||||
for link in "${!symlinks[@]}"; do
|
||||
if [ -L "$env_path/$link" ] && [ ! -e "$env_path/$link" ]; then
|
||||
echo "Broken symlink found: $env_path/$link"
|
||||
broken_found=true
|
||||
fi
|
||||
done
|
||||
if [ "$broken_found" = false ]; then
|
||||
echo "No broken symlinks found."
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to fix broken symlinks
|
||||
fix_broken_symlinks() {
|
||||
echo "Fixing broken symlinks..."
|
||||
for link in "${!symlinks[@]}"; do
|
||||
if [ -L "$env_path/$link" ] && [ ! -e "$env_path/$link" ]; then
|
||||
echo "Removing broken link: $env_path/$link"
|
||||
rm "$env_path/$link"
|
||||
|
||||
# Find all possible target files
|
||||
target_files=($(find "$env_path" -name "${symlinks[$link]}*"))
|
||||
|
||||
if [ ${#target_files[@]} -gt 0 ]; then
|
||||
echo "Select a target file for $link:"
|
||||
for i in "${!target_files[@]}"; do
|
||||
echo "$((i+1)). ${target_files[$i]}"
|
||||
done
|
||||
|
||||
read -p "Enter the number of the target file to use: " choice
|
||||
choice=$((choice-1))
|
||||
|
||||
if [[ $choice -ge 0 && $choice -lt ${#target_files[@]} ]]; then
|
||||
target="${target_files[$choice]}"
|
||||
echo "Creating new symlink: $env_path/$link -> $target"
|
||||
ln -s "$target" "$env_path/$link"
|
||||
else
|
||||
echo "Invalid choice, skipping $link."
|
||||
fi
|
||||
else
|
||||
echo "No target files found for $link."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
echo "Verification of symbolic links:"
|
||||
ls -l $env_path | grep -E "libcufile.so|libcudart.so|libcufile_rdma.so|libnvjpeg.so|libcurand.so|libnvJitLink.so|libnvrtc-builtins.so|libnvrtc.so"
|
||||
}
|
||||
|
||||
# Menu
|
||||
while true; do
|
||||
echo "Select an option:"
|
||||
echo "1. Test for broken symlinks"
|
||||
echo "2. Fix broken symlinks"
|
||||
echo "3. Exit"
|
||||
read -p "Enter your choice [1-3]: " choice
|
||||
case $choice in
|
||||
1)
|
||||
test_broken_symlinks
|
||||
;;
|
||||
2)
|
||||
fix_broken_symlinks
|
||||
;;
|
||||
3)
|
||||
echo "Exiting."
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Invalid choice. Please select 1, 2, or 3."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -0,0 +1,720 @@
|
||||
The birch canoe slid on the smooth planks.
|
||||
Glue the sheet to the dark blue background.
|
||||
It's easy to tell the depth of a well.
|
||||
These days a chicken leg is a rare dish.
|
||||
Rice is often served in round bowls.
|
||||
The juice of lemons makes fine punch.
|
||||
The box was thrown beside the parked truck.
|
||||
The hogs were fed chopped corn and garbage.
|
||||
Four hours of steady work faced us.
|
||||
A large size in stockings is hard to sell.
|
||||
The boy was there when the sun rose.
|
||||
A rod is used to catch pink salmon.
|
||||
The source of the huge river is the clear spring.
|
||||
Kick the ball straight and follow through.
|
||||
Help the woman get back to her feet.
|
||||
A pot of tea helps to pass the evening.
|
||||
Smoky fires lack flame and heat.
|
||||
The soft cushion broke the man's fall.
|
||||
The salt breeze came across from the sea.
|
||||
The girl at the booth sold fifty bonds.
|
||||
The small pup gnawed a hole in the sock.
|
||||
The fish twisted and turned on the bent hook.
|
||||
Press the pants and sew a button on the vest.
|
||||
The swan dive was far short of perfect.
|
||||
The beauty of the view stunned the young boy.
|
||||
Two blue fish swam in the tank.
|
||||
Her purse was full of useless trash.
|
||||
The colt reared and threw the tall rider.
|
||||
It snowed, rained, and hailed the same morning.
|
||||
Read verse out loud for pleasure.
|
||||
Hoist the load to your left shoulder.
|
||||
Take the winding path to reach the lake.
|
||||
Note closely the size of the gas tank.
|
||||
Wipe the grease off his dirty face.
|
||||
Mend the coat before you go out.
|
||||
The wrist was badly strained and hung limp.
|
||||
The stray cat gave birth to kittens.
|
||||
The young girl gave no clear response.
|
||||
The meal was cooked before the bell rang.
|
||||
What joy there is in living.
|
||||
A king ruled the state in the early days.
|
||||
The ship was torn apart on the sharp reef.
|
||||
Sickness kept him home the third week.
|
||||
The wide road shimmered in the hot sun.
|
||||
The lazy cow lay in the cool grass.
|
||||
Lift the square stone over the fence.
|
||||
The rope will bind the seven books at once.
|
||||
Hop over the fence and plunge in.
|
||||
The friendly gang left the drug store.
|
||||
Mesh wire keeps chicks inside.
|
||||
The frosty air passed through the coat.
|
||||
The crooked maze failed to fool the mouse.
|
||||
Adding fast leads to wrong sums.
|
||||
The show was a flop from the very start.
|
||||
A saw is a tool used for making boards.
|
||||
The wagon moved on well oiled wheels.
|
||||
March the soldiers past the next hill.
|
||||
A cup of sugar makes sweet fudge.
|
||||
Place a rosebush near the porch steps.
|
||||
Both lost their lives in the raging storm.
|
||||
We talked of the side show in the circus.
|
||||
Use a pencil to write the first draft.
|
||||
He ran half way to the hardware store.
|
||||
The clock struck to mark the third period.
|
||||
A small creek cut across the field.
|
||||
Cars and busses stalled in snow drifts.
|
||||
The set of china hit the floor with a crash.
|
||||
This is a grand season for hikes on the road.
|
||||
The dune rose from the edge of the water.
|
||||
Those words were the cue for the actor to leave.
|
||||
A yacht slid around the point into the bay.
|
||||
The two met while playing on the sand.
|
||||
The ink stain dried on the finished page.
|
||||
The walled town was seized without a fight.
|
||||
The lease ran out in sixteen weeks.
|
||||
A tame squirrel makes a nice pet.
|
||||
The horn of the car woke the sleeping cop.
|
||||
The heart beat strongly and with firm strokes.
|
||||
The pearl was worn in a thin silver ring.
|
||||
The fruit peel was cut in thick slices.
|
||||
The Navy attacked the big task force.
|
||||
See the cat glaring at the scared mouse.
|
||||
There are more than two factors here.
|
||||
The hat brim was wide and too droopy.
|
||||
The lawyer tried to lose his case.
|
||||
The grass curled around the fence post.
|
||||
Cut the pie into large parts.
|
||||
Men strive but seldom get rich.
|
||||
Always close the barn door tight.
|
||||
He lay prone and hardly moved a limb.
|
||||
The slush lay deep along the street.
|
||||
A wisp of cloud hung in the blue air.
|
||||
A pound of sugar costs more than eggs.
|
||||
The fin was sharp and cut the clear water.
|
||||
The play seems dull and quite stupid.
|
||||
Bail the boat to stop it from sinking.
|
||||
The term ended in late June that year.
|
||||
A tusk is used to make costly gifts.
|
||||
Ten pins were set in order.
|
||||
The bill was paid every third week.
|
||||
Oak is strong and also gives shade.
|
||||
Cats and dogs each hate the other.
|
||||
The pipe began to rust while new.
|
||||
Open the crate but don't break the glass.
|
||||
Add the sum to the product of these three.
|
||||
Thieves who rob friends deserve jail.
|
||||
The ripe taste of cheese improves with age.
|
||||
Act on these orders with great speed.
|
||||
The hog crawled under the high fence.
|
||||
Move the vat over the hot fire.
|
||||
The bark of the pine tree was shiny and dark.
|
||||
Leaves turn brown and yellow in the fall.
|
||||
The pennant waved when the wind blew.
|
||||
Split the log with a quick, sharp blow.
|
||||
Burn peat after the logs give out.
|
||||
He ordered peach pie with ice cream.
|
||||
Weave the carpet on the right hand side.
|
||||
Hemp is a weed found in parts of the tropics.
|
||||
A lame back kept his score low.
|
||||
We find joy in the simplest things.
|
||||
Type out three lists of orders.
|
||||
The harder he tried the less he got done.
|
||||
The boss ran the show with a watchful eye.
|
||||
The cup cracked and spilled its contents.
|
||||
Paste can cleanse the most dirty brass.
|
||||
The slang word for raw whiskey is booze.
|
||||
It caught its hind paw in a rusty trap.
|
||||
The wharf could be seen at the farther shore.
|
||||
Feel the heat of the weak dying flame.
|
||||
The tiny girl took off her hat.
|
||||
A cramp is no small danger on a swim.
|
||||
He said the same phrase thirty times.
|
||||
Pluck the bright rose without leaves.
|
||||
Two plus seven is less than ten.
|
||||
The glow deepened in the eyes of the sweet girl.
|
||||
Bring your problems to the wise chief.
|
||||
Write a fond note to the friend you cherish.
|
||||
Clothes and lodging are free to new men.
|
||||
We frown when events take a bad turn.
|
||||
Port is a strong wine with a smoky taste.
|
||||
The young kid jumped the rusty gate.
|
||||
Guess the results from the first scores.
|
||||
A salt pickle tastes fine with ham.
|
||||
The just claim got the right verdict.
|
||||
These thistles bend in a high wind.
|
||||
Pure bred poodles have curls.
|
||||
The tree top waved in a graceful way.
|
||||
The spot on the blotter was made by green ink.
|
||||
Mud was spattered on the front of his white shirt.
|
||||
The cigar burned a hole in the desk top.
|
||||
The empty flask stood on the tin tray.
|
||||
A speedy man can beat this track mark.
|
||||
He broke a new shoelace that day.
|
||||
The coffee stand is too high for the couch.
|
||||
The urge to write short stories is rare.
|
||||
The pencils have all been used.
|
||||
The pirates seized the crew of the lost ship.
|
||||
We tried to replace the coin but failed.
|
||||
She sewed the torn coat quite neatly.
|
||||
The sofa cushion is red and of light weight.
|
||||
The jacket hung on the back of the wide chair.
|
||||
At that high level the air is pure.
|
||||
Drop the two when you add the figures.
|
||||
A filing case is now hard to buy.
|
||||
An abrupt start does not win the prize.
|
||||
Wood is best for making toys and blocks.
|
||||
The office paint was a dull, sad tan.
|
||||
He knew the skill of the great young actress.
|
||||
A rag will soak up spilled water.
|
||||
A shower of dirt fell from the hot pipes.
|
||||
Steam hissed from the broken valve.
|
||||
The child almost hurt the small dog.
|
||||
There was a sound of dry leaves outside.
|
||||
The sky that morning was clear and bright blue.
|
||||
Torn scraps littered the stone floor.
|
||||
Sunday is the best part of the week.
|
||||
The doctor cured him with these pills.
|
||||
The new girl was fired today at noon.
|
||||
They felt gay when the ship arrived in port.
|
||||
Add the store's account to the last cent.
|
||||
Acid burns holes in wool cloth.
|
||||
Fairy tales should be fun to write.
|
||||
Eight miles of woodland burned to waste.
|
||||
The third act was dull and tired the players.
|
||||
A young child should not suffer fright.
|
||||
Add the column and put the sum here.
|
||||
We admire and love a good cook.
|
||||
There the flood mark is ten inches.
|
||||
He carved a head from the round block of marble.
|
||||
She has a smart way of wearing clothes.
|
||||
The fruit of a fig tree is apple-shaped.
|
||||
Corn cobs can be used to kindle a fire.
|
||||
Where were they when the noise started.
|
||||
The paper box is full of thumb tacks.
|
||||
Sell your gift to a buyer at a good gain.
|
||||
The tongs lay beside the ice pail.
|
||||
The petals fall with the next puff of wind.
|
||||
Bring your best compass to the third class.
|
||||
They could laugh although they were sad.
|
||||
Farmers came in to thresh the oat crop.
|
||||
The brown house was on fire to the attic.
|
||||
The lure is used to catch trout and flounder.
|
||||
Float the soap on top of the bath water.
|
||||
A blue crane is a tall wading bird.
|
||||
A fresh start will work such wonders.
|
||||
The club rented the rink for the fifth night.
|
||||
After the dance, they went straight home.
|
||||
The hostess taught the new maid to serve.
|
||||
He wrote his last novel there at the inn.
|
||||
Even the worst will beat his low score.
|
||||
The cement had dried when he moved it.
|
||||
The loss of the second ship was hard to take.
|
||||
The fly made its way along the wall.
|
||||
Do that with a wooden stick.
|
||||
Live wires should be kept covered.
|
||||
The large house had hot water taps.
|
||||
It is hard to erase blue or red ink.
|
||||
Write at once or you may forget it.
|
||||
The doorknob was made of bright clean brass.
|
||||
The wreck occurred by the bank on Main Street.
|
||||
A pencil with black lead writes best.
|
||||
Coax a young calf to drink from a bucket.
|
||||
Schools for ladies teach charm and grace.
|
||||
The lamp shone with a steady green flame.
|
||||
They took the axe and the saw to the forest.
|
||||
The ancient coin was quite dull and worn.
|
||||
The shaky barn fell with a loud crash.
|
||||
Jazz and swing fans like fast music.
|
||||
Rake the rubbish up and then burn it.
|
||||
Slash the gold cloth into fine ribbons.
|
||||
Try to have the court decide the case.
|
||||
They are pushed back each time they attack.
|
||||
He broke his ties with groups of former friends.
|
||||
They floated on the raft to sun their white backs.
|
||||
The map had an X that meant nothing.
|
||||
Whitings are small fish caught in nets.
|
||||
Some ads serve to cheat buyers.
|
||||
Jerk the rope and the bell rings weakly.
|
||||
A waxed floor makes us lose balance.
|
||||
Madam, this is the best brand of corn.
|
||||
On the islands the sea breeze is soft and mild.
|
||||
The play began as soon as we sat down.
|
||||
This will lead the world to more sound and fury.
|
||||
Add salt before you fry the egg.
|
||||
The rush for funds reached its peak Tuesday.
|
||||
The birch looked stark white and lonesome.
|
||||
The box is held by a bright red snapper.
|
||||
To make pure ice, you freeze water.
|
||||
The first worm gets snapped early.
|
||||
Jump the fence and hurry up the bank.
|
||||
Yell and clap as the curtain slides back.
|
||||
They are men who walk the middle of the road.
|
||||
Both brothers wear the same size.
|
||||
In some form or other we need fun.
|
||||
The prince ordered his head chopped off.
|
||||
The houses are built of red clay bricks.
|
||||
Ducks fly north but lack a compass.
|
||||
Fruit flavors are used in fizz drinks.
|
||||
These pills do less good than others.
|
||||
Canned pears lack full flavor.
|
||||
The dark pot hung in the front closet.
|
||||
Carry the pail to the wall and spill it there.
|
||||
The train brought our hero to the big town.
|
||||
We are sure that one war is enough.
|
||||
Gray paint stretched for miles around.
|
||||
The rude laugh filled the empty room.
|
||||
High seats are best for football fans.
|
||||
Tea served from the brown jug is tasty.
|
||||
A dash of pepper spoils beef stew.
|
||||
A zestful food is the hot-cross bun.
|
||||
The horse trotted around the field at a brisk pace.
|
||||
Find the twin who stole the pearl necklace.
|
||||
Cut the cord that binds the box tightly.
|
||||
The red tape bound the smuggled food.
|
||||
Look in the corner to find the tan shirt.
|
||||
The cold drizzle will halt the bond drive.
|
||||
Nine men were hired to dig the ruins.
|
||||
The junk yard had a mouldy smell.
|
||||
The flint sputtered and lit a pine torch.
|
||||
Soak the cloth and drown the sharp odor.
|
||||
The shelves were bare of both jam or crackers.
|
||||
A joy to every child is the swan boat.
|
||||
All sat frozen and watched the screen.
|
||||
A cloud of dust stung his tender eyes.
|
||||
To reach the end he needs much courage.
|
||||
Shape the clay gently into block form.
|
||||
A ridge on a smooth surface is a bump or flaw.
|
||||
Hedge apples may stain your hands green.
|
||||
Quench your thirst, then eat the crackers.
|
||||
Tight curls get limp on rainy days.
|
||||
The mute muffled the high tones of the horn.
|
||||
The gold ring fits only a pierced ear.
|
||||
The old pan was covered with hard fudge.
|
||||
Watch the log float in the wide river.
|
||||
The node on the stalk of wheat grew daily.
|
||||
The heap of fallen leaves was set on fire.
|
||||
Write fast if you want to finish early.
|
||||
His shirt was clean but one button was gone.
|
||||
The barrel of beer was a brew of malt and hops.
|
||||
Tin cans are absent from store shelves.
|
||||
Slide the box into that empty space.
|
||||
The plant grew large and green in the window.
|
||||
The beam dropped down on the workmen's head.
|
||||
Pink clouds floated with the breeze.
|
||||
She danced like a swan, tall and graceful.
|
||||
The tube was blown and the tire flat and useless.
|
||||
It is late morning on the old wall clock.
|
||||
Let's all join as we sing the last chorus.
|
||||
The last switch cannot be turned off.
|
||||
The fight will end in just six minutes.
|
||||
The store walls were lined with colored frocks.
|
||||
The peace league met to discuss their plans.
|
||||
The rise to fame of a person takes luck.
|
||||
Paper is scarce, so write with much care.
|
||||
The quick fox jumped on the sleeping cat.
|
||||
The nozzle of the fire hose was bright brass.
|
||||
Screw the round cap on as tight as needed.
|
||||
Time brings us many changes.
|
||||
The purple tie was ten years old.
|
||||
Men think and plan and sometimes act.
|
||||
Fill the ink jar with sticky glue.
|
||||
He smoke a big pipe with strong contents.
|
||||
We need grain to keep our mules healthy.
|
||||
Pack the records in a neat thin case.
|
||||
The crunch of feet in the snow was the only sound.
|
||||
The copper bowl shone in the sun's rays.
|
||||
Boards will warp unless kept dry.
|
||||
The plush chair leaned against the wall.
|
||||
Glass will clink when struck by metal.
|
||||
Bathe and relax in the cool green grass.
|
||||
Nine rows of soldiers stood in line.
|
||||
The beach is dry and shallow at low tide.
|
||||
The idea is to sew both edges straight.
|
||||
The kitten chased the dog down the street.
|
||||
Pages bound in cloth make a book.
|
||||
Try to trace the fine lines of the painting.
|
||||
Women form less than half of the group.
|
||||
The zones merge in the central part of town.
|
||||
A gem in the rough needs work to polish.
|
||||
Code is used when secrets are sent.
|
||||
Most of the news is easy for us to hear.
|
||||
He used the lathe to make brass objects.
|
||||
The vane on top of the pole revolved in the wind.
|
||||
Mince pie is a dish served to children.
|
||||
The clan gathered on each dull night.
|
||||
Let it burn, it gives us warmth and comfort.
|
||||
A castle built from sand fails to endure.
|
||||
A child's wit saved the day for us.
|
||||
Tack the strip of carpet to the worn floor.
|
||||
Next Tuesday we must vote.
|
||||
Pour the stew from the pot into the plate.
|
||||
Each penny shone like new.
|
||||
The man went to the woods to gather sticks.
|
||||
The dirt piles were lines along the road.
|
||||
The logs fell and tumbled into the clear stream.
|
||||
Just hoist it up and take it away.
|
||||
A ripe plum is fit for a king's palate.
|
||||
Our plans right now are hazy.
|
||||
Brass rings are sold by these natives.
|
||||
It takes a good trap to capture a bear.
|
||||
Feed the white mouse some flower seeds.
|
||||
The thaw came early and freed the stream.
|
||||
He took the lead and kept it the whole distance.
|
||||
The key you designed will fit the lock.
|
||||
Plead to the council to free the poor thief.
|
||||
Better hash is made of rare beef.
|
||||
This plank was made for walking on.
|
||||
The lake sparkled in the red hot sun.
|
||||
He crawled with care along the ledge.
|
||||
Tend the sheep while the dog wanders.
|
||||
It takes a lot of help to finish these.
|
||||
Mark the spot with a sign painted red.
|
||||
Take two shares as a fair profit.
|
||||
The fur of cats goes by many names.
|
||||
North winds bring colds and fevers.
|
||||
He asks no person to vouch for him.
|
||||
Go now and come here later.
|
||||
A sash of gold silk will trim her dress.
|
||||
Soap can wash most dirt away.
|
||||
That move means the game is over.
|
||||
He wrote down a long list of items.
|
||||
A siege will crack the strong defense.
|
||||
Grape juice and water mix well.
|
||||
Roads are paved with sticky tar.
|
||||
Fake stones shine but cost little.
|
||||
The drip of the rain made a pleasant sound.
|
||||
Smoke poured out of every crack.
|
||||
Serve the hot rum to the tired heroes.
|
||||
Much of the story makes good sense.
|
||||
The sun came up to light the eastern sky.
|
||||
Heave the line over the port side.
|
||||
A lathe cuts and trims any wood.
|
||||
It's a dense crowd in two distinct ways.
|
||||
His hip struck the knee of the next player.
|
||||
The stale smell of old beer lingers.
|
||||
The desk was firm on the shaky floor.
|
||||
It takes heat to bring out the odor.
|
||||
Beef is scarcer than some lamb.
|
||||
Raise the sail and steer the ship northward.
|
||||
A cone costs five cents on Mondays.
|
||||
A pod is what peas always grow in.
|
||||
Jerk the dart from the cork target.
|
||||
No cement will hold hard wood.
|
||||
We now have a new base for shipping.
|
||||
A list of names is carved around the base.
|
||||
The sheep were led home by a dog.
|
||||
Three for a dime, the young peddler cried.
|
||||
The sense of smell is better than that of touch.
|
||||
No hardship seemed to keep him sad.
|
||||
Grace makes up for lack of beauty.
|
||||
Nudge gently but wake her now.
|
||||
The news struck doubt into restless minds.
|
||||
Once we stood beside the shore.
|
||||
A chink in the wall allowed a draft to blow.
|
||||
Fasten two pins on each side.
|
||||
A cold dip restores health and zest.
|
||||
He takes the oath of office each March.
|
||||
The sand drifts over the sill of the old house.
|
||||
The point of the steel pen was bent and twisted.
|
||||
There is a lag between thought and act.
|
||||
Seed is needed to plant the spring corn.
|
||||
Draw the chart with heavy black lines.
|
||||
The boy owed his pal thirty cents.
|
||||
The chap slipped into the crowd and was lost.
|
||||
Hats are worn to tea and not to dinner.
|
||||
The ramp led up to the wide highway.
|
||||
Beat the dust from the rug onto the lawn.
|
||||
Say it slowly but make it ring clear.
|
||||
The straw nest housed five robins.
|
||||
Screen the porch with woven straw mats.
|
||||
This horse will nose his way to the finish.
|
||||
The dry wax protects the deep scratch.
|
||||
He picked up the dice for a second roll.
|
||||
These coins will be needed to pay his debt.
|
||||
The nag pulled the frail cart along.
|
||||
Twist the valve and release hot steam.
|
||||
The vamp of the shoe had a gold buckle.
|
||||
The smell of burned rags itches my nose.
|
||||
New pants lack cuffs and pockets.
|
||||
The marsh will freeze when cold enough.
|
||||
They slice the sausage thin with a knife.
|
||||
The bloom of the rose lasts a few days.
|
||||
A gray mare walked before the colt.
|
||||
Breakfast buns are fine with a hot drink.
|
||||
Bottles hold four kinds of rum.
|
||||
The man wore a feather in his felt hat.
|
||||
He wheeled the bike past the winding road.
|
||||
Drop the ashes on the worn old rug.
|
||||
The desk and both chairs were painted tan.
|
||||
Throw out the used paper cup and plate.
|
||||
A clean neck means a neat collar.
|
||||
The couch cover and hall drapes were blue.
|
||||
The stems of the tall glasses cracked and broke.
|
||||
The wall phone rang loud and often.
|
||||
The clothes dried on a thin wooden rack.
|
||||
Turn on the lantern which gives us light.
|
||||
The cleat sank deeply into the soft turf.
|
||||
The bills were mailed promptly on the tenth of the month.
|
||||
To have is better than to wait and hope.
|
||||
The price is fair for a good antique clock.
|
||||
The music played on while they talked.
|
||||
Dispense with a vest on a day like this.
|
||||
The bunch of grapes was pressed into wine.
|
||||
He sent the figs, but kept the ripe cherries.
|
||||
The hinge on the door creaked with old age.
|
||||
The screen before the fire kept in the sparks.
|
||||
Fly by night, and you waste little time.
|
||||
Thick glasses helped him read the print.
|
||||
Birth and death mark the limits of life.
|
||||
The chair looked strong but had no bottom.
|
||||
The kite flew wildly in the high wind.
|
||||
A fur muff is stylish once more.
|
||||
The tin box held priceless stones.
|
||||
We need an end of all such matter.
|
||||
The case was puzzling to the old and wise.
|
||||
The bright lanterns were gay on the dark lawn.
|
||||
We don't get much money but we have fun.
|
||||
The youth drove with zest, but little skill.
|
||||
Five years he lived with a shaggy dog.
|
||||
A fence cuts through the corner lot.
|
||||
The way to save money is not to spend much.
|
||||
Shut the hatch before the waves push it in.
|
||||
The odor of spring makes young hearts jump.
|
||||
Crack the walnut with your sharp side teeth.
|
||||
He offered proof in the form of a large chart.
|
||||
Send the stuff in a thick paper bag.
|
||||
A quart of milk is water for the most part.
|
||||
They told wild tales to frighten him.
|
||||
The three story house was built of stone.
|
||||
In the rear of the ground floor was a large passage.
|
||||
A man in a blue sweater sat at the desk.
|
||||
Oats are a food eaten by horse and man.
|
||||
Their eyelids droop for want of sleep.
|
||||
A sip of tea revives his tired friend.
|
||||
There are many ways to do these things.
|
||||
Tuck the sheet under the edge of the mat.
|
||||
A force equal to that would move the earth.
|
||||
We like to see clear weather.
|
||||
The work of the tailor is seen on each side.
|
||||
Take a chance and win a china doll.
|
||||
Shake the dust from your shoes, stranger.
|
||||
She was kind to sick old people.
|
||||
The square wooden crate was packed to be shipped.
|
||||
The dusty bench stood by the stone wall.
|
||||
We dress to suit the weather of most days.
|
||||
Smile when you say nasty words.
|
||||
A bowl of rice is free with chicken stew.
|
||||
The water in this well is a source of good health.
|
||||
Take shelter in this tent, but keep still.
|
||||
That guy is the writer of a few banned books.
|
||||
The little tales they tell are false.
|
||||
The door was barred, locked, and bolted as well.
|
||||
Ripe pears are fit for a queen's table.
|
||||
A big wet stain was on the round carpet.
|
||||
The kite dipped and swayed, but stayed aloft.
|
||||
The pleasant hours fly by much too soon.
|
||||
The room was crowded with a wild mob.
|
||||
This strong arm shall shield your honor.
|
||||
She blushed when he gave her a white orchid.
|
||||
The beetle droned in the hot June sun.
|
||||
Press the pedal with your left foot.
|
||||
Neat plans fail without luck.
|
||||
The black trunk fell from the landing.
|
||||
The bank pressed for payment of the debt.
|
||||
The theft of the pearl pin was kept secret.
|
||||
Shake hands with this friendly child.
|
||||
The vast space stretched into the far distance.
|
||||
A rich farm is rare in this sandy waste.
|
||||
His wide grin earned many friends.
|
||||
Flax makes a fine brand of paper.
|
||||
Hurdle the pit with the aid of a long pole.
|
||||
A strong bid may scare your partner stiff.
|
||||
Even a just cause needs power to win.
|
||||
Peep under the tent and see the clowns.
|
||||
The leaf drifts along with a slow spin.
|
||||
Cheap clothes are flashy but don't last.
|
||||
A thing of small note can cause despair.
|
||||
Flood the mails with requests for this book.
|
||||
A thick coat of black paint covered all.
|
||||
The pencil was cut to be sharp at both ends.
|
||||
Those last words were a strong statement.
|
||||
He wrote his name boldly at the top of the sheet.
|
||||
Dill pickles are sour but taste fine.
|
||||
Down that road is the way to the grain farmer.
|
||||
Either mud or dust are found at all times.
|
||||
The best method is to fix it in place with clips.
|
||||
If you mumble your speech will be lost.
|
||||
At night the alarm roused him from a deep sleep.
|
||||
Read just what the meter says.
|
||||
Fill your pack with bright trinkets for the poor.
|
||||
The small red neon lamp went out.
|
||||
Clams are small, round, soft, and tasty.
|
||||
The fan whirled its round blades softly.
|
||||
The line where the edges join was clean.
|
||||
Breathe deep and smell the piny air.
|
||||
It matters not if he reads these words or those.
|
||||
A brown leather bag hung from its strap.
|
||||
A toad and a frog are hard to tell apart.
|
||||
A white silk jacket goes with any shoes.
|
||||
A break in the dam almost caused a flood.
|
||||
Paint the sockets in the wall dull green.
|
||||
The child crawled into the dense grass.
|
||||
Bribes fail where honest men work.
|
||||
Trample the spark, else the flames will spread.
|
||||
The hilt of the sword was carved with fine designs.
|
||||
A round hole was drilled through the thin board.
|
||||
Footprints showed the path he took up the beach.
|
||||
She was waiting at my front lawn.
|
||||
A vent near the edge brought in fresh air.
|
||||
Prod the old mule with a crooked stick.
|
||||
It is a band of steel three inches wide.
|
||||
The pipe ran almost the length of the ditch.
|
||||
It was hidden from sight by a mass of leaves and shrubs.
|
||||
The weight of the package was seen on the high scale.
|
||||
Wake and rise, and step into the green outdoors.
|
||||
The green light in the brown box flickered.
|
||||
The brass tube circled the high wall.
|
||||
The lobes of her ears were pierced to hold rings.
|
||||
Hold the hammer near the end to drive the nail.
|
||||
Next Sunday is the twelfth of the month.
|
||||
Every word and phrase he speaks is true.
|
||||
He put his last cartridge into the gun and fired.
|
||||
They took their kids from the public school.
|
||||
Drive the screw straight into the wood.
|
||||
Keep the hatch tight and the watch constant.
|
||||
Sever the twine with a quick snip of the knife.
|
||||
Paper will dry out when wet.
|
||||
Slide the catch back and open the desk.
|
||||
Help the weak to preserve their strength.
|
||||
A sullen smile gets few friends.
|
||||
Stop whistling and watch the boys march.
|
||||
Jerk the cord, and out tumbles the gold.
|
||||
Slide the tray across the glass top.
|
||||
The cloud moved in a stately way and was gone.
|
||||
Light maple makes for a swell room.
|
||||
Set the piece here and say nothing.
|
||||
Dull stories make her laugh.
|
||||
A stiff cord will do to fasten your shoe.
|
||||
Get the trust fund to the bank early.
|
||||
Choose between the high road and the low.
|
||||
A plea for funds seems to come again.
|
||||
He lent his coat to the tall gaunt stranger.
|
||||
There is a strong chance it will happen once more.
|
||||
The duke left the park in a silver coach.
|
||||
Greet the new guests and leave quickly.
|
||||
When the frost has come it is time for turkey.
|
||||
Sweet words work better than fierce.
|
||||
A thin stripe runs down the middle.
|
||||
A six comes up more often than a ten.
|
||||
Lush fern grow on the lofty rocks.
|
||||
The ram scared the school children off.
|
||||
The team with the best timing looks good.
|
||||
The farmer swapped his horse for a brown ox.
|
||||
Sit on the perch and tell the others what to do.
|
||||
A steep trail is painful for our feet.
|
||||
The early phase of life moves fast.
|
||||
Green moss grows on the northern side.
|
||||
Tea in thin china has a sweet taste.
|
||||
Pitch the straw through the door of the stable.
|
||||
The latch on the back gate needed a nail.
|
||||
The goose was brought straight from the old market.
|
||||
The sink is the thing in which we pile dishes.
|
||||
A whiff of it will cure the most stubborn cold.
|
||||
The facts don't always show who is right.
|
||||
She flaps her cape as she parades the street.
|
||||
The loss of the cruiser was a blow to the fleet.
|
||||
Loop the braid to the left and then over.
|
||||
Plead with the lawyer to drop the lost cause.
|
||||
Calves thrive on tender spring grass.
|
||||
Post no bills on this office wall.
|
||||
Tear a thin sheet from the yellow pad.
|
||||
A cruise in warm waters in a sleek yacht is fun.
|
||||
A streak of color ran down the left edge.
|
||||
It was done before the boy could see it.
|
||||
Crouch before you jump or miss the mark.
|
||||
Pack the kits and don't forget the salt.
|
||||
The square peg will settle in the round hole.
|
||||
Fine soap saves tender skin.
|
||||
Poached eggs and tea must suffice.
|
||||
Bad nerves are jangled by a door slam.
|
||||
Ship maps are different from those for planes.
|
||||
Dimes showered down from all sides.
|
||||
They sang the same tunes at each party.
|
||||
The sky in the west is tinged with orange red.
|
||||
The pods of peas ferment in bare fields.
|
||||
The horse balked and threw the tall rider.
|
||||
The hitch between the horse and cart broke.
|
||||
Pile the coal high in the shed corner.
|
||||
A gold vase is both rare and costly.
|
||||
The knife was hung inside its bright sheath.
|
||||
The rarest spice comes from the far East.
|
||||
The roof should be tilted at a sharp slant.
|
||||
A smatter of French is worse than none.
|
||||
The mule trod the treadmill day and night.
|
||||
The aim of the contest is to raise a great fund.
|
||||
To send it now in large amounts is bad.
|
||||
There is a fine hard tang in salty air.
|
||||
Cod is the main business of the north shore.
|
||||
The slab was hewn from heavy blocks of slate.
|
||||
Dunk the stale biscuits into strong drink.
|
||||
Hang tinsel from both branches.
|
||||
Cap the jar with a tight brass cover.
|
||||
The poor boy missed the boat again.
|
||||
Be sure to set the lamp firmly in the hole.
|
||||
Pick a card and slip it under the pack.
|
||||
A round mat will cover the dull spot.
|
||||
The first part of the plan needs changing.
|
||||
A good book informs of what we ought to know.
|
||||
The mail comes in three batches per day.
|
||||
You cannot brew tea in a cold pot.
|
||||
Dots of light betrayed the black cat.
|
||||
Put the chart on the mantel and tack it down.
|
||||
The night shift men rate extra pay.
|
||||
The red paper brightened the dim stage.
|
||||
See the player scoot to third base.
|
||||
Slide the bill between the two leaves.
|
||||
Many hands help get the job done.
|
||||
We don't like to admit our small faults.
|
||||
No doubt about the way the wind blows.
|
||||
Dig deep in the earth for pirate's gold.
|
||||
The steady drip is worse than a drenching rain.
|
||||
A flat pack takes less luggage space.
|
||||
Green ice frosted the punch bowl.
|
||||
A stuffed chair slipped from the moving van.
|
||||
The stitch will serve but needs to be shortened.
|
||||
A thin book fits in the side pocket.
|
||||
The gloss on top made it unfit to read.
|
||||
The hail pattered on the burnt brown grass.
|
||||
Seven seals were stamped on great sheets.
|
||||
Our troops are set to strike heavy blows.
|
||||
The store was jammed before the sale could start.
|
||||
It was a bad error on the part of the new judge.
|
||||
One step more and the board will collapse.
|
||||
Take the match and strike it against your shoe.
|
||||
The pot boiled, but the contents failed to jell.
|
||||
The baby puts his right foot in his mouth.
|
||||
The bombs left most of the town in ruins.
|
||||
Stop and stare at the hard working man.
|
||||
The streets are narrow and full of sharp turns.
|
||||
The pup jerked the leash as he saw a feline shape.
|
||||
Open your book to the first page.
|
||||
Fish evade the net and swim off.
|
||||
Dip the pail once and let it settle.
|
||||
Will you please answer that phone.
|
||||
The big red apple fell to the ground.
|
||||
The curtain rose and the show was on.
|
||||
The young prince became heir to the throne.
|
||||
He sent the boy on a short errand.
|
||||
Leave now and you will arrive on time.
|
||||
The corner store was robbed last night.
|
||||
A gold ring will please most any girl.
|
||||
The long journey home took a year.
|
||||
She saw a cat in the neighbor's house.
|
||||
A pink shell was found on the sandy beach.
|
||||
Small children came to see him.
|
||||
The grass and bushes were wet with dew.
|
||||
The blind man counted his old coins.
|
||||
A severe storm tore down the barn.
|
||||
She called his name many times.
|
||||
When you hear the bell, come quickly.
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Arabic": "ar",
|
||||
"Chinese": "zh-cn",
|
||||
"Czech": "cs",
|
||||
"Dutch": "nl",
|
||||
"English": "en",
|
||||
"French": "fr",
|
||||
"German": "de",
|
||||
"Hungarian": "hu",
|
||||
"Italian": "it",
|
||||
"Japanese": "ja",
|
||||
"Korean": "ko",
|
||||
"Polish": "pl",
|
||||
"Portuguese": "pt",
|
||||
"Russian": "ru",
|
||||
"Spanish": "es",
|
||||
"Turkish": "tr"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
find {addpathheretoscan}/alltalk_environment/env/lib -xtype l
|
||||
|
||||
here are the list on Cuda-Toolkit 12.1, others vary
|
||||
|
||||
.../alltalk_environment/env/lib/libcufile.so
|
||||
.../alltalk_environment/env/lib/libcudart.so
|
||||
.../alltalk_environment/env/lib/libcufile_rdma.so
|
||||
.../alltalk_environment/env/lib/libnvjpeg.so
|
||||
.../alltalk_environment/env/lib/libcurand.so
|
||||
.../alltalk_environment/env/lib/libnvJitLink.so
|
||||
.../alltalk_environment/env/lib/libnvrtc-builtins.so
|
||||
.../alltalk_environment/env/lib/libnvrtc.so
|
||||
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,54 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
def merge_vocabularies(base_vocab_path, new_vocab_path, output_path):
|
||||
# Load the base model's vocab.json
|
||||
with open(base_vocab_path, 'r') as f:
|
||||
base_data = json.load(f)
|
||||
|
||||
# Load the new bpe_tokenizer.json
|
||||
with open(new_vocab_path, 'r') as f:
|
||||
new_data = json.load(f)
|
||||
|
||||
# Extract the vocabularies
|
||||
base_vocab = base_data['model']['vocab']
|
||||
new_vocab = new_data['model']['vocab']
|
||||
|
||||
# Find the maximum value in the base vocabulary
|
||||
max_value = max(base_vocab.values())
|
||||
|
||||
# Merge the vocabularies
|
||||
for key, value in new_vocab.items():
|
||||
if key not in base_vocab:
|
||||
max_value += 1
|
||||
base_vocab[key] = max_value
|
||||
|
||||
# Update the base data with the merged vocabulary
|
||||
base_data['model']['vocab'] = base_vocab
|
||||
|
||||
# Extract the merges
|
||||
base_merges = base_data['model']['merges']
|
||||
new_merges = new_data['model']['merges']
|
||||
|
||||
# Merge the merges
|
||||
merged_merges = base_merges.copy()
|
||||
for merge in new_merges:
|
||||
if merge not in merged_merges:
|
||||
merged_merges.append(merge)
|
||||
|
||||
# Update the base data with the merged merges
|
||||
base_data['model']['merges'] = merged_merges
|
||||
|
||||
# Write the merged vocabulary and merges to the output file
|
||||
with open(output_path, 'w') as f:
|
||||
json.dump(base_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"Merged vocabulary and merges saved to {output_path}")
|
||||
|
||||
# Define file paths
|
||||
base_vocab_path = "/alltalk_tts/models/xtts/xttsv2_2.0.2/originalvocab.json" # base model vocab.json path (2.0.2)
|
||||
new_vocab_path = "/alltalk_tts/expanded_models/tortoise_tokenizer2xttsv2.json" # path to the custom dataset vocab.json
|
||||
output_path = "/alltalk_tts/expanded_models/combined_vocab.json" # location for combined vocab.json
|
||||
|
||||
# Merge the vocabularies
|
||||
merge_vocabularies(base_vocab_path, new_vocab_path, output_path)
|
||||
@@ -0,0 +1,60 @@
|
||||
# Credit Jarod Mica https://github.com/JarodMica/tortoise_dataset_tools/blob/master/bpe_tokenizer_tools/train_bpe_tokenizer.py
|
||||
# provide a cleaned txt file with only the transcription. Youll get back out the datasets vocab.json
|
||||
|
||||
from tokenizers import Tokenizer
|
||||
from tokenizers.models import BPE
|
||||
from tokenizers.trainers import BpeTrainer
|
||||
from tokenizers.pre_tokenizers import Whitespace
|
||||
from tkinter import Tk, filedialog
|
||||
import json
|
||||
import re
|
||||
|
||||
def clean_text(input_file_path, output_file_path):
|
||||
# Define the pattern to match numbers, specific symbols, and new lines
|
||||
# add \d to match any digit, and | is used to specify alternatives
|
||||
pattern = r'|�|«|\$|\n'
|
||||
|
||||
with open(input_file_path, 'r', encoding='utf-8') as input_file:
|
||||
text = input_file.read()
|
||||
cleaned_text = re.sub(pattern, '', text)
|
||||
|
||||
with open(output_file_path, 'w', encoding='utf-8') as output_file:
|
||||
output_file.write(cleaned_text)
|
||||
|
||||
def train_tokenizer(input_path, tokenizer_path, language, special_tokens=["[STOP]", "[UNK]", "[SPACE]" ], vocab_size=256):
|
||||
# Initialize a tokenizer with the BPE model
|
||||
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
|
||||
# Use a basic whitespace pre-tokenizer
|
||||
tokenizer.pre_tokenizer = Whitespace()
|
||||
|
||||
# trainer = BpeTrainer(special_tokens=["[STOP]", "[UNK]", "[SPACE]", "0","1","2","3","4","5","6","7","8","9",], vocab_size=256)
|
||||
trainer = BpeTrainer(special_tokens=special_tokens, vocab_size=vocab_size)
|
||||
|
||||
|
||||
clean_text(input_path, input_path)
|
||||
tokenizer.train([input_path], trainer)
|
||||
|
||||
tokenizer.save(tokenizer_path)
|
||||
|
||||
with open(tokenizer_path, 'r', encoding='utf-8') as f:
|
||||
tokenizer_json = json.load(f)
|
||||
|
||||
# Add language to tokenizer
|
||||
tokenizer_json['model']['language'] = language
|
||||
|
||||
with open(tokenizer_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(tokenizer_json, f, ensure_ascii=False, indent=4)
|
||||
|
||||
def choose_file():
|
||||
root = Tk()
|
||||
root.withdraw()
|
||||
file = filedialog.askopenfilename()
|
||||
root.destroy()
|
||||
return file
|
||||
|
||||
if __name__ == "__main__":
|
||||
input_path = choose_file()
|
||||
tokenizer_path = "/alltalk_tts/expanded_models/tortoise_tokenizer2xttsv2.json" # define path to put the newly create vocab.json
|
||||
special_tokens = ["[STOP]", "[UNK]", "[SPACE]"]
|
||||
vocab_size = 256 # model is stuck at this size
|
||||
train_tokenizer(input_path, tokenizer_path, language='multi', special_tokens=special_tokens, vocab_size=vocab_size)
|
||||
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
This script does not have any specific alltalk integration yet. So we are manually entering paths and it works as a standalone.
|
||||
|
||||
The script does the following:
|
||||
- Expands the embedding layer of the base XTTSv2 model according to the user created/trained bpe_tokenizer-vocab.json and the base model vocab.json.
|
||||
Set variable paths with the base config and base model.pth.
|
||||
Set the new tokenizer/vocab bpe_tokenizer-vocab.json location.
|
||||
The new model will be saved at \expanded_models\expanded_model.pth
|
||||
|
||||
Once this is done the new expanded model must be swapped in for the base model.pth then combined with the dvae.pth, vocab.json(bpe_tokenizer-vocab.json), base model config.json,
|
||||
base model speaker_xtts.pth and base model vocoder.json.
|
||||
|
||||
|
||||
I left some print debug statements in the script, they may be nice for the user to see during the process.
|
||||
|
||||
"""
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import json
|
||||
from TTS.tts.models.xtts import Xtts
|
||||
from TTS.tts.layers.xtts.trainer.gpt_trainer import GPTTrainerConfig
|
||||
from TTS.tts.layers.xtts.tokenizer import VoiceBpeTokenizer
|
||||
|
||||
config_path = "/alltalk_tts/models/xtts/xttsv2_2.0.3/config.json" # Path to the base model config.json
|
||||
pretrained_model_path = "/alltalk_tts/models/xtts/xttsv2_2.0.3/model.pth" # Path to the base model.pth
|
||||
new_tokenizer_path = "/expanded_model/expanded_vocab.json" # Path to the new combined expanded_vocab.json
|
||||
expanded_model_path = "/expanded_model/expanded_model.pth" # Path to where you want the new expanded_model.pth
|
||||
|
||||
|
||||
# Open and load the configuration file
|
||||
with open(config_path, "r") as f:
|
||||
config_dict = json.load(f)
|
||||
|
||||
# Create a GPTTrainerConfig object and populate it with the loaded configuration
|
||||
config = GPTTrainerConfig()
|
||||
config.from_dict(data=config_dict)
|
||||
|
||||
# Function to get the vocabulary size from a tokenizer file
|
||||
def get_vocab_size(tokenizer_path):
|
||||
tokenizer = VoiceBpeTokenizer(vocab_file=tokenizer_path)
|
||||
return len(tokenizer.tokenizer.get_vocab())
|
||||
|
||||
# Function to adjust the pretrained model with a new tokenizer
|
||||
def adjust_pretrained_model(
|
||||
pretrained_model_path, adjusted_model_path, new_tokenizer_path):
|
||||
state_dict = torch.load(pretrained_model_path)
|
||||
pretrained_state_dict = state_dict["model"]
|
||||
model = Xtts(config)
|
||||
|
||||
# Load the pretrained state dictionary into the new model
|
||||
missing_keys, unexpected_keys = model.load_state_dict(pretrained_state_dict, strict=False)
|
||||
if missing_keys:
|
||||
print(f"Missing keys: {missing_keys}")
|
||||
if unexpected_keys:
|
||||
print(f"Unexpected keys: {unexpected_keys}")
|
||||
print("Pretrained model loaded successfully.")
|
||||
|
||||
# Create a new tokenizer with the new vocabulary
|
||||
new_tokenizer = VoiceBpeTokenizer(vocab_file=new_tokenizer_path)
|
||||
|
||||
# Get the old and new vocabulary sizes, and the embedding dimension
|
||||
old_vocab_size = model.gpt.text_embedding.num_embeddings
|
||||
new_vocab_size = len(new_tokenizer.tokenizer.get_vocab())
|
||||
embedding_dim = model.gpt.text_embedding.embedding_dim
|
||||
|
||||
print(f"Old vocab size: {old_vocab_size}")
|
||||
print(f"New vocab size: {new_vocab_size}")
|
||||
print(f"Embedding dimension: {embedding_dim}")
|
||||
|
||||
# Adjust the embedding layer with the new vocabulary size
|
||||
adjust_embedding_layer(model, new_vocab_size, adjusted_model_path)
|
||||
|
||||
# Freeze all parameters except the position embeddings
|
||||
freeze_except_position_embeddings(model)
|
||||
|
||||
# Function to adjust the embedding layer for the new vocabulary size
|
||||
def adjust_embedding_layer(model, new_vocab_size, adjusted_model_path):
|
||||
old_vocab_size = model.gpt.text_embedding.num_embeddings
|
||||
embedding_dim = model.gpt.text_embedding.embedding_dim
|
||||
|
||||
# Create new embedding and linear layers with the new vocabulary size
|
||||
new_text_embedding = nn.Embedding(new_vocab_size, embedding_dim)
|
||||
new_text_head = nn.Linear(embedding_dim, new_vocab_size)
|
||||
|
||||
# Copy weights from the old embedding layer to the new one
|
||||
if new_vocab_size > old_vocab_size:
|
||||
new_text_embedding.weight.data[:old_vocab_size] = model.gpt.text_embedding.weight.data
|
||||
new_text_head.weight.data[:old_vocab_size] = model.gpt.text_head.weight.data
|
||||
new_text_head.bias.data[:old_vocab_size] = model.gpt.text_head.bias.data
|
||||
|
||||
new_text_embedding.weight.data[old_vocab_size:].normal_(mean=0.0, std=0.02)
|
||||
new_text_head.weight.data[old_vocab_size:].normal_(mean=0.0, std=0.02)
|
||||
new_text_head.bias.data[old_vocab_size:].normal_(mean=0.0, std=0.02)
|
||||
else:
|
||||
new_text_embedding.weight.data = model.gpt.text_embedding.weight.data[:new_vocab_size]
|
||||
new_text_head.weight.data = model.gpt.text_head.weight.data[:new_vocab_size]
|
||||
new_text_head.bias.data = model.gpt.text_head.bias.data[:new_vocab_size]
|
||||
|
||||
model.gpt.text_embedding = new_text_embedding
|
||||
model.gpt.text_head = new_text_head
|
||||
|
||||
checkpoint = {"model": model.state_dict()}
|
||||
torch.save(checkpoint, adjusted_model_path)
|
||||
print(f"Adjusted model saved to {adjusted_model_path}")
|
||||
|
||||
# Function to freeze all parameters except the position embeddings
|
||||
def freeze_except_position_embeddings(model):
|
||||
for param in model.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
for name, param in model.named_parameters():
|
||||
if 'pos_embedding' in name:
|
||||
param.requires_grad = True
|
||||
|
||||
# Verify which parameters are frozen and which are not, comment this out if you dont want to debug. You should see only two true values
|
||||
for name, param in model.named_parameters():
|
||||
print(f"{name}: requires_grad={param.requires_grad}")
|
||||
|
||||
# Expand the pretrained model with the new tokenizer
|
||||
adjust_pretrained_model(pretrained_model_path, expanded_model_path, new_tokenizer_path)
|
||||
@@ -0,0 +1,26 @@
|
||||
# Simple script to remove LJ Speech formatting for the tokenizer
|
||||
# Combine metadata_train and metadata_eval.csv into a single file then run
|
||||
import csv
|
||||
|
||||
# Input and output file names
|
||||
input_file = '/alltalkbeta/metadata_eval.csv' # combine metadata_train and metadata_eval.csv
|
||||
output_file = '/alltalkbeta/dataset.txt' # this goes to the tokenizer
|
||||
|
||||
# Read the input CSV and write to the output file
|
||||
with open(input_file, 'r', newline='', encoding='utf-8') as infile, \
|
||||
open(output_file, 'w', newline='', encoding='utf-8') as outfile:
|
||||
|
||||
# Create CSV reader and writer objects
|
||||
reader = csv.reader(infile, delimiter='|')
|
||||
writer = csv.writer(outfile, delimiter='|')
|
||||
|
||||
# Skip the header
|
||||
next(reader, None)
|
||||
|
||||
# Process each row
|
||||
for row in reader:
|
||||
if len(row) >= 2:
|
||||
# Write only the second column (index 1) to the output file
|
||||
writer.writerow([row[1]])
|
||||
|
||||
print(f"Processing complete. Output written to {output_file}")
|
||||
@@ -0,0 +1,869 @@
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
from functools import cached_property
|
||||
|
||||
import pypinyin
|
||||
import torch
|
||||
from hangul_romanize import Transliter
|
||||
from hangul_romanize.rule import academic
|
||||
from num2words import num2words
|
||||
from spacy.lang.ar import Arabic
|
||||
from spacy.lang.en import English
|
||||
from spacy.lang.es import Spanish
|
||||
from spacy.lang.ja import Japanese
|
||||
from spacy.lang.zh import Chinese
|
||||
from tokenizers import Tokenizer
|
||||
|
||||
from TTS.tts.layers.xtts.zh_num2words import TextNorm as zh_num2words
|
||||
|
||||
|
||||
def get_spacy_lang(lang):
|
||||
if lang == "zh":
|
||||
return Chinese()
|
||||
elif lang == "ja":
|
||||
return Japanese()
|
||||
elif lang == "ar":
|
||||
return Arabic()
|
||||
elif lang == "es":
|
||||
return Spanish()
|
||||
else:
|
||||
# For most languages, Enlish does the job
|
||||
return English()
|
||||
|
||||
|
||||
def split_sentence(text, lang, text_split_length=250):
|
||||
"""Preprocess the input text"""
|
||||
text_splits = []
|
||||
if text_split_length is not None and len(text) >= text_split_length:
|
||||
text_splits.append("")
|
||||
nlp = get_spacy_lang(lang)
|
||||
nlp.add_pipe("sentencizer")
|
||||
doc = nlp(text)
|
||||
for sentence in doc.sents:
|
||||
if len(text_splits[-1]) + len(str(sentence)) <= text_split_length:
|
||||
# if the last sentence + the current sentence is less than the text_split_length
|
||||
# then add the current sentence to the last sentence
|
||||
text_splits[-1] += " " + str(sentence)
|
||||
text_splits[-1] = text_splits[-1].lstrip()
|
||||
elif len(str(sentence)) > text_split_length:
|
||||
# if the current sentence is greater than the text_split_length
|
||||
for line in textwrap.wrap(
|
||||
str(sentence),
|
||||
width=text_split_length,
|
||||
drop_whitespace=True,
|
||||
break_on_hyphens=False,
|
||||
tabsize=1,
|
||||
):
|
||||
text_splits.append(str(line))
|
||||
else:
|
||||
text_splits.append(str(sentence))
|
||||
|
||||
if len(text_splits) > 1:
|
||||
if text_splits[0] == "":
|
||||
del text_splits[0]
|
||||
else:
|
||||
text_splits = [text.lstrip()]
|
||||
|
||||
return text_splits
|
||||
|
||||
|
||||
_whitespace_re = re.compile(r"\s+")
|
||||
|
||||
# List of (regular expression, replacement) pairs for abbreviations:
|
||||
_abbreviations = {
|
||||
"en": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("mrs", "misess"),
|
||||
("mr", "mister"),
|
||||
("dr", "doctor"),
|
||||
("st", "saint"),
|
||||
("co", "company"),
|
||||
("jr", "junior"),
|
||||
("maj", "major"),
|
||||
("gen", "general"),
|
||||
("drs", "doctors"),
|
||||
("rev", "reverend"),
|
||||
("lt", "lieutenant"),
|
||||
("hon", "honorable"),
|
||||
("sgt", "sergeant"),
|
||||
("capt", "captain"),
|
||||
("esq", "esquire"),
|
||||
("ltd", "limited"),
|
||||
("col", "colonel"),
|
||||
("ft", "fort"),
|
||||
]
|
||||
],
|
||||
"es": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("sra", "señora"),
|
||||
("sr", "señor"),
|
||||
("dr", "doctor"),
|
||||
("dra", "doctora"),
|
||||
("st", "santo"),
|
||||
("co", "compañía"),
|
||||
("jr", "junior"),
|
||||
("ltd", "limitada"),
|
||||
]
|
||||
],
|
||||
"fr": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("mme", "madame"),
|
||||
("mr", "monsieur"),
|
||||
("dr", "docteur"),
|
||||
("st", "saint"),
|
||||
("co", "compagnie"),
|
||||
("jr", "junior"),
|
||||
("ltd", "limitée"),
|
||||
]
|
||||
],
|
||||
"de": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("fr", "frau"),
|
||||
("dr", "doktor"),
|
||||
("st", "sankt"),
|
||||
("co", "firma"),
|
||||
("jr", "junior"),
|
||||
]
|
||||
],
|
||||
"pt": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("sra", "senhora"),
|
||||
("sr", "senhor"),
|
||||
("dr", "doutor"),
|
||||
("dra", "doutora"),
|
||||
("st", "santo"),
|
||||
("co", "companhia"),
|
||||
("jr", "júnior"),
|
||||
("ltd", "limitada"),
|
||||
]
|
||||
],
|
||||
"it": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
# ("sig.ra", "signora"),
|
||||
("sig", "signore"),
|
||||
("dr", "dottore"),
|
||||
("st", "santo"),
|
||||
("co", "compagnia"),
|
||||
("jr", "junior"),
|
||||
("ltd", "limitata"),
|
||||
]
|
||||
],
|
||||
"pl": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("p", "pani"),
|
||||
("m", "pan"),
|
||||
("dr", "doktor"),
|
||||
("sw", "święty"),
|
||||
("jr", "junior"),
|
||||
]
|
||||
],
|
||||
"ar": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
# There are not many common abbreviations in Arabic as in English.
|
||||
]
|
||||
],
|
||||
"zh": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
# Chinese doesn't typically use abbreviations in the same way as Latin-based scripts.
|
||||
]
|
||||
],
|
||||
"cs": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("dr", "doktor"), # doctor
|
||||
("ing", "inženýr"), # engineer
|
||||
("p", "pan"), # Could also map to pani for woman but no easy way to do it
|
||||
# Other abbreviations would be specialized and not as common.
|
||||
]
|
||||
],
|
||||
"ru": [
|
||||
(re.compile("\\b%s\\b" % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("г-жа", "госпожа"), # Mrs.
|
||||
("г-н", "господин"), # Mr.
|
||||
("д-р", "доктор"), # doctor
|
||||
# Other abbreviations are less common or specialized.
|
||||
]
|
||||
],
|
||||
"nl": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("dhr", "de heer"), # Mr.
|
||||
("mevr", "mevrouw"), # Mrs.
|
||||
("dr", "dokter"), # doctor
|
||||
("jhr", "jonkheer"), # young lord or nobleman
|
||||
# Dutch uses more abbreviations, but these are the most common ones.
|
||||
]
|
||||
],
|
||||
"tr": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("b", "bay"), # Mr.
|
||||
("byk", "büyük"), # büyük
|
||||
("dr", "doktor"), # doctor
|
||||
# Add other Turkish abbreviations here if needed.
|
||||
]
|
||||
],
|
||||
"hu": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("dr", "doktor"), # doctor
|
||||
("b", "bácsi"), # Mr.
|
||||
("nőv", "nővér"), # nurse
|
||||
# Add other Hungarian abbreviations here if needed.
|
||||
]
|
||||
],
|
||||
"ko": [
|
||||
(re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
# Korean doesn't typically use abbreviations in the same way as Latin-based scripts.
|
||||
]
|
||||
],
|
||||
"ja": [
|
||||
(re.compile("\\b%s\\b" % x[0]), x[1])
|
||||
for x in [
|
||||
("氏", "さん"), # Mr.
|
||||
("夫人", "おんなのひと"), # Mrs.
|
||||
("博士", "はかせ"), # Doctor or PhD
|
||||
("株", "株式会社"), # Corporation
|
||||
("有", "有限会社"), # Limited company
|
||||
("大学", "だいがく"), # University
|
||||
("先生", "せんせい"), # Teacher/Professor/Master
|
||||
("君", "くん") # Used at the end of boys' names to express familiarity or affection.
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def expand_abbreviations_multilingual(text, lang="en"):
|
||||
for regex, replacement in _abbreviations[lang]:
|
||||
text = re.sub(regex, replacement, text)
|
||||
return text
|
||||
|
||||
|
||||
_symbols_multilingual = {
|
||||
"en": [
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " and "),
|
||||
("@", " at "),
|
||||
("%", " percent "),
|
||||
("#", " hash "),
|
||||
("$", " dollar "),
|
||||
("£", " pound "),
|
||||
("°", " degree "),
|
||||
]
|
||||
],
|
||||
"es": [
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " y "),
|
||||
("@", " arroba "),
|
||||
("%", " por ciento "),
|
||||
("#", " numeral "),
|
||||
("$", " dolar "),
|
||||
("£", " libra "),
|
||||
("°", " grados "),
|
||||
]
|
||||
],
|
||||
"fr": [
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " et "),
|
||||
("@", " arobase "),
|
||||
("%", " pour cent "),
|
||||
("#", " dièse "),
|
||||
("$", " dollar "),
|
||||
("£", " livre "),
|
||||
("°", " degrés "),
|
||||
]
|
||||
],
|
||||
"de": [
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " und "),
|
||||
("@", " at "),
|
||||
("%", " prozent "),
|
||||
("#", " raute "),
|
||||
("$", " dollar "),
|
||||
("£", " pfund "),
|
||||
("°", " grad "),
|
||||
]
|
||||
],
|
||||
"pt": [
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " e "),
|
||||
("@", " arroba "),
|
||||
("%", " por cento "),
|
||||
("#", " cardinal "),
|
||||
("$", " dólar "),
|
||||
("£", " libra "),
|
||||
("°", " graus "),
|
||||
]
|
||||
],
|
||||
"it": [
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " e "),
|
||||
("@", " chiocciola "),
|
||||
("%", " per cento "),
|
||||
("#", " cancelletto "),
|
||||
("$", " dollaro "),
|
||||
("£", " sterlina "),
|
||||
("°", " gradi "),
|
||||
]
|
||||
],
|
||||
"pl": [
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " i "),
|
||||
("@", " małpa "),
|
||||
("%", " procent "),
|
||||
("#", " krzyżyk "),
|
||||
("$", " dolar "),
|
||||
("£", " funt "),
|
||||
("°", " stopnie "),
|
||||
]
|
||||
],
|
||||
"ar": [
|
||||
# Arabic
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " و "),
|
||||
("@", " على "),
|
||||
("%", " في المئة "),
|
||||
("#", " رقم "),
|
||||
("$", " دولار "),
|
||||
("£", " جنيه "),
|
||||
("°", " درجة "),
|
||||
]
|
||||
],
|
||||
"zh": [
|
||||
# Chinese
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " 和 "),
|
||||
("@", " 在 "),
|
||||
("%", " 百分之 "),
|
||||
("#", " 号 "),
|
||||
("$", " 美元 "),
|
||||
("£", " 英镑 "),
|
||||
("°", " 度 "),
|
||||
]
|
||||
],
|
||||
"cs": [
|
||||
# Czech
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " a "),
|
||||
("@", " na "),
|
||||
("%", " procento "),
|
||||
("#", " křížek "),
|
||||
("$", " dolar "),
|
||||
("£", " libra "),
|
||||
("°", " stupně "),
|
||||
]
|
||||
],
|
||||
"ru": [
|
||||
# Russian
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " и "),
|
||||
("@", " собака "),
|
||||
("%", " процентов "),
|
||||
("#", " номер "),
|
||||
("$", " доллар "),
|
||||
("£", " фунт "),
|
||||
("°", " градус "),
|
||||
]
|
||||
],
|
||||
"nl": [
|
||||
# Dutch
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " en "),
|
||||
("@", " bij "),
|
||||
("%", " procent "),
|
||||
("#", " hekje "),
|
||||
("$", " dollar "),
|
||||
("£", " pond "),
|
||||
("°", " graden "),
|
||||
]
|
||||
],
|
||||
"tr": [
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " ve "),
|
||||
("@", " at "),
|
||||
("%", " yüzde "),
|
||||
("#", " diyez "),
|
||||
("$", " dolar "),
|
||||
("£", " sterlin "),
|
||||
("°", " derece "),
|
||||
]
|
||||
],
|
||||
"hu": [
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " és "),
|
||||
("@", " kukac "),
|
||||
("%", " százalék "),
|
||||
("#", " kettőskereszt "),
|
||||
("$", " dollár "),
|
||||
("£", " font "),
|
||||
("°", " fok "),
|
||||
]
|
||||
],
|
||||
"ko": [
|
||||
# Korean
|
||||
(re.compile(r"%s" % re.escape(x[0]), re.IGNORECASE), x[1])
|
||||
for x in [
|
||||
("&", " 그리고 "),
|
||||
("@", " 에 "),
|
||||
("%", " 퍼센트 "),
|
||||
("#", " 번호 "),
|
||||
("$", " 달러 "),
|
||||
("£", " 파운드 "),
|
||||
("°", " 도 "),
|
||||
]
|
||||
],
|
||||
"ja": [
|
||||
(re.compile(r"%s" % re.escape(x[0])), x[1])
|
||||
for x in [
|
||||
("&", " と "),
|
||||
("@", " アットマーク "),
|
||||
("%", " パーセント "),
|
||||
("#", " ナンバー "),
|
||||
("$", " ドル "),
|
||||
("£", " ポンド "),
|
||||
("°", " 度"),
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def expand_symbols_multilingual(text, lang="en"):
|
||||
for regex, replacement in _symbols_multilingual[lang]:
|
||||
text = re.sub(regex, replacement, text)
|
||||
text = text.replace(" ", " ") # Ensure there are no double spaces
|
||||
return text.strip()
|
||||
|
||||
|
||||
_ordinal_re = {
|
||||
"en": re.compile(r"([0-9]+)(st|nd|rd|th)"),
|
||||
"es": re.compile(r"([0-9]+)(º|ª|er|o|a|os|as)"),
|
||||
"fr": re.compile(r"([0-9]+)(º|ª|er|re|e|ème)"),
|
||||
"de": re.compile(r"([0-9]+)(st|nd|rd|th|º|ª|\.(?=\s|$))"),
|
||||
"pt": re.compile(r"([0-9]+)(º|ª|o|a|os|as)"),
|
||||
"it": re.compile(r"([0-9]+)(º|°|ª|o|a|i|e)"),
|
||||
"pl": re.compile(r"([0-9]+)(º|ª|st|nd|rd|th)"),
|
||||
"ar": re.compile(r"([0-9]+)(ون|ين|ث|ر|ى)"),
|
||||
"cs": re.compile(r"([0-9]+)\.(?=\s|$)"), # In Czech, a dot is often used after the number to indicate ordinals.
|
||||
"ru": re.compile(r"([0-9]+)(-й|-я|-е|-ое|-ье|-го)"),
|
||||
"nl": re.compile(r"([0-9]+)(de|ste|e)"),
|
||||
"tr": re.compile(r"([0-9]+)(\.|inci|nci|uncu|üncü|\.)"),
|
||||
"hu": re.compile(r"([0-9]+)(\.|adik|edik|odik|edik|ödik|ödike|ik)"),
|
||||
"ko": re.compile(r"([0-9]+)(번째|번|차|째)"),
|
||||
"ja": re.compile(r"([0-9]+)(番|回|つ|目|等|位)")
|
||||
}
|
||||
_number_re = re.compile(r"[0-9]+")
|
||||
_currency_re = {
|
||||
"USD": re.compile(r"((\$[0-9\.\,]*[0-9]+)|([0-9\.\,]*[0-9]+\$))"),
|
||||
"GBP": re.compile(r"((£[0-9\.\,]*[0-9]+)|([0-9\.\,]*[0-9]+£))"),
|
||||
"EUR": re.compile(r"(([0-9\.\,]*[0-9]+€)|((€[0-9\.\,]*[0-9]+)))"),
|
||||
}
|
||||
|
||||
_comma_number_re = re.compile(r"\b\d{1,3}(,\d{3})*(\.\d+)?\b")
|
||||
_dot_number_re = re.compile(r"\b\d{1,3}(.\d{3})*(\,\d+)?\b")
|
||||
_decimal_number_re = re.compile(r"([0-9]+[.,][0-9]+)")
|
||||
|
||||
|
||||
def _remove_commas(m):
|
||||
text = m.group(0)
|
||||
if "," in text:
|
||||
text = text.replace(",", "")
|
||||
return text
|
||||
|
||||
|
||||
def _remove_dots(m):
|
||||
text = m.group(0)
|
||||
if "." in text:
|
||||
text = text.replace(".", "")
|
||||
return text
|
||||
|
||||
|
||||
def _expand_decimal_point(m, lang="en"):
|
||||
amount = m.group(1).replace(",", ".")
|
||||
return num2words(float(amount), lang=lang if lang != "cs" else "cz")
|
||||
|
||||
|
||||
def _expand_currency(m, lang="en", currency="USD"):
|
||||
amount = float((re.sub(r"[^\d.]", "", m.group(0).replace(",", "."))))
|
||||
full_amount = num2words(amount, to="currency", currency=currency, lang=lang if lang != "cs" else "cz")
|
||||
|
||||
and_equivalents = {
|
||||
"en": ", ",
|
||||
"es": " con ",
|
||||
"fr": " et ",
|
||||
"de": " und ",
|
||||
"pt": " e ",
|
||||
"it": " e ",
|
||||
"pl": ", ",
|
||||
"cs": ", ",
|
||||
"ru": ", ",
|
||||
"nl": ", ",
|
||||
"ar": ", ",
|
||||
"tr": ", ",
|
||||
"hu": ", ",
|
||||
"ko": ", ",
|
||||
}
|
||||
|
||||
if amount.is_integer():
|
||||
last_and = full_amount.rfind(and_equivalents[lang])
|
||||
if last_and != -1:
|
||||
full_amount = full_amount[:last_and]
|
||||
|
||||
return full_amount
|
||||
|
||||
|
||||
def _expand_ordinal(m, lang="en"):
|
||||
return num2words(int(m.group(1)), ordinal=True, lang=lang if lang != "cs" else "cz")
|
||||
|
||||
|
||||
def _expand_number(m, lang="en"):
|
||||
return num2words(int(m.group(0)), lang=lang if lang != "cs" else "cz")
|
||||
|
||||
|
||||
def expand_numbers_multilingual(text, lang="en"):
|
||||
if lang == "zh":
|
||||
text = zh_num2words()(text)
|
||||
else:
|
||||
if lang in ["en", "ru"]:
|
||||
text = re.sub(_comma_number_re, _remove_commas, text)
|
||||
else:
|
||||
text = re.sub(_dot_number_re, _remove_dots, text)
|
||||
try:
|
||||
text = re.sub(_currency_re["GBP"], lambda m: _expand_currency(m, lang, "GBP"), text)
|
||||
text = re.sub(_currency_re["USD"], lambda m: _expand_currency(m, lang, "USD"), text)
|
||||
text = re.sub(_currency_re["EUR"], lambda m: _expand_currency(m, lang, "EUR"), text)
|
||||
except:
|
||||
pass
|
||||
if lang != "tr":
|
||||
text = re.sub(_decimal_number_re, lambda m: _expand_decimal_point(m, lang), text)
|
||||
text = re.sub(_ordinal_re[lang], lambda m: _expand_ordinal(m, lang), text)
|
||||
text = re.sub(_number_re, lambda m: _expand_number(m, lang), text)
|
||||
return text
|
||||
|
||||
|
||||
def lowercase(text):
|
||||
return text.lower()
|
||||
|
||||
|
||||
def collapse_whitespace(text):
|
||||
return re.sub(_whitespace_re, " ", text)
|
||||
|
||||
|
||||
def multilingual_cleaners(text, lang):
|
||||
text = text.replace('"', "")
|
||||
if lang == "tr":
|
||||
text = text.replace("İ", "i")
|
||||
text = text.replace("Ö", "ö")
|
||||
text = text.replace("Ü", "ü")
|
||||
text = lowercase(text)
|
||||
text = expand_numbers_multilingual(text, lang)
|
||||
text = expand_abbreviations_multilingual(text, lang)
|
||||
text = expand_symbols_multilingual(text, lang=lang)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def basic_cleaners(text):
|
||||
"""Basic pipeline that lowercases and collapses whitespace without transliteration."""
|
||||
text = lowercase(text)
|
||||
text = collapse_whitespace(text)
|
||||
return text
|
||||
|
||||
|
||||
def chinese_transliterate(text):
|
||||
return "".join(
|
||||
[p[0] for p in pypinyin.pinyin(text, style=pypinyin.Style.TONE3, heteronym=False, neutral_tone_with_five=True)]
|
||||
)
|
||||
|
||||
|
||||
def japanese_cleaners(text, katsu):
|
||||
text = katsu.romaji(text)
|
||||
text = lowercase(text)
|
||||
return text
|
||||
|
||||
|
||||
def korean_transliterate(text):
|
||||
r = Transliter(academic)
|
||||
return r.translit(text)
|
||||
|
||||
|
||||
DEFAULT_VOCAB_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/tokenizer.json")
|
||||
|
||||
|
||||
class VoiceBpeTokenizer:
|
||||
def __init__(self, vocab_file=None):
|
||||
self.tokenizer = None
|
||||
if vocab_file is not None:
|
||||
self.tokenizer = Tokenizer.from_file(vocab_file)
|
||||
self.char_limits = {
|
||||
"en": 250,
|
||||
"de": 253,
|
||||
"fr": 273,
|
||||
"es": 239,
|
||||
"it": 213,
|
||||
"pt": 203,
|
||||
"pl": 224,
|
||||
"zh": 82,
|
||||
"ar": 166,
|
||||
"cs": 186,
|
||||
"ru": 182,
|
||||
"nl": 251,
|
||||
"tr": 226,
|
||||
"ja": 71,
|
||||
"hu": 224,
|
||||
"ko": 95,
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def katsu(self):
|
||||
import cutlet
|
||||
|
||||
return cutlet.Cutlet()
|
||||
|
||||
def check_input_length(self, txt, lang):
|
||||
lang = lang.split("-")[0] # remove the region
|
||||
limit = self.char_limits.get(lang, 250)
|
||||
if len(txt) > limit:
|
||||
print(
|
||||
f"[!] Warning: The text length exceeds the character limit of {limit} for language '{lang}', this might cause truncated audio."
|
||||
)
|
||||
|
||||
def preprocess_text(self, txt, lang):
|
||||
if lang in {"ar", "cs", "de", "en", "es", "fr", "hu", "it", "nl", "pl", "pt", "ru", "tr", "zh", "ko"}:
|
||||
txt = multilingual_cleaners(txt, lang)
|
||||
if lang == "zh":
|
||||
txt = chinese_transliterate(txt)
|
||||
if lang == "ko":
|
||||
txt = korean_transliterate(txt)
|
||||
elif lang == "ja":
|
||||
txt = japanese_cleaners(txt, self.katsu)
|
||||
elif lang == "hi":
|
||||
# @manmay will implement this
|
||||
txt = basic_cleaners(txt)
|
||||
else:
|
||||
raise NotImplementedError(f"Language '{lang}' is not supported.")
|
||||
return txt
|
||||
|
||||
def encode(self, txt, lang):
|
||||
lang = lang.split("-")[0] # remove the region
|
||||
self.check_input_length(txt, lang)
|
||||
txt = self.preprocess_text(txt, lang)
|
||||
lang = "zh-cn" if lang == "zh" else lang
|
||||
txt = f"[{lang}]{txt}"
|
||||
txt = txt.replace(" ", "[SPACE]")
|
||||
return self.tokenizer.encode(txt).ids
|
||||
|
||||
def decode(self, seq):
|
||||
if isinstance(seq, torch.Tensor):
|
||||
seq = seq.cpu().numpy()
|
||||
txt = self.tokenizer.decode(seq, skip_special_tokens=False).replace(" ", "")
|
||||
txt = txt.replace("[SPACE]", " ")
|
||||
txt = txt.replace("[STOP]", "")
|
||||
txt = txt.replace("[UNK]", "")
|
||||
return txt
|
||||
|
||||
def __len__(self):
|
||||
return self.tokenizer.get_vocab_size()
|
||||
|
||||
def get_number_tokens(self):
|
||||
return max(self.tokenizer.get_vocab().values()) + 1
|
||||
|
||||
|
||||
def test_expand_numbers_multilingual():
|
||||
test_cases = [
|
||||
# English
|
||||
("In 12.5 seconds.", "In twelve point five seconds.", "en"),
|
||||
("There were 50 soldiers.", "There were fifty soldiers.", "en"),
|
||||
("This is a 1st test", "This is a first test", "en"),
|
||||
("That will be $20 sir.", "That will be twenty dollars sir.", "en"),
|
||||
("That will be 20€ sir.", "That will be twenty euro sir.", "en"),
|
||||
("That will be 20.15€ sir.", "That will be twenty euro, fifteen cents sir.", "en"),
|
||||
("That's 100,000.5.", "That's one hundred thousand point five.", "en"),
|
||||
# French
|
||||
("En 12,5 secondes.", "En douze virgule cinq secondes.", "fr"),
|
||||
("Il y avait 50 soldats.", "Il y avait cinquante soldats.", "fr"),
|
||||
("Ceci est un 1er test", "Ceci est un premier test", "fr"),
|
||||
("Cela vous fera $20 monsieur.", "Cela vous fera vingt dollars monsieur.", "fr"),
|
||||
("Cela vous fera 20€ monsieur.", "Cela vous fera vingt euros monsieur.", "fr"),
|
||||
("Cela vous fera 20,15€ monsieur.", "Cela vous fera vingt euros et quinze centimes monsieur.", "fr"),
|
||||
("Ce sera 100.000,5.", "Ce sera cent mille virgule cinq.", "fr"),
|
||||
# German
|
||||
("In 12,5 Sekunden.", "In zwölf Komma fünf Sekunden.", "de"),
|
||||
("Es gab 50 Soldaten.", "Es gab fünfzig Soldaten.", "de"),
|
||||
("Dies ist ein 1. Test", "Dies ist ein erste Test", "de"), # Issue with gender
|
||||
("Das macht $20 Herr.", "Das macht zwanzig Dollar Herr.", "de"),
|
||||
("Das macht 20€ Herr.", "Das macht zwanzig Euro Herr.", "de"),
|
||||
("Das macht 20,15€ Herr.", "Das macht zwanzig Euro und fünfzehn Cent Herr.", "de"),
|
||||
# Spanish
|
||||
("En 12,5 segundos.", "En doce punto cinco segundos.", "es"),
|
||||
("Había 50 soldados.", "Había cincuenta soldados.", "es"),
|
||||
("Este es un 1er test", "Este es un primero test", "es"),
|
||||
("Eso le costará $20 señor.", "Eso le costará veinte dólares señor.", "es"),
|
||||
("Eso le costará 20€ señor.", "Eso le costará veinte euros señor.", "es"),
|
||||
("Eso le costará 20,15€ señor.", "Eso le costará veinte euros con quince céntimos señor.", "es"),
|
||||
# Italian
|
||||
("In 12,5 secondi.", "In dodici virgola cinque secondi.", "it"),
|
||||
("C'erano 50 soldati.", "C'erano cinquanta soldati.", "it"),
|
||||
("Questo è un 1° test", "Questo è un primo test", "it"),
|
||||
("Ti costerà $20 signore.", "Ti costerà venti dollari signore.", "it"),
|
||||
("Ti costerà 20€ signore.", "Ti costerà venti euro signore.", "it"),
|
||||
("Ti costerà 20,15€ signore.", "Ti costerà venti euro e quindici centesimi signore.", "it"),
|
||||
# Portuguese
|
||||
("Em 12,5 segundos.", "Em doze vírgula cinco segundos.", "pt"),
|
||||
("Havia 50 soldados.", "Havia cinquenta soldados.", "pt"),
|
||||
("Este é um 1º teste", "Este é um primeiro teste", "pt"),
|
||||
("Isso custará $20 senhor.", "Isso custará vinte dólares senhor.", "pt"),
|
||||
("Isso custará 20€ senhor.", "Isso custará vinte euros senhor.", "pt"),
|
||||
(
|
||||
"Isso custará 20,15€ senhor.",
|
||||
"Isso custará vinte euros e quinze cêntimos senhor.",
|
||||
"pt",
|
||||
), # "cêntimos" should be "centavos" num2words issue
|
||||
# Polish
|
||||
("W 12,5 sekundy.", "W dwanaście przecinek pięć sekundy.", "pl"),
|
||||
("Było 50 żołnierzy.", "Było pięćdziesiąt żołnierzy.", "pl"),
|
||||
("To będzie kosztować 20€ panie.", "To będzie kosztować dwadzieścia euro panie.", "pl"),
|
||||
("To będzie kosztować 20,15€ panie.", "To będzie kosztować dwadzieścia euro, piętnaście centów panie.", "pl"),
|
||||
# Arabic
|
||||
("في الـ 12,5 ثانية.", "في الـ اثنا عشر , خمسون ثانية.", "ar"),
|
||||
("كان هناك 50 جنديًا.", "كان هناك خمسون جنديًا.", "ar"),
|
||||
# ("ستكون النتيجة $20 يا سيد.", 'ستكون النتيجة عشرون دولار يا سيد.', 'ar'), # $ and € are mising from num2words
|
||||
# ("ستكون النتيجة 20€ يا سيد.", 'ستكون النتيجة عشرون يورو يا سيد.', 'ar'),
|
||||
# Czech
|
||||
("Za 12,5 vteřiny.", "Za dvanáct celá pět vteřiny.", "cs"),
|
||||
("Bylo tam 50 vojáků.", "Bylo tam padesát vojáků.", "cs"),
|
||||
("To bude stát 20€ pane.", "To bude stát dvacet euro pane.", "cs"),
|
||||
("To bude 20.15€ pane.", "To bude dvacet euro, patnáct centů pane.", "cs"),
|
||||
# Russian
|
||||
("Через 12.5 секунды.", "Через двенадцать запятая пять секунды.", "ru"),
|
||||
("Там было 50 солдат.", "Там было пятьдесят солдат.", "ru"),
|
||||
("Это будет 20.15€ сэр.", "Это будет двадцать евро, пятнадцать центов сэр.", "ru"),
|
||||
("Это будет стоить 20€ господин.", "Это будет стоить двадцать евро господин.", "ru"),
|
||||
# Dutch
|
||||
("In 12,5 seconden.", "In twaalf komma vijf seconden.", "nl"),
|
||||
("Er waren 50 soldaten.", "Er waren vijftig soldaten.", "nl"),
|
||||
("Dat wordt dan $20 meneer.", "Dat wordt dan twintig dollar meneer.", "nl"),
|
||||
("Dat wordt dan 20€ meneer.", "Dat wordt dan twintig euro meneer.", "nl"),
|
||||
# Chinese (Simplified)
|
||||
("在12.5秒内", "在十二点五秒内", "zh"),
|
||||
("有50名士兵", "有五十名士兵", "zh"),
|
||||
# ("那将是$20先生", '那将是二十美元先生', 'zh'), currency doesn't work
|
||||
# ("那将是20€先生", '那将是二十欧元先生', 'zh'),
|
||||
# Turkish
|
||||
# ("12,5 saniye içinde.", 'On iki virgül beş saniye içinde.', 'tr'), # decimal doesn't work for TR
|
||||
("50 asker vardı.", "elli asker vardı.", "tr"),
|
||||
("Bu 1. test", "Bu birinci test", "tr"),
|
||||
# ("Bu 100.000,5.", 'Bu yüz bin virgül beş.', 'tr'),
|
||||
# Hungarian
|
||||
("12,5 másodperc alatt.", "tizenkettő egész öt tized másodperc alatt.", "hu"),
|
||||
("50 katona volt.", "ötven katona volt.", "hu"),
|
||||
("Ez az 1. teszt", "Ez az első teszt", "hu"),
|
||||
# Korean
|
||||
("12.5 초 안에.", "십이 점 다섯 초 안에.", "ko"),
|
||||
("50 명의 병사가 있었다.", "오십 명의 병사가 있었다.", "ko"),
|
||||
("이것은 1 번째 테스트입니다", "이것은 첫 번째 테스트입니다", "ko"),
|
||||
]
|
||||
for a, b, lang in test_cases:
|
||||
out = expand_numbers_multilingual(a, lang=lang)
|
||||
assert out == b, f"'{out}' vs '{b}'"
|
||||
|
||||
|
||||
def test_abbreviations_multilingual():
|
||||
test_cases = [
|
||||
# English
|
||||
("Hello Mr. Smith.", "Hello mister Smith.", "en"),
|
||||
("Dr. Jones is here.", "doctor Jones is here.", "en"),
|
||||
# Spanish
|
||||
("Hola Sr. Garcia.", "Hola señor Garcia.", "es"),
|
||||
("La Dra. Martinez es muy buena.", "La doctora Martinez es muy buena.", "es"),
|
||||
# French
|
||||
("Bonjour Mr. Dupond.", "Bonjour monsieur Dupond.", "fr"),
|
||||
("Mme. Moreau est absente aujourd'hui.", "madame Moreau est absente aujourd'hui.", "fr"),
|
||||
# German
|
||||
("Frau Dr. Müller ist sehr klug.", "Frau doktor Müller ist sehr klug.", "de"),
|
||||
# Portuguese
|
||||
("Olá Sr. Silva.", "Olá senhor Silva.", "pt"),
|
||||
("Dra. Costa, você está disponível?", "doutora Costa, você está disponível?", "pt"),
|
||||
# Italian
|
||||
("Buongiorno, Sig. Rossi.", "Buongiorno, signore Rossi.", "it"),
|
||||
# ("Sig.ra Bianchi, posso aiutarti?", 'signora Bianchi, posso aiutarti?', 'it'), # Issue with matching that pattern
|
||||
# Polish
|
||||
("Dzień dobry, P. Kowalski.", "Dzień dobry, pani Kowalski.", "pl"),
|
||||
("M. Nowak, czy mogę zadać pytanie?", "pan Nowak, czy mogę zadać pytanie?", "pl"),
|
||||
# Czech
|
||||
("P. Novák", "pan Novák", "cs"),
|
||||
("Dr. Vojtěch", "doktor Vojtěch", "cs"),
|
||||
# Dutch
|
||||
("Dhr. Jansen", "de heer Jansen", "nl"),
|
||||
("Mevr. de Vries", "mevrouw de Vries", "nl"),
|
||||
# Russian
|
||||
("Здравствуйте Г-н Иванов.", "Здравствуйте господин Иванов.", "ru"),
|
||||
("Д-р Смирнов здесь, чтобы увидеть вас.", "доктор Смирнов здесь, чтобы увидеть вас.", "ru"),
|
||||
# Turkish
|
||||
("Merhaba B. Yılmaz.", "Merhaba bay Yılmaz.", "tr"),
|
||||
("Dr. Ayşe burada.", "doktor Ayşe burada.", "tr"),
|
||||
# Hungarian
|
||||
("Dr. Szabó itt van.", "doktor Szabó itt van.", "hu"),
|
||||
]
|
||||
|
||||
for a, b, lang in test_cases:
|
||||
out = expand_abbreviations_multilingual(a, lang=lang)
|
||||
assert out == b, f"'{out}' vs '{b}'"
|
||||
|
||||
|
||||
def test_symbols_multilingual():
|
||||
test_cases = [
|
||||
("I have 14% battery", "I have 14 percent battery", "en"),
|
||||
("Te veo @ la fiesta", "Te veo arroba la fiesta", "es"),
|
||||
("J'ai 14° de fièvre", "J'ai 14 degrés de fièvre", "fr"),
|
||||
("Die Rechnung beträgt £ 20", "Die Rechnung beträgt pfund 20", "de"),
|
||||
("O meu email é ana&joao@gmail.com", "O meu email é ana e joao arroba gmail.com", "pt"),
|
||||
("linguaggio di programmazione C#", "linguaggio di programmazione C cancelletto", "it"),
|
||||
("Moja temperatura to 36.6°", "Moja temperatura to 36.6 stopnie", "pl"),
|
||||
("Mám 14% baterie", "Mám 14 procento baterie", "cs"),
|
||||
("Těším se na tebe @ party", "Těším se na tebe na party", "cs"),
|
||||
("У меня 14% заряда", "У меня 14 процентов заряда", "ru"),
|
||||
("Я буду @ дома", "Я буду собака дома", "ru"),
|
||||
("Ik heb 14% batterij", "Ik heb 14 procent batterij", "nl"),
|
||||
("Ik zie je @ het feest", "Ik zie je bij het feest", "nl"),
|
||||
("لدي 14% في البطارية", "لدي 14 في المئة في البطارية", "ar"),
|
||||
("我的电量为 14%", "我的电量为 14 百分之", "zh"),
|
||||
("Pilim %14 dolu.", "Pilim yüzde 14 dolu.", "tr"),
|
||||
("Az akkumulátorom töltöttsége 14%", "Az akkumulátorom töltöttsége 14 százalék", "hu"),
|
||||
("배터리 잔량이 14%입니다.", "배터리 잔량이 14 퍼센트입니다.", "ko"),
|
||||
]
|
||||
|
||||
for a, b, lang in test_cases:
|
||||
out = expand_symbols_multilingual(a, lang=lang)
|
||||
assert out == b, f"'{out}' vs '{b}'"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_expand_numbers_multilingual()
|
||||
test_abbreviations_multilingual()
|
||||
test_symbols_multilingual()
|
||||
@@ -0,0 +1,76 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import gradio as gr
|
||||
|
||||
# Define directories
|
||||
this_dir = Path(__file__).parent.resolve()
|
||||
main_dir = this_dir.parent.parent.resolve()
|
||||
|
||||
# Folder paths
|
||||
folders = {
|
||||
"models": main_dir / "models",
|
||||
"finetune": main_dir / "finetune",
|
||||
"voices": main_dir / "voices",
|
||||
"outputs": main_dir / "outputs",
|
||||
"alltalk_environment": main_dir / "alltalk_environment"
|
||||
}
|
||||
|
||||
def get_folder_size_scandir(folder_path):
|
||||
total_size = 0
|
||||
for entry in os.scandir(folder_path):
|
||||
if entry.is_file(follow_symlinks=False):
|
||||
try:
|
||||
total_size += entry.stat(follow_symlinks=False).st_size
|
||||
except OSError as e:
|
||||
print(f"Error getting size for file {entry.path}: {e}")
|
||||
elif entry.is_dir(follow_symlinks=False):
|
||||
try:
|
||||
total_size += get_folder_size_scandir(entry.path)
|
||||
except OSError as e:
|
||||
print(f"Error accessing directory {entry.path}: {e}")
|
||||
return total_size
|
||||
|
||||
def format_size(size):
|
||||
# Convert size to a readable format
|
||||
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
||||
if size < 1024:
|
||||
return f"{size:.2f} {unit}"
|
||||
size /= 1024
|
||||
|
||||
def calculate_disk_space():
|
||||
sizes = {}
|
||||
for name, path in folders.items():
|
||||
if path.exists():
|
||||
folder_size = get_folder_size_scandir(path)
|
||||
# print(f"Folder: {name}, Total Size: {folder_size} bytes")
|
||||
sizes[name] = format_size(folder_size)
|
||||
else:
|
||||
sizes[name] = "Not present"
|
||||
|
||||
return sizes['models'], sizes['finetune'], sizes['voices'], sizes['outputs'], sizes['alltalk_environment']
|
||||
|
||||
def disk_space_page():
|
||||
# Define the layout
|
||||
with gr.Tab("Disk Space Usage"):
|
||||
with gr.Row():
|
||||
models_space = gr.Textbox(label="Models Folder", interactive=False)
|
||||
finetune_space = gr.Textbox(label="Finetune Folder", interactive=False)
|
||||
voices_space = gr.Textbox(label="Voices Folder", interactive=False)
|
||||
outputs_space = gr.Textbox(label="Outputs Folder", interactive=False)
|
||||
alltalk_env_space = gr.Textbox(label="Alltalk Python Environment", interactive=False)
|
||||
|
||||
calculate_button = gr.Button("Calculate Disk Space Usage")
|
||||
|
||||
# Define actions for buttons
|
||||
calculate_button.click(calculate_disk_space, outputs=[models_space, finetune_space, voices_space, outputs_space, alltalk_env_space])
|
||||
|
||||
def get_disk_interface():
|
||||
return disk_space_page
|
||||
|
||||
# Example usage in the main script
|
||||
if __name__ == "__main__":
|
||||
app = gr.Blocks()
|
||||
with app:
|
||||
disk_space_page()
|
||||
|
||||
app.launch()
|
||||
@@ -0,0 +1,114 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import importlib
|
||||
|
||||
# Hard-coded paths
|
||||
this_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
||||
config_file = os.path.join(this_folder, "confignew.json")
|
||||
gradio_pages_folder = os.path.join(this_folder, "system", "gradio_pages", "themes")
|
||||
theme_list_file = os.path.join(gradio_pages_folder, "theme_list.json")
|
||||
themes_file_path = os.path.join(gradio_pages_folder, "themes.py")
|
||||
|
||||
# Add gradio_pages folder to the system path
|
||||
sys.path.append(gradio_pages_folder)
|
||||
|
||||
def get_class(filename):
|
||||
with open(filename, "r", encoding="utf8") as file:
|
||||
for line in file:
|
||||
if "class " in line:
|
||||
return line.split("class ")[1].split(":")[0].split("(")[0].strip()
|
||||
return None
|
||||
|
||||
|
||||
def get_list():
|
||||
themes_from_files = [
|
||||
os.path.splitext(name)[0]
|
||||
for name in os.listdir(gradio_pages_folder)
|
||||
if name.endswith(".py")
|
||||
]
|
||||
try:
|
||||
with open(theme_list_file, "r", encoding="utf8") as json_file:
|
||||
themes_from_url = [item["id"] for item in json.load(json_file)]
|
||||
except FileNotFoundError:
|
||||
themes_from_url = []
|
||||
|
||||
combined_themes = sorted(set(themes_from_files + themes_from_url))
|
||||
return list(combined_themes)
|
||||
|
||||
|
||||
def select_theme(name):
|
||||
selected_file = name + ".py"
|
||||
full_path = os.path.join(gradio_pages_folder, selected_file)
|
||||
|
||||
try:
|
||||
with open(config_file, "r", encoding="utf8") as json_file:
|
||||
config_data = json.load(json_file)
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
# If the theme is from theme_list.json, update only the class field
|
||||
config_data["theme"]["file"] = None
|
||||
config_data["theme"]["class"] = name
|
||||
message = f"Theme {name} successfully selected, restart AllTalk."
|
||||
else:
|
||||
# If the theme file exists locally, update both file and class fields
|
||||
class_found = get_class(full_path)
|
||||
if class_found:
|
||||
config_data["theme"]["file"] = selected_file
|
||||
config_data["theme"]["class"] = class_found
|
||||
message = f"Theme {name} successfully selected, restart AllTalk."
|
||||
else:
|
||||
message = f"Theme {name} was not found."
|
||||
|
||||
with open(config_file, "w", encoding="utf8") as json_file:
|
||||
json.dump(config_data, json_file, indent=2)
|
||||
|
||||
print(message)
|
||||
return message
|
||||
|
||||
except Exception as e:
|
||||
print(f"[AllTalk TTS] Error selecting theme {name}: {str(e)}")
|
||||
return message
|
||||
|
||||
def read_json():
|
||||
try:
|
||||
with open(config_file, "r", encoding="utf8") as json_file:
|
||||
data = json.load(json_file)
|
||||
selected_file = data["theme"]["file"]
|
||||
class_name = data["theme"]["class"]
|
||||
|
||||
if selected_file and class_name:
|
||||
return class_name
|
||||
elif not selected_file and class_name:
|
||||
return class_name
|
||||
else:
|
||||
return "gradio/base"
|
||||
except Exception as e:
|
||||
print(f"[AllTalk TTS] Error reading config.json: {e}")
|
||||
return "gradio/base"
|
||||
|
||||
|
||||
def load_json():
|
||||
try:
|
||||
with open(config_file, "r", encoding="utf8") as json_file:
|
||||
data = json.load(json_file)
|
||||
selected_file = data["theme"]["file"]
|
||||
class_name = data["theme"]["class"]
|
||||
|
||||
if selected_file and class_name:
|
||||
module_name = os.path.splitext(selected_file)[0]
|
||||
module_spec = importlib.util.spec_from_file_location(module_name, themes_file_path)
|
||||
module = importlib.util.module_from_spec(module_spec)
|
||||
module_spec.loader.exec_module(module)
|
||||
obtained_class = getattr(module, class_name)
|
||||
instance = obtained_class()
|
||||
print(f"Theme Loaded: {class_name}")
|
||||
return instance
|
||||
elif not selected_file and class_name:
|
||||
return class_name
|
||||
else:
|
||||
print("The theme is incorrect.")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error Loading: {str(e)}")
|
||||
return None
|
||||
@@ -0,0 +1,81 @@
|
||||
[
|
||||
{"id": "freddyaboulton/dracula_revamped"},
|
||||
{"id": "freddyaboulton/bad-theme-space"},
|
||||
{"id": "gradio/dracula_revamped"},
|
||||
{"id": "abidlabs/dracula_revamped"},
|
||||
{"id": "gradio/dracula_test"},
|
||||
{"id": "abidlabs/dracula_test"},
|
||||
{"id": "gradio/seafoam"},
|
||||
{"id": "gradio/glass"},
|
||||
{"id": "gradio/monochrome"},
|
||||
{"id": "gradio/soft"},
|
||||
{"id": "gradio/default"},
|
||||
{"id": "gradio/base"},
|
||||
{"id": "abidlabs/pakistan"},
|
||||
{"id": "dawood/microsoft_windows"},
|
||||
{"id": "ysharma/steampunk"},
|
||||
{"id": "ysharma/huggingface"},
|
||||
{"id": "gstaff/xkcd"},
|
||||
{"id": "JohnSmith9982/small_and_pretty"},
|
||||
{"id": "abidlabs/Lime"},
|
||||
{"id": "freddyaboulton/this-theme-does-not-exist-2"},
|
||||
{"id": "aliabid94/new-theme"},
|
||||
{"id": "aliabid94/test2"},
|
||||
{"id": "aliabid94/test3"},
|
||||
{"id": "aliabid94/test4"},
|
||||
{"id": "abidlabs/banana"},
|
||||
{"id": "freddyaboulton/test-blue"},
|
||||
{"id": "gstaff/sketch"},
|
||||
{"id": "gstaff/whiteboard"},
|
||||
{"id": "ysharma/llamas"},
|
||||
{"id": "abidlabs/font-test"},
|
||||
{"id": "YenLai/Superhuman"},
|
||||
{"id": "bethecloud/storj_theme"},
|
||||
{"id": "sudeepshouche/minimalist"},
|
||||
{"id": "knotdgaf/gradiotest"},
|
||||
{"id": "ParityError/Interstellar"},
|
||||
{"id": "ParityError/Anime"},
|
||||
{"id": "Ajaxon6255/Emerald_Isle"},
|
||||
{"id": "ParityError/LimeFace"},
|
||||
{"id": "finlaymacklon/smooth_slate"},
|
||||
{"id": "finlaymacklon/boxy_violet"},
|
||||
{"id": "derekzen/stardust"},
|
||||
{"id": "EveryPizza/Cartoony-Gradio-Theme"},
|
||||
{"id": "Ifeanyi/Cyanister"},
|
||||
{"id": "Tshackelton/IBMPlex-DenseReadable"},
|
||||
{"id": "snehilsanyal/scikit-learn"},
|
||||
{"id": "Himhimhim/xkcd"},
|
||||
{"id": "shivi/calm_seafoam"},
|
||||
{"id": "nota-ai/theme"},
|
||||
{"id": "rawrsor1/Everforest"},
|
||||
{"id": "SebastianBravo/simci_css"},
|
||||
{"id": "rottenlittlecreature/Moon_Goblin"},
|
||||
{"id": "abidlabs/test-yellow"},
|
||||
{"id": "abidlabs/test-yellow3"},
|
||||
{"id": "idspicQstitho/dracula_revamped"},
|
||||
{"id": "kfahn/AnimalPose"},
|
||||
{"id": "HaleyCH/HaleyCH_Theme"},
|
||||
{"id": "simulKitke/dracula_test"},
|
||||
{"id": "braintacles/CrimsonNight"},
|
||||
{"id": "wentaohe/whiteboardv2"},
|
||||
{"id": "reilnuud/polite"},
|
||||
{"id": "remilia/Ghostly"},
|
||||
{"id": "Franklisi/darkmode"},
|
||||
{"id": "coding-alt/soft"},
|
||||
{"id": "xiaobaiyuan/theme_land"},
|
||||
{"id": "step-3-profit/Midnight-Deep"},
|
||||
{"id": "xiaobaiyuan/theme_demo"},
|
||||
{"id": "Taithrah/Minimal"},
|
||||
{"id": "Insuz/SimpleIndigo"},
|
||||
{"id": "zkunn/Alipay_Gradio_theme"},
|
||||
{"id": "Insuz/Mocha"},
|
||||
{"id": "xiaobaiyuan/theme_brief"},
|
||||
{"id": "Ama434/434-base-Barlow"},
|
||||
{"id": "Ama434/def_barlow"},
|
||||
{"id": "Ama434/neutral-barlow"},
|
||||
{"id": "dawood/dracula_test"},
|
||||
{"id": "nuttea/Softblue"},
|
||||
{"id": "BlueDancer/Alien_Diffusion"},
|
||||
{"id": "naughtondale/monochrome"},
|
||||
{"id": "Dagfinn1962/standard"}
|
||||
]
|
||||
@@ -0,0 +1,284 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Iterable
|
||||
import gradio as gr
|
||||
|
||||
# gr.themes.builder()
|
||||
from gradio.themes.base import Base
|
||||
from gradio.themes.utils import colors, fonts, sizes
|
||||
import time
|
||||
|
||||
|
||||
class alltalk(Base):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
primary_hue: colors.Color | str = colors.neutral,
|
||||
secondary_hue: colors.Color | str = colors.neutral,
|
||||
neutral_hue: colors.Color | str = colors.neutral,
|
||||
spacing_size: sizes.Size | str = sizes.spacing_md,
|
||||
radius_size: sizes.Size | str = sizes.radius_md,
|
||||
text_size: sizes.Size | str = sizes.text_lg,
|
||||
font: fonts.Font | str | Iterable[fonts.Font | str] = (
|
||||
"Syne V",
|
||||
fonts.GoogleFont("Syne"),
|
||||
"ui-sans-serif",
|
||||
"system-ui",
|
||||
),
|
||||
font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
|
||||
"ui-monospace",
|
||||
fonts.GoogleFont("Nunito Sans"),
|
||||
),
|
||||
):
|
||||
super().__init__(
|
||||
primary_hue=primary_hue,
|
||||
secondary_hue=secondary_hue,
|
||||
neutral_hue=neutral_hue,
|
||||
spacing_size=spacing_size,
|
||||
radius_size=radius_size,
|
||||
text_size=text_size,
|
||||
font=font,
|
||||
font_mono=font_mono,
|
||||
)
|
||||
self.name = ("alltalk",)
|
||||
self.secondary_100 = ("#dbeafe",)
|
||||
self.secondary_200 = ("#bfdbfe",)
|
||||
self.secondary_300 = ("#93c5fd",)
|
||||
self.secondary_400 = ("#60a5fa",)
|
||||
self.secondary_50 = ("#eff6ff",)
|
||||
self.secondary_500 = ("#3b82f6",)
|
||||
self.secondary_600 = ("#2563eb",)
|
||||
self.secondary_700 = ("#1d4ed8",)
|
||||
self.secondary_800 = ("#1e40af",)
|
||||
self.secondary_900 = ("#1e3a8a",)
|
||||
self.secondary_950 = ("#1d3660",)
|
||||
|
||||
super().set(
|
||||
# Blaise
|
||||
background_fill_primary="#110F0F",
|
||||
background_fill_primary_dark="#110F0F",
|
||||
background_fill_secondary="#110F0F",
|
||||
background_fill_secondary_dark="#110F0F",
|
||||
block_background_fill="*neutral_800",
|
||||
block_background_fill_dark="*neutral_800",
|
||||
block_border_color="*border_color_primary",
|
||||
block_border_color_dark="*border_color_primary",
|
||||
block_border_width="1px",
|
||||
block_border_width_dark="1px",
|
||||
block_info_text_color="*body_text_color_subdued",
|
||||
block_info_text_color_dark="*body_text_color_subdued",
|
||||
block_info_text_size="*text_sm",
|
||||
block_info_text_weight="400",
|
||||
block_label_background_fill="*background_fill_primary",
|
||||
block_label_background_fill_dark="*background_fill_secondary",
|
||||
block_label_border_color="*border_color_primary",
|
||||
block_label_border_color_dark="*border_color_primary",
|
||||
block_label_border_width="1px",
|
||||
block_label_border_width_dark="1px",
|
||||
block_label_margin="0",
|
||||
block_label_padding="*spacing_sm *spacing_lg",
|
||||
block_label_radius="calc(*radius_lg - 1px) 0 calc(*radius_lg - 1px) 0",
|
||||
block_label_right_radius="0 calc(*radius_lg - 1px) 0 calc(*radius_lg - 1px)",
|
||||
block_label_shadow="*block_shadow",
|
||||
block_label_text_color="*#110F0F",
|
||||
block_label_text_color_dark="*#110F0F",
|
||||
block_label_text_weight="400",
|
||||
block_padding="*spacing_xl",
|
||||
block_radius="*radius_md",
|
||||
block_shadow="none",
|
||||
block_shadow_dark="none",
|
||||
block_title_background_fill="rgb(255,255,255)",
|
||||
block_title_background_fill_dark="rgb(255,255,255)",
|
||||
block_title_border_color="none",
|
||||
block_title_border_color_dark="none",
|
||||
block_title_border_width="0px",
|
||||
block_title_padding="*block_label_padding",
|
||||
block_title_radius="*block_label_radius",
|
||||
block_title_text_color="#110F0F",
|
||||
block_title_text_color_dark="#110F0F",
|
||||
block_title_text_size="*text_md",
|
||||
block_title_text_weight="600",
|
||||
body_background_fill="#110F0F",
|
||||
body_background_fill_dark="#110F0F",
|
||||
body_text_color="white",
|
||||
body_text_color_dark="white",
|
||||
body_text_color_subdued="*neutral_400",
|
||||
body_text_color_subdued_dark="*neutral_400",
|
||||
body_text_size="*text_md",
|
||||
body_text_weight="400",
|
||||
border_color_accent="*neutral_600",
|
||||
border_color_accent_dark="*neutral_600",
|
||||
border_color_primary="*neutral_800",
|
||||
border_color_primary_dark="*neutral_800",
|
||||
button_border_width="*input_border_width",
|
||||
button_border_width_dark="*input_border_width",
|
||||
button_cancel_background_fill="*button_secondary_background_fill",
|
||||
button_cancel_background_fill_dark="*button_secondary_background_fill",
|
||||
button_cancel_background_fill_hover="*button_cancel_background_fill",
|
||||
button_cancel_background_fill_hover_dark="*button_cancel_background_fill",
|
||||
button_cancel_border_color="*button_secondary_border_color",
|
||||
button_cancel_border_color_dark="*button_secondary_border_color",
|
||||
button_cancel_border_color_hover="*button_cancel_border_color",
|
||||
button_cancel_border_color_hover_dark="*button_cancel_border_color",
|
||||
button_cancel_text_color="#110F0F",
|
||||
button_cancel_text_color_dark="#110F0F",
|
||||
button_cancel_text_color_hover="#110F0F",
|
||||
button_cancel_text_color_hover_dark="#110F0F",
|
||||
button_large_padding="*spacing_lg calc(2 * *spacing_lg)",
|
||||
button_large_radius="*radius_lg",
|
||||
button_large_text_size="*text_lg",
|
||||
button_large_text_weight="600",
|
||||
button_primary_background_fill="*primary_600",
|
||||
button_primary_background_fill_dark="*primary_600",
|
||||
button_primary_background_fill_hover="*primary_500",
|
||||
button_primary_background_fill_hover_dark="*primary_500",
|
||||
button_primary_border_color="*primary_500",
|
||||
button_primary_border_color_dark="*primary_500",
|
||||
button_primary_border_color_hover="*primary_400",
|
||||
button_primary_border_color_hover_dark="*primary_400",
|
||||
button_primary_text_color="white",
|
||||
button_primary_text_color_dark="white",
|
||||
button_primary_text_color_hover="#110F0F",
|
||||
button_primary_text_color_hover_dark="#110F0F",
|
||||
button_secondary_background_fill="transparent",
|
||||
button_secondary_background_fill_dark="transparent",
|
||||
button_secondary_background_fill_hover="*neutral_800",
|
||||
button_secondary_background_fill_hover_dark="*neutral_800",
|
||||
button_secondary_border_color="*neutral_700",
|
||||
button_secondary_border_color_dark="*neutral_700",
|
||||
button_secondary_border_color_hover="*neutral_600",
|
||||
button_secondary_border_color_hover_dark="*neutral_600",
|
||||
button_secondary_text_color="white",
|
||||
button_secondary_text_color_dark="white",
|
||||
button_secondary_text_color_hover="*button_secondary_text_color",
|
||||
button_secondary_text_color_hover_dark="*button_secondary_text_color",
|
||||
button_shadow="none",
|
||||
button_shadow_active="*shadow_inset",
|
||||
button_shadow_hover="none",
|
||||
button_small_padding="*spacing_sm calc(2 * *spacing_sm)",
|
||||
button_small_radius="*radius_lg",
|
||||
button_small_text_size="*text_md",
|
||||
button_small_text_weight="400",
|
||||
button_transition="0.3s ease all",
|
||||
checkbox_background_color="*neutral_700",
|
||||
checkbox_background_color_dark="*neutral_700",
|
||||
checkbox_background_color_focus="*checkbox_background_color",
|
||||
checkbox_background_color_focus_dark="*checkbox_background_color",
|
||||
checkbox_background_color_hover="*checkbox_background_color",
|
||||
checkbox_background_color_hover_dark="*checkbox_background_color",
|
||||
checkbox_background_color_selected="*secondary_600",
|
||||
checkbox_background_color_selected_dark="*secondary_600",
|
||||
checkbox_border_color="*neutral_700",
|
||||
checkbox_border_color_dark="*neutral_700",
|
||||
checkbox_border_color_focus="*secondary_500",
|
||||
checkbox_border_color_focus_dark="*secondary_500",
|
||||
checkbox_border_color_hover="*neutral_600",
|
||||
checkbox_border_color_hover_dark="*neutral_600",
|
||||
checkbox_border_color_selected="*secondary_600",
|
||||
checkbox_border_color_selected_dark="*secondary_600",
|
||||
checkbox_border_radius="*radius_sm",
|
||||
checkbox_border_width="*input_border_width",
|
||||
checkbox_border_width_dark="*input_border_width",
|
||||
checkbox_check="url(\"data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e\")",
|
||||
checkbox_label_background_fill="transparent",
|
||||
checkbox_label_background_fill_dark="transparent",
|
||||
checkbox_label_background_fill_hover="transparent",
|
||||
checkbox_label_background_fill_hover_dark="transparent",
|
||||
checkbox_label_background_fill_selected="transparent",
|
||||
checkbox_label_background_fill_selected_dark="transparent",
|
||||
checkbox_label_border_color="transparent",
|
||||
checkbox_label_border_color_dark="transparent",
|
||||
checkbox_label_border_color_hover="transparent",
|
||||
checkbox_label_border_color_hover_dark="transparent",
|
||||
checkbox_label_border_width="transparent",
|
||||
checkbox_label_border_width_dark="transparent",
|
||||
checkbox_label_gap="*spacing_lg",
|
||||
checkbox_label_padding="*spacing_md calc(2 * *spacing_md)",
|
||||
checkbox_label_shadow="none",
|
||||
checkbox_label_text_color="*body_text_color",
|
||||
checkbox_label_text_color_dark="*body_text_color",
|
||||
checkbox_label_text_color_selected="*checkbox_label_text_color",
|
||||
checkbox_label_text_color_selected_dark="*checkbox_label_text_color",
|
||||
checkbox_label_text_size="*text_md",
|
||||
checkbox_label_text_weight="400",
|
||||
checkbox_shadow="*input_shadow",
|
||||
color_accent="*primary_500",
|
||||
color_accent_soft="*primary_50",
|
||||
color_accent_soft_dark="*neutral_700",
|
||||
container_radius="*radius_xl",
|
||||
embed_radius="*radius_lg",
|
||||
error_background_fill="*background_fill_primary",
|
||||
error_background_fill_dark="*background_fill_primary",
|
||||
error_border_color="*border_color_primary",
|
||||
error_border_color_dark="*border_color_primary",
|
||||
error_border_width="1px",
|
||||
error_border_width_dark="1px",
|
||||
error_text_color="#ef4444",
|
||||
error_text_color_dark="#ef4444",
|
||||
form_gap_width="0px",
|
||||
input_background_fill="*neutral_900",
|
||||
input_background_fill_dark="*neutral_900",
|
||||
input_background_fill_focus="*secondary_600",
|
||||
input_background_fill_focus_dark="*secondary_600",
|
||||
input_background_fill_hover="*input_background_fill",
|
||||
input_background_fill_hover_dark="*input_background_fill",
|
||||
input_border_color="*neutral_700",
|
||||
input_border_color_dark="*neutral_700",
|
||||
input_border_color_focus="*secondary_600",
|
||||
input_border_color_focus_dark="*primary_600",
|
||||
input_border_color_hover="*input_border_color",
|
||||
input_border_color_hover_dark="*input_border_color",
|
||||
input_border_width="1px",
|
||||
input_border_width_dark="1px",
|
||||
input_padding="*spacing_xl",
|
||||
input_placeholder_color="*neutral_500",
|
||||
input_placeholder_color_dark="*neutral_500",
|
||||
input_radius="*radius_lg",
|
||||
input_shadow="none",
|
||||
input_shadow_dark="none",
|
||||
input_shadow_focus="*input_shadow",
|
||||
input_shadow_focus_dark="*input_shadow",
|
||||
input_text_size="*text_md",
|
||||
input_text_weight="400",
|
||||
layout_gap="*spacing_xxl",
|
||||
link_text_color="*secondary_500",
|
||||
link_text_color_active="*secondary_500",
|
||||
link_text_color_active_dark="*secondary_500",
|
||||
link_text_color_dark="*secondary_500",
|
||||
link_text_color_hover="*secondary_400",
|
||||
link_text_color_hover_dark="*secondary_400",
|
||||
link_text_color_visited="*secondary_600",
|
||||
link_text_color_visited_dark="*secondary_600",
|
||||
loader_color="*color_accent",
|
||||
loader_color_dark="*color_accent",
|
||||
panel_background_fill="*background_fill_secondary",
|
||||
panel_background_fill_dark="*background_fill_secondary",
|
||||
panel_border_color="*border_color_primary",
|
||||
panel_border_color_dark="*border_color_primary",
|
||||
panel_border_width="1px",
|
||||
panel_border_width_dark="1px",
|
||||
prose_header_text_weight="600",
|
||||
prose_text_size="*text_md",
|
||||
prose_text_weight="400",
|
||||
radio_circle="url(\"data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e\")",
|
||||
section_header_text_size="*text_md",
|
||||
section_header_text_weight="400",
|
||||
shadow_drop="rgba(0,0,0,0.05) 0px 1px 2px 0px",
|
||||
shadow_drop_lg="0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
|
||||
shadow_inset="rgba(0,0,0,0.05) 0px 2px 4px 0px inset",
|
||||
shadow_spread="3px",
|
||||
shadow_spread_dark="1px",
|
||||
slider_color="#9E9E9E",
|
||||
slider_color_dark="#9E9E9E",
|
||||
stat_background_fill="*primary_500",
|
||||
stat_background_fill_dark="*primary_500",
|
||||
table_border_color="*neutral_700",
|
||||
table_border_color_dark="*neutral_700",
|
||||
table_even_background_fill="*neutral_950",
|
||||
table_even_background_fill_dark="*neutral_950",
|
||||
table_odd_background_fill="*neutral_900",
|
||||
table_odd_background_fill_dark="*neutral_900",
|
||||
table_radius="*radius_lg",
|
||||
table_row_focus="*color_accent_soft",
|
||||
table_row_focus_dark="*color_accent_soft",
|
||||
)
|
||||
@@ -0,0 +1,196 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OpenAI API/AllTalk TTS API Test</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #1e1e1e;
|
||||
color: #c7c7c7;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
h1 {
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.container {
|
||||
background-color: #2b2b2b;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin: 10px 0 5px;
|
||||
color: #ffffff;
|
||||
}
|
||||
input[type="text"], input[type="number"], textarea, select {
|
||||
width: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
background-color: #3b3b3b;
|
||||
color: #ffffff;
|
||||
}
|
||||
input[type="submit"] {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="submit"]:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.config, .response {
|
||||
background-color: #3b3b3b;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.form-group > div {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.form-group > div:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>OpenAI API/AllTalk TTS API Test</h1>
|
||||
<div class="config form-group">
|
||||
<div>
|
||||
<label for="ip">IP Address:</label>
|
||||
<input type="text" id="ip" name="ip" value="localhost" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="port">Port Number:</label>
|
||||
<input type="number" id="port" name="port" value="7851" required>
|
||||
</div>
|
||||
</div>
|
||||
<form id="ttsForm">
|
||||
<label for="model">Model:</label>
|
||||
<input type="text" id="model" name="model" value="some-model" required><br>
|
||||
|
||||
<label for="input">Input Text:</label>
|
||||
<textarea id="input" name="input" rows="8" required>Hello, this is a test.</textarea><br>
|
||||
<p id="charCount">Character count: 21</p>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<label for="voice">Voice:</label>
|
||||
<select id="voice" name="voice" required>
|
||||
<option value="alloy">Alloy</option>
|
||||
<option value="echo">Echo</option>
|
||||
<option value="fable">Fable</option>
|
||||
<option value="nova">Nova</option>
|
||||
<option value="onyx">Onyx</option>
|
||||
<option value="shimmer">Shimmer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="response_format">Response Format:</label>
|
||||
<select id="response_format" name="response_format" required>
|
||||
<option value="wav">WAV</option>
|
||||
<option value="mp3">MP3</option>
|
||||
<option value="ogg">OGG</option>
|
||||
<option value="flac">FLAC</option>
|
||||
<option value="aac">AAC</option>
|
||||
<option value="m4a">M4A</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="speed">Speed:</label>
|
||||
<input type="number" id="speed" name="speed" value="1.0" step="0.1" required><br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="autoplay">Auto-play:</label>
|
||||
<input type="checkbox" id="autoplay" name="autoplay"><br><br>
|
||||
|
||||
<input type="submit" value="Submit"><br><br>
|
||||
</form>
|
||||
<div id="response" class="response"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Real-time character count
|
||||
const inputField = document.getElementById('input');
|
||||
const charCountDisplay = document.getElementById('charCount');
|
||||
|
||||
inputField.addEventListener('input', () => {
|
||||
const trimmedInput = inputField.value.trim();
|
||||
charCountDisplay.textContent = `Character count: ${trimmedInput.length}`;
|
||||
});
|
||||
|
||||
document.getElementById('ttsForm').addEventListener('submit', async function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(event.target);
|
||||
const jsonData = {};
|
||||
|
||||
// Sanitize input: trim spaces
|
||||
formData.forEach((value, key) => {
|
||||
if (key === 'input') {
|
||||
value = value.trim().replace(/\s+/g, ' '); // Trim and normalize spaces
|
||||
}
|
||||
jsonData[key] = key === 'speed' ? parseFloat(value) : value;
|
||||
});
|
||||
|
||||
const ip = document.getElementById('ip').value;
|
||||
const port = document.getElementById('port').value;
|
||||
const url = `http://${ip}:${port}/v1/audio/speech`;
|
||||
|
||||
const responseDiv = document.getElementById('response');
|
||||
responseDiv.innerHTML = 'Sending request...';
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(jsonData),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const audio = new Audio(url);
|
||||
audio.controls = true;
|
||||
if (document.getElementById('autoplay').checked) {
|
||||
audio.autoplay = true;
|
||||
}
|
||||
responseDiv.innerHTML = '';
|
||||
responseDiv.appendChild(audio);
|
||||
} else {
|
||||
const errorText = await response.text();
|
||||
responseDiv.innerHTML = 'Error: ' + errorText;
|
||||
}
|
||||
} catch (error) {
|
||||
responseDiv.innerHTML = 'Error: ' + error.message;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,51 @@
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
from OpenSSL import crypto
|
||||
import sys
|
||||
|
||||
def generate_certificate(cert_name: str, output_dir: Path, cn: str = "localhost", days: int = 365):
|
||||
k = crypto.PKey()
|
||||
k.generate_key(crypto.TYPE_RSA, 2048)
|
||||
|
||||
cert = crypto.X509()
|
||||
cert.get_subject().CN = cn
|
||||
cert.set_serial_number(1000)
|
||||
cert.gmtime_adj_notBefore(0)
|
||||
cert.gmtime_adj_notAfter(days*24*60*60)
|
||||
cert.set_issuer(cert.get_subject())
|
||||
cert.set_pubkey(k)
|
||||
cert.sign(k, 'sha256')
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(output_dir / f"{cert_name}_cert.pem", "wb") as f:
|
||||
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
with open(output_dir / f"{cert_name}_key.pem", "wb") as f:
|
||||
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k))
|
||||
|
||||
def main():
|
||||
print("Certificate Generator")
|
||||
print("-" * 20)
|
||||
|
||||
# Get default paths
|
||||
default_path = Path(__file__).parent / "certificates"
|
||||
|
||||
# Get user input with defaults
|
||||
cert_name = input(f"Certificate name (default: test_cert): ") or "test_cert"
|
||||
cn = input(f"Common Name (default: localhost): ") or "localhost"
|
||||
days = input(f"Valid days (default: 365): ") or "365"
|
||||
output_dir = input(f"Output directory (default: {default_path}): ") or str(default_path)
|
||||
|
||||
try:
|
||||
days = int(days)
|
||||
output_path = Path(output_dir)
|
||||
generate_certificate(cert_name, output_path, cn, days)
|
||||
print(f"\nSuccess! Certificate files created:")
|
||||
print(f"- {output_path / f'{cert_name}_cert.pem'}")
|
||||
print(f"- {output_path / f'{cert_name}_key.pem'}")
|
||||
except Exception as e:
|
||||
print(f"\nError: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,120 @@
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
import ssl
|
||||
import OpenSSL
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class CertificateInfo:
|
||||
subject: str
|
||||
issuer: str
|
||||
valid_from: datetime
|
||||
valid_until: datetime
|
||||
serial_number: str
|
||||
is_valid: bool
|
||||
|
||||
class CertificateManager:
|
||||
def __init__(self, proxy_manager):
|
||||
self.proxy_manager = proxy_manager
|
||||
self.logger = logging.getLogger("ProxyCertManager")
|
||||
self.certs_path = proxy_manager.certs_path
|
||||
|
||||
def validate_certificate(self, cert_path: Path) -> Optional[CertificateInfo]:
|
||||
"""Validate and get information about a certificate"""
|
||||
try:
|
||||
with open(cert_path, 'rb') as cert_file:
|
||||
cert_data = cert_file.read()
|
||||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_data)
|
||||
|
||||
valid_from = datetime.strptime(x509.get_notBefore().decode(), '%Y%m%d%H%M%SZ')
|
||||
valid_until = datetime.strptime(x509.get_notAfter().decode(), '%Y%m%d%H%M%SZ')
|
||||
now = datetime.now()
|
||||
|
||||
return CertificateInfo(
|
||||
subject=x509.get_subject().CN,
|
||||
issuer=x509.get_issuer().CN,
|
||||
valid_from=valid_from,
|
||||
valid_until=valid_until,
|
||||
serial_number=str(x509.get_serial_number()),
|
||||
is_valid=valid_from <= now <= valid_until
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Certificate validation error: {e}")
|
||||
return None
|
||||
|
||||
def install_certificate(self, cert_path: Path, key_path: Path, name: str) -> bool:
|
||||
try:
|
||||
import shutil
|
||||
# Validate certificate first
|
||||
cert_info = self.validate_certificate(cert_path)
|
||||
if not cert_info or not cert_info.is_valid:
|
||||
self.logger.error("Invalid or corrupt certificate")
|
||||
return False
|
||||
|
||||
cert_dest = self.certs_path / f"{name}_cert.pem"
|
||||
key_dest = self.certs_path / f"{name}_key.pem"
|
||||
|
||||
shutil.copy2(cert_path, cert_dest)
|
||||
shutil.copy2(key_path, key_dest)
|
||||
|
||||
# Update config
|
||||
config = self.proxy_manager.config_manager.get_instance()
|
||||
config.proxy_settings.gradio_endpoint.cert_name = name
|
||||
config.proxy_settings.api_endpoint.cert_name = name
|
||||
config.save()
|
||||
|
||||
self.logger.info(f"Certificate '{name}' installed successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Certificate installation error: {e}")
|
||||
return False
|
||||
|
||||
def remove_certificate(self, name: str) -> bool:
|
||||
"""Remove a certificate"""
|
||||
try:
|
||||
cert_path = self.certs_path / f"{name}_cert.pem"
|
||||
key_path = self.certs_path / f"{name}_key.pem"
|
||||
|
||||
if cert_path.exists():
|
||||
cert_path.unlink()
|
||||
if key_path.exists():
|
||||
key_path.unlink()
|
||||
|
||||
self.logger.info(f"Certificate '{name}' removed successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Certificate removal error: {e}")
|
||||
return False
|
||||
|
||||
def get_certificates_status(self) -> Dict[str, Any]:
|
||||
"""Get status of all installed certificates"""
|
||||
certificates = {}
|
||||
for cert_file in self.certs_path.glob("*_cert.pem"):
|
||||
name = cert_file.name.replace("_cert.pem", "")
|
||||
cert_info = self.validate_certificate(cert_file)
|
||||
if cert_info:
|
||||
certificates[name] = {
|
||||
"subject": cert_info.subject,
|
||||
"issuer": cert_info.issuer,
|
||||
"valid_from": cert_info.valid_from.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
"valid_until": cert_info.valid_until.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
"is_valid": cert_info.is_valid,
|
||||
"days_until_expiry": (cert_info.valid_until - datetime.now()).days
|
||||
}
|
||||
return certificates
|
||||
|
||||
def check_expiring_certificates(self, days_warning: int = 30) -> Dict[str, int]:
|
||||
"""Check for certificates nearing expiration"""
|
||||
expiring = {}
|
||||
for cert_file in self.certs_path.glob("*_cert.pem"):
|
||||
name = cert_file.name.replace("_cert.pem", "")
|
||||
cert_info = self.validate_certificate(cert_file)
|
||||
if cert_info:
|
||||
days_left = (cert_info.valid_until - datetime.now()).days
|
||||
if days_left < days_warning:
|
||||
expiring[name] = days_left
|
||||
return expiring
|
||||
@@ -0,0 +1,127 @@
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from typing import Dict, Any
|
||||
import requests
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
@dataclass
|
||||
class HealthMetrics:
|
||||
last_check: float = 0
|
||||
uptime_start: float = 0
|
||||
gradio_endpoint_up: bool = False
|
||||
api_endpoint_up: bool = False
|
||||
consecutive_failures: int = 0
|
||||
total_restarts: int = 0
|
||||
response_times: Dict[str, float] = field(default_factory=dict)
|
||||
|
||||
class HealthMonitor:
|
||||
def __init__(self, proxy_manager):
|
||||
self.proxy_manager = proxy_manager
|
||||
self.logger = logging.getLogger("ProxyHealthMonitor")
|
||||
self.metrics = HealthMetrics()
|
||||
self.monitoring = False
|
||||
self.monitor_thread = None
|
||||
self.check_interval = 30 # seconds
|
||||
|
||||
def start_monitoring(self):
|
||||
"""Start health monitoring thread"""
|
||||
if self.monitoring:
|
||||
return
|
||||
|
||||
self.monitoring = True
|
||||
self.metrics.uptime_start = time.time()
|
||||
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True) # Make thread daemon
|
||||
self.monitor_thread.start()
|
||||
self.logger.info("Health monitoring started")
|
||||
|
||||
def stop_monitoring(self):
|
||||
"""Stop health monitoring thread"""
|
||||
if not self.monitoring:
|
||||
return
|
||||
|
||||
self.monitoring = False
|
||||
if self.monitor_thread and self.monitor_thread.is_alive():
|
||||
try:
|
||||
# Give the thread a chance to finish naturally
|
||||
self.monitor_thread.join(timeout=5)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Could not gracefully stop monitoring thread: {e}")
|
||||
finally:
|
||||
self.monitor_thread = None
|
||||
|
||||
self.logger.info("Health monitoring stopped")
|
||||
|
||||
def _monitor_loop(self):
|
||||
"""Main monitoring loop"""
|
||||
while self.monitoring:
|
||||
self._check_health()
|
||||
time.sleep(self.check_interval)
|
||||
|
||||
def _check_health(self):
|
||||
"""Check health of proxy endpoints"""
|
||||
config = self.proxy_manager.config_manager.get_instance()
|
||||
proxy_settings = config.proxy_settings
|
||||
self.metrics.last_check = time.time()
|
||||
|
||||
try:
|
||||
# Only check endpoints that are enabled and running
|
||||
if proxy_settings.gradio_endpoint.enabled == "Enabled":
|
||||
try:
|
||||
start_time = time.time()
|
||||
response = requests.get(
|
||||
f"http://127.0.0.1:{config.gradio_port_number}/", # Use internal port
|
||||
timeout=5
|
||||
)
|
||||
self.metrics.response_times['gradio'] = time.time() - start_time
|
||||
self.metrics.gradio_endpoint_up = response.status_code == 200
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Gradio endpoint check failed: {e}")
|
||||
self.metrics.gradio_endpoint_up = False
|
||||
|
||||
if proxy_settings.api_endpoint.enabled == "Enabled":
|
||||
try:
|
||||
start_time = time.time()
|
||||
response = requests.get(
|
||||
f"http://127.0.0.1:{config.api_def.api_port_number}/", # Use internal port
|
||||
timeout=5
|
||||
)
|
||||
self.metrics.response_times['api'] = time.time() - start_time
|
||||
self.metrics.api_endpoint_up = response.status_code == 200
|
||||
except Exception as e:
|
||||
self.logger.debug(f"API endpoint check failed: {e}")
|
||||
self.metrics.api_endpoint_up = False
|
||||
|
||||
if not self.metrics.gradio_endpoint_up and not self.metrics.api_endpoint_up:
|
||||
self.metrics.consecutive_failures += 1
|
||||
else:
|
||||
self.metrics.consecutive_failures = 0
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Health check failed: {str(e)}")
|
||||
self.metrics.consecutive_failures += 1
|
||||
|
||||
# Only attempt restart if we have multiple complete failures
|
||||
if self.metrics.consecutive_failures >= 3:
|
||||
self.logger.warning("Too many consecutive failures, attempting restart")
|
||||
try:
|
||||
if self.proxy_manager.stop_proxy() and self.proxy_manager.start_proxy():
|
||||
self.metrics.total_restarts += 1
|
||||
self.metrics.consecutive_failures = 0
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to restart proxy: {e}")
|
||||
|
||||
def get_health_status(self) -> Dict[str, Any]:
|
||||
"""Get current health metrics"""
|
||||
uptime = time.time() - self.metrics.uptime_start if self.metrics.uptime_start else 0
|
||||
return {
|
||||
"status": "healthy" if not self.metrics.consecutive_failures else "degraded",
|
||||
"uptime": f"{int(uptime/3600)}h {int((uptime%3600)/60)}m",
|
||||
"last_check": datetime.fromtimestamp(self.metrics.last_check).strftime('%Y-%m-%d %H:%M:%S')
|
||||
if self.metrics.last_check else "Never",
|
||||
"gradio_endpoint": "up" if self.metrics.gradio_endpoint_up else "down",
|
||||
"api_endpoint": "up" if self.metrics.api_endpoint_up else "down",
|
||||
"response_times": self.metrics.response_times,
|
||||
"total_restarts": self.metrics.total_restarts
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import gradio as gr
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .proxy_manager import ProxyManager
|
||||
|
||||
def create_proxy_interface(proxy_manager: 'ProxyManager'):
|
||||
"""Create the proxy management interface"""
|
||||
config = proxy_manager.config_manager.get_instance()
|
||||
|
||||
def validate_ports(gradio_port, api_port):
|
||||
"""Validate port configurations"""
|
||||
if gradio_port == api_port and gradio_port != 0 and api_port != 0:
|
||||
return False, "Gradio and API ports cannot be the same!"
|
||||
if not (0 <= gradio_port <= 65535) or not (0 <= api_port <= 65535):
|
||||
return False, "Ports must be between 0 and 65535"
|
||||
return True, "Valid port configuration"
|
||||
|
||||
def handle_save_config(proxy_enabled, gradio_enabled, api_enabled,
|
||||
gradio_port, api_port, startup, external_ip):
|
||||
# Validate ports first
|
||||
valid, message = validate_ports(gradio_port, api_port)
|
||||
if not valid:
|
||||
return message
|
||||
|
||||
config.proxy_settings.proxy_enabled = proxy_enabled
|
||||
config.proxy_settings.start_on_startup = startup
|
||||
|
||||
# Update Gradio endpoint
|
||||
config.proxy_settings.gradio_endpoint.enabled = gradio_enabled
|
||||
config.proxy_settings.gradio_endpoint.external_port = int(gradio_port) if gradio_enabled else 0
|
||||
config.proxy_settings.gradio_endpoint.external_ip = external_ip
|
||||
|
||||
# Update API endpoint
|
||||
config.proxy_settings.api_endpoint.enabled = api_enabled
|
||||
config.proxy_settings.api_endpoint.external_port = int(api_port) if api_enabled else 0
|
||||
config.proxy_settings.api_endpoint.external_ip = external_ip
|
||||
|
||||
config.save()
|
||||
return "Configuration saved successfully"
|
||||
|
||||
def update_control_states(proxy_enabled):
|
||||
"""Update enabled/disabled state of controls based on master switch"""
|
||||
return {
|
||||
start_button: gr.update(interactive=proxy_enabled),
|
||||
stop_button: gr.update(interactive=proxy_enabled),
|
||||
gradio_enabled: gr.update(interactive=proxy_enabled),
|
||||
api_enabled: gr.update(interactive=proxy_enabled),
|
||||
start_on_startup: gr.update(interactive=proxy_enabled)
|
||||
}
|
||||
|
||||
def delete_all_certificates():
|
||||
"""Delete all certificates and reset config"""
|
||||
try:
|
||||
# Delete certificate files
|
||||
for cert_file in proxy_manager.certs_path.glob("*.pem"):
|
||||
cert_file.unlink()
|
||||
|
||||
# Reset config
|
||||
config = proxy_manager.config_manager.get_instance()
|
||||
config.proxy_settings.gradio_endpoint.cert_name = ""
|
||||
config.proxy_settings.api_endpoint.cert_name = ""
|
||||
config.save()
|
||||
|
||||
return "All certificates deleted and config reset"
|
||||
except Exception as e:
|
||||
return f"Error deleting certificates: {str(e)}"
|
||||
|
||||
with gr.Tab("Proxy Settings"):
|
||||
with gr.Column():
|
||||
with gr.Row():
|
||||
with gr.Group():
|
||||
proxy_enabled = gr.Checkbox(
|
||||
label="Enable Proxy System",
|
||||
value=config.proxy_settings.proxy_enabled,
|
||||
info="Master switch/safety lockout for proxy functionality"
|
||||
)
|
||||
start_on_startup = gr.Checkbox(
|
||||
label="Start Proxy Automatically on Startup",
|
||||
value=config.proxy_settings.start_on_startup,
|
||||
info="Automatically start proxy when app launches"
|
||||
)
|
||||
external_ip = gr.Textbox(
|
||||
label="External IP",
|
||||
value=config.proxy_settings.gradio_endpoint.external_ip,
|
||||
info="Use 0.0.0.0 to bind to all interfaces"
|
||||
)
|
||||
with gr.Group():
|
||||
gr.Markdown("API interface Proxy Management")
|
||||
api_enabled = gr.Dropdown(
|
||||
choices=["Enabled", "Disabled"],
|
||||
value="Enabled" if config.proxy_settings.api_endpoint.enabled == "Enabled" else "Disabled",
|
||||
label="API (TTS Generation) Proxy",
|
||||
info="Enable or Disable Proxying the API (TTS Generation)"
|
||||
)
|
||||
api_port = gr.Number(
|
||||
value=config.proxy_settings.api_endpoint.external_port,
|
||||
label="API Port",
|
||||
info="External port you want to use for the API (TTS Generation)"
|
||||
)
|
||||
with gr.Group():
|
||||
gr.Markdown("Gradio interface Proxy Management")
|
||||
gradio_enabled = gr.Dropdown(
|
||||
choices=["Enabled", "Disabled"],
|
||||
value="Enabled" if config.proxy_settings.gradio_endpoint.enabled == "Enabled" else "Disabled",
|
||||
label="Gradio Interface Proxy",
|
||||
info="Enable or Disable Proxying the Gradio interface"
|
||||
)
|
||||
gradio_port = gr.Number(
|
||||
value=config.proxy_settings.gradio_endpoint.external_port,
|
||||
label="Gradio Interface Port",
|
||||
info="External port you want to use for the Gradio interface"
|
||||
)
|
||||
|
||||
# Status and control section
|
||||
with gr.Row():
|
||||
with gr.Column(scale=2):
|
||||
status_output = gr.Textbox(
|
||||
label="Status",
|
||||
interactive=False,
|
||||
lines=4
|
||||
)
|
||||
with gr.Column(scale=1):
|
||||
save_button = gr.Button("Save Configuration", variant="primary")
|
||||
with gr.Row():
|
||||
start_button = gr.Button("Start Service")
|
||||
stop_button = gr.Button("Stop Service")
|
||||
status_button = gr.Button("Check Status")
|
||||
|
||||
# Certificate section
|
||||
with gr.Row():
|
||||
gr.Markdown("### Certificate Management")
|
||||
with gr.Row():
|
||||
with gr.Column(scale=1):
|
||||
cert_file = gr.File(label="Certificate (.crt/.pem)")
|
||||
with gr.Column(scale=1):
|
||||
key_file = gr.File(label="Key (.key)")
|
||||
with gr.Column(scale=1):
|
||||
cert_name = gr.Textbox(label="Certificate Name", placeholder="e.g., gradio_cert")
|
||||
upload_button = gr.Button("Upload Certificate", variant="primary")
|
||||
delete_button = gr.Button("Delete Certificates", variant="secondary")
|
||||
|
||||
# Connect everything
|
||||
save_button.click(
|
||||
fn=handle_save_config,
|
||||
inputs=[
|
||||
proxy_enabled,
|
||||
gradio_enabled,
|
||||
api_enabled,
|
||||
gradio_port,
|
||||
api_port,
|
||||
start_on_startup,
|
||||
external_ip
|
||||
],
|
||||
outputs=status_output
|
||||
)
|
||||
proxy_enabled.change(
|
||||
fn=update_control_states,
|
||||
inputs=[proxy_enabled],
|
||||
outputs=[start_button, stop_button, gradio_enabled, api_enabled, start_on_startup]
|
||||
)
|
||||
start_button.click(fn=proxy_manager.start_proxy, outputs=status_output)
|
||||
stop_button.click(fn=proxy_manager.stop_proxy, outputs=status_output)
|
||||
status_button.click(fn=proxy_manager.get_status, outputs=status_output)
|
||||
upload_button.click(
|
||||
fn=proxy_manager.handle_cert_upload,
|
||||
inputs=[cert_file, key_file, cert_name],
|
||||
outputs=status_output
|
||||
)
|
||||
delete_button.click(fn=delete_all_certificates, outputs=status_output)
|
||||
|
||||
return proxy_enabled, start_on_startup, gradio_port, api_port
|
||||
@@ -0,0 +1,94 @@
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from typing import Dict, Any
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@dataclass
|
||||
class ProxyMetrics:
|
||||
start_time: float = field(default_factory=time.time)
|
||||
total_requests: int = 0
|
||||
active_connections: int = 0
|
||||
bytes_transferred: int = 0
|
||||
request_times: Dict[str, float] = field(default_factory=lambda: defaultdict(float))
|
||||
requests_per_minute: Dict[str, int] = field(default_factory=lambda: defaultdict(int))
|
||||
status_codes: Dict[str, int] = field(default_factory=lambda: defaultdict(int))
|
||||
|
||||
class MetricsCollector:
|
||||
def __init__(self, proxy_manager):
|
||||
self.proxy_manager = proxy_manager
|
||||
self.logger = logging.getLogger("ProxyMetrics")
|
||||
self.metrics = ProxyMetrics()
|
||||
self.collecting = False
|
||||
self.collector_thread = None
|
||||
self.collect_interval = 60 # seconds
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def start_collecting(self):
|
||||
"""Start metrics collection"""
|
||||
if self.collecting:
|
||||
return
|
||||
|
||||
self.collecting = True
|
||||
self.metrics = ProxyMetrics() # Reset metrics
|
||||
self.collector_thread = threading.Thread(target=self._collect_loop)
|
||||
self.collector_thread.daemon = True
|
||||
self.collector_thread.start()
|
||||
self.logger.info("Metrics collection started")
|
||||
|
||||
def stop_collecting(self):
|
||||
"""Stop metrics collection"""
|
||||
self.collecting = False
|
||||
if self.collector_thread:
|
||||
self.collector_thread.join()
|
||||
self.logger.info("Metrics collection stopped")
|
||||
|
||||
def _collect_loop(self):
|
||||
"""Main collection loop"""
|
||||
while self.collecting:
|
||||
self._update_metrics()
|
||||
time.sleep(self.collect_interval)
|
||||
|
||||
def _update_metrics(self):
|
||||
"""Update periodic metrics"""
|
||||
with self._lock:
|
||||
# Reset per-minute counters
|
||||
self.metrics.requests_per_minute.clear()
|
||||
|
||||
def record_request(self, endpoint: str, status_code: int, response_time: float, bytes_sent: int):
|
||||
"""Record metrics for a single request"""
|
||||
with self._lock:
|
||||
self.metrics.total_requests += 1
|
||||
self.metrics.bytes_transferred += bytes_sent
|
||||
self.metrics.request_times[endpoint] = response_time
|
||||
self.metrics.status_codes[str(status_code)] += 1
|
||||
self.metrics.requests_per_minute[endpoint] += 1
|
||||
|
||||
def get_current_metrics(self) -> Dict[str, Any]:
|
||||
"""Get current metrics"""
|
||||
with self._lock:
|
||||
uptime = time.time() - self.metrics.start_time
|
||||
return {
|
||||
"uptime": str(timedelta(seconds=int(uptime))),
|
||||
"total_requests": self.metrics.total_requests,
|
||||
"active_connections": self.metrics.active_connections,
|
||||
"bytes_transferred": f"{self.metrics.bytes_transferred / 1024 / 1024:.2f} MB",
|
||||
"average_response_times": {
|
||||
endpoint: f"{time:.2f}ms"
|
||||
for endpoint, time in self.metrics.request_times.items()
|
||||
},
|
||||
"requests_per_minute": dict(self.metrics.requests_per_minute),
|
||||
"status_codes": dict(self.metrics.status_codes)
|
||||
}
|
||||
|
||||
def increment_connections(self):
|
||||
"""Increment active connection count"""
|
||||
with self._lock:
|
||||
self.metrics.active_connections += 1
|
||||
|
||||
def decrement_connections(self):
|
||||
"""Decrement active connection count"""
|
||||
with self._lock:
|
||||
self.metrics.active_connections = max(0, self.metrics.active_connections - 1)
|
||||
@@ -0,0 +1,235 @@
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any
|
||||
from dataclasses import asdict
|
||||
import psutil
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import logging
|
||||
import os
|
||||
from .security import SecurityManager
|
||||
from .health_monitor import HealthMonitor
|
||||
from .cert_manager import CertificateManager
|
||||
from .metrics import MetricsCollector
|
||||
|
||||
class ProxyManager:
|
||||
def __init__(self, config_manager, base_path: str = None):
|
||||
self.config_manager = config_manager
|
||||
# Set up base paths first
|
||||
self.base_path = Path(base_path) if base_path else Path(__file__).parent
|
||||
|
||||
# Create required directories
|
||||
self.certs_path = self.base_path / "certificates"
|
||||
self.logs_path = self.base_path / "logs"
|
||||
self.config_path = self.base_path / "config"
|
||||
|
||||
for path in [self.certs_path, self.logs_path, self.config_path]:
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Setup logging before managers
|
||||
self.setup_logging()
|
||||
|
||||
# Initialize managers after paths are created
|
||||
self.security = SecurityManager(self)
|
||||
self.health_monitor = HealthMonitor(self)
|
||||
self.cert_manager = CertificateManager(self)
|
||||
self.metrics = MetricsCollector(self)
|
||||
|
||||
self.proxy_process = None
|
||||
self.logger.info("Proxy manager initialized successfully")
|
||||
|
||||
def setup_logging(self):
|
||||
"""Setup logging configuration with customized format"""
|
||||
config = self.config_manager.get_instance()
|
||||
log_level = getattr(logging, config.proxy_settings.log_level, logging.INFO)
|
||||
|
||||
# Configure rotating file handler
|
||||
log_file = self.logs_path / "proxy.log"
|
||||
file_handler = RotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=5*1024*1024,
|
||||
backupCount=5,
|
||||
encoding='utf-8'
|
||||
)
|
||||
|
||||
# Configure logging formats
|
||||
file_format = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
console_format = logging.Formatter(
|
||||
'[AllTalk PRX] %(message)s'
|
||||
)
|
||||
|
||||
file_handler.setFormatter(file_format)
|
||||
|
||||
# Setup logger
|
||||
self.logger = logging.getLogger("ProxyManager")
|
||||
self.logger.setLevel(log_level)
|
||||
self.logger.addHandler(file_handler)
|
||||
|
||||
# Only add console handler if proxy debugging is enabled
|
||||
if config.debugging.debug_proxy:
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(console_format)
|
||||
self.logger.addHandler(console_handler)
|
||||
self.logger.info("Proxy debug logging enabled")
|
||||
|
||||
def _monitor_output(self, pipe, name):
|
||||
"""Monitor subprocess output and redirect to logger"""
|
||||
try:
|
||||
for line in iter(pipe.readline, ''):
|
||||
line = line.strip()
|
||||
if line:
|
||||
if name == "stderr":
|
||||
self.logger.error(line)
|
||||
else:
|
||||
# Pass through with our logging format
|
||||
print(line) # Direct print for formatted output
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error monitoring {name}: {e}")
|
||||
finally:
|
||||
pipe.close()
|
||||
|
||||
def start_proxy(self) -> bool:
|
||||
"""Start the proxy server with current configuration"""
|
||||
if self.proxy_process and self.proxy_process.poll() is None:
|
||||
self.logger.warning("Proxy already running")
|
||||
return False
|
||||
|
||||
config = self.config_manager.get_instance()
|
||||
proxy_settings = config.proxy_settings
|
||||
|
||||
try:
|
||||
|
||||
cmd = [
|
||||
"python", "-m", "twisted_server",
|
||||
"--external_ip_a", proxy_settings.api_endpoint.external_ip if proxy_settings.api_endpoint.enabled == "Enabled" else "0",
|
||||
"--internal_ip_a", "127.0.0.1",
|
||||
"--external_ip_b", proxy_settings.gradio_endpoint.external_ip if proxy_settings.gradio_endpoint.enabled == "Enabled" else "0",
|
||||
"--internal_ip_b", "127.0.0.1",
|
||||
"--cert_file_a", proxy_settings.api_endpoint.cert_name or "0",
|
||||
"--cert_file_b", proxy_settings.gradio_endpoint.cert_name or "0"
|
||||
]
|
||||
|
||||
# Start process with pipe for output
|
||||
self.proxy_process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=str(self.base_path),
|
||||
text=True,
|
||||
bufsize=1,
|
||||
env={**os.environ, 'PYTHONUNBUFFERED': '1'}, # Force unbuffered output
|
||||
universal_newlines=True
|
||||
)
|
||||
|
||||
# Start output monitoring threads
|
||||
threading.Thread(target=self._monitor_output,
|
||||
args=(self.proxy_process.stdout, "stdout"),
|
||||
daemon=True).start()
|
||||
threading.Thread(target=self._monitor_output,
|
||||
args=(self.proxy_process.stderr, "stderr"),
|
||||
daemon=True).start()
|
||||
|
||||
if proxy_settings.proxy_enabled:
|
||||
self.health_monitor.start_monitoring()
|
||||
self.metrics.start_collecting()
|
||||
|
||||
self.logger.info("Proxy server started successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to start proxy: {e}")
|
||||
return False
|
||||
|
||||
def stop_proxy(self) -> bool:
|
||||
"""Stop the proxy server and all monitoring"""
|
||||
if not self.proxy_process:
|
||||
self.logger.warning("No proxy process running")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Stop all monitoring first
|
||||
self.health_monitor.stop_monitoring()
|
||||
self.metrics.stop_collecting()
|
||||
|
||||
# Stop the main process
|
||||
process = psutil.Process(self.proxy_process.pid)
|
||||
for proc in process.children(recursive=True):
|
||||
proc.terminate()
|
||||
process.terminate()
|
||||
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except psutil.TimeoutExpired:
|
||||
process.kill()
|
||||
|
||||
self.proxy_process = None
|
||||
self.logger.info("Proxy server stopped successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to stop proxy: {e}")
|
||||
return False
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
"""Get comprehensive status of the proxy system"""
|
||||
config = self.config_manager.get_instance()
|
||||
proxy_config = config.proxy_settings
|
||||
|
||||
status = {
|
||||
"running": self.proxy_process is not None and self.proxy_process.poll() is None,
|
||||
"config": {
|
||||
"gradio_endpoint": asdict(proxy_config.gradio_endpoint),
|
||||
"api_endpoint": asdict(proxy_config.api_endpoint),
|
||||
"start_on_startup": proxy_config.start_on_startup
|
||||
},
|
||||
"certificates": self.cert_manager.get_certificates_status(),
|
||||
"health": self.health_monitor.get_health_status(),
|
||||
"metrics": self.metrics.get_current_metrics(),
|
||||
"security": self.security.get_security_status()
|
||||
}
|
||||
|
||||
return status
|
||||
|
||||
def handle_cert_upload(self, cert_file, key_file, name):
|
||||
"""Handle certificate upload from the interface"""
|
||||
if not cert_file or not key_file or not name:
|
||||
return "Please provide both certificate and key files, and a name"
|
||||
|
||||
cert_path = Path(cert_file.name)
|
||||
key_path = Path(key_file.name)
|
||||
|
||||
success = self.cert_manager.install_certificate(cert_path, key_path, name)
|
||||
return "Certificate uploaded successfully" if success else "Failed to upload certificate"
|
||||
|
||||
def handle_start(self):
|
||||
"""Handle proxy start from the interface"""
|
||||
success = self.start_proxy()
|
||||
return "Proxy Server: Starting..." if success else "Failed to start proxy server"
|
||||
|
||||
def handle_stop(self):
|
||||
"""Handle proxy stop from the interface"""
|
||||
success = self.stop_proxy()
|
||||
return "Proxy Server: Stopped" if success else "Failed to stop proxy server"
|
||||
|
||||
def handle_status(self):
|
||||
"""Handle status request from the interface"""
|
||||
status = self.get_status()
|
||||
if status['running']:
|
||||
return "Proxy Server: Running\n" + \
|
||||
f"API Endpoint: {'Active' if status['config']['api_endpoint']['enabled'] == 'Enabled' else 'Disabled'}\n" + \
|
||||
f"Gradio Endpoint: {'Active' if status['config']['gradio_endpoint']['enabled'] == 'Enabled' else 'Disabled'}\n" + \
|
||||
f"Certificates: {len(status['certificates'])} configured"
|
||||
else:
|
||||
return "Proxy Server: Stopped"
|
||||
|
||||
def handle_security_event(self, event_type: str, details: Dict[str, Any]):
|
||||
"""Handle security events from the SecurityManager"""
|
||||
self.logger.warning(f"Security event: {event_type} - {details}")
|
||||
# Implement security response logic here
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup resources before shutdown"""
|
||||
self.stop_proxy()
|
||||
self.logger.info("Proxy manager cleanup completed")
|
||||