From 4f80b39cd06fe390e892e8e0ebb900ef292052b3 Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Sat, 6 Jan 2024 20:49:58 +0000 Subject: [PATCH 01/18] Update README.md --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 08c8d35..62278fd 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ # AllTalk TTS AllTalk is an updated version of the Coqui_tts extension for Text Generation web UI. Features include: +- **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. +- **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. +- **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) - **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. +- **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. ## Index @@ -22,6 +23,7 @@ AllTalk is an updated version of the Coqui_tts extension for Text Generation web - ⚫ [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) +- ⬜ [AllTalk TTS Generator](https://github.com/erew123/alltalk_tts?#-alltalk-tts-generator) - 🟠 [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) @@ -149,6 +151,7 @@ Assuming its all working fine and you are happy, you can delete the old alltalk_ |:---:|:---:| |![image](https://github.com/erew123/alltalk_tts/assets/35898566/b0e13dba-c6b1-4ab7-845d-244ac1158330) | ![image](https://github.com/erew123/alltalk_tts/assets/35898566/e35e987c-543a-486b-b4fb-ee6ebe6f59c6) | |![image](https://github.com/erew123/alltalk_tts/assets/35898566/68edc968-bdd2-4f98-9d56-b9ca40decf26) | ![image](https://github.com/erew123/alltalk_tts/assets/35898566/eb5e617b-2345-418d-8267-a9d30f705a10) | +|![image](https://github.com/erew123/alltalk_tts/assets/35898566/4bd0f02e-298d-4e46-95ea-afc003c1cb70) | ## 🟨 Help with problems ####      🔄 **Minor updates/bug fixes list** can be found [here](https://github.com/erew123/alltalk_tts/issues/25) @@ -538,6 +541,39 @@ 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.

+## ⬜ 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.

The link to open the TTS generator can be found on the built-in Settings and Documentation page. + +#### ⬜ Quick Start +- **Text Input:** Enter the text you wish to convert into speech in the 'Text Input' box. +- **Generate TTS:** Hit this to start the text-to-speech conversion. +- **Pause/Resume:** Used to pause and resume the playback of the initial generation of wavs or the stream. +- **Stop Playback:** This will stop the current audio playing back. It does not stop the text from being generated however. +Once you have sent text off to be generated, either as a stream or wav file generation, the TTS server will remain busy until this process has competed. As such, think carefully as to how much you want to send to the server. +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 +- **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. +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”. +#### ⬜ Playback and List Management +- **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. +- **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. +#### ⬜ Exporting Your Audio +- **Export to WAV:** Combine all generated TTS from the list, into one single WAV file for easy download and distribution. +#### ⬜ Customization and Preferences +- **Character Voice:** Choose the voice that will read your text. +- **Language:** Select the language of your text. +- **Chunk Sizes:** Decide the size of text chunks for generation. Smaller sizes are recommended for better TTS quality. +#### ⬜ Interface and Accessibility +- **Dark/Light Mode:** Switch between themes for your visual comfort. +- **Word Count and Generation Queue:** Keep track of the word count and the generation progress. +#### ⬜ Notes on Usage +- For seamless TTS generation, it's advised to keep text chunks under 250 characters, which you can control with the Chunk sizes. +- Generated audio can be played back from the list, which also highlights the currently playing chunk. +- The TTS Generator remembers your settings, so you can pick up where you left off even after refreshing the page. + ## 🟠 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. @@ -713,7 +749,6 @@ 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. +- SillyTavern Support - Possibly add some additional TTS engines (TBD). - Have a break! From 7cb206e531ca923eddca336e06f74c349bf20b67 Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Sat, 6 Jan 2024 21:19:25 +0000 Subject: [PATCH 02/18] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62278fd..1f7c377 100644 --- a/README.md +++ b/README.md @@ -542,7 +542,7 @@ 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.

## ⬜ 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.

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.

The link to open the TTS generator can be found on the built-in Settings and Documentation page.

**DeepSpeed** is **highly** recommended to speed generation. #### ⬜ Quick Start - **Text Input:** Enter the text you wish to convert into speech in the 'Text Input' box. From dedc13b3b6232c8e484a55a2a45e8c1d5d2c873e Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Sat, 6 Jan 2024 22:05:10 +0000 Subject: [PATCH 03/18] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1f7c377..bdfdd5a 100644 --- a/README.md +++ b/README.md @@ -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) - **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). -- **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) - **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) @@ -542,7 +542,7 @@ 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.

## ⬜ 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.

The link to open the TTS generator can be found on the built-in Settings and Documentation page.

