Compare commits
22
Commits
1.8d
...
DeepSpeed-12.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd45008e88 | ||
|
|
840889ac9a | ||
|
|
d3e061b127 | ||
|
|
50aa6de96c | ||
|
|
10299194f8 | ||
|
|
d80d1ace3c | ||
|
|
f19f4e9a29 | ||
|
|
7e4ceef0e8 | ||
|
|
c1bc1f26cd | ||
|
|
70d385133d | ||
|
|
13b2eb5fe3 | ||
|
|
ff4b9d9e99 | ||
|
|
a22830f700 | ||
|
|
374c794fa9 | ||
|
|
d2adbd9c1e | ||
|
|
1dc4b8bf50 | ||
|
|
0eba72fc47 | ||
|
|
f2c87dcdab | ||
|
|
4a727ce14a | ||
|
|
6e15d43e63 | ||
|
|
dedc13b3b6 | ||
|
|
7cb206e531 |
No files matched your search
@@ -4,7 +4,7 @@ AllTalk is an updated version of the Coqui_tts extension for Text Generation web
|
|||||||
- **Can be run as a** [standalone](https://github.com/erew123/alltalk_tts?#-running-alltalk-as-a-standalone-app) **or part of** [Text-generation-webui](https://github.com/erew123/alltalk_tts?#-installation-on-text-generation-web-ui)
|
- **Can be run as a** [standalone](https://github.com/erew123/alltalk_tts?#-running-alltalk-as-a-standalone-app) **or part of** [Text-generation-webui](https://github.com/erew123/alltalk_tts?#-installation-on-text-generation-web-ui)
|
||||||
- **Model Finetuning:** Train the model specifically on a voice of your choosing for better reproduction.
|
- **Model Finetuning:** Train the model specifically on a voice of your choosing for better reproduction.
|
||||||
- **Local/Custom models:** Use any of the XTTSv2 models (API Local and XTTSv2 Local).
|
- **Local/Custom models:** Use any of the XTTSv2 models (API Local and XTTSv2 Local).
|
||||||
- **Bulk TTS Generator/Editor:** Generate hours of TTS into one big file or have something read back to you.
|
- **Bulk TTS Generator/Editor:** Generate hours of TTS into one big file or have something read back to you [demo](https://www.youtube.com/watch?v=hunvXn0mLzc).
|
||||||
- **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)
|
- **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)
|
||||||
- **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)
|
- **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)
|
||||||
- **Custom Start-up Settings:** Adjust your default start-up settings. [Screenshot](https://github.com/erew123/alltalk_tts#screenshots)
|
- **Custom Start-up Settings:** Adjust your default start-up settings. [Screenshot](https://github.com/erew123/alltalk_tts#screenshots)
|
||||||
@@ -542,7 +542,10 @@ You can now either install the CUDA 11.8 or 12.1 version of Torch and Torchaudio
|
|||||||
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.<br><br>
|
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.<br><br>
|
||||||
|
|
||||||
## ⬜ AllTalk TTS Generator
|
## ⬜ AllTalk TTS Generator
|
||||||
AllTalk TTS Generator is the solution for converting large volumes of text into speech using the voice of your choice. Whether you're creating audio content or just want to hear text read aloud, the TTS Generator is equipped to handle it all efficiently.<br><br>The link to open the TTS generator can be found on the built-in Settings and Documentation page.
|
AllTalk TTS Generator is the solution for converting large volumes of text into speech using the voice of your choice. Whether you're creating audio content or just want to hear text read aloud, the TTS Generator is equipped to handle it all efficiently. Please see here for a quick [demo](https://www.youtube.com/watch?v=hunvXn0mLzc)<br><br>The link to open the TTS generator can be found on the built-in Settings and Documentation page.<br><br>**DeepSpeed** is **highly** recommended to speed up generation. **Low VRAM** would be best turned off and your LLM model unloaded from your GPU VRAM (unload your model). **No Playback** will reduce memory overhead on very large generations (15,000 words or more). Splitting **Export to Wav** into smaller groups will also reduce memory overhead at the point of exporting your wav files (so good for low memory systems).
|
||||||
|
|
||||||
|
#### ⬜ Estimated Throughput
|
||||||
|
This will vary by system for a multitude of reasons, however, while generating a 58,000 word document to TTS, with DeepSpeed enabled, LowVram disabled, splitting size 2 and on an Nvidia RTX 4070, throughput was around 1,000 words per minute. Meaning, this took 1 hour to generate the TTS. Exporting to combined wavs took about 2-3 minutes total.
|
||||||
|
|
||||||
#### ⬜ Quick Start
|
#### ⬜ Quick Start
|
||||||
- **Text Input:** Enter the text you wish to convert into speech in the 'Text Input' box.
|
- **Text Input:** Enter the text you wish to convert into speech in the 'Text Input' box.
|
||||||
@@ -553,15 +556,16 @@ Once you have sent text off to be generated, either as a stream or wav file gene
|
|||||||
If you are generating wav files and populating the queue, you can generate one lot of text to speech, then input your next lot of text and it will continue adding to the list.
|
If you are generating wav files and populating the queue, you can generate one lot of text to speech, then input your next lot of text and it will continue adding to the list.
|
||||||
#### ⬜ TTS Generation Modes
|
#### ⬜ TTS Generation Modes
|
||||||
- **Wav Chunks:** Perfect for creating audio books, or anything you want to keep long term. Breaks down your text into manageable wav files and queues them up. Generation begins automatically, and playback will start after a few chunks have been prepared ahead. You can set the volume to 0 if you don’t want to hear playback. With Wav chunks, you can edit and/or regenerate portions of the TTS as needed.
|
- **Wav Chunks:** Perfect for creating audio books, or anything you want to keep long term. Breaks down your text into manageable wav files and queues them up. Generation begins automatically, and playback will start after a few chunks have been prepared ahead. You can set the volume to 0 if you don’t want to hear playback. With Wav chunks, you can edit and/or regenerate portions of the TTS as needed.
|
||||||
- **Streaming:** For immediate playback without the ability to save. Ideal for on-the-fly speech generation and listening. This will not generate wav files and it will play back through your browser. You cannot stop the server generating the TTS once it has been sent.
|
- **Streaming:** For immediate playback without the ability to save. Ideal for on-the-fly speech generation and listening. This will not generate wav files and it will play back through your browser. You cannot stop the server generating the TTS once it has been sent.<br><br>
|
||||||
With wav chunks you can either playback “In Browser” which is the web page you are on, or “On Server” which is through the console/terminal where AllTalk is running from. Only generation “In Browser” can play back smoothly and populate the Generated TTS List. Setting the Volume will affect the volume level played back both “In Browser” and “On Server”.
|
With wav chunks you can either playback “In Browser” which is the web page you are on, or “On Server” which is through the console/terminal where AllTalk is running from, or "No Playback". Only generation “In Browser” can play back smoothly and populate the Generated TTS List. Setting the Volume will affect the volume level played back both “In Browser” and “On Server”.<br><br>
|
||||||
|
For generating **large amounts of TTS**, it's recommended to select the **No Playback** option. This setting minimizes the memory usage in your web browser by avoiding the loading and playing of audio files directly within the browser, which is particularly beneficial for handling extensive audio generations. The definition of large will vary depending on your system RAM availability (will update when I have more information as to guidelines). Once the audio is generated, you can export your list to JSON (for safety) and use the **Play List** option to play back your audio.
|
||||||
#### ⬜ Playback and List Management
|
#### ⬜ Playback and List Management
|
||||||
- **Playback Controls:** Utilize 'Play List' to start from the beginning or 'Stop Playback' to halt at any time.
|
- **Playback Controls:** Utilize 'Play List' to start from the beginning or 'Stop Playback' to halt at any time.
|
||||||
- **Custom Start:** Jump into your list at a specific ID to hear a particular section.
|
- **Custom Start:** Jump into your list at a specific ID to hear a particular section.
|
||||||
- **Regeneration and Editing:** If a chunk isn't quite right, you can opt to regenerate it or edit the text directly. Click off the text to save changes and hit regenerate for the specific line.
|
- **Regeneration and Editing:** If a chunk isn't quite right, you can opt to regenerate it or edit the text directly. Click off the text to save changes and hit regenerate for the specific line.
|
||||||
- **Export/Import List:** Save your TTS list as a JSON file or import one. Note: Existing wav files are needed for playback. Exporting is handy if you want to take your files away into another program and have a list of which wav is which, or if you keep your audio files, but want to come back at a later date, edit one or two lines, regenerate the speech and re-combine the wav’s into one new long wav.
|
- **Export/Import List:** Save your TTS list as a JSON file or import one. Note: Existing wav files are needed for playback. Exporting is handy if you want to take your files away into another program and have a list of which wav is which, or if you keep your audio files, but want to come back at a later date, edit one or two lines, regenerate the speech and re-combine the wav’s into one new long wav.
|
||||||
#### ⬜ Exporting Your Audio
|
#### ⬜ Exporting Your Audio
|
||||||
- **Export to WAV:** Combine all generated TTS from the list, into one single WAV file for easy download and distribution.
|
- **Export to WAV:** Combine all generated TTS from the list, into one single WAV file for easy download and distribution. Its always recommended to export your list to a JSON before exporting, so that you have a backup, should something go wrong. You can simply re-import the list and try exporting again.<br><br>When exporting, there is a file size limit of 1GB and as such you have the option to choose how many files to include in each block of audio exported. 600 is just on the limit of 1GB, depending on the average file size, so 500 or less is a good amount to work with. You can combine the generated files after if you wish, in Audacity or similar.<br><br>Additionally, lower export batches will lower the memory requirements, so if your system is low on memory (maybe 8 or 16GB system), you can use smaller export batches to keep the memory requirement down.
|
||||||
#### ⬜ Customization and Preferences
|
#### ⬜ Customization and Preferences
|
||||||
- **Character Voice:** Choose the voice that will read your text.
|
- **Character Voice:** Choose the voice that will read your text.
|
||||||
- **Language:** Select the language of your text.
|
- **Language:** Select the language of your text.
|
||||||
|
|||||||
@@ -736,93 +736,16 @@ def input_modifier(string, state):
|
|||||||
def ui():
|
def ui():
|
||||||
with gr.Accordion(params["branding"] + " TTS (XTTSv2)"):
|
with gr.Accordion(params["branding"] + " TTS (XTTSv2)"):
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
activate = gr.Checkbox(value=params["activate"], label="Activate TTS")
|
activate = gr.Checkbox(value=params['activate'], label='Enable TTS')
|
||||||
autoplay = gr.Checkbox(
|
autoplay = gr.Checkbox(value=params['autoplay'], label='Autoplay TTS')
|
||||||
value=params["autoplay"], label="Play TTS automatically"
|
show_text = gr.Checkbox(value=params['show_text'], label='Show Text')
|
||||||
)
|
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
show_text = gr.Checkbox(
|
low_vram = gr.Checkbox(value=params['low_vram'], label='Enable Low VRAM Mode')
|
||||||
value=params["show_text"], label="Show message text under audio player"
|
|
||||||
)
|
|
||||||
remove_trailing_dots = gr.Checkbox(
|
|
||||||
value=params["remove_trailing_dots"],
|
|
||||||
label='Remove trailing "." from text segments before generation',
|
|
||||||
)
|
|
||||||
|
|
||||||
with gr.Row():
|
|
||||||
with gr.Row():
|
|
||||||
available_voices = get_available_voices()
|
|
||||||
default_voice = params["voice"]
|
|
||||||
# Check if the default voice is in the list of available voices
|
|
||||||
if default_voice not in available_voices:
|
|
||||||
# Handle the case where the default voice is not in the list (choose a default value or update it)
|
|
||||||
default_voice = available_voices[
|
|
||||||
0
|
|
||||||
] # Choose the first available voice as the default
|
|
||||||
# Add allow_custom_value=True to the Dropdown
|
|
||||||
voice = gr.Dropdown(
|
|
||||||
available_voices,
|
|
||||||
label="Default Voice",
|
|
||||||
value=default_voice,
|
|
||||||
allow_custom_value=True,
|
|
||||||
)
|
|
||||||
create_refresh_button(
|
|
||||||
voice,
|
|
||||||
lambda: None,
|
|
||||||
lambda: {
|
|
||||||
"choices": get_available_voices(),
|
|
||||||
"value": params["voice"],
|
|
||||||
},
|
|
||||||
"refresh-button",
|
|
||||||
)
|
|
||||||
|
|
||||||
language = gr.Dropdown(
|
|
||||||
languages.keys(),
|
|
||||||
label="Language",
|
|
||||||
allow_custom_value=True,
|
|
||||||
value=params["language"],
|
|
||||||
)
|
|
||||||
|
|
||||||
with gr.Row():
|
|
||||||
with gr.Row():
|
|
||||||
narrator_voice_gr = gr.Dropdown(
|
|
||||||
get_available_voices(),
|
|
||||||
label="Narrator Voice",
|
|
||||||
allow_custom_value=True,
|
|
||||||
value=params["narrator_voice"],
|
|
||||||
)
|
|
||||||
create_refresh_button(
|
|
||||||
narrator_voice_gr,
|
|
||||||
lambda: None,
|
|
||||||
lambda: {
|
|
||||||
"choices": get_available_voices(),
|
|
||||||
"value": params["narrator_voice"],
|
|
||||||
},
|
|
||||||
"refresh-button",
|
|
||||||
)
|
|
||||||
narrator_enabled_gr = gr.Radio(
|
|
||||||
choices={"Enabled": "true", "Disabled": "false"},
|
|
||||||
label="Narrator Activation",
|
|
||||||
value="Enabled" if params.get("narrator_enabled") else "Disabled",
|
|
||||||
)
|
|
||||||
non_quoted_text_is_gr = gr.Radio(
|
|
||||||
choices={"Char": "true", "Narrator": "false"},
|
|
||||||
label='Text NOT inside of * or " is',
|
|
||||||
value="Char" if non_quoted_text_is else "Narrator",
|
|
||||||
)
|
|
||||||
|
|
||||||
with gr.Row():
|
|
||||||
low_vram = gr.Checkbox(
|
|
||||||
value=params["low_vram"], label="Low VRAM mode (Read NOTE)"
|
|
||||||
)
|
|
||||||
low_vram_play = gr.HTML(visible=False)
|
low_vram_play = gr.HTML(visible=False)
|
||||||
deepspeed_checkbox = gr.Checkbox(
|
deepspeed_checkbox = gr.Checkbox(value=params['deepspeed_activate'], label='Enable DeepSpeed', visible=deepspeed_installed)
|
||||||
value=params["deepspeed_activate"],
|
|
||||||
label="Activate DeepSpeed (Read NOTE)",
|
|
||||||
visible=deepspeed_installed,
|
|
||||||
)
|
|
||||||
deepspeed_checkbox_play = gr.HTML(visible=False)
|
deepspeed_checkbox_play = gr.HTML(visible=False)
|
||||||
|
remove_trailing_dots = gr.Checkbox(value=params['remove_trailing_dots'], label='Remove trailing "."')
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
model_loader_choices=["API TTS", "API Local", "XTTSv2 Local"]
|
model_loader_choices=["API TTS", "API Local", "XTTSv2 Local"]
|
||||||
@@ -830,13 +753,37 @@ def ui():
|
|||||||
model_loader_choices.append("XTTSv2 FT")
|
model_loader_choices.append("XTTSv2 FT")
|
||||||
tts_radio_buttons = gr.Radio(
|
tts_radio_buttons = gr.Radio(
|
||||||
choices=model_loader_choices,
|
choices=model_loader_choices,
|
||||||
label="Select TTS Generation Method (Read NOTE)",
|
label="TTS Method (Each method sounds slightly different)",
|
||||||
value=gr_modelchoice, # Set the default value
|
value=gr_modelchoice, # Set the default value
|
||||||
)
|
)
|
||||||
tts_radio_buttons_play = gr.HTML(visible=False)
|
tts_radio_buttons_play = gr.HTML(visible=False)
|
||||||
|
with gr.Row():
|
||||||
|
available_voices = get_available_voices()
|
||||||
|
default_voice = params["voice"] # Check if the default voice is in the list of available voices
|
||||||
|
|
||||||
|
if default_voice not in available_voices:
|
||||||
|
default_voice = available_voices[ 0 ] # Choose the first available voice as the default
|
||||||
|
# Add allow_custom_value=True to the Dropdown
|
||||||
|
voice = gr.Dropdown(available_voices, label="Character Voice", value=default_voice, allow_custom_value=True, )
|
||||||
|
create_refresh_button(voice, lambda: None, lambda: {"choices": get_available_voices(), "value": params["voice"],}, "refresh-button",)
|
||||||
|
|
||||||
|
with gr.Row():
|
||||||
|
language = gr.Dropdown(languages.keys(), label="Language", value=params["language"])
|
||||||
|
with gr.Row():
|
||||||
|
narrator_voice_gr = gr.Dropdown(get_available_voices(), label="Narrator Voice", allow_custom_value=True, value=params["narrator_voice"])
|
||||||
|
create_refresh_button(narrator_voice_gr, lambda: None, lambda: {"choices": get_available_voices(), "value": params["narrator_voice"],}, "refresh-button")
|
||||||
|
|
||||||
|
# Temperature and Repetition Penalty (Not yet parsed to api/implemented)
|
||||||
|
# with gr.Row():
|
||||||
|
# local_temperature_gr = gr.Slider(minimum=0, maximum=1, step=0.05, label="Temperature", value=params["local_temperature"])
|
||||||
|
# local_repetition_penalty_gr = gr.Slider(minimum=0, maximum=20, step=0.5, label="Repetition Penalty", value=params["local_repetition_penalty"])
|
||||||
|
|
||||||
|
with gr.Row():
|
||||||
|
with gr.Row():
|
||||||
|
narrator_enabled_gr = gr.Radio(choices={"Enabled": "true", "Disabled": "false"}, label="Narrator", value="Enabled" if params.get("narrator_enabled") else "Disabled")
|
||||||
|
non_quoted_text_is_gr = gr.Radio(choices={"Character": "true", "Narrator": "false"}, label='Unmarked text NOT inside of * or " is', value="Character" if non_quoted_text_is else "Narrator")
|
||||||
explanation_text = gr.HTML(
|
explanation_text = gr.HTML(
|
||||||
f"<p>⚙️ <a href='http://{params['ip_address']}:{params['port_number']}'>Click here for the Settings page, help & instructions</a><a href='http://{params['ip_address']}:{params['port_number']}'></a>⚙️<br>NOTE: Switching Model Type, Low VRAM & DeepSpeed, each takes 15 seconds. Each TTS generation method has a slightly different sound. DeepSpeed checkbox is only visible if DeepSpeed is installed on your system."
|
f"<p>⚙️ <a href='http://{params['ip_address']}:{params['port_number']}'>Settings and Documentation Page</a><a href='http://{params['ip_address']}:{params['port_number']}'></a>⚙️<br>- Low VRAM Mode and Deepspeed take 15 seconds to be enabled or disabled.<br>- The DeepSpeed checkbox is only visible if DeepSpeed is present.</p>")
|
||||||
)
|
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
preview_text = gr.Text(
|
preview_text = gr.Text(
|
||||||
@@ -915,6 +862,10 @@ def ui():
|
|||||||
voice.change(lambda x: params.update({"voice": x}), voice, None)
|
voice.change(lambda x: params.update({"voice": x}), voice, None)
|
||||||
language.change(lambda x: params.update({"language": x}), language, None)
|
language.change(lambda x: params.update({"language": x}), language, None)
|
||||||
|
|
||||||
|
# TTS Settings (Not yet parsed to api/implemented)
|
||||||
|
# local_temperature_gr.change(lambda x: params.update({"local_temperature": x}), local_temperature_gr, None)
|
||||||
|
# local_repetition_penalty_gr.change(lambda x: params.update({"local_repetition_penalty": x}), local_repetition_penalty_gr, None)
|
||||||
|
|
||||||
# Narrator selection actions
|
# Narrator selection actions
|
||||||
narrator_enabled_gr.change(update_narrator_enabled, narrator_enabled_gr, None)
|
narrator_enabled_gr.change(update_narrator_enabled, narrator_enabled_gr, None)
|
||||||
non_quoted_text_is_gr.change(update_non_quoted_text_is, non_quoted_text_is_gr, None)
|
non_quoted_text_is_gr.change(update_non_quoted_text_is, non_quoted_text_is_gr, None)
|
||||||
|
|||||||
@@ -420,7 +420,8 @@
|
|||||||
let cleanedText = textInput.replace(/ \- | \– /g, ' '); // Replace hyphens
|
let cleanedText = textInput.replace(/ \- | \– /g, ' '); // Replace hyphens
|
||||||
cleanedText = cleanedText.replace(/%/g, " percent");
|
cleanedText = cleanedText.replace(/%/g, " percent");
|
||||||
//cleanedText = cleanedText.replace(/([!?.])\1+/g, '$1');
|
//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]\$/g, '')
|
||||||
cleanedText = cleanedText.replace(/\n+/g, ' ');
|
cleanedText = cleanedText.replace(/\n+/g, ' ');
|
||||||
cleanedText = cleanedText.replace(/\.\s'/g, ".'"); // Remove space between period and single quote
|
cleanedText = cleanedText.replace(/\.\s'/g, ".'"); // Remove space between period and single quote
|
||||||
//cleanedText = cleanedText.replace(/'/g, ""); // Remove all single quotes
|
//cleanedText = cleanedText.replace(/'/g, ""); // Remove all single quotes
|
||||||
|
|||||||
@@ -182,20 +182,25 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column; /* Stack items vertically */
|
flex-direction: column;
|
||||||
align-items: center; /* Center items horizontally within the container */
|
/* Stack items vertically */
|
||||||
justify-content: center; /* Center items vertically within the container */
|
align-items: center;
|
||||||
|
/* Center items horizontally within the container */
|
||||||
|
justify-content: center;
|
||||||
|
/* Center items vertically within the container */
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure the text is wrapped in a container with a class, e.g., .progress-text */
|
/* Ensure the text is wrapped in a container with a class, e.g., .progress-text */
|
||||||
.progress-text {
|
.progress-text {
|
||||||
text-align: right; /* Center the text horizontally */
|
text-align: right;
|
||||||
margin-top: 10px; /* Add space between the spinner and the text */
|
/* Center the text horizontally */
|
||||||
}
|
margin-top: 10px;
|
||||||
|
/* Add space between the spinner and the text */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#progressIndicator {
|
#progressIndicator {
|
||||||
/* Adjust this if you want to position the spinning disk differently */
|
/* Adjust this if you want to position the spinning disk differently */
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
@@ -413,6 +418,78 @@
|
|||||||
margin-left: 45px;
|
margin-left: 45px;
|
||||||
/* Adjust the value as needed */
|
/* Adjust the value as needed */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pagination Buttons Styling */
|
||||||
|
.pagination-btn {
|
||||||
|
padding: 5px 10px;
|
||||||
|
/* Adjust padding to reduce height */
|
||||||
|
font-size: 0.8em;
|
||||||
|
/* Adjust font size for smaller buttons */
|
||||||
|
line-height: 1;
|
||||||
|
/* Adjust line height to match the smaller size */
|
||||||
|
margin: 2px;
|
||||||
|
/* Optional: adjust spacing between buttons */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smaller input field for jump-to-page */
|
||||||
|
.pagination-input {
|
||||||
|
height: 20px;
|
||||||
|
/* Adjust height for smaller input field */
|
||||||
|
padding: 2px 5px;
|
||||||
|
/* Adjust padding for smaller input field */
|
||||||
|
font-size: 0.8em;
|
||||||
|
/* Adjust font size for smaller input field */
|
||||||
|
line-height: 1;
|
||||||
|
/* Adjust line height to match the smaller size */
|
||||||
|
vertical-align: middle;
|
||||||
|
/* Align with buttons if necessary */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply the styles to your pagination controls */
|
||||||
|
#paginationControls button {
|
||||||
|
/* Apply the button styling */
|
||||||
|
height: auto;
|
||||||
|
/* Let padding control the height */
|
||||||
|
min-height: 20px;
|
||||||
|
/* Set a minimum height if needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
#paginationControls input[type="number"] {
|
||||||
|
/* Apply the input styling */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smaller input field for jump-to-page */
|
||||||
|
.pagination-input {
|
||||||
|
height: 2px;
|
||||||
|
/* Reduce the height */
|
||||||
|
padding: 0px 0px;
|
||||||
|
/* Reduce the padding to decrease overall height */
|
||||||
|
font-size: 0.8em;
|
||||||
|
/* Optionally reduce the font size */
|
||||||
|
margin-bottom: 13px;
|
||||||
|
margin-top: 2px;
|
||||||
|
border: 0px solid #ccc;
|
||||||
|
/* Adjust border styling if needed */
|
||||||
|
line-height: normal;
|
||||||
|
/* Adjust line height to match the new size */
|
||||||
|
vertical-align: top;
|
||||||
|
/* Align vertically with buttons */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply the styles to your pagination controls */
|
||||||
|
#paginationControls1 button {
|
||||||
|
/* Apply the button styling */
|
||||||
|
height: auto;
|
||||||
|
/* Let padding control the height */
|
||||||
|
min-height: 20px;
|
||||||
|
/* Set a minimum height if needed */
|
||||||
|
margin-top: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
#paginationControls1 input[type="number"] {
|
||||||
|
/* Apply the input styling */
|
||||||
|
margin-top: 15px
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
@@ -469,6 +546,7 @@
|
|||||||
<select id="playMode" name="playMode" class="fixed-size-dropdown">
|
<select id="playMode" name="playMode" class="fixed-size-dropdown">
|
||||||
<option value="local" selected>In Browser</option>
|
<option value="local" selected>In Browser</option>
|
||||||
<option value="remote">On Server</option>
|
<option value="remote">On Server</option>
|
||||||
|
<option value="none">No Playback</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -524,54 +602,93 @@
|
|||||||
<button onclick="exportToJSON()" style="margin-right: 10px;">Export List as JSON</button>
|
<button onclick="exportToJSON()" style="margin-right: 10px;">Export List as JSON</button>
|
||||||
<button onclick="importFromJSON()" style="margin-right: 10px;">Import JSON to list</button>
|
<button onclick="importFromJSON()" style="margin-right: 10px;">Import JSON to list</button>
|
||||||
<button onclick="exportMP3()" style="margin-right: 10px;">Export to Wav</button>
|
<button onclick="exportMP3()" style="margin-right: 10px;">Export to Wav</button>
|
||||||
|
<label for="splitEvery">Split every:</label>
|
||||||
|
<select id="splitEvery" name="splitEvery">
|
||||||
|
<option value="100">100</option>
|
||||||
|
<option value="200">200</option>
|
||||||
|
<option value="300">300</option>
|
||||||
|
<option value="400">400</option>
|
||||||
|
<option value="500" selected>500</option>
|
||||||
|
<option value="600">600</option>
|
||||||
|
</select>
|
||||||
|
<div id="paginationControls">
|
||||||
|
<button id="prevPage" class="pagination-btn">Previous</button>
|
||||||
|
<span id="pageIndicator">Page 1 of X</span>
|
||||||
|
<button id="nextPage" class="pagination-btn">Next</button>
|
||||||
|
<input type="number" id="jumpToPage" class="pagination-input" min="1" placeholder="Jump to Page">
|
||||||
|
<button id="jumpButton" class="pagination-btn">Go</button>
|
||||||
|
</div>
|
||||||
<div id="ttsListDisplay" class="tts-list-display">
|
<div id="ttsListDisplay" class="tts-list-display">
|
||||||
<!-- Dynamically populated TTS details will go here -->
|
<!-- Dynamically populated TTS details will go here -->
|
||||||
</div>
|
</div>
|
||||||
|
<div id="paginationControls1">
|
||||||
|
<button id="prevPage1" class="pagination-btn">Previous</button>
|
||||||
|
<span id="pageIndicator1">Page 1 of X</span>
|
||||||
|
<button id="nextPage1" class="pagination-btn">Next</button>
|
||||||
|
<input type="number" id="jumpToPage1" class="pagination-input" min="1" placeholder="Jump to Page">
|
||||||
|
<button id="jumpButton1" class="pagination-btn">Go</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Howler.js script inclusion -->
|
<!-- Howler.js script inclusion -->
|
||||||
<script src="./howler.min.js"></script>
|
<script src="./howler.min.js"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
// Import Crunker from the CDN
|
|
||||||
import Crunker from './crunker.esm.js';
|
import Crunker from './crunker.esm.js';
|
||||||
|
|
||||||
async function exportMP3() {
|
async function exportMP3() {
|
||||||
const crunker = new Crunker();
|
const crunker = new Crunker();
|
||||||
const fileUrls = ttsList.map(item => item.fileUrl);
|
const fileUrls = ttsList.map(item => item.fileUrl);
|
||||||
|
const splitEvery = parseInt(document.getElementById('splitEvery').value) || 500;
|
||||||
const progressIndicator = document.getElementById('progressIndicator');
|
const progressIndicator = document.getElementById('progressIndicator');
|
||||||
const progressText = document.getElementById('progressText');
|
const progressText = document.getElementById('progressText');
|
||||||
|
|
||||||
// Show progress indicator and update text
|
|
||||||
if (progressIndicator && progressText) {
|
if (progressIndicator && progressText) {
|
||||||
progressText.textContent = 'Export';
|
|
||||||
progressText.style.color = 'black'; // Set the text color to black
|
|
||||||
progressIndicator.style.display = 'block';
|
progressIndicator.style.display = 'block';
|
||||||
|
progressText.textContent = 'Starting export...';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const audioBuffers = await crunker.fetchAudio(...fileUrls);
|
for (let i = 0; i < fileUrls.length; i += splitEvery) {
|
||||||
|
const currentBatch = Math.floor(i / splitEvery) + 1;
|
||||||
|
const totalBatches = Math.ceil(fileUrls.length / splitEvery);
|
||||||
|
if (progressText) {
|
||||||
|
progressText.textContent = `Processing batch ${currentBatch} of ${totalBatches}`;
|
||||||
|
progressText.style.color = 'black'; // Set the text color to black
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchUrls = fileUrls.slice(i, Math.min(i + splitEvery, fileUrls.length));
|
||||||
|
const audioBuffers = await crunker.fetchAudio(...batchUrls);
|
||||||
const combined = crunker.concatAudio(audioBuffers);
|
const combined = crunker.concatAudio(audioBuffers);
|
||||||
const output = crunker.export(combined, "audio/wav");
|
const output = crunker.export(combined, "audio/wav");
|
||||||
crunker.download(output.blob, "combined_audio"); // This will download the file as 'combined_audio.mp3'
|
crunker.download(output.blob, `combined_audio_part_${currentBatch}.wav`);
|
||||||
|
|
||||||
|
// Clear audioBuffers array to free up memory
|
||||||
|
audioBuffers.length = 0;
|
||||||
|
|
||||||
|
console.log(`Exported batch ${currentBatch} of ${totalBatches}`);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error combining files:', error);
|
console.error('Error combining files:', error);
|
||||||
if (progressText) {
|
if (progressText) {
|
||||||
progressText.textContent = 'Error during export!';
|
progressText.textContent = 'Error during export!';
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// Hide progress indicator after completion or error
|
if (progressIndicator && progressText) {
|
||||||
if (progressIndicator) {
|
|
||||||
progressIndicator.style.display = 'none';
|
progressIndicator.style.display = 'none';
|
||||||
|
progressText.textContent = ''; // Reset progress text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach exportMP3 to the window object to make it globally accessible
|
window.exportMP3 = exportMP3;
|
||||||
window.exportMP3 = exportMP3;
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
@@ -582,7 +699,7 @@
|
|||||||
let audioQueue = [];
|
let audioQueue = [];
|
||||||
let currentlyPlayingIndex = 0;
|
let currentlyPlayingIndex = 0;
|
||||||
let isPlaying = false;
|
let isPlaying = false;
|
||||||
let playModeValue = 'local';
|
let playModeValue = localStorage.getItem('playMode') || 'local';
|
||||||
let currentSound = null;
|
let currentSound = null;
|
||||||
let preloadThreshold = 2; // Number of files to preload before starting playback
|
let preloadThreshold = 2; // Number of files to preload before starting playback
|
||||||
let isStreamInitialized = false;
|
let isStreamInitialized = false;
|
||||||
@@ -591,6 +708,9 @@
|
|||||||
const howlInstances = {};
|
const howlInstances = {};
|
||||||
let listPlaying = false;
|
let listPlaying = false;
|
||||||
let listPlayIndex = 0;
|
let listPlayIndex = 0;
|
||||||
|
let currentPage = 1;
|
||||||
|
let itemsPerPage = 100; // Adjust as needed
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////
|
////////////////////////////////////
|
||||||
//////// Progress Indicator ////////
|
//////// Progress Indicator ////////
|
||||||
@@ -602,7 +722,7 @@
|
|||||||
|
|
||||||
if (progressIndicator && generationProgress) {
|
if (progressIndicator && generationProgress) {
|
||||||
let message = '';
|
let message = '';
|
||||||
console.log("PlayType is:", playType);
|
//console.log("PlayType is:", playType);
|
||||||
if (playType === 'stream') {
|
if (playType === 'stream') {
|
||||||
// When streaming, display a different message
|
// When streaming, display a different message
|
||||||
message = '<span class="highlight-count">      Streaming...</span>';
|
message = '<span class="highlight-count">      Streaming...</span>';
|
||||||
@@ -650,13 +770,13 @@
|
|||||||
const volume = parseFloat(document.getElementById("volume").value);
|
const volume = parseFloat(document.getElementById("volume").value);
|
||||||
const chunkSize = parseInt(document.getElementById("chunkSize").value);
|
const chunkSize = parseInt(document.getElementById("chunkSize").value);
|
||||||
const playType = document.getElementById('playType').value; // Play type (chunk or stream)
|
const playType = document.getElementById('playType').value; // Play type (chunk or stream)
|
||||||
|
playModeValue = document.getElementById('playMode').value;
|
||||||
|
|
||||||
let cleanedText = textInput.replace(/ \- | \– /g, ' ');
|
let cleanedText = textInput.replace(/ \- | \– /g, ' ');
|
||||||
cleanedText = cleanedText.replace(/%/g, " percent");
|
cleanedText = cleanedText.replace(/%/g, " percent");
|
||||||
cleanedText = cleanedText.replace(/\.\s'/g, ".'");
|
cleanedText = cleanedText.replace(/\.\s'/g, ".'");
|
||||||
cleanedText = cleanedText.replace(/[^a-zA-Z0-9\s\.,;:!?\-\'"À-ÿ\u0400-\u04FF\u0150\u0151\u0170\u0171]/g, '');
|
cleanedText = cleanedText.replace(/[^a-zA-Z0-9\s\.,;:!?\-\'"À-ÿ\u0400-\u04FF\u0150\u0151\u0170\u0171]\$/g, '');
|
||||||
//let sentences = cleanedText.match(/("[^"]+"|[^.!?]+[.!?]+)/g);
|
let sentences = cleanedText.split(/(?<=[.!?:])\s+/);
|
||||||
let sentences = cleanedText.match(/(?:\d+\.\d+|[^\d.!?])+\b[.!?]+(?=\s|$)/g);
|
|
||||||
|
|
||||||
if (!sentences) {
|
if (!sentences) {
|
||||||
console.error("No sentences found in the input text.");
|
console.error("No sentences found in the input text.");
|
||||||
@@ -686,9 +806,14 @@
|
|||||||
if (stopTTSGeneration) break;
|
if (stopTTSGeneration) break;
|
||||||
showProgress(i + 1, totalChunks, playType);
|
showProgress(i + 1, totalChunks, playType);
|
||||||
if (playModeValue === 'local') {
|
if (playModeValue === 'local') {
|
||||||
|
// Generate the chunk and play it locally
|
||||||
await generateAndCache(chunks[i], language, characterVoice, volume);
|
await generateAndCache(chunks[i], language, characterVoice, volume);
|
||||||
} else {
|
} else if (playModeValue === 'remote') {
|
||||||
|
// Generate the chunk and play it remotely
|
||||||
await generateAndPlayRemote(chunks[i], language, characterVoice, volume);
|
await generateAndPlayRemote(chunks[i], language, characterVoice, volume);
|
||||||
|
} else if (playModeValue === 'none') {
|
||||||
|
// Generate the chunk but do not play it
|
||||||
|
await generateWithoutPlayback(chunks[i], language, characterVoice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -736,6 +861,7 @@
|
|||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.output_file_url) {
|
if (result.output_file_url) {
|
||||||
|
//console.log('TTS Generation Result:', result);
|
||||||
// If an ID was passed in, we're regenerating an existing item
|
// If an ID was passed in, we're regenerating an existing item
|
||||||
if (id !== null) {
|
if (id !== null) {
|
||||||
let itemIndex = ttsList.findIndex(item => item.id === id);
|
let itemIndex = ttsList.findIndex(item => item.id === id);
|
||||||
@@ -749,6 +875,15 @@
|
|||||||
// No ID was passed in, so we add a new item to the list
|
// No ID was passed in, so we add a new item to the list
|
||||||
addToTTSList(result.output_file_url, chunk, characterVoice, language);
|
addToTTSList(result.output_file_url, chunk, characterVoice, language);
|
||||||
}
|
}
|
||||||
|
const newPage = Math.ceil(ttsList.length / itemsPerPage);
|
||||||
|
//console.log('New Page Calculated:', newPage);
|
||||||
|
if (currentPage !== newPage) {
|
||||||
|
currentPage = newPage; // Move to the new page where the item was added
|
||||||
|
//console.log('Updating Current Page to:', currentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the list display to reflect the new pagination state
|
||||||
|
updateUIList();
|
||||||
|
|
||||||
// Update the audio queue and UI
|
// Update the audio queue and UI
|
||||||
const sound = new Howl({
|
const sound = new Howl({
|
||||||
@@ -770,6 +905,56 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generateWithoutPlayback(chunk, language, characterVoice, id = null) {
|
||||||
|
// Perform the generation process as usual but do not play the audio
|
||||||
|
try {
|
||||||
|
const data = new URLSearchParams({
|
||||||
|
text_input: chunk.trim(),
|
||||||
|
text_filtering: "standard",
|
||||||
|
character_voice_gen: characterVoice,
|
||||||
|
narrator_enabled: "false",
|
||||||
|
narrator_voice_gen: characterVoice,
|
||||||
|
text_not_inside: "character",
|
||||||
|
language: language,
|
||||||
|
output_file_name: "TTS",
|
||||||
|
output_file_timestamp: "true",
|
||||||
|
autoplay: "false",
|
||||||
|
autoplay_volume: "0.1"
|
||||||
|
});
|
||||||
|
const response = await fetch("http://127.0.0.1:7851/api/tts-generate", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.output_file_url) {
|
||||||
|
// If an ID was passed in, we're regenerating an existing item
|
||||||
|
if (id !== null) {
|
||||||
|
let itemIndex = ttsList.findIndex(item => item.id === id);
|
||||||
|
if (itemIndex !== -1) {
|
||||||
|
ttsList[itemIndex].fileUrl = result.output_file_url;
|
||||||
|
// Other properties can be updated as needed, e.g., in case the language or voice has changed
|
||||||
|
ttsList[itemIndex].language = language;
|
||||||
|
ttsList[itemIndex].characterVoice = characterVoice;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No ID was passed in, so we add a new item to the list
|
||||||
|
addToTTSList(result.output_file_url, chunk, characterVoice, language);
|
||||||
|
}
|
||||||
|
const newPage = Math.ceil(ttsList.length / itemsPerPage);
|
||||||
|
if (currentPage !== newPage) {
|
||||||
|
currentPage = newPage; // Move to the new page where the item was added
|
||||||
|
}
|
||||||
|
updateUIList();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in generateWithoutPlayback:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function addToTTSList(fileUrl, text, characterVoice, language) {
|
function addToTTSList(fileUrl, text, characterVoice, language) {
|
||||||
try {
|
try {
|
||||||
ttsList.push({
|
ttsList.push({
|
||||||
@@ -785,6 +970,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function regenerateTTS(id) {
|
async function regenerateTTS(id) {
|
||||||
// Find the details of the TTS to regenerate
|
// Find the details of the TTS to regenerate
|
||||||
const ttsDetails = ttsList.find(item => item.id === id);
|
const ttsDetails = ttsList.find(item => item.id === id);
|
||||||
@@ -805,7 +991,13 @@
|
|||||||
}
|
}
|
||||||
listDisplay.innerHTML = ''; // Clear the current list
|
listDisplay.innerHTML = ''; // Clear the current list
|
||||||
|
|
||||||
ttsList.forEach(item => {
|
// Determine the range of items to display for pagination
|
||||||
|
const start = (currentPage - 1) * itemsPerPage;
|
||||||
|
const end = start + itemsPerPage;
|
||||||
|
const paginatedItems = ttsList.slice(start, end);
|
||||||
|
|
||||||
|
// Add items to the display for the current page
|
||||||
|
paginatedItems.forEach(item => {
|
||||||
// Extract just the file name from the URL
|
// Extract just the file name from the URL
|
||||||
const wavFileName = item.fileUrl.split('/').pop();
|
const wavFileName = item.fileUrl.split('/').pop();
|
||||||
|
|
||||||
@@ -815,7 +1007,6 @@
|
|||||||
const entryDiv = document.createElement('div');
|
const entryDiv = document.createElement('div');
|
||||||
entryDiv.id = `item-${item.id}`; // Assign an ID to the div for highlighting
|
entryDiv.id = `item-${item.id}`; // Assign an ID to the div for highlighting
|
||||||
entryDiv.classList.add('ttsListItem'); // Add a class for styling
|
entryDiv.classList.add('ttsListItem'); // Add a class for styling
|
||||||
entryDiv.id = `item-${item.id}`; // Make sure this is unique and correct
|
|
||||||
|
|
||||||
// Update the Play/Stop button's onclick to call a function that handles playing and highlighting
|
// Update the Play/Stop button's onclick to call a function that handles playing and highlighting
|
||||||
entryDiv.innerHTML = `
|
entryDiv.innerHTML = `
|
||||||
@@ -830,15 +1021,22 @@
|
|||||||
`;
|
`;
|
||||||
listDisplay.appendChild(entryDiv);
|
listDisplay.appendChild(entryDiv);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update pagination controls
|
||||||
|
const lastPage = Math.ceil(ttsList.length / itemsPerPage);
|
||||||
|
document.getElementById('pageIndicator').innerText = `Page ${currentPage} of ${lastPage}`;
|
||||||
|
document.getElementById('pageIndicator1').innerText = `Page ${currentPage} of ${lastPage}`;
|
||||||
|
document.getElementById('prevPage').disabled = (currentPage === 1);
|
||||||
|
document.getElementById('nextPage').disabled = (currentPage === lastPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function generateAndPlayRemote(chunk, language, characterVoice, volume) {
|
async function generateAndPlayRemote(chunk, language, characterVoice, volume) {
|
||||||
try {
|
try {
|
||||||
if (stopTTSGeneration) {
|
if (stopTTSGeneration) {
|
||||||
console.log("TTS generation stopped.");
|
console.log("TTS generation stopped.");
|
||||||
return; // Early return if stop flag is set
|
return; // Early return if stop flag is set
|
||||||
}
|
}
|
||||||
console.log("generateAndPlayRemote:", chunk);
|
|
||||||
const data = new URLSearchParams({
|
const data = new URLSearchParams({
|
||||||
text_input: chunk.trim(),
|
text_input: chunk.trim(),
|
||||||
text_filtering: "standard",
|
text_filtering: "standard",
|
||||||
@@ -914,13 +1112,10 @@
|
|||||||
src: [fileUrl],
|
src: [fileUrl],
|
||||||
volume: parseFloat(document.getElementById("volume").value),
|
volume: parseFloat(document.getElementById("volume").value),
|
||||||
onplay: function () {
|
onplay: function () {
|
||||||
console.log('Playback started for', fileUrl);
|
|
||||||
},
|
},
|
||||||
onend: function () {
|
onend: function () {
|
||||||
console.log('Playback finished for', fileUrl);
|
|
||||||
},
|
},
|
||||||
onpause: function () {
|
onpause: function () {
|
||||||
console.log('Playback paused for', fileUrl);
|
|
||||||
},
|
},
|
||||||
onloaderror: function (e) {
|
onloaderror: function (e) {
|
||||||
console.error('Load error for', fileUrl, ':', e);
|
console.error('Load error for', fileUrl, ':', e);
|
||||||
@@ -1055,15 +1250,26 @@
|
|||||||
function playNextLocalChunk() {
|
function playNextLocalChunk() {
|
||||||
if (audioQueue.length > 0 && currentlyPlayingIndex < audioQueue.length) {
|
if (audioQueue.length > 0 && currentlyPlayingIndex < audioQueue.length) {
|
||||||
isPlaying = true;
|
isPlaying = true;
|
||||||
audioQueue[currentlyPlayingIndex++].play();
|
let currentSound = audioQueue[currentlyPlayingIndex];
|
||||||
|
currentSound.play();
|
||||||
|
|
||||||
|
// Once a sound finishes playing, unload it
|
||||||
|
currentSound.on('end', () => {
|
||||||
|
currentSound.unload();
|
||||||
|
});
|
||||||
|
|
||||||
|
currentlyPlayingIndex++;
|
||||||
} else {
|
} else {
|
||||||
isPlaying = false;
|
isPlaying = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function stopPlayback() {
|
function stopPlayback() {
|
||||||
Howler.stop();
|
Howler.stop();
|
||||||
audioQueue = [];
|
audioQueue = [];
|
||||||
|
// Unload each sound in the queue
|
||||||
|
audioQueue.forEach(sound => sound.unload());
|
||||||
currentlyPlayingIndex = 0;
|
currentlyPlayingIndex = 0;
|
||||||
isPlaying = false;
|
isPlaying = false;
|
||||||
}
|
}
|
||||||
@@ -1103,7 +1309,6 @@
|
|||||||
const startFromIdElement = document.getElementById('startFromId');
|
const startFromIdElement = document.getElementById('startFromId');
|
||||||
if (startFromIdElement) {
|
if (startFromIdElement) {
|
||||||
startFromIdElement.setAttribute('value', '1');
|
startFromIdElement.setAttribute('value', '1');
|
||||||
console.log('Start From ID set to 1');
|
|
||||||
}
|
}
|
||||||
// Other initializations...
|
// Other initializations...
|
||||||
});
|
});
|
||||||
@@ -1123,6 +1328,7 @@
|
|||||||
// and highlight the corresponding item
|
// and highlight the corresponding item
|
||||||
playListFromIndex(startIndex);
|
playListFromIndex(startIndex);
|
||||||
highlightPlaying(startIndex + 1); // Assuming the list items are 1-indexed
|
highlightPlaying(startIndex + 1); // Assuming the list items are 1-indexed
|
||||||
|
updateCurrentPage(startIndex + 1); // Update the current page based on the playing item
|
||||||
} else {
|
} else {
|
||||||
// If list is set to pause, pause the current item
|
// If list is set to pause, pause the current item
|
||||||
// and remove highlighting
|
// and remove highlighting
|
||||||
@@ -1155,6 +1361,26 @@
|
|||||||
if (currentItem) {
|
if (currentItem) {
|
||||||
currentItem.classList.add('highlight');
|
currentItem.classList.add('highlight');
|
||||||
}
|
}
|
||||||
|
updateCurrentPage(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateCurrentPage(itemIndex) {
|
||||||
|
// Calculate the page number based on the item index
|
||||||
|
// Note: Ensure that the itemIndex is 1-based; if it's 0-based, add 1.
|
||||||
|
const newPage = Math.ceil(itemIndex / itemsPerPage);
|
||||||
|
|
||||||
|
// Update the current page and UI if the new page is different from the current page
|
||||||
|
if (currentPage !== newPage) {
|
||||||
|
currentPage = newPage; // Set the new current page
|
||||||
|
updateUIList(); // Refresh the list display to show the correct page
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally, update the 'Jump to Page' input to reflect the current page
|
||||||
|
const jumpToPageInput = document.getElementById('jumpToPage');
|
||||||
|
if (jumpToPageInput) {
|
||||||
|
jumpToPageInput.value = currentPage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1192,6 +1418,7 @@
|
|||||||
console.log('Index is out of bounds, stopping playback.');
|
console.log('Index is out of bounds, stopping playback.');
|
||||||
// Clear any highlights if out of bounds
|
// Clear any highlights if out of bounds
|
||||||
highlightPlaying(null);
|
highlightPlaying(null);
|
||||||
|
updateUIList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1213,6 +1440,14 @@
|
|||||||
if (startFromIdElement) {
|
if (startFromIdElement) {
|
||||||
startFromIdElement.value = '1';
|
startFromIdElement.value = '1';
|
||||||
}
|
}
|
||||||
|
const pageIndicator = document.getElementById('pageIndicator');
|
||||||
|
if (pageIndicator) {
|
||||||
|
pageIndicator.textContent = 'Page 1 of 1';
|
||||||
|
}
|
||||||
|
const pageIndicator1 = document.getElementById('pageIndicator1');
|
||||||
|
if (pageIndicator1) {
|
||||||
|
pageIndicator1.textContent = 'Page 1 of 1';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function editText(id) {
|
function editText(id) {
|
||||||
@@ -1300,7 +1535,7 @@
|
|||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
input.accept = '.json';
|
input.accept = '.json';
|
||||||
|
|
||||||
input.onchange = (e) => {
|
input.onchange = async (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@@ -1308,13 +1543,11 @@
|
|||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
try {
|
try {
|
||||||
const jsonData = JSON.parse(e.target.result);
|
const jsonData = JSON.parse(e.target.result);
|
||||||
console.log('JSON data imported:', jsonData);
|
|
||||||
|
|
||||||
// Update the ttsList with the imported data
|
|
||||||
ttsList = jsonData;
|
ttsList = jsonData;
|
||||||
|
currentPage = 1; // Reset to the first page
|
||||||
// Refresh the on-screen list
|
updateUIList();
|
||||||
updateUIList(); // This function updates the UI with the contents of ttsList
|
updatePaginationControls(); // Update pagination controls
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing JSON:', error);
|
console.error('Error parsing JSON:', error);
|
||||||
}
|
}
|
||||||
@@ -1323,8 +1556,34 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
input.click();
|
input.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePaginationControls() {
|
||||||
|
const lastPage = Math.ceil(ttsList.length / itemsPerPage);
|
||||||
|
const pageIndicator = document.getElementById('pageIndicator');
|
||||||
|
if (pageIndicator) {
|
||||||
|
pageIndicator.textContent = `Page ${currentPage} of ${lastPage}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const prevPageButton = document.getElementById('prevPage');
|
||||||
|
if (prevPageButton) {
|
||||||
|
prevPageButton.disabled = (currentPage === 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPageButton = document.getElementById('nextPage');
|
||||||
|
if (nextPageButton) {
|
||||||
|
nextPageButton.disabled = (currentPage === lastPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar updates for any additional pagination controls if you have them
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
//////// Other Things ////////
|
//////// Other Things ////////
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
@@ -1425,6 +1684,70 @@
|
|||||||
//////// General Dcoument Listeners ////////
|
//////// General Dcoument Listeners ////////
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Event listeners for pagination controls
|
||||||
|
document.getElementById('prevPage').addEventListener('click', () => {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
updateUIList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('nextPage').addEventListener('click', () => {
|
||||||
|
const lastPage = Math.ceil(ttsList.length / itemsPerPage);
|
||||||
|
if (currentPage < lastPage) {
|
||||||
|
currentPage++;
|
||||||
|
updateUIList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize the list on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', updateUIList);
|
||||||
|
|
||||||
|
|
||||||
|
// For the 'Previous' button at the bottom
|
||||||
|
document.getElementById('prevPage1').addEventListener('click', () => {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
updateUIList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// For the 'Next' button at the bottom
|
||||||
|
document.getElementById('nextPage1').addEventListener('click', () => {
|
||||||
|
let lastPage = Math.ceil(ttsList.length / itemsPerPage);
|
||||||
|
if (currentPage < lastPage) {
|
||||||
|
currentPage++;
|
||||||
|
updateUIList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleJumpButtonClick(event) {
|
||||||
|
// Determine which set of controls to use based on the button that was clicked
|
||||||
|
const suffix = event.target.id === 'jumpButton' ? '' : '1';
|
||||||
|
const jumpToInput = document.getElementById(`jumpToPage${suffix}`);
|
||||||
|
const jumpToValue = jumpToInput.value.trim(); // Get the trimmed value
|
||||||
|
const jumpTo = parseInt(jumpToValue, 10); // Parse the value as an integer
|
||||||
|
|
||||||
|
if (!isNaN(jumpTo)) {
|
||||||
|
let lastPage = Math.ceil(ttsList.length / itemsPerPage);
|
||||||
|
|
||||||
|
if (jumpTo >= 1 && jumpTo <= lastPage) {
|
||||||
|
currentPage = jumpTo;
|
||||||
|
updateUIList();
|
||||||
|
} else {
|
||||||
|
alert(`Please enter a valid page number between 1 and ${lastPage}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert("Please enter a number to jump to.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the event listener for both jump buttons
|
||||||
|
document.getElementById('jumpButton').addEventListener('click', handleJumpButtonClick);
|
||||||
|
document.getElementById('jumpButton1').addEventListener('click', handleJumpButtonClick);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Event listener for changing play type
|
// Event listener for changing play type
|
||||||
document.getElementById('playType').addEventListener('change', function () {
|
document.getElementById('playType').addEventListener('change', function () {
|
||||||
isStreamingMode = this.value === 'stream';
|
isStreamingMode = this.value === 'stream';
|
||||||
@@ -1546,11 +1869,13 @@
|
|||||||
adjustPlaybackAndChunkSize(savedPlayType); // Adjust selectability on page load
|
adjustPlaybackAndChunkSize(savedPlayType); // Adjust selectability on page load
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize playback based on preference
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const savedPlayMode = localStorage.getItem('playMode');
|
const savedPlayMode = localStorage.getItem('playMode');
|
||||||
if (savedPlayMode) {
|
if (savedPlayMode) {
|
||||||
document.getElementById('playMode').value = savedPlayMode;
|
document.getElementById('playMode').value = savedPlayMode;
|
||||||
}
|
}
|
||||||
|
playModeValue = savedPlayMode || 'local'; // Default to 'local' if nothing is saved
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize chunk size based on preference
|
// Initialize chunk size based on preference
|
||||||
const savedChunkSize = localStorage.getItem('chunkSize');
|
const savedChunkSize = localStorage.getItem('chunkSize');
|
||||||
|
|||||||
+3
-3
@@ -1006,7 +1006,7 @@ async def tts_generate(
|
|||||||
# Replace multiple exclamation marks, question marks, or other punctuation with a single instance
|
# Replace multiple exclamation marks, question marks, or other punctuation with a single instance
|
||||||
cleaned_part = re.sub(r'([!?.])\1+', r'\1', part)
|
cleaned_part = re.sub(r'([!?.])\1+', r'\1', part)
|
||||||
# Further clean to remove any other unwanted characters
|
# Further clean to remove any other unwanted characters
|
||||||
cleaned_part = re.sub(r'[^a-zA-Z0-9\s\.,;:!?\-\'"\u0400-\u04FFÀ-ÿ\u0150\u0151\u0170\u0171\$]', '', cleaned_part)
|
cleaned_part = re.sub(r'[^a-zA-Z0-9\s\.,;:!?\-\'"\u0400-\u04FFÀ-ÿ\u0150\u0151\u0170\u0171]\$', '', cleaned_part)
|
||||||
# Remove all newline characters (single or multiple)
|
# Remove all newline characters (single or multiple)
|
||||||
cleaned_part = re.sub(r'\n+', ' ', cleaned_part)
|
cleaned_part = re.sub(r'\n+', ' ', cleaned_part)
|
||||||
output_file = this_dir / "outputs" / f"{output_file_name}_{uuid.uuid4()}_{int(time.time())}.wav"
|
output_file = this_dir / "outputs" / f"{output_file_name}_{uuid.uuid4()}_{int(time.time())}.wav"
|
||||||
@@ -1030,13 +1030,13 @@ async def tts_generate(
|
|||||||
cleaned_string = html.unescape(standard_filtering(text_input))
|
cleaned_string = html.unescape(standard_filtering(text_input))
|
||||||
cleaned_string = re.sub(r'([!?.])\1+', r'\1', text_input)
|
cleaned_string = re.sub(r'([!?.])\1+', r'\1', text_input)
|
||||||
# Further clean to remove any other unwanted characters
|
# Further clean to remove any other unwanted characters
|
||||||
cleaned_string = re.sub(r'[^a-zA-Z0-9\s\.,;:!?\-\'"\u0400-\u04FFÀ-ÿ\u0150\u0151\u0170\u0171\$]', '', cleaned_string)
|
cleaned_string = re.sub(r'[^a-zA-Z0-9\s\.,;:!?\-\'"\u0400-\u04FFÀ-ÿ\u0150\u0151\u0170\u0171]\$', '', cleaned_string)
|
||||||
# Remove all newline characters (single or multiple)
|
# Remove all newline characters (single or multiple)
|
||||||
cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
|
cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
|
||||||
elif text_filtering == "standard":
|
elif text_filtering == "standard":
|
||||||
cleaned_string = re.sub(r'([!?.])\1+', r'\1', text_input)
|
cleaned_string = re.sub(r'([!?.])\1+', r'\1', text_input)
|
||||||
# Further clean to remove any other unwanted characters
|
# Further clean to remove any other unwanted characters
|
||||||
cleaned_string = re.sub(r'[^a-zA-Z0-9\s\.,;:!?\-\'"\u0400-\u04FFÀ-ÿ\u0150\u0151\u0170\u0171\$]', '', cleaned_string)
|
cleaned_string = re.sub(r'[^a-zA-Z0-9\s\.,;:!?\-\'"\u0400-\u04FFÀ-ÿ\u0150\u0151\u0170\u0171]\$', '', cleaned_string)
|
||||||
# Remove all newline characters (single or multiple)
|
# Remove all newline characters (single or multiple)
|
||||||
cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
|
cleaned_string = re.sub(r'\n+', ' ', cleaned_string)
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in new issue
Block a user