**DeepSpeed** is **highly** recommended to speed generation. +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)

The link to open the TTS generator can be found on the built-in Settings and Documentation page.

**DeepSpeed** is **highly** recommended to speed generation. #### ⬜ Quick Start - **Text Input:** Enter the text you wish to convert into speech in the 'Text Input' box. From 6e15d43e630ce28c19eb0151d412a670abd4c43f Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Sat, 6 Jan 2024 22:21:22 +0000 Subject: [PATCH 04/18] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bdfdd5a..3a84777 100644 --- a/README.md +++ b/README.md @@ -553,7 +553,7 @@ 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. #### ⬜ 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. -- **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.

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”. #### ⬜ Playback and List Management - **Playback Controls:** Utilize 'Play List' to start from the beginning or 'Stop Playback' to halt at any time. From 4a727ce14a3a7a0f91049d936c0429e0c2f381df Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Sat, 6 Jan 2024 22:32:19 +0000 Subject: [PATCH 05/18] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a84777..75c20b3 100644 --- a/README.md +++ b/README.md @@ -542,7 +542,7 @@ 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.

## ⬜ 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. Please see here for a quick [demo](https://www.youtube.com/watch?v=hunvXn0mLzc)

The link to open the TTS generator can be found on the built-in Settings and Documentation page.

**DeepSpeed** is **highly** recommended to speed generation. +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)

The link to open the TTS generator can be found on the built-in Settings and Documentation page.

**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). #### ⬜ Quick Start - **Text Input:** Enter the text you wish to convert into speech in the 'Text Input' box. From f2c87dcdabd4783ac1ff6ca6333ba7286dc5387b Mon Sep 17 00:00:00 2001 From: nicobubulle Date: Sun, 7 Jan 2024 09:25:52 +0100 Subject: [PATCH 06/18] Fix greedy option for regex --- tts_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tts_server.py b/tts_server.py index ab15968..55adf7e 100644 --- a/tts_server.py +++ b/tts_server.py @@ -1006,7 +1006,7 @@ async def tts_generate( # Replace multiple exclamation marks, question marks, or other punctuation with a single instance cleaned_part = re.sub(r'([!?.])\1+', r'\1', part) # 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) cleaned_part = re.sub(r'\n+', ' ', cleaned_part) 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 = re.sub(r'([!?.])\1+', r'\1', text_input) # 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) cleaned_string = re.sub(r'\n+', ' ', cleaned_string) elif text_filtering == "standard": cleaned_string = re.sub(r'([!?.])\1+', r'\1', text_input) # 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) cleaned_string = re.sub(r'\n+', ' ', cleaned_string) else: From 1dc4b8bf507da205db57cb104bf06cb143c04087 Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Sun, 7 Jan 2024 08:38:39 +0000 Subject: [PATCH 07/18] Add $ to demo page regex --- templates/admin.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/admin.html b/templates/admin.html index d0a0c4b..3b23b2d 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -420,7 +420,8 @@ 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\$]/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 From d2adbd9c1e0327fa9f72d6fbf45c16307af61a4d Mon Sep 17 00:00:00 2001 From: nicobubulle Date: Sun, 7 Jan 2024 18:06:32 +0100 Subject: [PATCH 08/18] Fix greedy option for regex in HTML files --- templates/admin.html | 2 +- templates/tts_chunk_player/tts_chunk_player.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/admin.html b/templates/admin.html index 3b23b2d..ac595b7 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -421,7 +421,7 @@ 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]\$/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 diff --git a/templates/tts_chunk_player/tts_chunk_player.html b/templates/tts_chunk_player/tts_chunk_player.html index acecfc8..68c38da 100644 --- a/templates/tts_chunk_player/tts_chunk_player.html +++ b/templates/tts_chunk_player/tts_chunk_player.html @@ -654,7 +654,7 @@ let cleanedText = textInput.replace(/ \- | \– /g, ' '); cleanedText = cleanedText.replace(/%/g, " percent"); 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.match(/(?:\d+\.\d+|[^\d.!?])+\b[.!?]+(?=\s|$)/g); From a22830f700f59c359efae483f26e8988edbfdc3c Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Sun, 7 Jan 2024 21:33:33 +0000 Subject: [PATCH 09/18] Update tts_chunk_player.html bumped sentence splitting --- templates/tts_chunk_player/tts_chunk_player.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/templates/tts_chunk_player/tts_chunk_player.html b/templates/tts_chunk_player/tts_chunk_player.html index 68c38da..21d3c6d 100644 --- a/templates/tts_chunk_player/tts_chunk_player.html +++ b/templates/tts_chunk_player/tts_chunk_player.html @@ -655,8 +655,7 @@ cleanedText = cleanedText.replace(/%/g, " percent"); cleanedText = cleanedText.replace(/\.\s'/g, ".'"); cleanedText = cleanedText.replace(/[^a-zA-Z0-9\s\.,;:!?\-\'"À-ÿ\u0400-\u04FF\u0150\u0151\u0170\u0171]\$/g, ''); - //let sentences = cleanedText.match(/("[^"]+"|[^.!?]+[.!?]+)/g); - let sentences = cleanedText.match(/(?:\d+\.\d+|[^\d.!?])+\b[.!?]+(?=\s|$)/g); + let sentences = cleanedText.split(/(?<=[.!?:])\s+/); if (!sentences) { console.error("No sentences found in the input text."); @@ -1666,4 +1665,4 @@ - \ No newline at end of file + From ff4b9d9e997e8cb754e37b521cc475df62ebc5a7 Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Mon, 8 Jan 2024 00:56:36 +0000 Subject: [PATCH 10/18] Updates to reduce browser load Pagination of files list. No Playback option Better clearing of played back audio from memory --- .../tts_chunk_player/tts_chunk_player.html | 413 +++++++++++++++--- 1 file changed, 345 insertions(+), 68 deletions(-) diff --git a/templates/tts_chunk_player/tts_chunk_player.html b/templates/tts_chunk_player/tts_chunk_player.html index 21d3c6d..41082dc 100644 --- a/templates/tts_chunk_player/tts_chunk_player.html +++ b/templates/tts_chunk_player/tts_chunk_player.html @@ -175,27 +175,32 @@ } .progress-overlay { - position: absolute; - top: 10px; - right: 10px; - background: rgba(255, 255, 255, 0.9); - border-radius: 5px; - padding: 10px; - display: flex; - flex-direction: column; /* Stack items vertically */ - align-items: center; /* Center items horizontally within the container */ - justify-content: center; /* Center items vertically within the container */ - z-index: 10; -} + position: absolute; + top: 10px; + right: 10px; + background: rgba(255, 255, 255, 0.9); + border-radius: 5px; + padding: 10px; + display: flex; + flex-direction: column; + /* Stack items vertically */ + align-items: center; + /* Center items horizontally within the container */ + justify-content: center; + /* Center items vertically within the container */ + z-index: 10; + } -/* Ensure the text is wrapped in a container with a class, e.g., .progress-text */ -.progress-text { - text-align: right; /* Center the text horizontally */ - margin-top: 10px; /* Add space between the spinner and the text */ -} + /* Ensure the text is wrapped in a container with a class, e.g., .progress-text */ + .progress-text { + text-align: right; + /* 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 */ display: inline-block; margin-left: 20px; @@ -413,6 +418,78 @@ margin-left: 45px; /* 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 + } @@ -469,6 +546,7 @@ @@ -524,9 +602,23 @@ +
+ + Page 1 of X + + + +
+
+ + Page 1 of X + + + +
@@ -537,35 +629,35 @@ import Crunker from './crunker.esm.js'; async function exportMP3() { - const crunker = new Crunker(); - const fileUrls = ttsList.map(item => item.fileUrl); - const progressIndicator = document.getElementById('progressIndicator'); - const progressText = document.getElementById('progressText'); + const crunker = new Crunker(); + const fileUrls = ttsList.map(item => item.fileUrl); + const progressIndicator = document.getElementById('progressIndicator'); + const progressText = document.getElementById('progressText'); - // Show progress indicator and update text - if (progressIndicator && progressText) { - progressText.textContent = 'Export'; - progressText.style.color = 'black'; // Set the text color to black - progressIndicator.style.display = 'block'; - } - - try { - const audioBuffers = await crunker.fetchAudio(...fileUrls); - const combined = crunker.concatAudio(audioBuffers); - const output = crunker.export(combined, "audio/wav"); - crunker.download(output.blob, "combined_audio"); // This will download the file as 'combined_audio.mp3' - } catch (error) { - console.error('Error combining files:', error); - if (progressText) { - progressText.textContent = 'Error during export!'; + // Show progress indicator and update text + if (progressIndicator && progressText) { + progressText.textContent = 'Export'; + progressText.style.color = 'black'; // Set the text color to black + progressIndicator.style.display = 'block'; } - } finally { - // Hide progress indicator after completion or error - if (progressIndicator) { - progressIndicator.style.display = 'none'; + + try { + const audioBuffers = await crunker.fetchAudio(...fileUrls); + const combined = crunker.concatAudio(audioBuffers); + const output = crunker.export(combined, "audio/wav"); + crunker.download(output.blob, "combined_audio"); // This will download the file as 'combined_audio.mp3' + } catch (error) { + console.error('Error combining files:', error); + if (progressText) { + progressText.textContent = 'Error during export!'; + } + } finally { + // Hide progress indicator after completion or error + if (progressIndicator) { + progressIndicator.style.display = 'none'; + } } } -} // Attach exportMP3 to the window object to make it globally accessible window.exportMP3 = exportMP3; @@ -582,7 +674,7 @@ let audioQueue = []; let currentlyPlayingIndex = 0; let isPlaying = false; - let playModeValue = 'local'; + let playModeValue = localStorage.getItem('playMode') || 'local'; let currentSound = null; let preloadThreshold = 2; // Number of files to preload before starting playback let isStreamInitialized = false; @@ -591,6 +683,9 @@ const howlInstances = {}; let listPlaying = false; let listPlayIndex = 0; + let currentPage = 1; + let itemsPerPage = 100; // Adjust as needed + //////////////////////////////////// //////// Progress Indicator //////// @@ -602,7 +697,7 @@ if (progressIndicator && generationProgress) { let message = ''; - console.log("PlayType is:", playType); + //console.log("PlayType is:", playType); if (playType === 'stream') { // When streaming, display a different message message = '      Streaming...'; @@ -650,6 +745,7 @@ const volume = parseFloat(document.getElementById("volume").value); const chunkSize = parseInt(document.getElementById("chunkSize").value); const playType = document.getElementById('playType').value; // Play type (chunk or stream) + playModeValue = document.getElementById('playMode').value; let cleanedText = textInput.replace(/ \- | \– /g, ' '); cleanedText = cleanedText.replace(/%/g, " percent"); @@ -685,9 +781,14 @@ if (stopTTSGeneration) break; showProgress(i + 1, totalChunks, playType); if (playModeValue === 'local') { + // Generate the chunk and play it locally 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); + } else if (playModeValue === 'none') { + // Generate the chunk but do not play it + await generateWithoutPlayback(chunks[i], language, characterVoice); } } } @@ -735,6 +836,7 @@ const result = await response.json(); if (result.output_file_url) { + //console.log('TTS Generation Result:', result); // If an ID was passed in, we're regenerating an existing item if (id !== null) { let itemIndex = ttsList.findIndex(item => item.id === id); @@ -748,6 +850,15 @@ // 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); + //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 const sound = new Howl({ @@ -769,6 +880,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) { try { ttsList.push({ @@ -784,6 +945,7 @@ } } + async function regenerateTTS(id) { // Find the details of the TTS to regenerate const ttsDetails = ttsList.find(item => item.id === id); @@ -804,7 +966,13 @@ } 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 const wavFileName = item.fileUrl.split('/').pop(); @@ -814,30 +982,36 @@ const entryDiv = document.createElement('div'); entryDiv.id = `item-${item.id}`; // Assign an ID to the div for highlighting 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 entryDiv.innerHTML = ` - - - ID: ${item.id} - Voice: ${item.characterVoice} - Language: ${item.language} - Wav: ${wavFileName} - - ${displayText} + + + ID: ${item.id} + Voice: ${item.characterVoice} + Language: ${item.language} + Wav: ${wavFileName} + + ${displayText} `; 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) { try { if (stopTTSGeneration) { console.log("TTS generation stopped."); return; // Early return if stop flag is set } - console.log("generateAndPlayRemote:", chunk); const data = new URLSearchParams({ text_input: chunk.trim(), text_filtering: "standard", @@ -913,13 +1087,10 @@ src: [fileUrl], volume: parseFloat(document.getElementById("volume").value), onplay: function () { - console.log('Playback started for', fileUrl); }, onend: function () { - console.log('Playback finished for', fileUrl); }, onpause: function () { - console.log('Playback paused for', fileUrl); }, onloaderror: function (e) { console.error('Load error for', fileUrl, ':', e); @@ -1054,15 +1225,26 @@ function playNextLocalChunk() { if (audioQueue.length > 0 && currentlyPlayingIndex < audioQueue.length) { 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 { isPlaying = false; } } + function stopPlayback() { Howler.stop(); audioQueue = []; + // Unload each sound in the queue + audioQueue.forEach(sound => sound.unload()); currentlyPlayingIndex = 0; isPlaying = false; } @@ -1102,7 +1284,6 @@ const startFromIdElement = document.getElementById('startFromId'); if (startFromIdElement) { startFromIdElement.setAttribute('value', '1'); - console.log('Start From ID set to 1'); } // Other initializations... }); @@ -1122,6 +1303,7 @@ // and highlight the corresponding item playListFromIndex(startIndex); highlightPlaying(startIndex + 1); // Assuming the list items are 1-indexed + updateCurrentPage(startIndex + 1); // Update the current page based on the playing item } else { // If list is set to pause, pause the current item // and remove highlighting @@ -1154,6 +1336,26 @@ if (currentItem) { 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; + } } @@ -1191,6 +1393,7 @@ console.log('Index is out of bounds, stopping playback.'); // Clear any highlights if out of bounds highlightPlaying(null); + updateUIList(); } } @@ -1212,6 +1415,14 @@ if (startFromIdElement) { 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) { @@ -1424,6 +1635,70 @@ //////// 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 document.getElementById('playType').addEventListener('change', function () { isStreamingMode = this.value === 'stream'; @@ -1545,11 +1820,13 @@ adjustPlaybackAndChunkSize(savedPlayType); // Adjust selectability on page load } - // Initialize playback based on preference - const savedPlayMode = localStorage.getItem('playMode'); - if (savedPlayMode) { - document.getElementById('playMode').value = savedPlayMode; - } + document.addEventListener('DOMContentLoaded', function () { + const savedPlayMode = localStorage.getItem('playMode'); + if (savedPlayMode) { + document.getElementById('playMode').value = savedPlayMode; + } + playModeValue = savedPlayMode || 'local'; // Default to 'local' if nothing is saved + }); // Initialize chunk size based on preference const savedChunkSize = localStorage.getItem('chunkSize'); @@ -1665,4 +1942,4 @@ - + \ No newline at end of file From 13b2eb5fe3f65a44ef229b2f439b13eaa8e494fe Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Mon, 8 Jan 2024 01:03:06 +0000 Subject: [PATCH 11/18] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 75c20b3..e34e953 100644 --- a/README.md +++ b/README.md @@ -554,7 +554,8 @@ If you are generating wav files and populating the queue, you can generate one l #### ⬜ 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. - **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.

-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”.

+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 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. From 70d385133d078bcc8c6ad9bf3c6d1daf3312e37d Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Mon, 8 Jan 2024 01:13:36 +0000 Subject: [PATCH 12/18] JSON import pagination --- .../tts_chunk_player/tts_chunk_player.html | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/templates/tts_chunk_player/tts_chunk_player.html b/templates/tts_chunk_player/tts_chunk_player.html index 41082dc..a241377 100644 --- a/templates/tts_chunk_player/tts_chunk_player.html +++ b/templates/tts_chunk_player/tts_chunk_player.html @@ -1506,35 +1506,59 @@ } function importFromJSON() { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = '.json'; + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; - input.onchange = (e) => { - const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.readAsText(file); - reader.onload = (e) => { - try { - const jsonData = JSON.parse(e.target.result); - console.log('JSON data imported:', jsonData); + input.onchange = async (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.readAsText(file); + reader.onload = (e) => { + try { + const jsonData = JSON.parse(e.target.result); - // Update the ttsList with the imported data - ttsList = jsonData; + ttsList = jsonData; + currentPage = 1; // Reset to the first page + updateUIList(); + updatePaginationControls(); // Update pagination controls + } catch (error) { + console.error('Error parsing JSON:', error); + } + }; + } + }; - // Refresh the on-screen list - updateUIList(); // This function updates the UI with the contents of ttsList - } catch (error) { - console.error('Error parsing JSON:', error); - } - }; - } - }; + 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 //////// ////////////////////////////// From c1bc1f26cde22a8a0bbaf6e1c30203a2bfc906bc Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Mon, 8 Jan 2024 12:54:14 +0000 Subject: [PATCH 13/18] Export split setting Avoids the 1GB file limit imposed by browsers and also lowers memory requirements per exported block. --- .../tts_chunk_player/tts_chunk_player.html | 85 ++++++++++++------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/templates/tts_chunk_player/tts_chunk_player.html b/templates/tts_chunk_player/tts_chunk_player.html index a241377..4c5f7ec 100644 --- a/templates/tts_chunk_player/tts_chunk_player.html +++ b/templates/tts_chunk_player/tts_chunk_player.html @@ -602,6 +602,15 @@ + +
Page 1 of X @@ -625,45 +634,61 @@ +