drewThomasson commited on
Commit
fc032f5
·
1 Parent(s): f34332c

v2.0 will add the voices folder in a sec

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Base_XTTS_Model/.DS_Store → .DS_Store +0 -0
  2. Audiobooks/ar.m4b +0 -0
  3. Audiobooks/cs.m4b +0 -0
  4. Audiobooks/de.m4b +0 -0
  5. Audiobooks/en.m4b +0 -0
  6. Audiobooks/es.m4b +0 -0
  7. Audiobooks/fr.m4b +0 -0
  8. Audiobooks/hu.m4b +0 -0
  9. Audiobooks/it.m4b +0 -0
  10. Audiobooks/ko.m4b +0 -0
  11. Audiobooks/mini_story - Drew.m4b +0 -3
  12. Audiobooks/nl.m4b +0 -0
  13. Audiobooks/pl.m4b +0 -0
  14. Audiobooks/pt.m4b +0 -0
  15. Audiobooks/ru.m4b +0 -0
  16. Audiobooks/tr.m4b +0 -0
  17. Audiobooks/zh-cn.m4b +0 -0
  18. Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/.DS_Store +0 -0
  19. Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/config.json +0 -159
  20. Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/hash.md5 +0 -1
  21. Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/model.pth +0 -3
  22. Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/tos_agreed.txt +0 -1
  23. Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/vocab.json +0 -0
  24. README.md +8 -179
  25. app.py +212 -1032
  26. default_voice.wav +0 -0
  27. download_tos_agreed_file.py +0 -23
  28. ebook2audiobook.sh +300 -0
  29. ebook2audiobookXTTS/Dockerfile +0 -93
  30. ebook2audiobookXTTS/LICENSE +0 -21
  31. ebook2audiobookXTTS/README.md +0 -171
  32. ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS.py +0 -487
  33. ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS_gradio.py +0 -612
  34. ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS_with_link_gradio.py +0 -704
  35. ebook2audiobookXTTS/default_voice.wav +0 -0
  36. ebook2audiobookXTTS/demo_mini_story_chapters_Drew.epub +0 -0
  37. ebook2audiobookXTTS/ebook2audiobook.py +0 -466
  38. ebook2audiobookXTTS/gradio_gui_with_email_and_que.py +0 -614
  39. ebook2audiobookXTTS/import_all_files.py +0 -5
  40. ebook2audiobookXTTS/import_locally_stored_tts_model_files.py +0 -23
  41. ebook2audiobookXTTS/import_nltk_files.py +0 -24
  42. ebook2audiobookXTTS/trash.py +0 -366
  43. import_all_files.py +0 -5
  44. import_locally_stored_tts_model_files.py +0 -23
  45. import_nltk_files.py +0 -24
  46. input_folder/test.txt +0 -2
  47. mini_story_long - Drew.epub +0 -0
  48. mini_story_long - Drew.m4b +0 -3
  49. nltk_data/tokenizers/punkt.zip +0 -3
  50. nltk_data/tokenizers/punkt/PY3/README +0 -98
Base_XTTS_Model/.DS_Store → .DS_Store RENAMED
Binary files a/Base_XTTS_Model/.DS_Store and b/.DS_Store differ
 
Audiobooks/ar.m4b DELETED
Binary file (456 kB)
 
Audiobooks/cs.m4b DELETED
Binary file (400 kB)
 
Audiobooks/de.m4b DELETED
Binary file (420 kB)
 
Audiobooks/en.m4b DELETED
Binary file (432 kB)
 
Audiobooks/es.m4b DELETED
Binary file (499 kB)
 
Audiobooks/fr.m4b DELETED
Binary file (412 kB)
 
Audiobooks/hu.m4b DELETED
Binary file (365 kB)
 
Audiobooks/it.m4b DELETED
Binary file (503 kB)
 
Audiobooks/ko.m4b DELETED
Binary file (440 kB)
 
Audiobooks/mini_story - Drew.m4b DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:b37c09b708bd87441d04d285e07dfa8bcd11b3d4b50e18065c29ee62c323db10
3
- size 4961567
 
 
 
 
Audiobooks/nl.m4b DELETED
Binary file (411 kB)
 
Audiobooks/pl.m4b DELETED
Binary file (413 kB)
 
Audiobooks/pt.m4b DELETED
Binary file (477 kB)
 
Audiobooks/ru.m4b DELETED
Binary file (412 kB)
 
Audiobooks/tr.m4b DELETED
Binary file (356 kB)
 
Audiobooks/zh-cn.m4b DELETED
Binary file (400 kB)
 
Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/.DS_Store DELETED
Binary file (6.15 kB)
 
Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/config.json DELETED
@@ -1,159 +0,0 @@
1
- {
2
- "output_path": "output",
3
- "logger_uri": null,
4
- "run_name": "run",
5
- "project_name": null,
6
- "run_description": "\ud83d\udc38Coqui trainer run.",
7
- "print_step": 25,
8
- "plot_step": 100,
9
- "model_param_stats": false,
10
- "wandb_entity": null,
11
- "dashboard_logger": "tensorboard",
12
- "save_on_interrupt": true,
13
- "log_model_step": null,
14
- "save_step": 10000,
15
- "save_n_checkpoints": 5,
16
- "save_checkpoints": true,
17
- "save_all_best": false,
18
- "save_best_after": 10000,
19
- "target_loss": null,
20
- "print_eval": false,
21
- "test_delay_epochs": 0,
22
- "run_eval": true,
23
- "run_eval_steps": null,
24
- "distributed_backend": "nccl",
25
- "distributed_url": "tcp://localhost:54321",
26
- "mixed_precision": false,
27
- "precision": "fp16",
28
- "epochs": 1000,
29
- "batch_size": 32,
30
- "eval_batch_size": 16,
31
- "grad_clip": 0.0,
32
- "scheduler_after_epoch": true,
33
- "lr": 0.001,
34
- "optimizer": "radam",
35
- "optimizer_params": null,
36
- "lr_scheduler": null,
37
- "lr_scheduler_params": {},
38
- "use_grad_scaler": false,
39
- "allow_tf32": false,
40
- "cudnn_enable": true,
41
- "cudnn_deterministic": false,
42
- "cudnn_benchmark": false,
43
- "training_seed": 54321,
44
- "model": "xtts",
45
- "num_loader_workers": 0,
46
- "num_eval_loader_workers": 0,
47
- "use_noise_augment": false,
48
- "audio": {
49
- "sample_rate": 22050,
50
- "output_sample_rate": 24000
51
- },
52
- "use_phonemes": false,
53
- "phonemizer": null,
54
- "phoneme_language": null,
55
- "compute_input_seq_cache": false,
56
- "text_cleaner": null,
57
- "enable_eos_bos_chars": false,
58
- "test_sentences_file": "",
59
- "phoneme_cache_path": null,
60
- "characters": null,
61
- "add_blank": false,
62
- "batch_group_size": 0,
63
- "loss_masking": null,
64
- "min_audio_len": 1,
65
- "max_audio_len": Infinity,
66
- "min_text_len": 1,
67
- "max_text_len": Infinity,
68
- "compute_f0": false,
69
- "compute_energy": false,
70
- "compute_linear_spec": false,
71
- "precompute_num_workers": 0,
72
- "start_by_longest": false,
73
- "shuffle": false,
74
- "drop_last": false,
75
- "datasets": [
76
- {
77
- "formatter": "",
78
- "dataset_name": "",
79
- "path": "",
80
- "meta_file_train": "",
81
- "ignored_speakers": null,
82
- "language": "",
83
- "phonemizer": "",
84
- "meta_file_val": "",
85
- "meta_file_attn_mask": ""
86
- }
87
- ],
88
- "test_sentences": [],
89
- "eval_split_max_size": null,
90
- "eval_split_size": 0.01,
91
- "use_speaker_weighted_sampler": false,
92
- "speaker_weighted_sampler_alpha": 1.0,
93
- "use_language_weighted_sampler": false,
94
- "language_weighted_sampler_alpha": 1.0,
95
- "use_length_weighted_sampler": false,
96
- "length_weighted_sampler_alpha": 1.0,
97
- "model_args": {
98
- "gpt_batch_size": 1,
99
- "enable_redaction": false,
100
- "kv_cache": true,
101
- "gpt_checkpoint": null,
102
- "clvp_checkpoint": null,
103
- "decoder_checkpoint": null,
104
- "num_chars": 255,
105
- "tokenizer_file": "",
106
- "gpt_max_audio_tokens": 605,
107
- "gpt_max_text_tokens": 402,
108
- "gpt_max_prompt_tokens": 70,
109
- "gpt_layers": 30,
110
- "gpt_n_model_channels": 1024,
111
- "gpt_n_heads": 16,
112
- "gpt_number_text_tokens": 6681,
113
- "gpt_start_text_token": null,
114
- "gpt_stop_text_token": null,
115
- "gpt_num_audio_tokens": 1026,
116
- "gpt_start_audio_token": 1024,
117
- "gpt_stop_audio_token": 1025,
118
- "gpt_code_stride_len": 1024,
119
- "gpt_use_masking_gt_prompt_approach": true,
120
- "gpt_use_perceiver_resampler": true,
121
- "input_sample_rate": 22050,
122
- "output_sample_rate": 24000,
123
- "output_hop_length": 256,
124
- "decoder_input_dim": 1024,
125
- "d_vector_dim": 512,
126
- "cond_d_vector_in_each_upsampling_layer": true,
127
- "duration_const": 102400
128
- },
129
- "model_dir": null,
130
- "languages": [
131
- "en",
132
- "es",
133
- "fr",
134
- "de",
135
- "it",
136
- "pt",
137
- "pl",
138
- "tr",
139
- "ru",
140
- "nl",
141
- "cs",
142
- "ar",
143
- "zh-cn",
144
- "hu",
145
- "ko",
146
- "ja",
147
- "hi"
148
- ],
149
- "temperature": 0.75,
150
- "length_penalty": 1.0,
151
- "repetition_penalty": 5.0,
152
- "top_k": 50,
153
- "top_p": 0.85,
154
- "num_gpt_outputs": 1,
155
- "gpt_cond_len": 30,
156
- "gpt_cond_chunk_len": 4,
157
- "max_ref_len": 30,
158
- "sound_norm_refs": false
159
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/hash.md5 DELETED
@@ -1 +0,0 @@
1
- 10f92b55c512af7a8d39d650547a15a7
 
 
Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/model.pth DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c7ea20001c6a0a841c77e252d8409f6a74fb423e79b3206a0771ba5989776187
3
- size 1867929118
 
 
 
 
Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/tos_agreed.txt DELETED
@@ -1 +0,0 @@
1
- I have read, understood and agreed to the Terms and Conditions.
 
 
Base_XTTS_Model/tts_models--multilingual--multi-dataset--xtts_v2/vocab.json DELETED
The diff for this file is too large to render. See raw diff
 
README.md CHANGED
@@ -1,185 +1,14 @@
1
  ---
2
- title: Ebook2audiobook
3
- emoji: 🐸📖
4
- colorFrom: red
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 4.44.0
8
  app_file: app.py
9
  pinned: true
10
- license: mit
11
- short_description: Convert any Ebook to AudioBook with Xtts + VoiceCloning!
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
15
-
16
-
17
-
18
-
19
-
20
-
21
- # 📚 ebook2audiobookXTTS
22
-
23
- Convert eBooks to audiobooks with chapters and metadata using Calibre and Coqui XTTS. Supports optional voice cloning and multiple languages!
24
-
25
- ## 🌟 Features
26
-
27
- - 📖 Converts eBooks to text format with Calibre.
28
- - 📚 Splits eBook into chapters for organized audio.
29
- - 🎙️ High-quality text-to-speech with Coqui XTTS.
30
- - 🗣️ Optional voice cloning with your own voice file.
31
- - 🌍 Supports multiple languages (English by default).
32
- - 🖥️ Designed to run on 4GB RAM.
33
-
34
- ## 🛠️ Requirements
35
-
36
- - Python 3.x
37
- - `coqui-tts` Python package
38
- - Calibre (for eBook conversion)
39
- - FFmpeg (for audiobook creation)
40
- - Optional: Custom voice file for voice cloning
41
-
42
- ### 🔧 Installation Instructions
43
-
44
- 1. **Install Python 3.x** from [Python.org](https://www.python.org/downloads/).
45
-
46
- 2. **Install Calibre**:
47
- - **Ubuntu**: `sudo apt-get install -y calibre`
48
- - **macOS**: `brew install calibre`
49
- - **Windows** (Admin Powershell): `choco install calibre`
50
-
51
- 3. **Install FFmpeg**:
52
- - **Ubuntu**: `sudo apt-get install -y ffmpeg`
53
- - **macOS**: `brew install ffmpeg`
54
- - **Windows** (Admin Powershell): `choco install ffmpeg`
55
-
56
- 4. **Optional: Install Mecab** (for non-Latin languages):
57
- - **Ubuntu**: `sudo apt-get install -y mecab libmecab-dev mecab-ipadic-utf8`
58
- - **macOS**: `brew install mecab`, `brew install mecab-ipadic`
59
- - **Windows** (Admin Powershell): `choco install mecab` (Note: Japanese support is limited)
60
-
61
- 5. **Install Python packages**:
62
- ```bash
63
- pip install tts==0.21.3 pydub nltk beautifulsoup4 ebooklib tqdm
64
- ```
65
-
66
- **For non-Latin languages**:
67
- ```bash
68
- python -m unidic download
69
- pip install mecab mecab-python3 unidic
70
- ```
71
-
72
- ## 🌐 Supported Languages
73
-
74
- - **English (en)**
75
- - **Spanish (es)**
76
- - **French (fr)**
77
- - **German (de)**
78
- - **Italian (it)**
79
- - **Portuguese (pt)**
80
- - **Polish (pl)**
81
- - **Turkish (tr)**
82
- - **Russian (ru)**
83
- - **Dutch (nl)**
84
- - **Czech (cs)**
85
- - **Arabic (ar)**
86
- - **Chinese (zh-cn)**
87
- - **Japanese (ja)**
88
- - **Hungarian (hu)**
89
- - **Korean (ko)**
90
-
91
- Specify the language code when running the script.
92
-
93
- ## 🚀 Usage
94
-
95
- ### 🖥️ Gradio Web Interface
96
-
97
- 1. **Run the Script**:
98
- ```bash
99
- python custom_model_ebook2audiobookXTTS_gradio.py
100
- ```
101
-
102
- 2. **Open the Web App**: Click the URL provided in the terminal to access the web app and convert eBooks.
103
-
104
- ### 📝 Basic Usage
105
-
106
- ```bash
107
- python ebook2audiobook.py <path_to_ebook_file> [path_to_voice_file] [language_code]
108
- ```
109
-
110
- - **<path_to_ebook_file>**: Path to your eBook file.
111
- - **[path_to_voice_file]**: Optional for voice cloning.
112
- - **[language_code]**: Optional to specify language.
113
-
114
- ### 🧩 Custom XTTS Model
115
-
116
- ```bash
117
- python custom_model_ebook2audiobookXTTS.py <ebook_file_path> <target_voice_file_path> <language> <custom_model_path> <custom_config_path> <custom_vocab_path>
118
- ```
119
-
120
- - **<ebook_file_path>**: Path to your eBook file.
121
- - **<target_voice_file_path>**: Optional for voice cloning.
122
- - **<language>**: Optional to specify language.
123
- - **<custom_model_path>**: Path to `model.pth`.
124
- - **<custom_config_path>**: Path to `config.json`.
125
- - **<custom_vocab_path>**: Path to `vocab.json`.
126
-
127
- ### 🐳 Using Docker
128
-
129
- You can also use Docker to run the eBook to Audiobook converter. This method ensures consistency across different environments and simplifies setup.
130
-
131
- #### 🚀 Running the Docker Container
132
-
133
- To run the Docker container and start the Gradio interface, use the following command:
134
-
135
- -Run with CPU only
136
- ```powershell
137
- docker run -it --rm -p 7860:7860 athomasson2/ebook2audiobookxtts:latest
138
- ```
139
- -Run with GPU Speedup (Nvida graphics cards only)
140
- ```powershell
141
- docker run -it --rm --gpus all -p 7860:7860 athomasson2/ebook2audiobookxtts:latest
142
- ```
143
-
144
- This command will start the Gradio interface on port 7860.(localhost:7860)
145
-
146
- #### 🖥️ Docker GUI
147
-
148
- <img width="1401" alt="Screenshot 2024-08-25 at 10 08 40 AM" src="https://github.com/user-attachments/assets/78cfd33e-cd46-41cc-8128-3820160a5e40">
149
- <img width="1406" alt="Screenshot 2024-08-25 at 10 08 51 AM" src="https://github.com/user-attachments/assets/dbfad9f6-e6e5-4cad-b248-adb76c5434f3">
150
-
151
- ### 🛠️ For Custom Xtts Models
152
-
153
- Models built to be better at a specific voice. Check out my Hugging Face page [here](https://huggingface.co/drewThomasson).
154
-
155
- To use a custom model, paste the link of the `Finished_model_files.zip` file like this:
156
-
157
- [David Attenborough fine tuned Finished_model_files.zip](https://huggingface.co/drewThomasson/xtts_David_Attenborough_fine_tune/resolve/main/Finished_model_files.zip?download=true)
158
-
159
-
160
-
161
-
162
- More details can be found at the [Dockerfile Hub Page]([https://github.com/DrewThomasson/ebook2audiobookXTTS](https://hub.docker.com/repository/docker/athomasson2/ebook2audiobookxtts/general)).
163
-
164
- ## 🌐 Fine Tuned Xtts models
165
-
166
- To find already fine-tuned XTTS models, visit [this Hugging Face link](https://huggingface.co/drewThomasson) 🌐. Search for models that include "xtts fine tune" in their names.
167
-
168
- ## 🎥 Demos
169
-
170
- https://github.com/user-attachments/assets/8486603c-38b1-43ce-9639-73757dfb1031
171
-
172
- ## 📚 Supported eBook Formats
173
-
174
- - `.epub`, `.pdf`, `.mobi`, `.txt`, `.html`, `.rtf`, `.chm`, `.lit`, `.pdb`, `.fb2`, `.odt`, `.cbr`, `.cbz`, `.prc`, `.lrf`, `.pml`, `.snb`, `.cbc`, `.rb`, `.tcr`
175
- - **Best results**: `.epub` or `.mobi` for automatic chapter detection
176
-
177
- ## 📂 Output
178
-
179
- - Creates an `.m4b` file with metadata and chapters.
180
- - **Example Output**: ![Example](https://github.com/DrewThomasson/VoxNovel/blob/dc5197dff97252fa44c391dc0596902d71278a88/readme_files/example_in_app.jpeg)
181
-
182
- ## 🙏 Special Thanks
183
-
184
- - **Coqui TTS**: [Coqui TTS GitHub](https://github.com/coqui-ai/TTS)
185
- - **Calibre**: [Calibre Website](https://calibre-ebook.com)
 
1
  ---
2
+ title: Ebook2audiobook V2.0 Beta
3
+ emoji: 🚀
4
+ colorFrom: indigo
5
+ colorTo: red
6
  sdk: gradio
7
+ sdk_version: 5.9.0
8
  app_file: app.py
9
  pinned: true
10
+ license: apache-2.0
11
+ short_description: Added improvements, 1107+ languages supported
12
  ---
13
 
14
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,1052 +1,232 @@
1
- print("starting...")
2
-
3
  import argparse
4
-
5
- language_options = [
6
- "en", "es", "fr", "de", "it", "pt", "pl", "tr", "ru", "nl", "cs", "ar", "zh-cn", "ja", "hu", "ko"
7
- ]
8
- char_limits = {
9
- "en": 250, # English
10
- "es": 239, # Spanish
11
- "fr": 273, # French
12
- "de": 253, # German
13
- "it": 213, # Italian
14
- "pt": 203, # Portuguese
15
- "pl": 224, # Polish
16
- "tr": 226, # Turkish
17
- "ru": 182, # Russian
18
- "nl": 251, # Dutch
19
- "cs": 186, # Czech
20
- "ar": 166, # Arabic
21
- "zh-cn": 82, # Chinese (Simplified)
22
- "ja": 71, # Japanese
23
- "hu": 224, # Hungarian
24
- "ko": 95, # Korean
25
- }
26
-
27
- # Mapping of language codes to NLTK's supported language names
28
- language_mapping = {
29
- "en": "english",
30
- "de": "german",
31
- "fr": "french",
32
- "es": "spanish",
33
- "it": "italian",
34
- "pt": "portuguese",
35
- "nl": "dutch",
36
- "pl": "polish",
37
- "cs": "czech",
38
- "ru": "russian",
39
- "tr": "turkish",
40
- "el": "greek",
41
- "et": "estonian",
42
- "no": "norwegian",
43
- "ml": "malayalam",
44
- "sl": "slovene",
45
- "da": "danish",
46
- "fi": "finnish",
47
- "sv": "swedish"
48
- }
49
-
50
-
51
- # Convert the list of languages to a string to display in the help text
52
- language_options_str = ", ".join(language_options)
53
-
54
- # Argument parser to handle optional parameters with descriptions
55
- parser = argparse.ArgumentParser(
56
- description="Convert eBooks to Audiobooks using a Text-to-Speech model. You can either launch the Gradio interface or run the script in headless mode for direct conversion.",
57
- epilog="Example: python script.py --headless --ebook path_to_ebook --voice path_to_voice --language en --use_custom_model True --custom_model model.pth --custom_config config.json --custom_vocab vocab.json"
58
- )
59
- parser.add_argument("--share", type=bool, default=False, help="Set to True to enable a public shareable Gradio link. Defaults to False.")
60
- parser.add_argument("--headless", type=bool, default=False, help="Set to True to run in headless mode without the Gradio interface. Defaults to False.")
61
- parser.add_argument("--ebook", type=str, help="Path to the ebook file for conversion. Required in headless mode.")
62
- parser.add_argument("--voice", type=str, help="Path to the target voice file for TTS. Optional, uses a default voice if not provided.")
63
- parser.add_argument("--language", type=str, default="en",
64
- help=f"Language for the audiobook conversion. Options: {language_options_str}. Defaults to English (en).")
65
- parser.add_argument("--use_custom_model", type=bool, default=False,
66
- help="Set to True to use a custom TTS model. Defaults to False. Must be True to use custom models, otherwise you'll get an error.")
67
- parser.add_argument("--custom_model", type=str, help="Path to the custom model file (.pth). Required if using a custom model.")
68
- parser.add_argument("--custom_config", type=str, help="Path to the custom config file (config.json). Required if using a custom model.")
69
- parser.add_argument("--custom_vocab", type=str, help="Path to the custom vocab file (vocab.json). Required if using a custom model.")
70
- parser.add_argument("--custom_model_url", type=str,
71
- help=("URL to download the custom model as a zip file. Optional, but will be used if provided. "
72
- "Examples include David Attenborough's model: "
73
- "'https://huggingface.co/drewThomasson/xtts_David_Attenborough_fine_tune/resolve/main/Finished_model_files.zip?download=true'. "
74
- "More XTTS fine-tunes can be found on my Hugging Face at 'https://huggingface.co/drewThomasson'."))
75
- parser.add_argument("--temperature", type=float, default=0.65, help="Temperature for the model. Defaults to 0.65. Higher Tempatures will lead to more creative outputs IE: more Hallucinations. Lower Tempatures will be more monotone outputs IE: less Hallucinations.")
76
- parser.add_argument("--length_penalty", type=float, default=1.0, help="A length penalty applied to the autoregressive decoder. Defaults to 1.0. Not applied to custom models.")
77
- parser.add_argument("--repetition_penalty", type=float, default=2.0, help="A penalty that prevents the autoregressive decoder from repeating itself. Defaults to 2.0.")
78
- parser.add_argument("--top_k", type=int, default=50, help="Top-k sampling. Lower values mean more likely outputs and increased audio generation speed. Defaults to 50.")
79
- parser.add_argument("--top_p", type=float, default=0.8, help="Top-p sampling. Lower values mean more likely outputs and increased audio generation speed. Defaults to 0.8.")
80
- parser.add_argument("--speed", type=float, default=1.0, help="Speed factor for the speech generation. IE: How fast the Narrerator will speak. Defaults to 1.0.")
81
- parser.add_argument("--enable_text_splitting", type=bool, default=False, help="Enable splitting text into sentences. Defaults to True.")
82
-
83
- args = parser.parse_args()
84
-
85
-
86
-
87
  import os
88
- import shutil
 
89
  import subprocess
90
- import re
91
- from pydub import AudioSegment
92
- import tempfile
93
- from pydub import AudioSegment
94
- import nltk
95
- from nltk.tokenize import sent_tokenize
96
  import sys
97
- import torch
98
- from TTS.api import TTS
99
- from TTS.tts.configs.xtts_config import XttsConfig
100
- from TTS.tts.models.xtts import Xtts
101
- from tqdm import tqdm
102
- import gradio as gr
103
- from gradio import Progress
104
- import urllib.request
105
- import zipfile
106
- import socket
107
- #import MeCab
108
- #import unidic
109
-
110
- #nltk.download('punkt_tab')
111
-
112
- # By using XTTS you agree to CPML license https://coqui.ai/cpml
113
- os.environ["COQUI_TOS_AGREED"] = "1"
114
-
115
- # Import the locally stored Xtts default model
116
- import import_locally_stored_tts_model_files
117
-
118
- #make the nltk folder point to the nltk folder in the app dir
119
- nltk.data.path.append('/home/user/app/nltk_data')
120
-
121
- # Download UniDic if it's not already installed
122
- #unidic.download()
123
-
124
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
125
- print(f"Device selected is: {device}")
126
-
127
- #nltk.download('punkt') # Make sure to download the necessary models
128
-
129
-
130
- def download_and_extract_zip(url, extract_to='.'):
131
- try:
132
- # Ensure the directory exists
133
- os.makedirs(extract_to, exist_ok=True)
134
-
135
- zip_path = os.path.join(extract_to, 'model.zip')
136
-
137
- # Download with progress bar
138
- with tqdm(unit='B', unit_scale=True, miniters=1, desc="Downloading Model") as t:
139
- def reporthook(blocknum, blocksize, totalsize):
140
- t.total = totalsize
141
- t.update(blocknum * blocksize - t.n)
142
-
143
- urllib.request.urlretrieve(url, zip_path, reporthook=reporthook)
144
- print(f"Downloaded zip file to {zip_path}")
145
-
146
- # Unzipping with progress bar
147
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
148
- files = zip_ref.namelist()
149
- with tqdm(total=len(files), unit="file", desc="Extracting Files") as t:
150
- for file in files:
151
- if not file.endswith('/'): # Skip directories
152
- # Extract the file to the temporary directory
153
- extracted_path = zip_ref.extract(file, extract_to)
154
- # Move the file to the base directory
155
- base_file_path = os.path.join(extract_to, os.path.basename(file))
156
- os.rename(extracted_path, base_file_path)
157
- t.update(1)
158
-
159
- # Cleanup: Remove the ZIP file and any empty folders
160
- os.remove(zip_path)
161
- for root, dirs, files in os.walk(extract_to, topdown=False):
162
- for name in dirs:
163
- os.rmdir(os.path.join(root, name))
164
- print(f"Extracted files to {extract_to}")
165
-
166
- # Check if all required files are present
167
- required_files = ['model.pth', 'config.json', 'vocab.json_']
168
- missing_files = [file for file in required_files if not os.path.exists(os.path.join(extract_to, file))]
169
-
170
- if not missing_files:
171
- print("All required files (model.pth, config.json, vocab.json_) found.")
172
- else:
173
- print(f"Missing files: {', '.join(missing_files)}")
174
-
175
- except Exception as e:
176
- print(f"Failed to download or extract zip file: {e}")
177
-
178
-
179
-
180
- def is_folder_empty(folder_path):
181
- if os.path.exists(folder_path) and os.path.isdir(folder_path):
182
- # List directory contents
183
- if not os.listdir(folder_path):
184
- return True # The folder is empty
185
- else:
186
- return False # The folder is not empty
187
  else:
188
- print(f"The path {folder_path} is not a valid folder.")
189
- return None # The path is not a valid folder
190
-
191
- def remove_folder_with_contents(folder_path):
 
192
  try:
193
- shutil.rmtree(folder_path)
194
- print(f"Successfully removed {folder_path} and all of its contents.")
195
- except Exception as e:
196
- print(f"Error removing {folder_path}: {e}")
197
-
198
-
199
-
200
-
201
- def wipe_folder(folder_path):
202
- # Check if the folder exists
203
- if not os.path.exists(folder_path):
204
- print(f"The folder {folder_path} does not exist.")
205
- return
206
-
207
- # Iterate over all the items in the given folder
208
- for item in os.listdir(folder_path):
209
- item_path = os.path.join(folder_path, item)
210
- # If it's a file, remove it and print a message
211
- if os.path.isfile(item_path):
212
- os.remove(item_path)
213
- print(f"Removed file: {item_path}")
214
- # If it's a directory, remove it recursively and print a message
215
- elif os.path.isdir(item_path):
216
- shutil.rmtree(item_path)
217
- print(f"Removed directory and its contents: {item_path}")
218
-
219
- print(f"All contents wiped from {folder_path}.")
220
-
221
-
222
- # Example usage
223
- # folder_to_wipe = 'path_to_your_folder'
224
- # wipe_folder(folder_to_wipe)
225
-
226
 
227
- def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
228
- # Function to sort chapters based on their numeric order
229
- def sort_key(chapter_file):
230
- numbers = re.findall(r'\d+', chapter_file)
231
- return int(numbers[0]) if numbers else 0
232
-
233
- # Extract metadata and cover image from the eBook file
234
- def extract_metadata_and_cover(ebook_path):
235
- try:
236
- cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
237
- subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
238
- if os.path.exists(cover_path):
239
- return cover_path
240
- except Exception as e:
241
- print(f"Error extracting eBook metadata or cover: {e}")
242
- return None
243
- # Combine WAV files into a single file
244
- def combine_wav_files(chapter_files, output_path, batch_size=256):
245
- # Initialize an empty audio segment
246
- combined_audio = AudioSegment.empty()
247
-
248
- # Process the chapter files in batches
249
- for i in range(0, len(chapter_files), batch_size):
250
- batch_files = chapter_files[i:i + batch_size]
251
- batch_audio = AudioSegment.empty() # Initialize an empty AudioSegment for the batch
252
-
253
- # Sequentially append each file in the current batch to the batch_audio
254
- for chapter_file in batch_files:
255
- audio_segment = AudioSegment.from_wav(chapter_file)
256
- batch_audio += audio_segment
257
-
258
- # Combine the batch audio with the overall combined_audio
259
- combined_audio += batch_audio
260
-
261
- # Export the combined audio to the output file path
262
- combined_audio.export(output_path, format='wav')
263
- print(f"Combined audio saved to {output_path}")
264
-
265
- # Function to generate metadata for M4B chapters
266
- def generate_ffmpeg_metadata(chapter_files, metadata_file):
267
- with open(metadata_file, 'w') as file:
268
- file.write(';FFMETADATA1\n')
269
- start_time = 0
270
- for index, chapter_file in enumerate(chapter_files):
271
- duration_ms = len(AudioSegment.from_wav(chapter_file))
272
- file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
273
- file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
274
- start_time += duration_ms
275
-
276
- # Generate the final M4B file using ffmpeg
277
- def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
278
- # Ensure the output directory exists
279
- os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
280
-
281
- ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
282
- if cover_image:
283
- ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
284
- else:
285
- ffmpeg_cmd += ['-map', '0:a']
286
 
287
- ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
288
- if cover_image:
289
- ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
290
- ffmpeg_cmd += [output_m4b]
291
-
292
- subprocess.run(ffmpeg_cmd, check=True)
293
-
294
-
295
-
296
- # Main logic
297
- chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
298
- temp_dir = tempfile.gettempdir()
299
- temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
300
- metadata_file = os.path.join(temp_dir, 'metadata.txt')
301
- cover_image = extract_metadata_and_cover(ebook_file)
302
- output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
303
-
304
- combine_wav_files(chapter_files, temp_combined_wav)
305
- generate_ffmpeg_metadata(chapter_files, metadata_file)
306
- create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
307
-
308
- # Cleanup
309
- if os.path.exists(temp_combined_wav):
310
- os.remove(temp_combined_wav)
311
- if os.path.exists(metadata_file):
312
- os.remove(metadata_file)
313
- if cover_image and os.path.exists(cover_image):
314
- os.remove(cover_image)
315
-
316
- # Example usage
317
- # create_m4b_from_chapters('path_to_chapter_wavs', 'path_to_ebook_file', 'path_to_output_dir')
318
-
319
-
320
-
321
-
322
-
323
-
324
- #this code right here isnt the book grabbing thing but its before to refrence in order to create the sepecial chapter labeled book thing with calibre idk some systems cant seem to get it so just in case but the next bit of code after this is the book grabbing code with booknlp
325
- import os
326
- import subprocess
327
- import ebooklib
328
- from ebooklib import epub
329
- from bs4 import BeautifulSoup
330
- import re
331
- import csv
332
- import nltk
333
-
334
- # Only run the main script if Value is True
335
- def create_chapter_labeled_book(ebook_file_path):
336
- # Function to ensure the existence of a directory
337
- def ensure_directory(directory_path):
338
- if not os.path.exists(directory_path):
339
- os.makedirs(directory_path)
340
- print(f"Created directory: {directory_path}")
341
-
342
- ensure_directory(os.path.join(".", 'Working_files', 'Book'))
343
-
344
- def convert_to_epub(input_path, output_path):
345
- # Convert the ebook to EPUB format using Calibre's ebook-convert
346
  try:
347
- subprocess.run(['ebook-convert', input_path, output_path], check=True)
 
348
  except subprocess.CalledProcessError as e:
349
- print(f"An error occurred while converting the eBook: {e}")
350
- return False
351
- return True
352
-
353
- def save_chapters_as_text(epub_path):
354
- # Create the directory if it doesn't exist
355
- directory = os.path.join(".", "Working_files", "temp_ebook")
356
- ensure_directory(directory)
357
-
358
- # Open the EPUB file
359
- book = epub.read_epub(epub_path)
360
-
361
- previous_chapter_text = ''
362
- previous_filename = ''
363
- chapter_counter = 0
364
-
365
- # Iterate through the items in the EPUB file
366
- for item in book.get_items():
367
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
368
- # Use BeautifulSoup to parse HTML content
369
- soup = BeautifulSoup(item.get_content(), 'html.parser')
370
- text = soup.get_text()
371
-
372
- # Check if the text is not empty
373
- if text.strip():
374
- if len(text) < 2300 and previous_filename:
375
- # Append text to the previous chapter if it's short
376
- with open(previous_filename, 'a', encoding='utf-8') as file:
377
- file.write('\n' + text)
378
- else:
379
- # Create a new chapter file and increment the counter
380
- previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
381
- chapter_counter += 1
382
- with open(previous_filename, 'w', encoding='utf-8') as file:
383
- file.write(text)
384
- print(f"Saved chapter: {previous_filename}")
385
-
386
- # Example usage
387
- input_ebook = ebook_file_path # Replace with your eBook file path
388
- output_epub = os.path.join(".", "Working_files", "temp.epub")
389
-
390
-
391
- if os.path.exists(output_epub):
392
- os.remove(output_epub)
393
- print(f"File {output_epub} has been removed.")
394
- else:
395
- print(f"The file {output_epub} does not exist.")
396
-
397
- if convert_to_epub(input_ebook, output_epub):
398
- save_chapters_as_text(output_epub)
399
-
400
- # Download the necessary NLTK data (if not already present)
401
- #nltk.download('punkt')
402
-
403
- def process_chapter_files(folder_path, output_csv):
404
- with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
405
- writer = csv.writer(csvfile)
406
- # Write the header row
407
- writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
408
-
409
- # Process each chapter file
410
- chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
411
- for filename in chapter_files:
412
- if filename.startswith('chapter_') and filename.endswith('.txt'):
413
- chapter_number = int(filename.split('_')[1].split('.')[0])
414
- file_path = os.path.join(folder_path, filename)
415
-
416
- try:
417
- with open(file_path, 'r', encoding='utf-8') as file:
418
- text = file.read()
419
- # Insert "NEWCHAPTERABC" at the beginning of each chapter's text
420
- if text:
421
- text = "NEWCHAPTERABC" + text
422
- sentences = nltk.tokenize.sent_tokenize(text)
423
- for sentence in sentences:
424
- start_location = text.find(sentence)
425
- end_location = start_location + len(sentence)
426
- writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
427
- except Exception as e:
428
- print(f"Error processing file {filename}: {e}")
429
-
430
- # Example usage
431
- folder_path = os.path.join(".", "Working_files", "temp_ebook")
432
- output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
433
-
434
- process_chapter_files(folder_path, output_csv)
435
-
436
- def sort_key(filename):
437
- """Extract chapter number for sorting."""
438
- match = re.search(r'chapter_(\d+)\.txt', filename)
439
- return int(match.group(1)) if match else 0
440
-
441
- def combine_chapters(input_folder, output_file):
442
- # Create the output folder if it doesn't exist
443
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
444
-
445
- # List all txt files and sort them by chapter number
446
- files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
447
- sorted_files = sorted(files, key=sort_key)
448
-
449
- with open(output_file, 'w', encoding='utf-8') as outfile: # Specify UTF-8 encoding here
450
- for i, filename in enumerate(sorted_files):
451
- with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile: # And here
452
- outfile.write(infile.read())
453
- # Add the marker unless it's the last file
454
- if i < len(sorted_files) - 1:
455
- outfile.write("\nNEWCHAPTERABC\n")
456
-
457
- # Paths
458
- input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
459
- output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
460
-
461
-
462
- # Combine the chapters
463
- combine_chapters(input_folder, output_file)
464
-
465
- ensure_directory(os.path.join(".", "Working_files", "Book"))
466
-
467
-
468
- #create_chapter_labeled_book()
469
-
470
-
471
-
472
-
473
- import os
474
- import subprocess
475
- import sys
476
- import torchaudio
477
-
478
- # Check if Calibre's ebook-convert tool is installed
479
- def calibre_installed():
480
- try:
481
- subprocess.run(['ebook-convert', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
482
- return True
483
- except FileNotFoundError:
484
- print("Calibre is not installed. Please install Calibre for this functionality.")
485
- return False
486
-
487
-
488
- import os
489
- import torch
490
- from TTS.api import TTS
491
- from nltk.tokenize import sent_tokenize
492
- from pydub import AudioSegment
493
-
494
- default_target_voice_path = "default_voice.wav" # Ensure this is a valid path
495
- default_language_code = "en"
496
-
497
-
498
- # Function to check if vocab.json exists and rename it
499
- def rename_vocab_file_if_exists(directory):
500
- vocab_path = os.path.join(directory, 'vocab.json')
501
- new_vocab_path = os.path.join(directory, 'vocab.json_')
502
-
503
- # Check if vocab.json exists
504
- if os.path.exists(vocab_path):
505
- # Rename the file
506
- os.rename(vocab_path, new_vocab_path)
507
- print(f"Renamed {vocab_path} to {new_vocab_path}")
508
- return True # Return True if the file was found and renamed
509
-
510
-
511
- def combine_wav_files(input_directory, output_directory, file_name):
512
- # Ensure that the output directory exists, create it if necessary
513
- os.makedirs(output_directory, exist_ok=True)
514
-
515
- # Specify the output file path
516
- output_file_path = os.path.join(output_directory, file_name)
517
-
518
- # Initialize an empty audio segment
519
- combined_audio = AudioSegment.empty()
520
-
521
- # Get a list of all .wav files in the specified input directory and sort them
522
- input_file_paths = sorted(
523
- [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith(".wav")],
524
- key=lambda f: int(''.join(filter(str.isdigit, f)))
525
  )
526
-
527
- # Sequentially append each file to the combined_audio
528
- for input_file_path in input_file_paths:
529
- audio_segment = AudioSegment.from_wav(input_file_path)
530
- combined_audio += audio_segment
531
-
532
- # Export the combined audio to the output file path
533
- combined_audio.export(output_file_path, format='wav')
534
-
535
- print(f"Combined audio saved to {output_file_path}")
536
-
537
- # Function to split long strings into parts
538
- # Modify the function to handle special cases for Chinese, Italian, and default for others
539
- def split_long_sentence(sentence, language='en', max_pauses=10):
540
- """
541
- Splits a sentence into parts based on length or number of pauses without recursion.
542
-
543
- :param sentence: The sentence to split.
544
- :param language: The language of the sentence (default is English).
545
- :param max_pauses: Maximum allowed number of pauses in a sentence.
546
- :return: A list of sentence parts that meet the criteria.
547
- """
548
- #Get the Max character length for the selected language -2 : with a default of 248 if no language is found
549
- max_length = (char_limits.get(language, 250)-2)
550
-
551
- # Adjust the pause punctuation symbols based on language
552
- if language == 'zh-cn':
553
- punctuation = [',', '。', ';', '?', '!'] # Chinese-specific pause punctuation including sentence-ending marks
554
- elif language == 'ja':
555
- punctuation = ['、', '。', ';', '?', '!'] # Japanese-specific pause punctuation
556
- elif language == 'ko':
557
- punctuation = [',', '。', ';', '?', '!'] # Korean-specific pause punctuation
558
- elif language == 'ar':
559
- punctuation = ['،', '؛', '؟', '!', '·', '؛', '.'] # Arabic-specific punctuation
560
- elif language == 'en':
561
- punctuation = [',', ';', '.'] # English-specific pause punctuation
562
- else:
563
- # Default pause punctuation for other languages (es, fr, de, it, pt, pl, cs, ru, nl, tr, hu)
564
- punctuation = [',', '.', ';', ':', '?', '!']
565
-
566
-
567
-
568
- parts = []
569
- while len(sentence) > max_length or sum(sentence.count(p) for p in punctuation) > max_pauses:
570
- possible_splits = [i for i, char in enumerate(sentence) if char in punctuation and i < max_length]
571
- if possible_splits:
572
- # Find the best place to split the sentence, preferring the last possible split to keep parts longer
573
- split_at = possible_splits[-1] + 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
574
  else:
575
- # If no punctuation to split on within max_length, split at max_length
576
- split_at = max_length
577
-
578
- # Split the sentence and add the first part to the list
579
- parts.append(sentence[:split_at].strip())
580
- sentence = sentence[split_at:].strip()
581
-
582
- # Add the remaining part of the sentence
583
- parts.append(sentence)
584
- return parts
585
-
586
- """
587
- if 'tts' not in locals():
588
- tts = TTS(selected_tts_model, progress_bar=True).to(device)
589
- """
590
- from tqdm import tqdm
591
-
592
- # Convert chapters to audio using XTTS
593
-
594
- def convert_chapters_to_audio_custom_model(chapters_dir, output_audio_dir, temperature, length_penalty, repetition_penalty, top_k, top_p, speed, enable_text_splitting, target_voice_path=None, language=None, custom_model=None):
595
-
596
- if target_voice_path==None:
597
- target_voice_path = default_target_voice_path
598
-
599
- if custom_model:
600
- print("Loading custom model...")
601
- config = XttsConfig()
602
- config.load_json(custom_model['config'])
603
- model = Xtts.init_from_config(config)
604
- model.load_checkpoint(config, checkpoint_path=custom_model['model'], vocab_path=custom_model['vocab'], use_deepspeed=False)
605
- model.to(device)
606
- print("Computing speaker latents...")
607
- gpt_cond_latent, speaker_embedding = model.get_conditioning_latents(audio_path=[target_voice_path])
608
- else:
609
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
610
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
611
-
612
- if not os.path.exists(output_audio_dir):
613
- os.makedirs(output_audio_dir)
614
-
615
- for chapter_file in sorted(os.listdir(chapters_dir)):
616
- if chapter_file.endswith('.txt'):
617
- match = re.search(r"chapter_(\d+).txt", chapter_file)
618
- if match:
619
- chapter_num = int(match.group(1))
620
  else:
621
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
622
- continue
623
-
624
- chapter_path = os.path.join(chapters_dir, chapter_file)
625
- output_file_name = f"audio_chapter_{chapter_num}.wav"
626
- output_file_path = os.path.join(output_audio_dir, output_file_name)
627
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
628
- os.makedirs(temp_audio_directory, exist_ok=True)
629
- temp_count = 0
630
-
631
- with open(chapter_path, 'r', encoding='utf-8') as file:
632
- chapter_text = file.read()
633
- # Check if the language code is supported
634
- nltk_language = language_mapping.get(language)
635
- if nltk_language:
636
- # If the language is supported, tokenize using sent_tokenize
637
- sentences = sent_tokenize(chapter_text, language=nltk_language)
638
  else:
639
- # If the language is not supported, handle it (e.g., return the text unchanged)
640
- sentences = [chapter_text] # No tokenization, just wrap the text in a list
641
- #sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
642
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
643
- fragments = split_long_sentence(sentence, language=language)
644
- for fragment in fragments:
645
- if fragment != "":
646
- print(f"Generating fragment: {fragment}...")
647
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
648
- if custom_model:
649
- # length penalty will not apply for custome models, its just too much of a headache perhaps if someone else can do it for me lol, im just one man :(
650
- out = model.inference(fragment, language, gpt_cond_latent, speaker_embedding, temperature=temperature, repetition_penalty=repetition_penalty, top_k=top_k, top_p=top_p, speed=speed, enable_text_splitting=enable_text_splitting)
651
- #out = model.inference(fragment, language, gpt_cond_latent, speaker_embedding, temperature, length_penalty, repetition_penalty, top_k, top_p, speed, enable_text_splitting)
652
- torchaudio.save(fragment_file_path, torch.tensor(out["wav"]).unsqueeze(0), 24000)
653
- else:
654
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
655
- language_code = language if language else default_language_code
656
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code, temperature=temperature, length_penalty=length_penalty, repetition_penalty=repetition_penalty, top_k=top_k, top_p=top_p, speed=speed, enable_text_splitting=enable_text_splitting)
657
-
658
- temp_count += 1
659
-
660
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
661
- wipe_folder(temp_audio_directory)
662
- print(f"Converted chapter {chapter_num} to audio.")
663
-
664
-
665
-
666
- def convert_chapters_to_audio_standard_model(chapters_dir, output_audio_dir, temperature, length_penalty, repetition_penalty, top_k, top_p, speed, enable_text_splitting, target_voice_path=None, language="en"):
667
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
668
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
669
-
670
- if not os.path.exists(output_audio_dir):
671
- os.makedirs(output_audio_dir)
672
-
673
- for chapter_file in sorted(os.listdir(chapters_dir)):
674
- if chapter_file.endswith('.txt'):
675
- match = re.search(r"chapter_(\d+).txt", chapter_file)
676
- if match:
677
- chapter_num = int(match.group(1))
678
  else:
679
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
680
- continue
681
-
682
- chapter_path = os.path.join(chapters_dir, chapter_file)
683
- output_file_name = f"audio_chapter_{chapter_num}.wav"
684
- output_file_path = os.path.join(output_audio_dir, output_file_name)
685
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
686
- os.makedirs(temp_audio_directory, exist_ok=True)
687
- temp_count = 0
688
-
689
- with open(chapter_path, 'r', encoding='utf-8') as file:
690
- chapter_text = file.read()
691
- # Check if the language code is supported
692
- nltk_language = language_mapping.get(language)
693
- if nltk_language:
694
- # If the language is supported, tokenize using sent_tokenize
695
- sentences = sent_tokenize(chapter_text, language=nltk_language)
696
- else:
697
- # If the language is not supported, handle it (e.g., return the text unchanged)
698
- sentences = [chapter_text] # No tokenization, just wrap the text in a list
699
- #sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
700
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
701
- fragments = split_long_sentence(sentence, language=language)
702
- for fragment in fragments:
703
- if fragment != "":
704
- print(f"Generating fragment: {fragment}...")
705
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
706
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
707
- tts.tts_to_file(
708
- text=fragment,
709
- file_path=fragment_file_path,
710
- speaker_wav=speaker_wav_path,
711
- language=language,
712
- temperature=temperature,
713
- length_penalty=length_penalty,
714
- repetition_penalty=repetition_penalty,
715
- top_k=top_k,
716
- top_p=top_p,
717
- speed=speed,
718
- enable_text_splitting=enable_text_splitting
719
- )
720
-
721
- temp_count += 1
722
-
723
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
724
- wipe_folder(temp_audio_directory)
725
- print(f"Converted chapter {chapter_num} to audio.")
726
-
727
 
 
 
 
 
 
728
 
729
- # Define the functions to be used in the Gradio interface
730
- def convert_ebook_to_audio(ebook_file, target_voice_file, language, use_custom_model, custom_model_file, custom_config_file, custom_vocab_file, temperature, length_penalty, repetition_penalty, top_k, top_p, speed, enable_text_splitting, custom_model_url=None, progress=gr.Progress()):
731
-
732
- ebook_file_path = args.ebook if args.ebook else ebook_file.name
733
- target_voice = args.voice if args.voice else target_voice_file.name if target_voice_file else None
734
- custom_model = None
735
-
736
-
737
- working_files = os.path.join(".", "Working_files", "temp_ebook")
738
- full_folder_working_files = os.path.join(".", "Working_files")
739
- chapters_directory = os.path.join(".", "Working_files", "temp_ebook")
740
- output_audio_directory = os.path.join(".", 'Chapter_wav_files')
741
- remove_folder_with_contents(full_folder_working_files)
742
- remove_folder_with_contents(output_audio_directory)
743
-
744
- # If running in headless mode, use the language from args
745
- if args.headless and args.language:
746
- language = args.language
747
- else:
748
- language = language # Gradio dropdown value
749
-
750
- # If headless is used with the custom model arguments
751
- if args.use_custom_model and args.custom_model and args.custom_config and args.custom_vocab:
752
- custom_model = {
753
- 'model': args.custom_model,
754
- 'config': args.custom_config,
755
- 'vocab': args.custom_vocab
756
- }
757
-
758
- elif use_custom_model and custom_model_file and custom_config_file and custom_vocab_file:
759
- custom_model = {
760
- 'model': custom_model_file.name,
761
- 'config': custom_config_file.name,
762
- 'vocab': custom_vocab_file.name
763
- }
764
- if (use_custom_model and custom_model_url) or (args.use_custom_model and custom_model_url):
765
- print(f"Received custom model URL: {custom_model_url}")
766
- download_dir = os.path.join(".", "Working_files", "custom_model")
767
- download_and_extract_zip(custom_model_url, download_dir)
768
-
769
- # Check if vocab.json exists and rename it
770
- if rename_vocab_file_if_exists(download_dir):
771
- print("vocab.json file was found and renamed.")
772
-
773
- custom_model = {
774
- 'model': os.path.join(download_dir, 'model.pth'),
775
- 'config': os.path.join(download_dir, 'config.json'),
776
- 'vocab': os.path.join(download_dir, 'vocab.json_')
777
- }
778
-
779
- try:
780
- progress(0, desc="Starting conversion")
781
- except Exception as e:
782
- print(f"Error updating progress: {e}")
783
-
784
- if not calibre_installed():
785
- return "Calibre is not installed."
786
-
787
-
788
- try:
789
- progress(0.1, desc="Creating chapter-labeled book")
790
- except Exception as e:
791
- print(f"Error updating progress: {e}")
792
-
793
- create_chapter_labeled_book(ebook_file_path)
794
- audiobook_output_path = os.path.join(".", "Audiobooks")
795
-
796
- try:
797
- progress(0.3, desc="Converting chapters to audio")
798
- except Exception as e:
799
- print(f"Error updating progress: {e}")
800
-
801
- if use_custom_model:
802
- convert_chapters_to_audio_custom_model(chapters_directory, output_audio_directory, temperature, length_penalty, repetition_penalty, top_k, top_p, speed, enable_text_splitting, target_voice, language, custom_model)
803
  else:
804
- convert_chapters_to_audio_standard_model(chapters_directory, output_audio_directory, temperature, length_penalty, repetition_penalty, top_k, top_p, speed, enable_text_splitting, target_voice, language)
805
-
806
- try:
807
- progress(0.9, desc="Creating M4B from chapters")
808
- except Exception as e:
809
- print(f"Error updating progress: {e}")
810
-
811
- create_m4b_from_chapters(output_audio_directory, ebook_file_path, audiobook_output_path)
812
-
813
- # Get the name of the created M4B file
814
- m4b_filename = os.path.splitext(os.path.basename(ebook_file_path))[0] + '.m4b'
815
- m4b_filepath = os.path.join(audiobook_output_path, m4b_filename)
816
-
817
- try:
818
- progress(1.0, desc="Conversion complete")
819
- except Exception as e:
820
- print(f"Error updating progress: {e}")
821
- print(f"Audiobook created at {m4b_filepath}")
822
- return f"Audiobook created at {m4b_filepath}", m4b_filepath
823
-
824
-
825
- def list_audiobook_files(audiobook_folder):
826
- # List all files in the audiobook folder
827
- files = []
828
- for filename in os.listdir(audiobook_folder):
829
- if filename.endswith('.m4b'): # Adjust the file extension as needed
830
- files.append(os.path.join(audiobook_folder, filename))
831
- return files
832
-
833
- def download_audiobooks():
834
- audiobook_output_path = os.path.join(".", "Audiobooks")
835
- return list_audiobook_files(audiobook_output_path)
836
-
837
-
838
- # Gradio UI setup
839
- def run_gradio_interface():
840
- language_options = [
841
- "en", "es", "fr", "de", "it", "pt", "pl", "tr", "ru", "nl", "cs", "ar", "zh-cn", "ja", "hu", "ko"
842
- ]
843
-
844
- theme = gr.themes.Soft(
845
- primary_hue="blue",
846
- secondary_hue="blue",
847
- neutral_hue="blue",
848
- text_size=gr.themes.sizes.text_md,
849
- )
850
-
851
- # Gradio UI setup
852
- def run_gradio_interface():
853
- language_options = [
854
- "en", "es", "fr", "de", "it", "pt", "pl", "tr", "ru", "nl", "cs", "ar", "zh-cn", "ja", "hu", "ko"
855
- ]
856
-
857
- theme = gr.themes.Soft(
858
- primary_hue="blue",
859
- secondary_hue="blue",
860
- neutral_hue="blue",
861
- text_size=gr.themes.sizes.text_md,
862
- )
863
-
864
- with gr.Blocks(theme=theme) as demo:
865
- gr.Markdown(
866
- """
867
- # eBook to Audiobook Converter
868
-
869
- Transform your eBooks into immersive audiobooks with optional custom TTS models.
870
-
871
- This interface is based on [Ebook2AudioBookXTTS](https://github.com/DrewThomasson/ebook2audiobookXTTS).
872
-
873
- Xtts is very slow, you should just run it locally with docker, info in ebook2audiobookxtts github
874
-
875
- Run it Locally for Free!
876
- [![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/DrewThomasson/ebook2audiobookXTTS)
877
-
878
- Or on Free Google Colab [![Free Google Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/DrewThomasson/ebook2audiobookXTTS/blob/main/Notebooks/colab_ebook2audiobookxtts.ipynb)
879
-
880
- """
881
- )
882
-
883
- with gr.Tabs(): # Create tabs for better UI organization
884
- with gr.TabItem("Input Options"):
885
- with gr.Row():
886
- with gr.Column(scale=3):
887
- ebook_file = gr.File(label="eBook File")
888
- target_voice_file = gr.File(label="Target Voice File (Optional)")
889
- language = gr.Dropdown(label="Language", choices=language_options, value="en")
890
-
891
- with gr.Column(scale=3):
892
- use_custom_model = gr.Checkbox(label="Use Custom Model")
893
- custom_model_file = gr.File(label="Custom Model File (Optional)", visible=False)
894
- custom_config_file = gr.File(label="Custom Config File (Optional)", visible=False)
895
- custom_vocab_file = gr.File(label="Custom Vocab File (Optional)", visible=False)
896
- custom_model_url = gr.Textbox(label="Custom Model Zip URL (Optional)", visible=False)
897
-
898
- with gr.TabItem("Audio Generation Preferences"): # New tab for preferences
899
- gr.Markdown(
900
- """
901
- ### Customize Audio Generation Parameters
902
-
903
- Adjust the settings below to influence how the audio is generated. You can control the creativity, speed, repetition, and more.
904
- """
905
- )
906
- temperature = gr.Slider(
907
- label="Temperature",
908
- minimum=0.1,
909
- maximum=10.0,
910
- step=0.1,
911
- value=0.65,
912
- info="Higher values lead to more creative, unpredictable outputs. Lower values make it more monotone."
913
- )
914
- length_penalty = gr.Slider(
915
- label="Length Penalty",
916
- minimum=0.5,
917
- maximum=10.0,
918
- step=0.1,
919
- value=1.0,
920
- info="Penalize longer sequences. Higher values produce shorter outputs. Not applied to custom models."
921
- )
922
- repetition_penalty = gr.Slider(
923
- label="Repetition Penalty",
924
- minimum=1.0,
925
- maximum=10.0,
926
- step=0.1,
927
- value=2.0,
928
- info="Penalizes repeated phrases. Higher values reduce repetition."
929
- )
930
- top_k = gr.Slider(
931
- label="Top-k Sampling",
932
- minimum=10,
933
- maximum=100,
934
- step=1,
935
- value=50,
936
- info="Lower values restrict outputs to more likely words and increase speed at which audio generates. "
937
- )
938
- top_p = gr.Slider(
939
- label="Top-p Sampling",
940
- minimum=0.1,
941
- maximum=1.0,
942
- step=.01,
943
- value=0.8,
944
- info="Controls cumulative probability for word selection. Lower values make the output more predictable and increase speed at which audio generates."
945
- )
946
- speed = gr.Slider(
947
- label="Speed",
948
- minimum=0.5,
949
- maximum=3.0,
950
- step=0.1,
951
- value=1.0,
952
- info="Adjusts How fast the narrator will speak."
953
- )
954
- enable_text_splitting = gr.Checkbox(
955
- label="Enable Text Splitting",
956
- value=False,
957
- info="Splits long texts into sentences to generate audio in chunks. Useful for very long inputs."
958
- )
959
-
960
- convert_btn = gr.Button("Convert to Audiobook", variant="primary")
961
- output = gr.Textbox(label="Conversion Status")
962
- audio_player = gr.Audio(label="Audiobook Player", type="filepath")
963
- download_btn = gr.Button("Download Audiobook Files")
964
- download_files = gr.File(label="Download Files", interactive=False)
965
-
966
- convert_btn.click(
967
- lambda *args: convert_ebook_to_audio(
968
- *args[:7],
969
- float(args[7]), # Ensure temperature is float
970
- float(args[8]), # Ensure length_penalty is float
971
- float(args[9]), # Ensure repetition_penalty is float
972
- int(args[10]), # Ensure top_k is int
973
- float(args[11]), # Ensure top_p is float
974
- float(args[12]), # Ensure speed is float
975
- *args[13:]
976
- ),
977
- inputs=[
978
- ebook_file, target_voice_file, language, use_custom_model, custom_model_file, custom_config_file,
979
- custom_vocab_file, temperature, length_penalty, repetition_penalty,
980
- top_k, top_p, speed, enable_text_splitting, custom_model_url
981
- ],
982
- outputs=[output, audio_player]
983
- )
984
-
985
-
986
- use_custom_model.change(
987
- lambda x: [gr.update(visible=x)] * 4,
988
- inputs=[use_custom_model],
989
- outputs=[custom_model_file, custom_config_file, custom_vocab_file, custom_model_url]
990
- )
991
-
992
- download_btn.click(
993
- download_audiobooks,
994
- outputs=[download_files]
995
- )
996
-
997
- # Get the correct local IP or localhost
998
- hostname = socket.gethostname()
999
- local_ip = socket.gethostbyname(hostname)
1000
-
1001
- # Ensure Gradio runs and prints the correct local IP
1002
- print(f"Running on local URL: http://{local_ip}:7860")
1003
- print(f"Running on local URL: http://localhost:7860")
1004
-
1005
- # Launch Gradio app
1006
- demo.launch(server_name="0.0.0.0", server_port=7860, share=args.share)
1007
-
1008
-
1009
-
1010
-
1011
-
1012
- # Check if running in headless mode
1013
- if args.headless:
1014
- # If the arg.custom_model_url exists then use it as the custom_model_url lol
1015
- custom_model_url = args.custom_model_url if args.custom_model_url else None
1016
-
1017
- if not args.ebook:
1018
- print("Error: In headless mode, you must specify an ebook file using --ebook.")
1019
- exit(1)
1020
-
1021
- ebook_file_path = args.ebook
1022
- target_voice = args.voice if args.voice else None
1023
- custom_model = None
1024
-
1025
- if args.use_custom_model:
1026
- # Check if custom_model_url is provided
1027
- if args.custom_model_url:
1028
- # Download the custom model from the provided URL
1029
- custom_model_url = args.custom_model_url
1030
  else:
1031
- # If no URL is provided, ensure all custom model files are provided
1032
- if not args.custom_model or not args.custom_config or not args.custom_vocab:
1033
- print("Error: You must provide either a --custom_model_url or all of the following arguments:")
1034
- print("--custom_model, --custom_config, and --custom_vocab")
1035
- exit(1)
1036
- else:
1037
- # Assign the custom model files
1038
- custom_model = {
1039
- 'model': args.custom_model,
1040
- 'config': args.custom_config,
1041
- 'vocab': args.custom_vocab
1042
- }
1043
-
1044
-
1045
 
1046
- # Example headless execution
1047
- convert_ebook_to_audio(ebook_file_path, target_voice, args.language, args.use_custom_model, args.custom_model, args.custom_config, args.custom_vocab, args.temperature, args.length_penalty, args.repetition_penalty, args.top_k, args.top_p, args.speed, args.enable_text_splitting, custom_model_url)
1048
-
1049
-
1050
- else:
1051
- # Launch Gradio UI
1052
- run_gradio_interface()
 
 
 
1
  import argparse
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import os
3
+ import regex as re
4
+ import socket
5
  import subprocess
 
 
 
 
 
 
6
  import sys
7
+ import unidic
8
+
9
+ from lib.conf import *
10
+ from lib.lang import language_mapping, default_language_code
11
+
12
+ def check_python_version():
13
+ current_version = sys.version_info[:2] # (major, minor)
14
+ if current_version < min_python_version or current_version > max_python_version:
15
+ error = f'''********** Error: Your OS Python version is not compatible! (current: {current_version[0]}.{current_version[1]})
16
+ Please create a virtual python environment verrsion {min_python_version[0]}.{min_python_version[1]} or {max_python_version[0]}.{max_python_version[1]}
17
+ with conda or python -v venv **********'''
18
+ print(error)
19
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  else:
21
+ return True
22
+
23
+ def check_and_install_requirements(file_path):
24
+ if not os.path.exists(file_path):
25
+ print(f'Warning: File {file_path} not found. Skipping package check.')
26
  try:
27
+ from importlib.metadata import version, PackageNotFoundError
28
+ with open(file_path, 'r') as f:
29
+ contents = f.read().replace('\r', '\n')
30
+ packages = [pkg.strip() for pkg in contents.splitlines() if pkg.strip()]
31
+
32
+ missing_packages = []
33
+ for package in packages:
34
+ # Extract package name without version specifier
35
+ pkg_name = re.split(r'[<>=]', package)[0].strip()
36
+ try:
37
+ installed_version = version(pkg_name)
38
+ except PackageNotFoundError:
39
+ print(f'{package} is missing.')
40
+ missing_packages.append(package)
41
+ pass
42
+
43
+ if missing_packages:
44
+ print('\nInstalling missing packages...')
45
+ try:
46
+ subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip'] + missing_packages)
47
+ except subprocess.CalledProcessError as e:
48
+ print(f'Failed to install packages: {e}')
49
+ return False
 
 
 
 
 
 
 
 
 
 
50
 
51
+ return True
52
+ except Exception as e:
53
+ raise(f'An error occurred: {e}')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ def check_dictionary():
56
+ unidic_path = unidic.DICDIR
57
+ dicrc = os.path.join(unidic_path, 'dicrc')
58
+ if not os.path.exists(dicrc) or os.path.getsize(dicrc) == 0:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  try:
60
+ print('UniDic dictionary not found or incomplete. Downloading now...')
61
+ subprocess.run(['python', '-m', 'unidic', 'download'], check=True)
62
  except subprocess.CalledProcessError as e:
63
+ print(f'Failed to download UniDic dictionary. Error: {e}')
64
+ raise SystemExit('Unable to continue without UniDic. Exiting...')
65
+ return True
66
+
67
+ def is_port_in_use(port):
68
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
69
+ return s.connect_ex(('0.0.0.0', port)) == 0
70
+
71
+ def main():
72
+ global is_gui_process
73
+
74
+ # Convert the list of languages to a string to display in the help text
75
+ lang_list_str = ', '.join(list(language_mapping.keys()))
76
+
77
+ # Argument parser to handle optional parameters with descriptions
78
+ parser = argparse.ArgumentParser(
79
+ description='Convert eBooks to Audiobooks using a Text-to-Speech model. You can either launch the Gradio interface or run the script in headless mode for direct conversion.',
80
+ epilog='''
81
+ Example usage:
82
+ Windows:
83
+ headless:
84
+ ebook2audiobook.cmd --headless --ebook 'path_to_ebook'
85
+ Graphic Interface:
86
+ ebook2audiobook.cmd
87
+ Linux/Mac:
88
+ headless:
89
+ ./ebook2audiobook.sh --headless --ebook 'path_to_ebook'
90
+ Graphic Interface:
91
+ ./ebook2audiobook.sh
92
+ ''',
93
+ formatter_class=argparse.RawTextHelpFormatter
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  )
95
+ options = [
96
+ '--script_mode', '--share', '--headless',
97
+ '--session', '--ebook', '--ebooks_dir',
98
+ '--voice', '--language', '--device', '--custom_model',
99
+ '--temperature', '--length_penalty', '--repetition_penalty',
100
+ '--top_k', '--top_p', '--speed',
101
+ '--enable_text_splitting', '--fine_tuned',
102
+ '--version', '--help'
103
+ ]
104
+ parser.add_argument(options[0], type=str,
105
+ help='Force the script to run in NATIVE or DOCKER_UTILS')
106
+ parser.add_argument(options[1], action='store_true',
107
+ help='Enable a public shareable Gradio link. Default to False.')
108
+ parser.add_argument(options[2], nargs='?', const=True, default=False,
109
+ help='Run in headless mode. Default to True if the flag is present without a value, False otherwise.')
110
+ parser.add_argument(options[3], type=str,
111
+ help='Session to reconnect in case of interruption (headless mode only)')
112
+ parser.add_argument(options[4], type=str,
113
+ help='Path to the ebook file for conversion. Required in headless mode.')
114
+ parser.add_argument(options[5], nargs='?', const='default', type=str,
115
+ help=f'Path to the directory containing ebooks for batch conversion. Default to "{os.path.basename(ebooks_dir)}" if "default" is provided.')
116
+ parser.add_argument(options[6], type=str, default=None,
117
+ help='Path to the target voice file for TTS. Optional, must be 24khz for XTTS and 16khz for fairseq models, uses a default voice if not provided.')
118
+ parser.add_argument(options[7], type=str, default=default_language_code,
119
+ help=f'Language for the audiobook conversion. Options: {lang_list_str}. Default to English (eng).')
120
+ parser.add_argument(options[8], type=str, default='cpu', choices=['cpu', 'gpu'],
121
+ help=f'Type of processor unit for the audiobook conversion. If not specified: check first if gpu available, if not cpu is selected.')
122
+ parser.add_argument(options[9], type=str,
123
+ help=f'Path to the custom model (.zip file containing {default_model_files}). Required if using a custom model.')
124
+ parser.add_argument(options[10], type=float, default=0.65,
125
+ help='Temperature for the model. Default to 0.65. Higher temperatures lead to more creative outputs.')
126
+ parser.add_argument(options[11], type=float, default=1.0,
127
+ help='A length penalty applied to the autoregressive decoder. Default to 1.0. Not applied to custom models.')
128
+ parser.add_argument(options[12], type=float, default=2.5,
129
+ help='A penalty that prevents the autoregressive decoder from repeating itself. Default to 2.5')
130
+ parser.add_argument(options[13], type=int, default=50,
131
+ help='Top-k sampling. Lower values mean more likely outputs and increased audio generation speed. Default to 50')
132
+ parser.add_argument(options[14], type=float, default=0.8,
133
+ help='Top-p sampling. Lower values mean more likely outputs and increased audio generation speed. Default to 0.8')
134
+ parser.add_argument(options[15], type=float, default=1.0,
135
+ help='Speed factor for the speech generation. Default to 1.0')
136
+ parser.add_argument(options[16], action='store_true',
137
+ help='Enable splitting text into sentences. Default to False.')
138
+ parser.add_argument(options[17], type=str, default=default_fine_tuned,
139
+ help='Name of the fine tuned model. Optional, uses the standard model according to the TTS engine and language.')
140
+ parser.add_argument(options[18], action='version',version=f'ebook2audiobook version {version}',
141
+ help='Show the version of the script and exit')
142
+
143
+ for arg in sys.argv:
144
+ if arg.startswith('--') and arg not in options:
145
+ print(f'Error: Unrecognized option "{arg}"')
146
+ sys.exit(1)
147
+
148
+ args = vars(parser.parse_args())
149
+
150
+ # Check if the port is already in use to prevent multiple launches
151
+ if not args['headless'] and is_port_in_use(interface_port):
152
+ print(f'Error: Port {interface_port} is already in use. The web interface may already be running.')
153
+ sys.exit(1)
154
+
155
+ args['script_mode'] = args['script_mode'] if args['script_mode'] else NATIVE
156
+ args['share'] = args['share'] if args['share'] else False
157
+
158
+ if args['script_mode'] == NATIVE:
159
+ check_pkg = check_and_install_requirements(requirements_file)
160
+ if check_pkg:
161
+ if not check_dictionary():
162
+ sys.exit(1)
163
  else:
164
+ print('Some packages could not be installed')
165
+ sys.exit(1)
166
+
167
+ from lib.functions import web_interface, convert_ebook
168
+
169
+ # Conditions based on the --headless flag
170
+ if args['headless']:
171
+ args['is_gui_process'] = False
172
+ args['audiobooks_dir'] = audiobooks_cli_dir
173
+
174
+ # Condition to stop if both --ebook and --ebooks_dir are provided
175
+ if args['ebook'] and args['ebooks_dir']:
176
+ print('Error: You cannot specify both --ebook and --ebooks_dir in headless mode.')
177
+ sys.exit(1)
178
+
179
+ # Condition 1: If --ebooks_dir exists, check value and set 'ebooks_dir'
180
+ if args['ebooks_dir']:
181
+ new_ebooks_dir = None
182
+ if args['ebooks_dir'] == 'default':
183
+ print(f'Using the default ebooks_dir: {ebooks_dir}')
184
+ new_ebooks_dir = os.path.abspath(ebooks_dir)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  else:
186
+ # Check if the directory exists
187
+ if os.path.exists(args['ebooks_dir']):
188
+ new_ebooks_dir = os.path.abspath(args['ebooks_dir'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  else:
190
+ print(f'Error: The provided --ebooks_dir "{args["ebooks_dir"]}" does not exist.')
191
+ sys.exit(1)
192
+
193
+ if os.path.exists(new_ebooks_dir):
194
+ for file in os.listdir(new_ebooks_dir):
195
+ # Process files with supported ebook formats
196
+ if any(file.endswith(ext) for ext in ebook_formats):
197
+ full_path = os.path.join(new_ebooks_dir, file)
198
+ print(f'Processing eBook file: {full_path}')
199
+ args['ebook'] = full_path
200
+ progress_status, audiobook_file = convert_ebook(args)
201
+ if audiobook_file is None:
202
+ print(f'Conversion failed: {progress_status}')
203
+ sys.exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  else:
205
+ print(f'Error: The directory {new_ebooks_dir} does not exist.')
206
+ sys.exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
+ elif args['ebook']:
209
+ progress_status, audiobook_file = convert_ebook(args)
210
+ if audiobook_file is None:
211
+ print(f'Conversion failed: {progress_status}')
212
+ sys.exit(1)
213
 
214
+ else:
215
+ print('Error: In headless mode, you must specify either an ebook file using --ebook or an ebook directory using --ebooks_dir.')
216
+ sys.exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  else:
218
+ args['is_gui_process'] = True
219
+ passed_arguments = sys.argv[1:]
220
+ allowed_arguments = {'--share', '--script_mode'}
221
+ passed_args_set = {arg for arg in passed_arguments if arg.startswith('--')}
222
+ if passed_args_set.issubset(allowed_arguments):
223
+ web_interface(args)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  else:
225
+ print('Error: In non-headless mode, no option or only --share can be passed')
226
+ sys.exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
+ if __name__ == '__main__':
229
+ if not check_python_version():
230
+ sys.exit(1)
231
+ else:
232
+ main()
 
 
default_voice.wav DELETED
Binary file (291 kB)
 
download_tos_agreed_file.py DELETED
@@ -1,23 +0,0 @@
1
- import os
2
- import urllib.request
3
-
4
- def download_tos_agreed():
5
- # Get the current user's home directory
6
- user_home = os.path.expanduser('~')
7
-
8
- # Set the destination directory and file URL
9
- dest_dir = os.path.join(user_home, '.local', 'share', 'tts', 'tts_models--multilingual--multi-dataset--xtts_v2')
10
- file_url = "https://github.com/DrewThomasson/VoxNovel/raw/main/readme_files/tos_agreed.txt"
11
-
12
- # Create the destination directory if it doesn't exist
13
- os.makedirs(dest_dir, exist_ok=True)
14
-
15
- # Download the file to the destination directory
16
- file_path = os.path.join(dest_dir, 'tos_agreed.txt')
17
- urllib.request.urlretrieve(file_url, file_path)
18
-
19
- print(f"File has been saved to {file_path}")
20
- print("The tos_agreed.txt file is so that you don't have to tell coqio TTS yes when downloading the xtts_v2 model.")
21
-
22
- # Run the download function
23
- download_tos_agreed()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobook.sh ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ PYTHON_VERSION="3.12"
4
+ export TTS_CACHE="./models"
5
+
6
+ ARGS="$@"
7
+
8
+ # Declare an associative array
9
+ declare -A arguments
10
+
11
+ # Parse arguments
12
+ while [[ "$#" -gt 0 ]]; do
13
+ case "$1" in
14
+ --*)
15
+ key="${1/--/}" # Remove leading '--'
16
+ if [[ -n "$2" && ! "$2" =~ ^-- ]]; then
17
+ # If the next argument is a value (not another option)
18
+ arguments[$key]="$2"
19
+ shift # Move past the value
20
+ else
21
+ # Set to true for flags without values
22
+ arguments[$key]=true
23
+ fi
24
+ ;;
25
+ *)
26
+ echo "Unknown option: $1"
27
+ exit 1
28
+ ;;
29
+ esac
30
+ shift # Move to the next argument
31
+ done
32
+
33
+ NATIVE="native"
34
+ DOCKER_UTILS="docker_utils"
35
+ FULL_DOCKER="full_docker"
36
+
37
+ SCRIPT_MODE="$NATIVE"
38
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
39
+
40
+ WGET=$(which wget 2>/dev/null)
41
+ REQUIRED_PROGRAMS=("calibre" "ffmpeg")
42
+ DOCKER_UTILS_IMG="utils"
43
+ PYTHON_ENV="python_env"
44
+ CURRENT_ENV=""
45
+
46
+ if [[ "$OSTYPE" = "darwin"* ]]; then
47
+ CONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh"
48
+ else
49
+ CONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh"
50
+ fi
51
+ CONDA_INSTALLER=/tmp/Miniconda3-latest.sh
52
+ CONDA_INSTALL_DIR=$HOME/miniconda3
53
+ CONDA_PATH=$HOME/miniconda3/bin
54
+ CONDA_ENV=~/miniconda3/etc/profile.d/conda.sh
55
+ CONFIG_FILE="$HOME/.bashrc"
56
+ PATH="$CONDA_PATH:$PATH"
57
+
58
+ declare -a programs_missing
59
+
60
+ # Check if the current script is run inside a docker container
61
+ if [[ -n "$container" || -f /.dockerenv ]]; then
62
+ SCRIPT_MODE="$FULL_DOCKER"
63
+ else
64
+ if [[ -n "${arguments['script_mode']+exists}" ]]; then
65
+ if [ "${arguments['script_mode']}" = "$NATIVE" ] || [ "${arguments['script_mode']}" = "$DOCKER_UTILS" ]; then
66
+ SCRIPT_MODE="${arguments['script_mode']}"
67
+ fi
68
+ fi
69
+ fi
70
+
71
+ # Check if running in a Conda or Python virtual environment
72
+ if [[ -n "$CONDA_DEFAULT_ENV" ]]; then
73
+ CURRENT_ENV="$CONDA_PREFIX"
74
+ elif [[ -n "$VIRTUAL_ENV" ]]; then
75
+ CURRENT_ENV="$VIRTUAL_ENV"
76
+ fi
77
+
78
+ # If neither environment variable is set, check Python path
79
+ if [[ -z "$CURRENT_ENV" ]]; then
80
+ PYTHON_PATH=$(which python 2>/dev/null)
81
+ if [[ ( -n "$CONDA_PREFIX" && "$PYTHON_PATH" == "$CONDA_PREFIX/bin/python" ) || ( -n "$VIRTUAL_ENV" && "$PYTHON_PATH" == "$VIRTUAL_ENV/bin/python" ) ]]; then
82
+ CURRENT_ENV="${CONDA_PREFIX:-$VIRTUAL_ENV}"
83
+ fi
84
+ fi
85
+
86
+ # Output result if a virtual environment is detected
87
+ if [[ -n "$CURRENT_ENV" ]]; then
88
+ echo -e "Current python virtual environment detected: $CURRENT_ENV."
89
+ echo -e "This script runs with its own virtual env and must be out of any other virtual environment when it's launched."
90
+ echo -e "If you are using miniconda then you would type in:"
91
+ echo -e "conda deactivate"
92
+ exit 1
93
+ fi
94
+
95
+ function required_programs_check {
96
+ local programs=("$@")
97
+ for program in "${programs[@]}"; do
98
+ if ! command -v "$program" >/dev/null 2>&1; then
99
+ echo -e "\e[33m$program is not installed.\e[0m"
100
+ programs_missing+=($program)
101
+ fi
102
+ done
103
+ local count=${#programs_missing[@]}
104
+ if [[ $count -eq 0 ]]; then
105
+ return 0
106
+ else
107
+ return 1
108
+ fi
109
+ }
110
+
111
+ function install_programs {
112
+ echo -e "\e[33mInstalling required programs. NOTE: you must have 'sudo' priviliges or it will fail.\e[0m"
113
+ if [[ "$OSTYPE" = "darwin"* ]]; then
114
+ PACK_MGR="brew install"
115
+ if ! command -v brew &> /dev/null; then
116
+ echo -e "\e[33mHomebrew is not installed. Installing Homebrew...\e[0m"
117
+ /usr/bin/env bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
118
+ echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
119
+ eval "$(/opt/homebrew/bin/brew shellenv)"
120
+ fi
121
+ else
122
+ if command -v emerge &> /dev/null; then
123
+ PACK_MGR="sudo emerge"
124
+ elif command -v dnf &> /dev/null; then
125
+ PACK_MGR="sudo dnf install"
126
+ PACK_MGR_OPTIONS="-y"
127
+ elif command -v yum &> /dev/null; then
128
+ PACK_MGR="sudo yum install"
129
+ PACK_MGR_OPTIONS="-y"
130
+ elif command -v zypper &> /dev/null; then
131
+ PACK_MGR="sudo zypper install"
132
+ PACK_MGR_OPTIONS="-y"
133
+ elif command -v pacman &> /dev/null; then
134
+ PACK_MGR="sudo pacman -Sy"
135
+ elif command -v apt-get &> /dev/null; then
136
+ sudo apt-get update
137
+ PACK_MGR="sudo apt-get install"
138
+ PACK_MGR_OPTIONS="-y"
139
+ elif command -v apk &> /dev/null; then
140
+ PACK_MGR="sudo apk add"
141
+ else
142
+ echo "Cannot recognize your applications package manager. Please install the required applications manually."
143
+ return 1
144
+ fi
145
+
146
+ fi
147
+ if [ -z "$WGET" ]; then
148
+ echo -e "\e[33m wget is missing! trying to install it... \e[0m"
149
+ result=$(eval "$PACK_MGR wget $PACK_MGR_OPTIONS" 2>&1)
150
+ result_code=$?
151
+ if [ $result_code -eq 0 ]; then
152
+ WGET=$(which wget 2>/dev/null)
153
+ else
154
+ echo "Cannot 'wget'. Please install 'wget' manually."
155
+ return 1
156
+ fi
157
+ fi
158
+ for program in "${programs_missing[@]}"; do
159
+ if [ "$program" = "calibre" ];then
160
+ # avoid conflict with calibre builtin lxml
161
+ pip uninstall lxml -y 2>/dev/null
162
+ echo -e "\e[33mInstalling Calibre...\e[0m"
163
+ if [[ "$OSTYPE" = "darwin"* ]]; then
164
+ eval "$PACK_MGR --cask calibre"
165
+ else
166
+ $WGET -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin
167
+ fi
168
+ if command -v calibre >/dev/null 2>&1; then
169
+ echo -e "\e[32m===============>>> Calibre is installed! <<===============\e[0m"
170
+ else
171
+ echo "Calibre installation failed."
172
+ fi
173
+ else
174
+ eval "$PACK_MGR $program $PKG_MGR_OPTIONS"
175
+ if command -v $program >/dev/null 2>&1; then
176
+ echo -e "\e[32m===============>>> $program is installed! <<===============\e[0m"
177
+ else
178
+ echo "$program installation failed."
179
+ fi
180
+ fi
181
+ done
182
+ if required_programs_check "${REQUIRED_PROGRAMS[@]}"; then
183
+ return 0
184
+ else
185
+ echo -e "\e[33mYou can run 'ebook2audiobook.sh --script_mode docker_utils' to avoid to install $REQUIRED_PROGRAMS natively.\e[0m"
186
+ return 1
187
+ fi
188
+ }
189
+
190
+ function conda_check {
191
+ if ! command -v conda &> /dev/null; then
192
+ echo -e "\e[33mconda is not installed!\e[0m"
193
+ echo -e "\e[33mDownloading conda installer...\e[0m"
194
+ wget -O "$CONDA_INSTALLER" "$CONDA_URL"
195
+ if [[ -f "$CONDA_INSTALLER" ]]; then
196
+ echo -e "\e[33mInstalling Miniconda...\e[0m"
197
+ bash "$CONDA_INSTALLER" -u -b -p "$CONDA_INSTALL_DIR"
198
+ rm -f "$CONDA_INSTALLER"
199
+ if [[ -f "$CONDA_INSTALL_DIR/bin/conda" ]]; then
200
+ conda init
201
+ echo -e "\e[32m===============>>> conda is installed! <<===============\e[0m"
202
+ else
203
+ echo -e "\e[31mconda installation failed.\e[0m"
204
+ return 1
205
+ fi
206
+ else
207
+ echo -e "\e[31mFailed to download Miniconda installer.\e[0m"
208
+ echo -e "\e[33mI'ts better to use the install.sh to install everything needed.\e[0m"
209
+ return 1
210
+ fi
211
+ fi
212
+ if [[ ! -d $SCRIPT_DIR/$PYTHON_ENV ]]; then
213
+ # Use this condition to chmod writable folders once
214
+ chmod -R 777 ./audiobooks ./tmp ./models
215
+ conda create --prefix $SCRIPT_DIR/$PYTHON_ENV python=$PYTHON_VERSION -y
216
+ source $CONDA_ENV
217
+ conda activate $SCRIPT_DIR/$PYTHON_ENV
218
+ python -m pip install --upgrade pip
219
+ python -m pip install --upgrade -r requirements.txt --progress-bar=on
220
+ conda deactivate
221
+ fi
222
+ return 0
223
+ }
224
+
225
+ function docker_check {
226
+ if ! command -v docker &> /dev/null; then
227
+ echo -e "\e[33m docker is missing! trying to install it... \e[0m"
228
+ if [[ "$OSTYPE" == "darwin"* ]]; then
229
+ echo "Installing Docker using Homebrew..."
230
+ $PACK_MGR --cask docker $PACK_MGR_OPTIONS
231
+ else
232
+ $WGET -qO get-docker.sh https://get.docker.com && \
233
+ sudo sh get-docker.sh
234
+ sudo systemctl start docker
235
+ sudo systemctl enable docker
236
+ docker run hello-world
237
+ rm -f get-docker.sh
238
+ fi
239
+ echo -e "\e[32m===============>>> docker is installed! <<===============\e[0m"
240
+ docker_build
241
+ else
242
+ # Check if Docker service is running
243
+ if docker info >/dev/null 2>&1; then
244
+ if [[ "$(docker images -q $DOCKER_UTILS_IMG 2> /dev/null)" = "" ]]; then
245
+ docker_build
246
+ fi
247
+ else
248
+ echo -e "\e[33mDocker is not running\e[0m"
249
+ return 1
250
+ fi
251
+ fi
252
+ return 0
253
+ }
254
+
255
+ function docker_build {
256
+ # Check if the Docker socket is accessible
257
+ if [[ -e /var/run/docker.sock || -e /run/docker.sock ]]; then
258
+ echo -e "\e[33mDocker image '$DOCKER_UTILS_IMG' not found. Trying to build it...\e[0m"
259
+ docker build -f DockerfileUtils -t utils .
260
+ else
261
+ echo -e "\e[33mcannot connect to docker socket. Check if the docker socket is running.\e[0m"
262
+ fi
263
+ }
264
+
265
+ if [ "$SCRIPT_MODE" = "$FULL_DOCKER" ]; then
266
+ echo -e "\e[33mRunning in $FULL_DOCKER mode\e[0m"
267
+ python app.py --script_mode $SCRIPT_MODE $ARGS
268
+ elif [[ "$SCRIPT_MODE" == "$NATIVE" || "$SCRIPT_MODE" = "$DOCKER_UTILS" ]]; then
269
+ pass=true
270
+ if [ "$SCRIPT_MODE" == "$NATIVE" ]; then
271
+ echo -e "\e[33mRunning in $NATIVE mode\e[0m"
272
+ if ! required_programs_check "${REQUIRED_PROGRAMS[@]}"; then
273
+ if ! install_programs; then
274
+ pass=false
275
+ fi
276
+ fi
277
+ else
278
+ echo -e "\e[33mRunning in $DOCKER_UTILS mode\e[0m"
279
+ if conda_check; then
280
+ if docker_check; then
281
+ source $CONDA_ENV
282
+ conda activate $SCRIPT_DIR/$PYTHON_ENV
283
+ python app.py --script_mode $DOCKER_UTILS $ARGS
284
+ conda deactivate
285
+ fi
286
+ fi
287
+ fi
288
+ if [ $pass = true ]; then
289
+ if conda_check; then
290
+ source $CONDA_ENV
291
+ conda activate $SCRIPT_DIR/$PYTHON_ENV
292
+ python app.py --script_mode $SCRIPT_MODE $ARGS
293
+ conda deactivate
294
+ fi
295
+ fi
296
+ else
297
+ echo -e "\e[33mebook2audiobook is not correctly installed or run.\e[0m"
298
+ fi
299
+
300
+ exit 0
ebook2audiobookXTTS/Dockerfile DELETED
@@ -1,93 +0,0 @@
1
- # Use an official NVIDIA CUDA image with cudnn8 and Ubuntu 20.04 as the base
2
- FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu20.04
3
-
4
- # Set non-interactive installation to avoid timezone and other prompts
5
- ENV DEBIAN_FRONTEND=noninteractive
6
-
7
- # Install necessary packages including Miniconda
8
- RUN apt-get update && apt-get install -y --no-install-recommends \
9
- wget \
10
- git \
11
- espeak \
12
- espeak-ng \
13
- ffmpeg \
14
- tk \
15
- mecab \
16
- libmecab-dev \
17
- mecab-ipadic-utf8 \
18
- build-essential \
19
- calibre \
20
- && rm -rf /var/lib/apt/lists/*
21
-
22
- RUN ebook-convert --version
23
-
24
- # Install Miniconda
25
- RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \
26
- bash ~/miniconda.sh -b -p /opt/conda && \
27
- rm ~/miniconda.sh
28
-
29
-
30
-
31
- # Set PATH to include conda
32
- ENV PATH=/opt/conda/bin:$PATH
33
-
34
- # Create a conda environment with Python 3.10
35
- RUN conda create -n ebookenv python=3.10 -y
36
-
37
- # Activate the conda environment
38
- SHELL ["conda", "run", "-n", "ebookenv", "/bin/bash", "-c"]
39
-
40
- # Install Python dependencies using conda and pip
41
- RUN conda install -n ebookenv -c conda-forge \
42
- pydub \
43
- nltk \
44
- mecab-python3 \
45
- && pip install --no-cache-dir \
46
- bs4 \
47
- beautifulsoup4 \
48
- ebooklib \
49
- tqdm \
50
- tts==0.21.3 \
51
- unidic \
52
- gradio
53
-
54
- # Download unidic
55
- RUN python -m unidic download
56
-
57
- # Set the working directory in the container
58
- WORKDIR /ebook2audiobookXTTS
59
-
60
- # Clone the ebook2audiobookXTTS repository
61
- RUN git clone https://github.com/DrewThomasson/ebook2audiobookXTTS.git .
62
-
63
- # Copy test audio file
64
- COPY default_voice.wav /ebook2audiobookXTTS/
65
-
66
- # Run a test to set up XTTS
67
- RUN echo "import torch" > /tmp/script1.py && \
68
- echo "from TTS.api import TTS" >> /tmp/script1.py && \
69
- echo "device = 'cuda' if torch.cuda.is_available() else 'cpu'" >> /tmp/script1.py && \
70
- echo "print(TTS().list_models())" >> /tmp/script1.py && \
71
- echo "tts = TTS('tts_models/multilingual/multi-dataset/xtts_v2').to(device)" >> /tmp/script1.py && \
72
- echo "wav = tts.tts(text='Hello world!', speaker_wav='default_voice.wav', language='en')" >> /tmp/script1.py && \
73
- echo "tts.tts_to_file(text='Hello world!', speaker_wav='default_voice.wav', language='en', file_path='output.wav')" >> /tmp/script1.py && \
74
- yes | python /tmp/script1.py
75
-
76
- # Remove the test audio file
77
- RUN rm -f /ebook2audiobookXTTS/output.wav
78
-
79
- # Verify that the script exists and has the correct permissions
80
- RUN ls -la /ebook2audiobookXTTS/
81
-
82
- # Check if the script exists and log its presence
83
- RUN if [ -f /ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS_with_link_gradio.py ]; then echo "Script found."; else echo "Script not found."; exit 1; fi
84
-
85
- # Modify the Python script to set share=True
86
- RUN sed -i 's/demo.launch(share=False)/demo.launch(share=True)/' /ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS_with_link_gradio.py
87
-
88
- # Download the punkt package for nltk
89
- RUN python -m nltk.downloader punkt
90
-
91
- # Set the command to run your GUI application using the conda environment
92
- CMD ["conda", "run", "--no-capture-output", "-n", "ebookenv", "python", "/ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS_with_link_gradio.py"]
93
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobookXTTS/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Drew Thomasson
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobookXTTS/README.md DELETED
@@ -1,171 +0,0 @@
1
- # 📚 ebook2audiobook
2
-
3
- Convert eBooks to audiobooks with chapters and metadata using Calibre and Coqui XTTS. Supports optional voice cloning and multiple languages!
4
-
5
- ## 🌟 Features
6
-
7
- - 📖 Converts eBooks to text format with Calibre.
8
- - 📚 Splits eBook into chapters for organized audio.
9
- - 🎙️ High-quality text-to-speech with Coqui XTTS.
10
- - 🗣️ Optional voice cloning with your own voice file.
11
- - 🌍 Supports multiple languages (English by default).
12
- - 🖥️ Designed to run on 4GB RAM.
13
-
14
- ## 🛠️ Requirements
15
-
16
- - Python 3.x
17
- - `coqui-tts` Python package
18
- - Calibre (for eBook conversion)
19
- - FFmpeg (for audiobook creation)
20
- - Optional: Custom voice file for voice cloning
21
-
22
- ### 🔧 Installation Instructions
23
-
24
- 1. **Install Python 3.x** from [Python.org](https://www.python.org/downloads/).
25
-
26
- 2. **Install Calibre**:
27
- - **Ubuntu**: `sudo apt-get install -y calibre`
28
- - **macOS**: `brew install calibre`
29
- - **Windows** (Admin Powershell): `choco install calibre`
30
-
31
- 3. **Install FFmpeg**:
32
- - **Ubuntu**: `sudo apt-get install -y ffmpeg`
33
- - **macOS**: `brew install ffmpeg`
34
- - **Windows** (Admin Powershell): `choco install ffmpeg`
35
-
36
- 4. **Optional: Install Mecab** (for non-Latin languages):
37
- - **Ubuntu**: `sudo apt-get install -y mecab libmecab-dev mecab-ipadic-utf8`
38
- - **macOS**: `brew install mecab`, `brew install mecab-ipadic`
39
- - **Windows**: [mecab-website-to-install-manually](https://taku910.github.io/mecab/#download) (Note: Japanese support is limited)
40
-
41
- 5. **Install Python packages**:
42
- ```bash
43
- pip install tts==0.21.3 pydub nltk beautifulsoup4 ebooklib tqdm
44
-
45
- python -m nltk.downloader punkt
46
- ```
47
-
48
- **For non-Latin languages**:
49
- ```bash
50
- pip install mecab mecab-python3 unidic
51
-
52
- python -m unidic download
53
- ```
54
-
55
- ## 🌐 Supported Languages
56
-
57
- - **English (en)**
58
- - **Spanish (es)**
59
- - **French (fr)**
60
- - **German (de)**
61
- - **Italian (it)**
62
- - **Portuguese (pt)**
63
- - **Polish (pl)**
64
- - **Turkish (tr)**
65
- - **Russian (ru)**
66
- - **Dutch (nl)**
67
- - **Czech (cs)**
68
- - **Arabic (ar)**
69
- - **Chinese (zh-cn)**
70
- - **Japanese (ja)**
71
- - **Hungarian (hu)**
72
- - **Korean (ko)**
73
-
74
- Specify the language code when running the script.
75
-
76
- ## 🚀 Usage
77
-
78
- ### 🖥️ Gradio Web Interface
79
-
80
- 1. **Run the Script**:
81
- ```bash
82
- python custom_model_ebook2audiobookXTTS_gradio.py
83
- ```
84
-
85
- 2. **Open the Web App**: Click the URL provided in the terminal to access the web app and convert eBooks.
86
-
87
- ### 📝 Basic Usage
88
-
89
- ```bash
90
- python ebook2audiobook.py <path_to_ebook_file> [path_to_voice_file] [language_code]
91
- ```
92
-
93
- - **<path_to_ebook_file>**: Path to your eBook file.
94
- - **[path_to_voice_file]**: Optional for voice cloning.
95
- - **[language_code]**: Optional to specify language.
96
-
97
- ### 🧩 Custom XTTS Model
98
-
99
- ```bash
100
- python custom_model_ebook2audiobookXTTS.py <ebook_file_path> <target_voice_file_path> <language> <custom_model_path> <custom_config_path> <custom_vocab_path>
101
- ```
102
-
103
- - **<ebook_file_path>**: Path to your eBook file.
104
- - **<target_voice_file_path>**: Optional for voice cloning.
105
- - **<language>**: Optional to specify language.
106
- - **<custom_model_path>**: Path to `model.pth`.
107
- - **<custom_config_path>**: Path to `config.json`.
108
- - **<custom_vocab_path>**: Path to `vocab.json`.
109
-
110
- ### 🐳 Using Docker
111
-
112
- You can also use Docker to run the eBook to Audiobook converter. This method ensures consistency across different environments and simplifies setup.
113
-
114
- #### 🚀 Running the Docker Container
115
-
116
- To run the Docker container and start the Gradio interface, use the following command:
117
-
118
- -Run with CPU only
119
- ```powershell
120
- docker run -it --rm -p 7860:7860 --platform=linux/amd64 athomasson2/ebook2audiobookxtts:huggingface python app.py
121
- ```
122
- -Run with GPU Speedup (Nvida graphics cards only)
123
- ```powershell
124
- docker run -it --rm --gpus all -p 7860:7860 --platform=linux/amd64 athomasson2/ebook2audiobookxtts:huggingface python app.py
125
- ```
126
-
127
- This command will start the Gradio interface on port 7860.(localhost:7860)
128
-
129
- #### 🖥️ Docker GUI
130
-
131
- <img width="1401" alt="Screenshot 2024-08-25 at 10 08 40 AM" src="https://github.com/user-attachments/assets/78cfd33e-cd46-41cc-8128-3820160a5e40">
132
- <img width="1406" alt="Screenshot 2024-08-25 at 10 08 51 AM" src="https://github.com/user-attachments/assets/dbfad9f6-e6e5-4cad-b248-adb76c5434f3">
133
-
134
- ### 🛠️ For Custom Xtts Models
135
-
136
- Models built to be better at a specific voice. Check out my Hugging Face page [here](https://huggingface.co/drewThomasson).
137
-
138
- To use a custom model, paste the link of the `Finished_model_files.zip` file like this:
139
-
140
- [David Attenborough fine tuned Finished_model_files.zip](https://huggingface.co/drewThomasson/xtts_David_Attenborough_fine_tune/resolve/main/Finished_model_files.zip?download=true)
141
-
142
-
143
-
144
-
145
- More details can be found at the [Dockerfile Hub Page]([https://github.com/DrewThomasson/ebook2audiobookXTTS](https://hub.docker.com/repository/docker/athomasson2/ebook2audiobookxtts/general)).
146
-
147
- ## 🌐 Fine Tuned Xtts models
148
-
149
- To find already fine-tuned XTTS models, visit [this Hugging Face link](https://huggingface.co/drewThomasson) 🌐. Search for models that include "xtts fine tune" in their names.
150
-
151
- ## 🎥 Demos
152
-
153
- https://github.com/user-attachments/assets/8486603c-38b1-43ce-9639-73757dfb1031
154
-
155
- ## 🤗 [Huggingface space demo](https://huggingface.co/spaces/drewThomasson/ebook2audiobookXTTS)
156
- - Huggingface space is running on free cpu tier so expect very slow or timeout lol, just don't give it giant files is all
157
- - Best to duplicate space or run locally.
158
- ## 📚 Supported eBook Formats
159
-
160
- - `.epub`, `.pdf`, `.mobi`, `.txt`, `.html`, `.rtf`, `.chm`, `.lit`, `.pdb`, `.fb2`, `.odt`, `.cbr`, `.cbz`, `.prc`, `.lrf`, `.pml`, `.snb`, `.cbc`, `.rb`, `.tcr`
161
- - **Best results**: `.epub` or `.mobi` for automatic chapter detection
162
-
163
- ## 📂 Output
164
-
165
- - Creates an `.m4b` file with metadata and chapters.
166
- - **Example Output**: ![Example](https://github.com/DrewThomasson/VoxNovel/blob/dc5197dff97252fa44c391dc0596902d71278a88/readme_files/example_in_app.jpeg)
167
-
168
- ## 🙏 Special Thanks
169
-
170
- - **Coqui TTS**: [Coqui TTS GitHub](https://github.com/coqui-ai/TTS)
171
- - **Calibre**: [Calibre Website](https://calibre-ebook.com)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS.py DELETED
@@ -1,487 +0,0 @@
1
- print("starting...")
2
-
3
- import os
4
- import shutil
5
- import subprocess
6
- import re
7
- from pydub import AudioSegment
8
- import tempfile
9
- from pydub import AudioSegment
10
- import os
11
- import nltk
12
- from nltk.tokenize import sent_tokenize
13
- import sys
14
- import torch
15
- from TTS.api import TTS
16
- from TTS.tts.configs.xtts_config import XttsConfig
17
- from TTS.tts.models.xtts import Xtts
18
- from tqdm import tqdm
19
-
20
- #make the nltk folder point to the nltk folder in the app dir
21
- nltk.data.path.append('/home/user/app/nltk_data')
22
-
23
- #nltk.download('punkt') # Make sure to download the necessary models
24
- def is_folder_empty(folder_path):
25
- if os.path.exists(folder_path) and os.path.isdir(folder_path):
26
- # List directory contents
27
- if not os.listdir(folder_path):
28
- return True # The folder is empty
29
- else:
30
- return False # The folder is not empty
31
- else:
32
- print(f"The path {folder_path} is not a valid folder.")
33
- return None # The path is not a valid folder
34
-
35
- def remove_folder_with_contents(folder_path):
36
- try:
37
- shutil.rmtree(folder_path)
38
- print(f"Successfully removed {folder_path} and all of its contents.")
39
- except Exception as e:
40
- print(f"Error removing {folder_path}: {e}")
41
-
42
-
43
-
44
-
45
- def wipe_folder(folder_path):
46
- # Check if the folder exists
47
- if not os.path.exists(folder_path):
48
- print(f"The folder {folder_path} does not exist.")
49
- return
50
-
51
- # Iterate over all the items in the given folder
52
- for item in os.listdir(folder_path):
53
- item_path = os.path.join(folder_path, item)
54
- # If it's a file, remove it and print a message
55
- if os.path.isfile(item_path):
56
- os.remove(item_path)
57
- print(f"Removed file: {item_path}")
58
- # If it's a directory, remove it recursively and print a message
59
- elif os.path.isdir(item_path):
60
- shutil.rmtree(item_path)
61
- print(f"Removed directory and its contents: {item_path}")
62
-
63
- print(f"All contents wiped from {folder_path}.")
64
-
65
-
66
- # Example usage
67
- # folder_to_wipe = 'path_to_your_folder'
68
- # wipe_folder(folder_to_wipe)
69
-
70
-
71
- def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
72
- # Function to sort chapters based on their numeric order
73
- def sort_key(chapter_file):
74
- numbers = re.findall(r'\d+', chapter_file)
75
- return int(numbers[0]) if numbers else 0
76
-
77
- # Extract metadata and cover image from the eBook file
78
- def extract_metadata_and_cover(ebook_path):
79
- try:
80
- cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
81
- subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
82
- if os.path.exists(cover_path):
83
- return cover_path
84
- except Exception as e:
85
- print(f"Error extracting eBook metadata or cover: {e}")
86
- return None
87
- # Combine WAV files into a single file
88
- def combine_wav_files(chapter_files, output_path):
89
- # Initialize an empty audio segment
90
- combined_audio = AudioSegment.empty()
91
-
92
- # Sequentially append each file to the combined_audio
93
- for chapter_file in chapter_files:
94
- audio_segment = AudioSegment.from_wav(chapter_file)
95
- combined_audio += audio_segment
96
- # Export the combined audio to the output file path
97
- combined_audio.export(output_path, format='wav')
98
- print(f"Combined audio saved to {output_path}")
99
-
100
- # Function to generate metadata for M4B chapters
101
- def generate_ffmpeg_metadata(chapter_files, metadata_file):
102
- with open(metadata_file, 'w') as file:
103
- file.write(';FFMETADATA1\n')
104
- start_time = 0
105
- for index, chapter_file in enumerate(chapter_files):
106
- duration_ms = len(AudioSegment.from_wav(chapter_file))
107
- file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
108
- file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
109
- start_time += duration_ms
110
-
111
- # Generate the final M4B file using ffmpeg
112
- def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
113
- # Ensure the output directory exists
114
- os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
115
-
116
- ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
117
- if cover_image:
118
- ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
119
- else:
120
- ffmpeg_cmd += ['-map', '0:a']
121
-
122
- ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
123
- if cover_image:
124
- ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
125
- ffmpeg_cmd += [output_m4b]
126
-
127
- subprocess.run(ffmpeg_cmd, check=True)
128
-
129
-
130
-
131
- # Main logic
132
- chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
133
- temp_dir = tempfile.gettempdir()
134
- temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
135
- metadata_file = os.path.join(temp_dir, 'metadata.txt')
136
- cover_image = extract_metadata_and_cover(ebook_file)
137
- output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
138
-
139
- combine_wav_files(chapter_files, temp_combined_wav)
140
- generate_ffmpeg_metadata(chapter_files, metadata_file)
141
- create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
142
-
143
- # Cleanup
144
- if os.path.exists(temp_combined_wav):
145
- os.remove(temp_combined_wav)
146
- if os.path.exists(metadata_file):
147
- os.remove(metadata_file)
148
- if cover_image and os.path.exists(cover_image):
149
- os.remove(cover_image)
150
-
151
- # Example usage
152
- # create_m4b_from_chapters('path_to_chapter_wavs', 'path_to_ebook_file', 'path_to_output_dir')
153
-
154
-
155
-
156
-
157
-
158
-
159
- #this code right here isnt the book grabbing thing but its before to refrence in ordero to create the sepecial chapter labeled book thing with calibre idk some systems cant seem to get it so just in case but the next bit of code after this is the book grabbing code with booknlp
160
- import os
161
- import subprocess
162
- import ebooklib
163
- from ebooklib import epub
164
- from bs4 import BeautifulSoup
165
- import re
166
- import csv
167
- import nltk
168
-
169
- # Only run the main script if Value is True
170
- def create_chapter_labeled_book(ebook_file_path):
171
- # Function to ensure the existence of a directory
172
- def ensure_directory(directory_path):
173
- if not os.path.exists(directory_path):
174
- os.makedirs(directory_path)
175
- print(f"Created directory: {directory_path}")
176
-
177
- ensure_directory(os.path.join(".", 'Working_files', 'Book'))
178
-
179
- def convert_to_epub(input_path, output_path):
180
- # Convert the ebook to EPUB format using Calibre's ebook-convert
181
- try:
182
- subprocess.run(['ebook-convert', input_path, output_path], check=True)
183
- except subprocess.CalledProcessError as e:
184
- print(f"An error occurred while converting the eBook: {e}")
185
- return False
186
- return True
187
-
188
- def save_chapters_as_text(epub_path):
189
- # Create the directory if it doesn't exist
190
- directory = os.path.join(".", "Working_files", "temp_ebook")
191
- ensure_directory(directory)
192
-
193
- # Open the EPUB file
194
- book = epub.read_epub(epub_path)
195
-
196
- previous_chapter_text = ''
197
- previous_filename = ''
198
- chapter_counter = 0
199
-
200
- # Iterate through the items in the EPUB file
201
- for item in book.get_items():
202
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
203
- # Use BeautifulSoup to parse HTML content
204
- soup = BeautifulSoup(item.get_content(), 'html.parser')
205
- text = soup.get_text()
206
-
207
- # Check if the text is not empty
208
- if text.strip():
209
- if len(text) < 2300 and previous_filename:
210
- # Append text to the previous chapter if it's short
211
- with open(previous_filename, 'a', encoding='utf-8') as file:
212
- file.write('\n' + text)
213
- else:
214
- # Create a new chapter file and increment the counter
215
- previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
216
- chapter_counter += 1
217
- with open(previous_filename, 'w', encoding='utf-8') as file:
218
- file.write(text)
219
- print(f"Saved chapter: {previous_filename}")
220
-
221
- # Example usage
222
- input_ebook = ebook_file_path # Replace with your eBook file path
223
- output_epub = os.path.join(".", "Working_files", "temp.epub")
224
-
225
-
226
- if os.path.exists(output_epub):
227
- os.remove(output_epub)
228
- print(f"File {output_epub} has been removed.")
229
- else:
230
- print(f"The file {output_epub} does not exist.")
231
-
232
- if convert_to_epub(input_ebook, output_epub):
233
- save_chapters_as_text(output_epub)
234
-
235
- # Download the necessary NLTK data (if not already present)
236
- #nltk.download('punkt')
237
-
238
- def process_chapter_files(folder_path, output_csv):
239
- with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
240
- writer = csv.writer(csvfile)
241
- # Write the header row
242
- writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
243
-
244
- # Process each chapter file
245
- chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
246
- for filename in chapter_files:
247
- if filename.startswith('chapter_') and filename.endswith('.txt'):
248
- chapter_number = int(filename.split('_')[1].split('.')[0])
249
- file_path = os.path.join(folder_path, filename)
250
-
251
- try:
252
- with open(file_path, 'r', encoding='utf-8') as file:
253
- text = file.read()
254
- # Insert "NEWCHAPTERABC" at the beginning of each chapter's text
255
- if text:
256
- text = "NEWCHAPTERABC" + text
257
- sentences = nltk.tokenize.sent_tokenize(text)
258
- for sentence in sentences:
259
- start_location = text.find(sentence)
260
- end_location = start_location + len(sentence)
261
- writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
262
- except Exception as e:
263
- print(f"Error processing file {filename}: {e}")
264
-
265
- # Example usage
266
- folder_path = os.path.join(".", "Working_files", "temp_ebook")
267
- output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
268
-
269
- process_chapter_files(folder_path, output_csv)
270
-
271
- def sort_key(filename):
272
- """Extract chapter number for sorting."""
273
- match = re.search(r'chapter_(\d+)\.txt', filename)
274
- return int(match.group(1)) if match else 0
275
-
276
- def combine_chapters(input_folder, output_file):
277
- # Create the output folder if it doesn't exist
278
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
279
-
280
- # List all txt files and sort them by chapter number
281
- files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
282
- sorted_files = sorted(files, key=sort_key)
283
-
284
- with open(output_file, 'w', encoding='utf-8') as outfile: # Specify UTF-8 encoding here
285
- for i, filename in enumerate(sorted_files):
286
- with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile: # And here
287
- outfile.write(infile.read())
288
- # Add the marker unless it's the last file
289
- if i < len(sorted_files) - 1:
290
- outfile.write("\nNEWCHAPTERABC\n")
291
-
292
- # Paths
293
- input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
294
- output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
295
-
296
-
297
- # Combine the chapters
298
- combine_chapters(input_folder, output_file)
299
-
300
- ensure_directory(os.path.join(".", "Working_files", "Book"))
301
-
302
-
303
- #create_chapter_labeled_book()
304
-
305
-
306
-
307
-
308
- import os
309
- import subprocess
310
- import sys
311
- import torchaudio
312
-
313
- # Check if Calibre's ebook-convert tool is installed
314
- def calibre_installed():
315
- try:
316
- subprocess.run(['ebook-convert', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
317
- return True
318
- except FileNotFoundError:
319
- print("Calibre is not installed. Please install Calibre for this functionality.")
320
- return False
321
-
322
-
323
- import os
324
- import torch
325
- from TTS.api import TTS
326
- from nltk.tokenize import sent_tokenize
327
- from pydub import AudioSegment
328
- # Assuming split_long_sentence and wipe_folder are defined elsewhere in your code
329
-
330
- default_target_voice_path = "default_voice.wav" # Ensure this is a valid path
331
- default_language_code = "en"
332
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
333
-
334
- def combine_wav_files(input_directory, output_directory, file_name):
335
- # Ensure that the output directory exists, create it if necessary
336
- os.makedirs(output_directory, exist_ok=True)
337
-
338
- # Specify the output file path
339
- output_file_path = os.path.join(output_directory, file_name)
340
-
341
- # Initialize an empty audio segment
342
- combined_audio = AudioSegment.empty()
343
-
344
- # Get a list of all .wav files in the specified input directory and sort them
345
- input_file_paths = sorted(
346
- [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith(".wav")],
347
- key=lambda f: int(''.join(filter(str.isdigit, f)))
348
- )
349
-
350
- # Sequentially append each file to the combined_audio
351
- for input_file_path in input_file_paths:
352
- audio_segment = AudioSegment.from_wav(input_file_path)
353
- combined_audio += audio_segment
354
-
355
- # Export the combined audio to the output file path
356
- combined_audio.export(output_file_path, format='wav')
357
-
358
- print(f"Combined audio saved to {output_file_path}")
359
-
360
- # Function to split long strings into parts
361
- def split_long_sentence(sentence, max_length=249, max_pauses=10):
362
- """
363
- Splits a sentence into parts based on length or number of pauses without recursion.
364
-
365
- :param sentence: The sentence to split.
366
- :param max_length: Maximum allowed length of a sentence.
367
- :param max_pauses: Maximum allowed number of pauses in a sentence.
368
- :return: A list of sentence parts that meet the criteria.
369
- """
370
- parts = []
371
- while len(sentence) > max_length or sentence.count(',') + sentence.count(';') + sentence.count('.') > max_pauses:
372
- possible_splits = [i for i, char in enumerate(sentence) if char in ',;.' and i < max_length]
373
- if possible_splits:
374
- # Find the best place to split the sentence, preferring the last possible split to keep parts longer
375
- split_at = possible_splits[-1] + 1
376
- else:
377
- # If no punctuation to split on within max_length, split at max_length
378
- split_at = max_length
379
-
380
- # Split the sentence and add the first part to the list
381
- parts.append(sentence[:split_at].strip())
382
- sentence = sentence[split_at:].strip()
383
-
384
- # Add the remaining part of the sentence
385
- parts.append(sentence)
386
- return parts
387
-
388
- """
389
- if 'tts' not in locals():
390
- tts = TTS(selected_tts_model, progress_bar=True).to(device)
391
- """
392
- from tqdm import tqdm
393
-
394
- # Convert chapters to audio using XTTS
395
- def convert_chapters_to_audio(chapters_dir, output_audio_dir, target_voice_path=None, language=None, custom_model=None):
396
- if custom_model:
397
- print("Loading custom model...")
398
- config = XttsConfig()
399
- config.load_json(custom_model['config'])
400
- model = Xtts.init_from_config(config)
401
- model.load_checkpoint(config, checkpoint_path=custom_model['model'], vocab_path=custom_model['vocab'], use_deepspeed=False)
402
- model.to(device)
403
- print("Computing speaker latents...")
404
- gpt_cond_latent, speaker_embedding = model.get_conditioning_latents(audio_path=[target_voice_path])
405
- else:
406
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
407
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
408
-
409
- if not os.path.exists(output_audio_dir):
410
- os.makedirs(output_audio_dir)
411
-
412
- for chapter_file in sorted(os.listdir(chapters_dir)):
413
- if chapter_file.endswith('.txt'):
414
- match = re.search(r"chapter_(\d+).txt", chapter_file)
415
- if match:
416
- chapter_num = int(match.group(1))
417
- else:
418
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
419
- continue
420
-
421
- chapter_path = os.path.join(chapters_dir, chapter_file)
422
- output_file_name = f"audio_chapter_{chapter_num}.wav"
423
- output_file_path = os.path.join(output_audio_dir, output_file_name)
424
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
425
- os.makedirs(temp_audio_directory, exist_ok=True)
426
- temp_count = 0
427
-
428
- with open(chapter_path, 'r', encoding='utf-8') as file:
429
- chapter_text = file.read()
430
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
431
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
432
- fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
433
- for fragment in fragments:
434
- if fragment != "":
435
- print(f"Generating fragment: {fragment}...")
436
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
437
- if custom_model:
438
- out = model.inference(fragment, language, gpt_cond_latent, speaker_embedding, temperature=0.7)
439
- torchaudio.save(fragment_file_path, torch.tensor(out["wav"]).unsqueeze(0), 24000)
440
- else:
441
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
442
- language_code = language if language else default_language_code
443
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
444
- temp_count += 1
445
-
446
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
447
- wipe_folder(temp_audio_directory)
448
- print(f"Converted chapter {chapter_num} to audio.")
449
-
450
-
451
- # Main execution flow
452
- if __name__ == "__main__":
453
- if len(sys.argv) < 2:
454
- print("Usage: python script.py <ebook_file_path> [target_voice_file_path] [language] [custom_model_path] [custom_config_path] [custom_vocab_path]")
455
- sys.exit(1)
456
-
457
- ebook_file_path = sys.argv[1]
458
- target_voice = sys.argv[2] if len(sys.argv) > 2 else None
459
- language = sys.argv[3] if len(sys.argv) > 3 else None
460
-
461
- custom_model = None
462
- if len(sys.argv) > 6:
463
- custom_model = {
464
- 'model': sys.argv[4],
465
- 'config': sys.argv[5],
466
- 'vocab': sys.argv[6]
467
- }
468
-
469
- if not calibre_installed():
470
- sys.exit(1)
471
-
472
- working_files = os.path.join(".", "Working_files", "temp_ebook")
473
- full_folder_working_files = os.path.join(".", "Working_files")
474
- chapters_directory = os.path.join(".", "Working_files", "temp_ebook")
475
- output_audio_directory = os.path.join(".", 'Chapter_wav_files')
476
-
477
- print("Wiping and removing Working_files folder...")
478
- remove_folder_with_contents(full_folder_working_files)
479
-
480
- print("Wiping and removing chapter_wav_files folder...")
481
- remove_folder_with_contents(output_audio_directory)
482
-
483
- create_chapter_labeled_book(ebook_file_path)
484
- audiobook_output_path = os.path.join(".", "Audiobooks")
485
- print(f"{chapters_directory}||||{output_audio_directory}|||||{target_voice}")
486
- convert_chapters_to_audio(chapters_directory, output_audio_directory, target_voice, language, custom_model)
487
- create_m4b_from_chapters(output_audio_directory, ebook_file_path, audiobook_output_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS_gradio.py DELETED
@@ -1,612 +0,0 @@
1
- print("starting...")
2
-
3
- import os
4
- import shutil
5
- import subprocess
6
- import re
7
- from pydub import AudioSegment
8
- import tempfile
9
- from pydub import AudioSegment
10
- import os
11
- import nltk
12
- from nltk.tokenize import sent_tokenize
13
- import sys
14
- import torch
15
- from TTS.api import TTS
16
- from TTS.tts.configs.xtts_config import XttsConfig
17
- from TTS.tts.models.xtts import Xtts
18
- from tqdm import tqdm
19
-
20
- #make the nltk folder point to the nltk folder in the app dir
21
- nltk.data.path.append('/home/user/app/nltk_data')
22
-
23
- #nltk.download('punkt') # Make sure to download the necessary models
24
-
25
- import gradio as gr
26
- from gradio import Progress
27
-
28
-
29
- def is_folder_empty(folder_path):
30
- if os.path.exists(folder_path) and os.path.isdir(folder_path):
31
- # List directory contents
32
- if not os.listdir(folder_path):
33
- return True # The folder is empty
34
- else:
35
- return False # The folder is not empty
36
- else:
37
- print(f"The path {folder_path} is not a valid folder.")
38
- return None # The path is not a valid folder
39
-
40
- def remove_folder_with_contents(folder_path):
41
- try:
42
- shutil.rmtree(folder_path)
43
- print(f"Successfully removed {folder_path} and all of its contents.")
44
- except Exception as e:
45
- print(f"Error removing {folder_path}: {e}")
46
-
47
-
48
-
49
-
50
- def wipe_folder(folder_path):
51
- # Check if the folder exists
52
- if not os.path.exists(folder_path):
53
- print(f"The folder {folder_path} does not exist.")
54
- return
55
-
56
- # Iterate over all the items in the given folder
57
- for item in os.listdir(folder_path):
58
- item_path = os.path.join(folder_path, item)
59
- # If it's a file, remove it and print a message
60
- if os.path.isfile(item_path):
61
- os.remove(item_path)
62
- print(f"Removed file: {item_path}")
63
- # If it's a directory, remove it recursively and print a message
64
- elif os.path.isdir(item_path):
65
- shutil.rmtree(item_path)
66
- print(f"Removed directory and its contents: {item_path}")
67
-
68
- print(f"All contents wiped from {folder_path}.")
69
-
70
-
71
- # Example usage
72
- # folder_to_wipe = 'path_to_your_folder'
73
- # wipe_folder(folder_to_wipe)
74
-
75
-
76
- def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
77
- # Function to sort chapters based on their numeric order
78
- def sort_key(chapter_file):
79
- numbers = re.findall(r'\d+', chapter_file)
80
- return int(numbers[0]) if numbers else 0
81
-
82
- # Extract metadata and cover image from the eBook file
83
- def extract_metadata_and_cover(ebook_path):
84
- try:
85
- cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
86
- subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
87
- if os.path.exists(cover_path):
88
- return cover_path
89
- except Exception as e:
90
- print(f"Error extracting eBook metadata or cover: {e}")
91
- return None
92
- # Combine WAV files into a single file
93
- def combine_wav_files(chapter_files, output_path):
94
- # Initialize an empty audio segment
95
- combined_audio = AudioSegment.empty()
96
-
97
- # Sequentially append each file to the combined_audio
98
- for chapter_file in chapter_files:
99
- audio_segment = AudioSegment.from_wav(chapter_file)
100
- combined_audio += audio_segment
101
- # Export the combined audio to the output file path
102
- combined_audio.export(output_path, format='wav')
103
- print(f"Combined audio saved to {output_path}")
104
-
105
- # Function to generate metadata for M4B chapters
106
- def generate_ffmpeg_metadata(chapter_files, metadata_file):
107
- with open(metadata_file, 'w') as file:
108
- file.write(';FFMETADATA1\n')
109
- start_time = 0
110
- for index, chapter_file in enumerate(chapter_files):
111
- duration_ms = len(AudioSegment.from_wav(chapter_file))
112
- file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
113
- file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
114
- start_time += duration_ms
115
-
116
- # Generate the final M4B file using ffmpeg
117
- def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
118
- # Ensure the output directory exists
119
- os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
120
-
121
- ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
122
- if cover_image:
123
- ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
124
- else:
125
- ffmpeg_cmd += ['-map', '0:a']
126
-
127
- ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
128
- if cover_image:
129
- ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
130
- ffmpeg_cmd += [output_m4b]
131
-
132
- subprocess.run(ffmpeg_cmd, check=True)
133
-
134
-
135
-
136
- # Main logic
137
- chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
138
- temp_dir = tempfile.gettempdir()
139
- temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
140
- metadata_file = os.path.join(temp_dir, 'metadata.txt')
141
- cover_image = extract_metadata_and_cover(ebook_file)
142
- output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
143
-
144
- combine_wav_files(chapter_files, temp_combined_wav)
145
- generate_ffmpeg_metadata(chapter_files, metadata_file)
146
- create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
147
-
148
- # Cleanup
149
- if os.path.exists(temp_combined_wav):
150
- os.remove(temp_combined_wav)
151
- if os.path.exists(metadata_file):
152
- os.remove(metadata_file)
153
- if cover_image and os.path.exists(cover_image):
154
- os.remove(cover_image)
155
-
156
- # Example usage
157
- # create_m4b_from_chapters('path_to_chapter_wavs', 'path_to_ebook_file', 'path_to_output_dir')
158
-
159
-
160
-
161
-
162
-
163
-
164
- #this code right here isnt the book grabbing thing but its before to refrence in ordero to create the sepecial chapter labeled book thing with calibre idk some systems cant seem to get it so just in case but the next bit of code after this is the book grabbing code with booknlp
165
- import os
166
- import subprocess
167
- import ebooklib
168
- from ebooklib import epub
169
- from bs4 import BeautifulSoup
170
- import re
171
- import csv
172
- import nltk
173
-
174
- # Only run the main script if Value is True
175
- def create_chapter_labeled_book(ebook_file_path):
176
- # Function to ensure the existence of a directory
177
- def ensure_directory(directory_path):
178
- if not os.path.exists(directory_path):
179
- os.makedirs(directory_path)
180
- print(f"Created directory: {directory_path}")
181
-
182
- ensure_directory(os.path.join(".", 'Working_files', 'Book'))
183
-
184
- def convert_to_epub(input_path, output_path):
185
- # Convert the ebook to EPUB format using Calibre's ebook-convert
186
- try:
187
- subprocess.run(['ebook-convert', input_path, output_path], check=True)
188
- except subprocess.CalledProcessError as e:
189
- print(f"An error occurred while converting the eBook: {e}")
190
- return False
191
- return True
192
-
193
- def save_chapters_as_text(epub_path):
194
- # Create the directory if it doesn't exist
195
- directory = os.path.join(".", "Working_files", "temp_ebook")
196
- ensure_directory(directory)
197
-
198
- # Open the EPUB file
199
- book = epub.read_epub(epub_path)
200
-
201
- previous_chapter_text = ''
202
- previous_filename = ''
203
- chapter_counter = 0
204
-
205
- # Iterate through the items in the EPUB file
206
- for item in book.get_items():
207
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
208
- # Use BeautifulSoup to parse HTML content
209
- soup = BeautifulSoup(item.get_content(), 'html.parser')
210
- text = soup.get_text()
211
-
212
- # Check if the text is not empty
213
- if text.strip():
214
- if len(text) < 2300 and previous_filename:
215
- # Append text to the previous chapter if it's short
216
- with open(previous_filename, 'a', encoding='utf-8') as file:
217
- file.write('\n' + text)
218
- else:
219
- # Create a new chapter file and increment the counter
220
- previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
221
- chapter_counter += 1
222
- with open(previous_filename, 'w', encoding='utf-8') as file:
223
- file.write(text)
224
- print(f"Saved chapter: {previous_filename}")
225
-
226
- # Example usage
227
- input_ebook = ebook_file_path # Replace with your eBook file path
228
- output_epub = os.path.join(".", "Working_files", "temp.epub")
229
-
230
-
231
- if os.path.exists(output_epub):
232
- os.remove(output_epub)
233
- print(f"File {output_epub} has been removed.")
234
- else:
235
- print(f"The file {output_epub} does not exist.")
236
-
237
- if convert_to_epub(input_ebook, output_epub):
238
- save_chapters_as_text(output_epub)
239
-
240
- # Download the necessary NLTK data (if not already present)
241
- #nltk.download('punkt')
242
-
243
- def process_chapter_files(folder_path, output_csv):
244
- with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
245
- writer = csv.writer(csvfile)
246
- # Write the header row
247
- writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
248
-
249
- # Process each chapter file
250
- chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
251
- for filename in chapter_files:
252
- if filename.startswith('chapter_') and filename.endswith('.txt'):
253
- chapter_number = int(filename.split('_')[1].split('.')[0])
254
- file_path = os.path.join(folder_path, filename)
255
-
256
- try:
257
- with open(file_path, 'r', encoding='utf-8') as file:
258
- text = file.read()
259
- # Insert "NEWCHAPTERABC" at the beginning of each chapter's text
260
- if text:
261
- text = "NEWCHAPTERABC" + text
262
- sentences = nltk.tokenize.sent_tokenize(text)
263
- for sentence in sentences:
264
- start_location = text.find(sentence)
265
- end_location = start_location + len(sentence)
266
- writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
267
- except Exception as e:
268
- print(f"Error processing file {filename}: {e}")
269
-
270
- # Example usage
271
- folder_path = os.path.join(".", "Working_files", "temp_ebook")
272
- output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
273
-
274
- process_chapter_files(folder_path, output_csv)
275
-
276
- def sort_key(filename):
277
- """Extract chapter number for sorting."""
278
- match = re.search(r'chapter_(\d+)\.txt', filename)
279
- return int(match.group(1)) if match else 0
280
-
281
- def combine_chapters(input_folder, output_file):
282
- # Create the output folder if it doesn't exist
283
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
284
-
285
- # List all txt files and sort them by chapter number
286
- files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
287
- sorted_files = sorted(files, key=sort_key)
288
-
289
- with open(output_file, 'w', encoding='utf-8') as outfile: # Specify UTF-8 encoding here
290
- for i, filename in enumerate(sorted_files):
291
- with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile: # And here
292
- outfile.write(infile.read())
293
- # Add the marker unless it's the last file
294
- if i < len(sorted_files) - 1:
295
- outfile.write("\nNEWCHAPTERABC\n")
296
-
297
- # Paths
298
- input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
299
- output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
300
-
301
-
302
- # Combine the chapters
303
- combine_chapters(input_folder, output_file)
304
-
305
- ensure_directory(os.path.join(".", "Working_files", "Book"))
306
-
307
-
308
- #create_chapter_labeled_book()
309
-
310
-
311
-
312
-
313
- import os
314
- import subprocess
315
- import sys
316
- import torchaudio
317
-
318
- # Check if Calibre's ebook-convert tool is installed
319
- def calibre_installed():
320
- try:
321
- subprocess.run(['ebook-convert', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
322
- return True
323
- except FileNotFoundError:
324
- print("Calibre is not installed. Please install Calibre for this functionality.")
325
- return False
326
-
327
-
328
- import os
329
- import torch
330
- from TTS.api import TTS
331
- from nltk.tokenize import sent_tokenize
332
- from pydub import AudioSegment
333
- # Assuming split_long_sentence and wipe_folder are defined elsewhere in your code
334
-
335
- default_target_voice_path = "default_voice.wav" # Ensure this is a valid path
336
- default_language_code = "en"
337
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
338
-
339
- def combine_wav_files(input_directory, output_directory, file_name):
340
- # Ensure that the output directory exists, create it if necessary
341
- os.makedirs(output_directory, exist_ok=True)
342
-
343
- # Specify the output file path
344
- output_file_path = os.path.join(output_directory, file_name)
345
-
346
- # Initialize an empty audio segment
347
- combined_audio = AudioSegment.empty()
348
-
349
- # Get a list of all .wav files in the specified input directory and sort them
350
- input_file_paths = sorted(
351
- [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith(".wav")],
352
- key=lambda f: int(''.join(filter(str.isdigit, f)))
353
- )
354
-
355
- # Sequentially append each file to the combined_audio
356
- for input_file_path in input_file_paths:
357
- audio_segment = AudioSegment.from_wav(input_file_path)
358
- combined_audio += audio_segment
359
-
360
- # Export the combined audio to the output file path
361
- combined_audio.export(output_file_path, format='wav')
362
-
363
- print(f"Combined audio saved to {output_file_path}")
364
-
365
- # Function to split long strings into parts
366
- def split_long_sentence(sentence, max_length=249, max_pauses=10):
367
- """
368
- Splits a sentence into parts based on length or number of pauses without recursion.
369
-
370
- :param sentence: The sentence to split.
371
- :param max_length: Maximum allowed length of a sentence.
372
- :param max_pauses: Maximum allowed number of pauses in a sentence.
373
- :return: A list of sentence parts that meet the criteria.
374
- """
375
- parts = []
376
- while len(sentence) > max_length or sentence.count(',') + sentence.count(';') + sentence.count('.') > max_pauses:
377
- possible_splits = [i for i, char in enumerate(sentence) if char in ',;.' and i < max_length]
378
- if possible_splits:
379
- # Find the best place to split the sentence, preferring the last possible split to keep parts longer
380
- split_at = possible_splits[-1] + 1
381
- else:
382
- # If no punctuation to split on within max_length, split at max_length
383
- split_at = max_length
384
-
385
- # Split the sentence and add the first part to the list
386
- parts.append(sentence[:split_at].strip())
387
- sentence = sentence[split_at:].strip()
388
-
389
- # Add the remaining part of the sentence
390
- parts.append(sentence)
391
- return parts
392
-
393
- """
394
- if 'tts' not in locals():
395
- tts = TTS(selected_tts_model, progress_bar=True).to(device)
396
- """
397
- from tqdm import tqdm
398
-
399
- # Convert chapters to audio using XTTS
400
-
401
- def convert_chapters_to_audio_custom_model(chapters_dir, output_audio_dir, target_voice_path=None, language=None, custom_model=None):
402
- if custom_model:
403
- print("Loading custom model...")
404
- config = XttsConfig()
405
- config.load_json(custom_model['config'])
406
- model = Xtts.init_from_config(config)
407
- model.load_checkpoint(config, checkpoint_path=custom_model['model'], vocab_path=custom_model['vocab'], use_deepspeed=False)
408
- model.to(device)
409
- print("Computing speaker latents...")
410
- gpt_cond_latent, speaker_embedding = model.get_conditioning_latents(audio_path=[target_voice_path])
411
- else:
412
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
413
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
414
-
415
- if not os.path.exists(output_audio_dir):
416
- os.makedirs(output_audio_dir)
417
-
418
- for chapter_file in sorted(os.listdir(chapters_dir)):
419
- if chapter_file.endswith('.txt'):
420
- match = re.search(r"chapter_(\d+).txt", chapter_file)
421
- if match:
422
- chapter_num = int(match.group(1))
423
- else:
424
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
425
- continue
426
-
427
- chapter_path = os.path.join(chapters_dir, chapter_file)
428
- output_file_name = f"audio_chapter_{chapter_num}.wav"
429
- output_file_path = os.path.join(output_audio_dir, output_file_name)
430
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
431
- os.makedirs(temp_audio_directory, exist_ok=True)
432
- temp_count = 0
433
-
434
- with open(chapter_path, 'r', encoding='utf-8') as file:
435
- chapter_text = file.read()
436
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
437
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
438
- fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
439
- for fragment in fragments:
440
- if fragment != "":
441
- print(f"Generating fragment: {fragment}...")
442
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
443
- if custom_model:
444
- out = model.inference(fragment, language, gpt_cond_latent, speaker_embedding, temperature=0.7)
445
- torchaudio.save(fragment_file_path, torch.tensor(out["wav"]).unsqueeze(0), 24000)
446
- else:
447
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
448
- language_code = language if language else default_language_code
449
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
450
- temp_count += 1
451
-
452
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
453
- wipe_folder(temp_audio_directory)
454
- print(f"Converted chapter {chapter_num} to audio.")
455
-
456
-
457
-
458
- def convert_chapters_to_audio_standard_model(chapters_dir, output_audio_dir, target_voice_path=None, language=None):
459
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
460
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
461
-
462
- if not os.path.exists(output_audio_dir):
463
- os.makedirs(output_audio_dir)
464
-
465
- for chapter_file in sorted(os.listdir(chapters_dir)):
466
- if chapter_file.endswith('.txt'):
467
- match = re.search(r"chapter_(\d+).txt", chapter_file)
468
- if match:
469
- chapter_num = int(match.group(1))
470
- else:
471
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
472
- continue
473
-
474
- chapter_path = os.path.join(chapters_dir, chapter_file)
475
- output_file_name = f"audio_chapter_{chapter_num}.wav"
476
- output_file_path = os.path.join(output_audio_dir, output_file_name)
477
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
478
- os.makedirs(temp_audio_directory, exist_ok=True)
479
- temp_count = 0
480
-
481
- with open(chapter_path, 'r', encoding='utf-8') as file:
482
- chapter_text = file.read()
483
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
484
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
485
- fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
486
- for fragment in fragments:
487
- if fragment != "":
488
- print(f"Generating fragment: {fragment}...")
489
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
490
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
491
- language_code = language if language else default_language_code
492
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
493
- temp_count += 1
494
-
495
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
496
- wipe_folder(temp_audio_directory)
497
- print(f"Converted chapter {chapter_num} to audio.")
498
-
499
-
500
-
501
- # Define the functions to be used in the Gradio interface
502
- def convert_ebook_to_audio(ebook_file, target_voice_file, language, use_custom_model, custom_model_file, custom_config_file, custom_vocab_file, progress=gr.Progress()):
503
- ebook_file_path = ebook_file.name
504
- target_voice = target_voice_file.name if target_voice_file else None
505
- custom_model = None
506
- if use_custom_model and custom_model_file and custom_config_file and custom_vocab_file:
507
- custom_model = {
508
- 'model': custom_model_file.name,
509
- 'config': custom_config_file.name,
510
- 'vocab': custom_vocab_file.name
511
- }
512
-
513
- try:
514
- progress(0, desc="Starting conversion")
515
- except Exception as e:
516
- print(f"Error updating progress: {e}")
517
-
518
- if not calibre_installed():
519
- return "Calibre is not installed."
520
-
521
- working_files = os.path.join(".", "Working_files", "temp_ebook")
522
- full_folder_working_files = os.path.join(".", "Working_files")
523
- chapters_directory = os.path.join(".", "Working_files", "temp_ebook")
524
- output_audio_directory = os.path.join(".", 'Chapter_wav_files')
525
- remove_folder_with_contents(full_folder_working_files)
526
- remove_folder_with_contents(output_audio_directory)
527
-
528
- try:
529
- progress(0.1, desc="Creating chapter-labeled book")
530
- except Exception as e:
531
- print(f"Error updating progress: {e}")
532
-
533
- create_chapter_labeled_book(ebook_file_path)
534
- audiobook_output_path = os.path.join(".", "Audiobooks")
535
-
536
- try:
537
- progress(0.3, desc="Converting chapters to audio")
538
- except Exception as e:
539
- print(f"Error updating progress: {e}")
540
-
541
- if use_custom_model:
542
- convert_chapters_to_audio_custom_model(chapters_directory, output_audio_directory, target_voice, language, custom_model)
543
- else:
544
- convert_chapters_to_audio_standard_model(chapters_directory, output_audio_directory, target_voice, language)
545
-
546
- try:
547
- progress(0.9, desc="Creating M4B from chapters")
548
- except Exception as e:
549
- print(f"Error updating progress: {e}")
550
-
551
- create_m4b_from_chapters(output_audio_directory, ebook_file_path, audiobook_output_path)
552
-
553
- # Get the name of the created M4B file
554
- m4b_filename = os.path.splitext(os.path.basename(ebook_file_path))[0] + '.m4b'
555
- m4b_filepath = os.path.join(audiobook_output_path, m4b_filename)
556
-
557
- try:
558
- progress(1.0, desc="Conversion complete")
559
- except Exception as e:
560
- print(f"Error updating progress: {e}")
561
-
562
- return f"Audiobook created at {m4b_filepath}", m4b_filepath
563
-
564
- language_options = [
565
- "en", "es", "fr", "de", "it", "pt", "pl", "tr", "ru", "nl", "cs", "ar", "zh-cn", "ja", "hu", "ko"
566
- ]
567
-
568
- theme = gr.themes.Soft(
569
- primary_hue="blue",
570
- secondary_hue="blue",
571
- neutral_hue="blue",
572
- text_size=gr.themes.sizes.text_md,
573
- )
574
-
575
- with gr.Blocks(theme=theme) as demo:
576
- gr.Markdown(
577
- """
578
- # eBook to Audiobook Converter
579
-
580
- Transform your eBooks into immersive audiobooks with optional custom TTS models.
581
- """
582
- )
583
-
584
- with gr.Row():
585
- with gr.Column(scale=3):
586
- ebook_file = gr.File(label="eBook File")
587
- target_voice_file = gr.File(label="Target Voice File (Optional)")
588
- language = gr.Dropdown(label="Language", choices=language_options, value="en")
589
-
590
- with gr.Column(scale=3):
591
- use_custom_model = gr.Checkbox(label="Use Custom Model")
592
- custom_model_file = gr.File(label="Custom Model File (Optional)", visible=False)
593
- custom_config_file = gr.File(label="Custom Config File (Optional)", visible=False)
594
- custom_vocab_file = gr.File(label="Custom Vocab File (Optional)", visible=False)
595
-
596
- convert_btn = gr.Button("Convert to Audiobook", variant="primary")
597
- output = gr.Textbox(label="Conversion Status")
598
- audio_player = gr.Audio(label="Audiobook Player", type="filepath")
599
-
600
- convert_btn.click(
601
- convert_ebook_to_audio,
602
- inputs=[ebook_file, target_voice_file, language, use_custom_model, custom_model_file, custom_config_file, custom_vocab_file],
603
- outputs=[output, audio_player]
604
- )
605
-
606
- use_custom_model.change(
607
- lambda x: [gr.update(visible=x)] * 3,
608
- inputs=[use_custom_model],
609
- outputs=[custom_model_file, custom_config_file, custom_vocab_file]
610
- )
611
-
612
- demo.launch(share=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS_with_link_gradio.py DELETED
@@ -1,704 +0,0 @@
1
- print("starting...")
2
-
3
- import os
4
- import shutil
5
- import subprocess
6
- import re
7
- from pydub import AudioSegment
8
- import tempfile
9
- from pydub import AudioSegment
10
- import os
11
- import nltk
12
- from nltk.tokenize import sent_tokenize
13
- import sys
14
- import torch
15
- from TTS.api import TTS
16
- from TTS.tts.configs.xtts_config import XttsConfig
17
- from TTS.tts.models.xtts import Xtts
18
- from tqdm import tqdm
19
- import gradio as gr
20
- from gradio import Progress
21
- import urllib.request
22
- import zipfile
23
-
24
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
25
- print(f"Device selected is: {device}")
26
-
27
-
28
- #make the nltk folder point to the nltk folder in the app dir
29
- nltk.data.path.append('/home/user/app/nltk_data')
30
-
31
- #nltk.download('punkt') # Make sure to download the necessary models
32
-
33
-
34
- def download_and_extract_zip(url, extract_to='.'):
35
- try:
36
- # Ensure the directory exists
37
- os.makedirs(extract_to, exist_ok=True)
38
-
39
- zip_path = os.path.join(extract_to, 'model.zip')
40
-
41
- # Download with progress bar
42
- with tqdm(unit='B', unit_scale=True, miniters=1, desc="Downloading Model") as t:
43
- def reporthook(blocknum, blocksize, totalsize):
44
- t.total = totalsize
45
- t.update(blocknum * blocksize - t.n)
46
-
47
- urllib.request.urlretrieve(url, zip_path, reporthook=reporthook)
48
- print(f"Downloaded zip file to {zip_path}")
49
-
50
- # Unzipping with progress bar
51
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
52
- files = zip_ref.namelist()
53
- with tqdm(total=len(files), unit="file", desc="Extracting Files") as t:
54
- for file in files:
55
- if not file.endswith('/'): # Skip directories
56
- # Extract the file to the temporary directory
57
- extracted_path = zip_ref.extract(file, extract_to)
58
- # Move the file to the base directory
59
- base_file_path = os.path.join(extract_to, os.path.basename(file))
60
- os.rename(extracted_path, base_file_path)
61
- t.update(1)
62
-
63
- # Cleanup: Remove the ZIP file and any empty folders
64
- os.remove(zip_path)
65
- for root, dirs, files in os.walk(extract_to, topdown=False):
66
- for name in dirs:
67
- os.rmdir(os.path.join(root, name))
68
- print(f"Extracted files to {extract_to}")
69
-
70
- # Check if all required files are present
71
- required_files = ['model.pth', 'config.json', 'vocab.json_']
72
- missing_files = [file for file in required_files if not os.path.exists(os.path.join(extract_to, file))]
73
-
74
- if not missing_files:
75
- print("All required files (model.pth, config.json, vocab.json_) found.")
76
- else:
77
- print(f"Missing files: {', '.join(missing_files)}")
78
-
79
- except Exception as e:
80
- print(f"Failed to download or extract zip file: {e}")
81
-
82
-
83
-
84
- def is_folder_empty(folder_path):
85
- if os.path.exists(folder_path) and os.path.isdir(folder_path):
86
- # List directory contents
87
- if not os.listdir(folder_path):
88
- return True # The folder is empty
89
- else:
90
- return False # The folder is not empty
91
- else:
92
- print(f"The path {folder_path} is not a valid folder.")
93
- return None # The path is not a valid folder
94
-
95
- def remove_folder_with_contents(folder_path):
96
- try:
97
- shutil.rmtree(folder_path)
98
- print(f"Successfully removed {folder_path} and all of its contents.")
99
- except Exception as e:
100
- print(f"Error removing {folder_path}: {e}")
101
-
102
-
103
-
104
-
105
- def wipe_folder(folder_path):
106
- # Check if the folder exists
107
- if not os.path.exists(folder_path):
108
- print(f"The folder {folder_path} does not exist.")
109
- return
110
-
111
- # Iterate over all the items in the given folder
112
- for item in os.listdir(folder_path):
113
- item_path = os.path.join(folder_path, item)
114
- # If it's a file, remove it and print a message
115
- if os.path.isfile(item_path):
116
- os.remove(item_path)
117
- print(f"Removed file: {item_path}")
118
- # If it's a directory, remove it recursively and print a message
119
- elif os.path.isdir(item_path):
120
- shutil.rmtree(item_path)
121
- print(f"Removed directory and its contents: {item_path}")
122
-
123
- print(f"All contents wiped from {folder_path}.")
124
-
125
-
126
- # Example usage
127
- # folder_to_wipe = 'path_to_your_folder'
128
- # wipe_folder(folder_to_wipe)
129
-
130
-
131
- def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
132
- # Function to sort chapters based on their numeric order
133
- def sort_key(chapter_file):
134
- numbers = re.findall(r'\d+', chapter_file)
135
- return int(numbers[0]) if numbers else 0
136
-
137
- # Extract metadata and cover image from the eBook file
138
- def extract_metadata_and_cover(ebook_path):
139
- try:
140
- cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
141
- subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
142
- if os.path.exists(cover_path):
143
- return cover_path
144
- except Exception as e:
145
- print(f"Error extracting eBook metadata or cover: {e}")
146
- return None
147
- # Combine WAV files into a single file
148
- def combine_wav_files(chapter_files, output_path):
149
- # Initialize an empty audio segment
150
- combined_audio = AudioSegment.empty()
151
-
152
- # Sequentially append each file to the combined_audio
153
- for chapter_file in chapter_files:
154
- audio_segment = AudioSegment.from_wav(chapter_file)
155
- combined_audio += audio_segment
156
- # Export the combined audio to the output file path
157
- combined_audio.export(output_path, format='wav')
158
- print(f"Combined audio saved to {output_path}")
159
-
160
- # Function to generate metadata for M4B chapters
161
- def generate_ffmpeg_metadata(chapter_files, metadata_file):
162
- with open(metadata_file, 'w') as file:
163
- file.write(';FFMETADATA1\n')
164
- start_time = 0
165
- for index, chapter_file in enumerate(chapter_files):
166
- duration_ms = len(AudioSegment.from_wav(chapter_file))
167
- file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
168
- file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
169
- start_time += duration_ms
170
-
171
- # Generate the final M4B file using ffmpeg
172
- def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
173
- # Ensure the output directory exists
174
- os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
175
-
176
- ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
177
- if cover_image:
178
- ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
179
- else:
180
- ffmpeg_cmd += ['-map', '0:a']
181
-
182
- ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
183
- if cover_image:
184
- ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
185
- ffmpeg_cmd += [output_m4b]
186
-
187
- subprocess.run(ffmpeg_cmd, check=True)
188
-
189
-
190
-
191
- # Main logic
192
- chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
193
- temp_dir = tempfile.gettempdir()
194
- temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
195
- metadata_file = os.path.join(temp_dir, 'metadata.txt')
196
- cover_image = extract_metadata_and_cover(ebook_file)
197
- output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
198
-
199
- combine_wav_files(chapter_files, temp_combined_wav)
200
- generate_ffmpeg_metadata(chapter_files, metadata_file)
201
- create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
202
-
203
- # Cleanup
204
- if os.path.exists(temp_combined_wav):
205
- os.remove(temp_combined_wav)
206
- if os.path.exists(metadata_file):
207
- os.remove(metadata_file)
208
- if cover_image and os.path.exists(cover_image):
209
- os.remove(cover_image)
210
-
211
- # Example usage
212
- # create_m4b_from_chapters('path_to_chapter_wavs', 'path_to_ebook_file', 'path_to_output_dir')
213
-
214
-
215
-
216
-
217
-
218
-
219
- #this code right here isnt the book grabbing thing but its before to refrence in ordero to create the sepecial chapter labeled book thing with calibre idk some systems cant seem to get it so just in case but the next bit of code after this is the book grabbing code with booknlp
220
- import os
221
- import subprocess
222
- import ebooklib
223
- from ebooklib import epub
224
- from bs4 import BeautifulSoup
225
- import re
226
- import csv
227
- import nltk
228
-
229
- # Only run the main script if Value is True
230
- def create_chapter_labeled_book(ebook_file_path):
231
- # Function to ensure the existence of a directory
232
- def ensure_directory(directory_path):
233
- if not os.path.exists(directory_path):
234
- os.makedirs(directory_path)
235
- print(f"Created directory: {directory_path}")
236
-
237
- ensure_directory(os.path.join(".", 'Working_files', 'Book'))
238
-
239
- def convert_to_epub(input_path, output_path):
240
- # Convert the ebook to EPUB format using Calibre's ebook-convert
241
- try:
242
- subprocess.run(['ebook-convert', input_path, output_path], check=True)
243
- except subprocess.CalledProcessError as e:
244
- print(f"An error occurred while converting the eBook: {e}")
245
- return False
246
- return True
247
-
248
- def save_chapters_as_text(epub_path):
249
- # Create the directory if it doesn't exist
250
- directory = os.path.join(".", "Working_files", "temp_ebook")
251
- ensure_directory(directory)
252
-
253
- # Open the EPUB file
254
- book = epub.read_epub(epub_path)
255
-
256
- previous_chapter_text = ''
257
- previous_filename = ''
258
- chapter_counter = 0
259
-
260
- # Iterate through the items in the EPUB file
261
- for item in book.get_items():
262
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
263
- # Use BeautifulSoup to parse HTML content
264
- soup = BeautifulSoup(item.get_content(), 'html.parser')
265
- text = soup.get_text()
266
-
267
- # Check if the text is not empty
268
- if text.strip():
269
- if len(text) < 2300 and previous_filename:
270
- # Append text to the previous chapter if it's short
271
- with open(previous_filename, 'a', encoding='utf-8') as file:
272
- file.write('\n' + text)
273
- else:
274
- # Create a new chapter file and increment the counter
275
- previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
276
- chapter_counter += 1
277
- with open(previous_filename, 'w', encoding='utf-8') as file:
278
- file.write(text)
279
- print(f"Saved chapter: {previous_filename}")
280
-
281
- # Example usage
282
- input_ebook = ebook_file_path # Replace with your eBook file path
283
- output_epub = os.path.join(".", "Working_files", "temp.epub")
284
-
285
-
286
- if os.path.exists(output_epub):
287
- os.remove(output_epub)
288
- print(f"File {output_epub} has been removed.")
289
- else:
290
- print(f"The file {output_epub} does not exist.")
291
-
292
- if convert_to_epub(input_ebook, output_epub):
293
- save_chapters_as_text(output_epub)
294
-
295
- # Download the necessary NLTK data (if not already present)
296
- #nltk.download('punkt')
297
-
298
- def process_chapter_files(folder_path, output_csv):
299
- with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
300
- writer = csv.writer(csvfile)
301
- # Write the header row
302
- writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
303
-
304
- # Process each chapter file
305
- chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
306
- for filename in chapter_files:
307
- if filename.startswith('chapter_') and filename.endswith('.txt'):
308
- chapter_number = int(filename.split('_')[1].split('.')[0])
309
- file_path = os.path.join(folder_path, filename)
310
-
311
- try:
312
- with open(file_path, 'r', encoding='utf-8') as file:
313
- text = file.read()
314
- # Insert "NEWCHAPTERABC" at the beginning of each chapter's text
315
- if text:
316
- text = "NEWCHAPTERABC" + text
317
- sentences = nltk.tokenize.sent_tokenize(text)
318
- for sentence in sentences:
319
- start_location = text.find(sentence)
320
- end_location = start_location + len(sentence)
321
- writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
322
- except Exception as e:
323
- print(f"Error processing file {filename}: {e}")
324
-
325
- # Example usage
326
- folder_path = os.path.join(".", "Working_files", "temp_ebook")
327
- output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
328
-
329
- process_chapter_files(folder_path, output_csv)
330
-
331
- def sort_key(filename):
332
- """Extract chapter number for sorting."""
333
- match = re.search(r'chapter_(\d+)\.txt', filename)
334
- return int(match.group(1)) if match else 0
335
-
336
- def combine_chapters(input_folder, output_file):
337
- # Create the output folder if it doesn't exist
338
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
339
-
340
- # List all txt files and sort them by chapter number
341
- files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
342
- sorted_files = sorted(files, key=sort_key)
343
-
344
- with open(output_file, 'w', encoding='utf-8') as outfile: # Specify UTF-8 encoding here
345
- for i, filename in enumerate(sorted_files):
346
- with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile: # And here
347
- outfile.write(infile.read())
348
- # Add the marker unless it's the last file
349
- if i < len(sorted_files) - 1:
350
- outfile.write("\nNEWCHAPTERABC\n")
351
-
352
- # Paths
353
- input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
354
- output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
355
-
356
-
357
- # Combine the chapters
358
- combine_chapters(input_folder, output_file)
359
-
360
- ensure_directory(os.path.join(".", "Working_files", "Book"))
361
-
362
-
363
- #create_chapter_labeled_book()
364
-
365
-
366
-
367
-
368
- import os
369
- import subprocess
370
- import sys
371
- import torchaudio
372
-
373
- # Check if Calibre's ebook-convert tool is installed
374
- def calibre_installed():
375
- try:
376
- subprocess.run(['ebook-convert', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
377
- return True
378
- except FileNotFoundError:
379
- print("Calibre is not installed. Please install Calibre for this functionality.")
380
- return False
381
-
382
-
383
- import os
384
- import torch
385
- from TTS.api import TTS
386
- from nltk.tokenize import sent_tokenize
387
- from pydub import AudioSegment
388
- # Assuming split_long_sentence and wipe_folder are defined elsewhere in your code
389
-
390
- default_target_voice_path = "default_voice.wav" # Ensure this is a valid path
391
- default_language_code = "en"
392
- def combine_wav_files(input_directory, output_directory, file_name):
393
- # Ensure that the output directory exists, create it if necessary
394
- os.makedirs(output_directory, exist_ok=True)
395
-
396
- # Specify the output file path
397
- output_file_path = os.path.join(output_directory, file_name)
398
-
399
- # Initialize an empty audio segment
400
- combined_audio = AudioSegment.empty()
401
-
402
- # Get a list of all .wav files in the specified input directory and sort them
403
- input_file_paths = sorted(
404
- [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith(".wav")],
405
- key=lambda f: int(''.join(filter(str.isdigit, f)))
406
- )
407
-
408
- # Sequentially append each file to the combined_audio
409
- for input_file_path in input_file_paths:
410
- audio_segment = AudioSegment.from_wav(input_file_path)
411
- combined_audio += audio_segment
412
-
413
- # Export the combined audio to the output file path
414
- combined_audio.export(output_file_path, format='wav')
415
-
416
- print(f"Combined audio saved to {output_file_path}")
417
-
418
- # Function to split long strings into parts
419
- def split_long_sentence(sentence, max_length=249, max_pauses=10):
420
- """
421
- Splits a sentence into parts based on length or number of pauses without recursion.
422
-
423
- :param sentence: The sentence to split.
424
- :param max_length: Maximum allowed length of a sentence.
425
- :param max_pauses: Maximum allowed number of pauses in a sentence.
426
- :return: A list of sentence parts that meet the criteria.
427
- """
428
- parts = []
429
- while len(sentence) > max_length or sentence.count(',') + sentence.count(';') + sentence.count('.') > max_pauses:
430
- possible_splits = [i for i, char in enumerate(sentence) if char in ',;.' and i < max_length]
431
- if possible_splits:
432
- # Find the best place to split the sentence, preferring the last possible split to keep parts longer
433
- split_at = possible_splits[-1] + 1
434
- else:
435
- # If no punctuation to split on within max_length, split at max_length
436
- split_at = max_length
437
-
438
- # Split the sentence and add the first part to the list
439
- parts.append(sentence[:split_at].strip())
440
- sentence = sentence[split_at:].strip()
441
-
442
- # Add the remaining part of the sentence
443
- parts.append(sentence)
444
- return parts
445
-
446
- """
447
- if 'tts' not in locals():
448
- tts = TTS(selected_tts_model, progress_bar=True).to(device)
449
- """
450
- from tqdm import tqdm
451
-
452
- # Convert chapters to audio using XTTS
453
-
454
- def convert_chapters_to_audio_custom_model(chapters_dir, output_audio_dir, target_voice_path=None, language=None, custom_model=None):
455
-
456
- if target_voice_path==None:
457
- target_voice_path = default_target_voice_path
458
-
459
- if custom_model:
460
- print("Loading custom model...")
461
- config = XttsConfig()
462
- config.load_json(custom_model['config'])
463
- model = Xtts.init_from_config(config)
464
- model.load_checkpoint(config, checkpoint_path=custom_model['model'], vocab_path=custom_model['vocab'], use_deepspeed=False)
465
- model.to(device)
466
- print("Computing speaker latents...")
467
- gpt_cond_latent, speaker_embedding = model.get_conditioning_latents(audio_path=[target_voice_path])
468
- else:
469
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
470
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
471
-
472
- if not os.path.exists(output_audio_dir):
473
- os.makedirs(output_audio_dir)
474
-
475
- for chapter_file in sorted(os.listdir(chapters_dir)):
476
- if chapter_file.endswith('.txt'):
477
- match = re.search(r"chapter_(\d+).txt", chapter_file)
478
- if match:
479
- chapter_num = int(match.group(1))
480
- else:
481
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
482
- continue
483
-
484
- chapter_path = os.path.join(chapters_dir, chapter_file)
485
- output_file_name = f"audio_chapter_{chapter_num}.wav"
486
- output_file_path = os.path.join(output_audio_dir, output_file_name)
487
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
488
- os.makedirs(temp_audio_directory, exist_ok=True)
489
- temp_count = 0
490
-
491
- with open(chapter_path, 'r', encoding='utf-8') as file:
492
- chapter_text = file.read()
493
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
494
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
495
- fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
496
- for fragment in fragments:
497
- if fragment != "":
498
- print(f"Generating fragment: {fragment}...")
499
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
500
- if custom_model:
501
- out = model.inference(fragment, language, gpt_cond_latent, speaker_embedding, temperature=0.7)
502
- torchaudio.save(fragment_file_path, torch.tensor(out["wav"]).unsqueeze(0), 24000)
503
- else:
504
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
505
- language_code = language if language else default_language_code
506
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
507
- temp_count += 1
508
-
509
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
510
- wipe_folder(temp_audio_directory)
511
- print(f"Converted chapter {chapter_num} to audio.")
512
-
513
-
514
-
515
- def convert_chapters_to_audio_standard_model(chapters_dir, output_audio_dir, target_voice_path=None, language=None):
516
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
517
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
518
-
519
- if not os.path.exists(output_audio_dir):
520
- os.makedirs(output_audio_dir)
521
-
522
- for chapter_file in sorted(os.listdir(chapters_dir)):
523
- if chapter_file.endswith('.txt'):
524
- match = re.search(r"chapter_(\d+).txt", chapter_file)
525
- if match:
526
- chapter_num = int(match.group(1))
527
- else:
528
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
529
- continue
530
-
531
- chapter_path = os.path.join(chapters_dir, chapter_file)
532
- output_file_name = f"audio_chapter_{chapter_num}.wav"
533
- output_file_path = os.path.join(output_audio_dir, output_file_name)
534
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
535
- os.makedirs(temp_audio_directory, exist_ok=True)
536
- temp_count = 0
537
-
538
- with open(chapter_path, 'r', encoding='utf-8') as file:
539
- chapter_text = file.read()
540
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
541
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
542
- fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
543
- for fragment in fragments:
544
- if fragment != "":
545
- print(f"Generating fragment: {fragment}...")
546
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
547
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
548
- language_code = language if language else default_language_code
549
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
550
- temp_count += 1
551
-
552
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
553
- wipe_folder(temp_audio_directory)
554
- print(f"Converted chapter {chapter_num} to audio.")
555
-
556
-
557
-
558
- # Define the functions to be used in the Gradio interface
559
- def convert_ebook_to_audio(ebook_file, target_voice_file, language, use_custom_model, custom_model_file, custom_config_file, custom_vocab_file, custom_model_url=None, progress=gr.Progress()):
560
- ebook_file_path = ebook_file.name
561
- target_voice = target_voice_file.name if target_voice_file else None
562
- custom_model = None
563
-
564
-
565
- working_files = os.path.join(".", "Working_files", "temp_ebook")
566
- full_folder_working_files = os.path.join(".", "Working_files")
567
- chapters_directory = os.path.join(".", "Working_files", "temp_ebook")
568
- output_audio_directory = os.path.join(".", 'Chapter_wav_files')
569
- remove_folder_with_contents(full_folder_working_files)
570
- remove_folder_with_contents(output_audio_directory)
571
-
572
- if use_custom_model and custom_model_file and custom_config_file and custom_vocab_file:
573
- custom_model = {
574
- 'model': custom_model_file.name,
575
- 'config': custom_config_file.name,
576
- 'vocab': custom_vocab_file.name
577
- }
578
- if use_custom_model and custom_model_url:
579
- print(f"Received custom model URL: {custom_model_url}")
580
- download_dir = os.path.join(".", "Working_files", "custom_model")
581
- download_and_extract_zip(custom_model_url, download_dir)
582
- custom_model = {
583
- 'model': os.path.join(download_dir, 'model.pth'),
584
- 'config': os.path.join(download_dir, 'config.json'),
585
- 'vocab': os.path.join(download_dir, 'vocab.json_')
586
- }
587
-
588
- try:
589
- progress(0, desc="Starting conversion")
590
- except Exception as e:
591
- print(f"Error updating progress: {e}")
592
-
593
- if not calibre_installed():
594
- return "Calibre is not installed."
595
-
596
-
597
- try:
598
- progress(0.1, desc="Creating chapter-labeled book")
599
- except Exception as e:
600
- print(f"Error updating progress: {e}")
601
-
602
- create_chapter_labeled_book(ebook_file_path)
603
- audiobook_output_path = os.path.join(".", "Audiobooks")
604
-
605
- try:
606
- progress(0.3, desc="Converting chapters to audio")
607
- except Exception as e:
608
- print(f"Error updating progress: {e}")
609
-
610
- if use_custom_model:
611
- convert_chapters_to_audio_custom_model(chapters_directory, output_audio_directory, target_voice, language, custom_model)
612
- else:
613
- convert_chapters_to_audio_standard_model(chapters_directory, output_audio_directory, target_voice, language)
614
-
615
- try:
616
- progress(0.9, desc="Creating M4B from chapters")
617
- except Exception as e:
618
- print(f"Error updating progress: {e}")
619
-
620
- create_m4b_from_chapters(output_audio_directory, ebook_file_path, audiobook_output_path)
621
-
622
- # Get the name of the created M4B file
623
- m4b_filename = os.path.splitext(os.path.basename(ebook_file_path))[0] + '.m4b'
624
- m4b_filepath = os.path.join(audiobook_output_path, m4b_filename)
625
-
626
- try:
627
- progress(1.0, desc="Conversion complete")
628
- except Exception as e:
629
- print(f"Error updating progress: {e}")
630
- print(f"Audiobook created at {m4b_filepath}")
631
- return f"Audiobook created at {m4b_filepath}", m4b_filepath
632
-
633
-
634
- def list_audiobook_files(audiobook_folder):
635
- # List all files in the audiobook folder
636
- files = []
637
- for filename in os.listdir(audiobook_folder):
638
- if filename.endswith('.m4b'): # Adjust the file extension as needed
639
- files.append(os.path.join(audiobook_folder, filename))
640
- return files
641
-
642
- def download_audiobooks():
643
- audiobook_output_path = os.path.join(".", "Audiobooks")
644
- return list_audiobook_files(audiobook_output_path)
645
-
646
-
647
- language_options = [
648
- "en", "es", "fr", "de", "it", "pt", "pl", "tr", "ru", "nl", "cs", "ar", "zh-cn", "ja", "hu", "ko"
649
- ]
650
-
651
- theme = gr.themes.Soft(
652
- primary_hue="blue",
653
- secondary_hue="blue",
654
- neutral_hue="blue",
655
- text_size=gr.themes.sizes.text_md,
656
- )
657
-
658
- # Gradio UI setup
659
- with gr.Blocks(theme=theme) as demo:
660
- gr.Markdown(
661
- """
662
- # eBook to Audiobook Converter
663
-
664
- Transform your eBooks into immersive audiobooks with optional custom TTS models.
665
- """
666
- )
667
-
668
- with gr.Row():
669
- with gr.Column(scale=3):
670
- ebook_file = gr.File(label="eBook File")
671
- target_voice_file = gr.File(label="Target Voice File (Optional)")
672
- language = gr.Dropdown(label="Language", choices=language_options, value="en")
673
-
674
- with gr.Column(scale=3):
675
- use_custom_model = gr.Checkbox(label="Use Custom Model")
676
- custom_model_file = gr.File(label="Custom Model File (Optional)", visible=False)
677
- custom_config_file = gr.File(label="Custom Config File (Optional)", visible=False)
678
- custom_vocab_file = gr.File(label="Custom Vocab File (Optional)", visible=False)
679
- custom_model_url = gr.Textbox(label="Custom Model Zip URL (Optional)", visible=False)
680
-
681
- convert_btn = gr.Button("Convert to Audiobook", variant="primary")
682
- output = gr.Textbox(label="Conversion Status")
683
- audio_player = gr.Audio(label="Audiobook Player", type="filepath")
684
- download_btn = gr.Button("Download Audiobook Files")
685
- download_files = gr.File(label="Download Files", interactive=False)
686
-
687
- convert_btn.click(
688
- convert_ebook_to_audio,
689
- inputs=[ebook_file, target_voice_file, language, use_custom_model, custom_model_file, custom_config_file, custom_vocab_file, custom_model_url],
690
- outputs=[output, audio_player]
691
- )
692
-
693
- use_custom_model.change(
694
- lambda x: [gr.update(visible=x)] * 4,
695
- inputs=[use_custom_model],
696
- outputs=[custom_model_file, custom_config_file, custom_vocab_file, custom_model_url]
697
- )
698
-
699
- download_btn.click(
700
- download_audiobooks,
701
- outputs=[download_files]
702
- )
703
-
704
- demo.launch(share=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobookXTTS/default_voice.wav DELETED
Binary file (291 kB)
 
ebook2audiobookXTTS/demo_mini_story_chapters_Drew.epub DELETED
Binary file (415 kB)
 
ebook2audiobookXTTS/ebook2audiobook.py DELETED
@@ -1,466 +0,0 @@
1
- print("starting...")
2
-
3
- import os
4
- import shutil
5
- import subprocess
6
- import re
7
- from pydub import AudioSegment
8
- import tempfile
9
- from pydub import AudioSegment
10
- import os
11
- import nltk
12
- from nltk.tokenize import sent_tokenize
13
-
14
- #make the nltk folder point to the nltk folder in the app dir
15
- nltk.data.path.append('/home/user/app/nltk_data')
16
-
17
- #nltk.download('punkt') # Make sure to download the necessary models
18
- def is_folder_empty(folder_path):
19
- if os.path.exists(folder_path) and os.path.isdir(folder_path):
20
- # List directory contents
21
- if not os.listdir(folder_path):
22
- return True # The folder is empty
23
- else:
24
- return False # The folder is not empty
25
- else:
26
- print(f"The path {folder_path} is not a valid folder.")
27
- return None # The path is not a valid folder
28
-
29
- def remove_folder_with_contents(folder_path):
30
- try:
31
- shutil.rmtree(folder_path)
32
- print(f"Successfully removed {folder_path} and all of its contents.")
33
- except Exception as e:
34
- print(f"Error removing {folder_path}: {e}")
35
-
36
-
37
-
38
-
39
- def wipe_folder(folder_path):
40
- # Check if the folder exists
41
- if not os.path.exists(folder_path):
42
- print(f"The folder {folder_path} does not exist.")
43
- return
44
-
45
- # Iterate over all the items in the given folder
46
- for item in os.listdir(folder_path):
47
- item_path = os.path.join(folder_path, item)
48
- # If it's a file, remove it and print a message
49
- if os.path.isfile(item_path):
50
- os.remove(item_path)
51
- print(f"Removed file: {item_path}")
52
- # If it's a directory, remove it recursively and print a message
53
- elif os.path.isdir(item_path):
54
- shutil.rmtree(item_path)
55
- print(f"Removed directory and its contents: {item_path}")
56
-
57
- print(f"All contents wiped from {folder_path}.")
58
-
59
-
60
- # Example usage
61
- # folder_to_wipe = 'path_to_your_folder'
62
- # wipe_folder(folder_to_wipe)
63
-
64
-
65
- def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
66
- # Function to sort chapters based on their numeric order
67
- def sort_key(chapter_file):
68
- numbers = re.findall(r'\d+', chapter_file)
69
- return int(numbers[0]) if numbers else 0
70
-
71
- # Extract metadata and cover image from the eBook file
72
- def extract_metadata_and_cover(ebook_path):
73
- try:
74
- cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
75
- subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
76
- if os.path.exists(cover_path):
77
- return cover_path
78
- except Exception as e:
79
- print(f"Error extracting eBook metadata or cover: {e}")
80
- return None
81
- # Combine WAV files into a single file
82
- def combine_wav_files(chapter_files, output_path):
83
- # Initialize an empty audio segment
84
- combined_audio = AudioSegment.empty()
85
-
86
- # Sequentially append each file to the combined_audio
87
- for chapter_file in chapter_files:
88
- audio_segment = AudioSegment.from_wav(chapter_file)
89
- combined_audio += audio_segment
90
- # Export the combined audio to the output file path
91
- combined_audio.export(output_path, format='wav')
92
- print(f"Combined audio saved to {output_path}")
93
-
94
- # Function to generate metadata for M4B chapters
95
- def generate_ffmpeg_metadata(chapter_files, metadata_file):
96
- with open(metadata_file, 'w') as file:
97
- file.write(';FFMETADATA1\n')
98
- start_time = 0
99
- for index, chapter_file in enumerate(chapter_files):
100
- duration_ms = len(AudioSegment.from_wav(chapter_file))
101
- file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
102
- file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
103
- start_time += duration_ms
104
-
105
- # Generate the final M4B file using ffmpeg
106
- def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
107
- # Ensure the output directory exists
108
- os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
109
-
110
- ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
111
- if cover_image:
112
- ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
113
- else:
114
- ffmpeg_cmd += ['-map', '0:a']
115
-
116
- ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
117
- if cover_image:
118
- ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
119
- ffmpeg_cmd += [output_m4b]
120
-
121
- subprocess.run(ffmpeg_cmd, check=True)
122
-
123
-
124
-
125
- # Main logic
126
- chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
127
- temp_dir = tempfile.gettempdir()
128
- temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
129
- metadata_file = os.path.join(temp_dir, 'metadata.txt')
130
- cover_image = extract_metadata_and_cover(ebook_file)
131
- output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
132
-
133
- combine_wav_files(chapter_files, temp_combined_wav)
134
- generate_ffmpeg_metadata(chapter_files, metadata_file)
135
- create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
136
-
137
- # Cleanup
138
- if os.path.exists(temp_combined_wav):
139
- os.remove(temp_combined_wav)
140
- if os.path.exists(metadata_file):
141
- os.remove(metadata_file)
142
- if cover_image and os.path.exists(cover_image):
143
- os.remove(cover_image)
144
-
145
- # Example usage
146
- # create_m4b_from_chapters('path_to_chapter_wavs', 'path_to_ebook_file', 'path_to_output_dir')
147
-
148
-
149
-
150
-
151
-
152
-
153
- #this code right here isnt the book grabbing thing but its before to refrence in ordero to create the sepecial chapter labeled book thing with calibre idk some systems cant seem to get it so just in case but the next bit of code after this is the book grabbing code with booknlp
154
- import os
155
- import subprocess
156
- import ebooklib
157
- from ebooklib import epub
158
- from bs4 import BeautifulSoup
159
- import re
160
- import csv
161
- import nltk
162
-
163
- # Only run the main script if Value is True
164
- def create_chapter_labeled_book(ebook_file_path):
165
- # Function to ensure the existence of a directory
166
- def ensure_directory(directory_path):
167
- if not os.path.exists(directory_path):
168
- os.makedirs(directory_path)
169
- print(f"Created directory: {directory_path}")
170
-
171
- ensure_directory(os.path.join(".", 'Working_files', 'Book'))
172
-
173
- def convert_to_epub(input_path, output_path):
174
- # Convert the ebook to EPUB format using Calibre's ebook-convert
175
- try:
176
- subprocess.run(['ebook-convert', input_path, output_path], check=True)
177
- except subprocess.CalledProcessError as e:
178
- print(f"An error occurred while converting the eBook: {e}")
179
- return False
180
- return True
181
-
182
- def save_chapters_as_text(epub_path):
183
- # Create the directory if it doesn't exist
184
- directory = os.path.join(".", "Working_files", "temp_ebook")
185
- ensure_directory(directory)
186
-
187
- # Open the EPUB file
188
- book = epub.read_epub(epub_path)
189
-
190
- previous_chapter_text = ''
191
- previous_filename = ''
192
- chapter_counter = 0
193
-
194
- # Iterate through the items in the EPUB file
195
- for item in book.get_items():
196
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
197
- # Use BeautifulSoup to parse HTML content
198
- soup = BeautifulSoup(item.get_content(), 'html.parser')
199
- text = soup.get_text()
200
-
201
- # Check if the text is not empty
202
- if text.strip():
203
- if len(text) < 2300 and previous_filename:
204
- # Append text to the previous chapter if it's short
205
- with open(previous_filename, 'a', encoding='utf-8') as file:
206
- file.write('\n' + text)
207
- else:
208
- # Create a new chapter file and increment the counter
209
- previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
210
- chapter_counter += 1
211
- with open(previous_filename, 'w', encoding='utf-8') as file:
212
- file.write(text)
213
- print(f"Saved chapter: {previous_filename}")
214
-
215
- # Example usage
216
- input_ebook = ebook_file_path # Replace with your eBook file path
217
- output_epub = os.path.join(".", "Working_files", "temp.epub")
218
-
219
-
220
- if os.path.exists(output_epub):
221
- os.remove(output_epub)
222
- print(f"File {output_epub} has been removed.")
223
- else:
224
- print(f"The file {output_epub} does not exist.")
225
-
226
- if convert_to_epub(input_ebook, output_epub):
227
- save_chapters_as_text(output_epub)
228
-
229
- # Download the necessary NLTK data (if not already present)
230
- #nltk.download('punkt')
231
-
232
- def process_chapter_files(folder_path, output_csv):
233
- with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
234
- writer = csv.writer(csvfile)
235
- # Write the header row
236
- writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
237
-
238
- # Process each chapter file
239
- chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
240
- for filename in chapter_files:
241
- if filename.startswith('chapter_') and filename.endswith('.txt'):
242
- chapter_number = int(filename.split('_')[1].split('.')[0])
243
- file_path = os.path.join(folder_path, filename)
244
-
245
- try:
246
- with open(file_path, 'r', encoding='utf-8') as file:
247
- text = file.read()
248
- # Insert "NEWCHAPTERABC" at the beginning of each chapter's text
249
- if text:
250
- text = "NEWCHAPTERABC" + text
251
- sentences = nltk.tokenize.sent_tokenize(text)
252
- for sentence in sentences:
253
- start_location = text.find(sentence)
254
- end_location = start_location + len(sentence)
255
- writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
256
- except Exception as e:
257
- print(f"Error processing file {filename}: {e}")
258
-
259
- # Example usage
260
- folder_path = os.path.join(".", "Working_files", "temp_ebook")
261
- output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
262
-
263
- process_chapter_files(folder_path, output_csv)
264
-
265
- def sort_key(filename):
266
- """Extract chapter number for sorting."""
267
- match = re.search(r'chapter_(\d+)\.txt', filename)
268
- return int(match.group(1)) if match else 0
269
-
270
- def combine_chapters(input_folder, output_file):
271
- # Create the output folder if it doesn't exist
272
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
273
-
274
- # List all txt files and sort them by chapter number
275
- files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
276
- sorted_files = sorted(files, key=sort_key)
277
-
278
- with open(output_file, 'w', encoding='utf-8') as outfile: # Specify UTF-8 encoding here
279
- for i, filename in enumerate(sorted_files):
280
- with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile: # And here
281
- outfile.write(infile.read())
282
- # Add the marker unless it's the last file
283
- if i < len(sorted_files) - 1:
284
- outfile.write("\nNEWCHAPTERABC\n")
285
-
286
- # Paths
287
- input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
288
- output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
289
-
290
-
291
- # Combine the chapters
292
- combine_chapters(input_folder, output_file)
293
-
294
- ensure_directory(os.path.join(".", "Working_files", "Book"))
295
-
296
-
297
- #create_chapter_labeled_book()
298
-
299
-
300
-
301
-
302
- import os
303
- import subprocess
304
- import sys
305
- import torchaudio
306
-
307
- # Check if Calibre's ebook-convert tool is installed
308
- def calibre_installed():
309
- try:
310
- subprocess.run(['ebook-convert', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
311
- return True
312
- except FileNotFoundError:
313
- print("Calibre is not installed. Please install Calibre for this functionality.")
314
- return False
315
-
316
-
317
- import os
318
- import torch
319
- from TTS.api import TTS
320
- from nltk.tokenize import sent_tokenize
321
- from pydub import AudioSegment
322
- # Assuming split_long_sentence and wipe_folder are defined elsewhere in your code
323
-
324
- default_target_voice_path = "default_voice.wav" # Ensure this is a valid path
325
- default_language_code = "en"
326
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
327
-
328
- def combine_wav_files(input_directory, output_directory, file_name):
329
- # Ensure that the output directory exists, create it if necessary
330
- os.makedirs(output_directory, exist_ok=True)
331
-
332
- # Specify the output file path
333
- output_file_path = os.path.join(output_directory, file_name)
334
-
335
- # Initialize an empty audio segment
336
- combined_audio = AudioSegment.empty()
337
-
338
- # Get a list of all .wav files in the specified input directory and sort them
339
- input_file_paths = sorted(
340
- [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith(".wav")],
341
- key=lambda f: int(''.join(filter(str.isdigit, f)))
342
- )
343
-
344
- # Sequentially append each file to the combined_audio
345
- for input_file_path in input_file_paths:
346
- audio_segment = AudioSegment.from_wav(input_file_path)
347
- combined_audio += audio_segment
348
-
349
- # Export the combined audio to the output file path
350
- combined_audio.export(output_file_path, format='wav')
351
-
352
- print(f"Combined audio saved to {output_file_path}")
353
-
354
- # Function to split long strings into parts
355
- def split_long_sentence(sentence, max_length=249, max_pauses=10):
356
- """
357
- Splits a sentence into parts based on length or number of pauses without recursion.
358
-
359
- :param sentence: The sentence to split.
360
- :param max_length: Maximum allowed length of a sentence.
361
- :param max_pauses: Maximum allowed number of pauses in a sentence.
362
- :return: A list of sentence parts that meet the criteria.
363
- """
364
- parts = []
365
- while len(sentence) > max_length or sentence.count(',') + sentence.count(';') + sentence.count('.') > max_pauses:
366
- possible_splits = [i for i, char in enumerate(sentence) if char in ',;.' and i < max_length]
367
- if possible_splits:
368
- # Find the best place to split the sentence, preferring the last possible split to keep parts longer
369
- split_at = possible_splits[-1] + 1
370
- else:
371
- # If no punctuation to split on within max_length, split at max_length
372
- split_at = max_length
373
-
374
- # Split the sentence and add the first part to the list
375
- parts.append(sentence[:split_at].strip())
376
- sentence = sentence[split_at:].strip()
377
-
378
- # Add the remaining part of the sentence
379
- parts.append(sentence)
380
- return parts
381
-
382
- """
383
- if 'tts' not in locals():
384
- tts = TTS(selected_tts_model, progress_bar=True).to(device)
385
- """
386
- from tqdm import tqdm
387
-
388
- # Convert chapters to audio using XTTS
389
- def convert_chapters_to_audio(chapters_dir, output_audio_dir, target_voice_path=None, language=None):
390
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
391
- tts = TTS(selected_tts_model, progress_bar=False).to(device) # Set progress_bar to False to avoid nested progress bars
392
-
393
- if not os.path.exists(output_audio_dir):
394
- os.makedirs(output_audio_dir)
395
-
396
- for chapter_file in sorted(os.listdir(chapters_dir)):
397
- if chapter_file.endswith('.txt'):
398
- # Extract chapter number from the filename
399
- match = re.search(r"chapter_(\d+).txt", chapter_file)
400
- if match:
401
- chapter_num = int(match.group(1))
402
- else:
403
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
404
- continue
405
-
406
- chapter_path = os.path.join(chapters_dir, chapter_file)
407
- output_file_name = f"audio_chapter_{chapter_num}.wav"
408
- output_file_path = os.path.join(output_audio_dir, output_file_name)
409
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
410
- os.makedirs(temp_audio_directory, exist_ok=True)
411
- temp_count = 0
412
-
413
- with open(chapter_path, 'r', encoding='utf-8') as file:
414
- chapter_text = file.read()
415
- # Use the specified language model for sentence tokenization
416
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
417
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
418
- fragments = []
419
- if language == "en":
420
- fragments = split_long_sentence(sentence, max_length=249, max_pauses=10)
421
- if language == "it":
422
- fragments = split_long_sentence(sentence, max_length=213, max_pauses=10)
423
- for fragment in fragments:
424
- if fragment != "": #a hot fix to avoid blank fragments
425
- print(f"Generating fragment: {fragment}...")
426
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
427
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
428
- language_code = language if language else default_language_code
429
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
430
- temp_count += 1
431
-
432
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
433
- wipe_folder(temp_audio_directory)
434
- print(f"Converted chapter {chapter_num} to audio.")
435
-
436
-
437
-
438
- # Main execution flow
439
- if __name__ == "__main__":
440
- if len(sys.argv) < 2:
441
- print("Usage: python script.py <ebook_file_path> [target_voice_file_path]")
442
- sys.exit(1)
443
-
444
- ebook_file_path = sys.argv[1]
445
- target_voice = sys.argv[2] if len(sys.argv) > 2 else None
446
- language = sys.argv[3] if len(sys.argv) > 3 else None
447
-
448
- if not calibre_installed():
449
- sys.exit(1)
450
-
451
- working_files = os.path.join(".","Working_files", "temp_ebook")
452
- full_folder_working_files =os.path.join(".","Working_files")
453
- chapters_directory = os.path.join(".","Working_files", "temp_ebook")
454
- output_audio_directory = os.path.join(".", 'Chapter_wav_files')
455
-
456
- print("Wiping and removeing Working_files folder...")
457
- remove_folder_with_contents(full_folder_working_files)
458
-
459
- print("Wiping and and removeing chapter_wav_files folder...")
460
- remove_folder_with_contents(output_audio_directory)
461
-
462
- create_chapter_labeled_book(ebook_file_path)
463
- audiobook_output_path = os.path.join(".", "Audiobooks")
464
- print(f"{chapters_directory}||||{output_audio_directory}|||||{target_voice}")
465
- convert_chapters_to_audio(chapters_directory, output_audio_directory, target_voice, language)
466
- create_m4b_from_chapters(output_audio_directory, ebook_file_path, audiobook_output_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobookXTTS/gradio_gui_with_email_and_que.py DELETED
@@ -1,614 +0,0 @@
1
- print("starting...")
2
- import ebooklib
3
- from ebooklib import epub
4
-
5
- import os
6
- import subprocess
7
- import ebooklib
8
- from ebooklib import epub
9
- from bs4 import BeautifulSoup
10
- import re
11
- import csv
12
- import nltk
13
-
14
- import os
15
- import subprocess
16
- import sys
17
- import torchaudio
18
-
19
- import os
20
- import torch
21
- from TTS.api import TTS
22
- from nltk.tokenize import sent_tokenize
23
- from pydub import AudioSegment
24
-
25
- from tqdm import tqdm
26
-
27
-
28
-
29
- import os
30
- import subprocess
31
- import ebooklib
32
- from ebooklib import epub
33
- from bs4 import BeautifulSoup
34
- import re
35
- import csv
36
- import nltk
37
-
38
- from bs4 import BeautifulSoup
39
- import os
40
- import shutil
41
- import subprocess
42
- import re
43
- from pydub import AudioSegment
44
- import tempfile
45
- import urllib.request
46
- import zipfile
47
- import requests
48
- from tqdm import tqdm
49
- import nltk
50
- from nltk.tokenize import sent_tokenize
51
- import torch
52
- import torchaudio
53
- import gradio as gr
54
- from threading import Lock, Thread
55
- from queue import Queue
56
- import smtplib
57
- from email.mime.text import MIMEText
58
-
59
-
60
- import os
61
- import shutil
62
- import subprocess
63
- import re
64
- from pydub import AudioSegment
65
- import tempfile
66
- from pydub import AudioSegment
67
- import os
68
- import nltk
69
- from nltk.tokenize import sent_tokenize
70
- import sys
71
- import torch
72
- from TTS.api import TTS
73
- from TTS.tts.configs.xtts_config import XttsConfig
74
- from TTS.tts.models.xtts import Xtts
75
- from tqdm import tqdm
76
- import gradio as gr
77
- from gradio import Progress
78
- import urllib.request
79
- import zipfile
80
-
81
-
82
- default_target_voice_path = "default_voice.wav" # Ensure this is a valid path
83
- default_language_code = "en"
84
-
85
-
86
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
87
- print(f"Device selected is: {device}")
88
-
89
- nltk.download('punkt') # Ensure necessary models are downloaded
90
-
91
- # Global variables for queue management
92
- queue = Queue()
93
- queue_lock = Lock()
94
-
95
- # Function to send an email with the download link
96
- def send_email(to_address, download_link):
97
- from_address = "[email protected]" # Replace with your email
98
- subject = "Your Audiobook is Ready"
99
- body = f"Your audiobook has been processed. You can download it from the following link: {download_link}"
100
-
101
- msg = MIMEText(body)
102
- msg['Subject'] = subject
103
- msg['From'] = from_address
104
- msg['To'] = to_address
105
-
106
- try:
107
- with smtplib.SMTP('smtp.example.com', 587) as server: # Replace with your SMTP server details
108
- server.starttls()
109
- server.login(from_address, "your_password") # Replace with your email password
110
- server.sendmail(from_address, [to_address], msg.as_string())
111
- print(f"Email sent to {to_address}")
112
- except Exception as e:
113
- print(f"Failed to send email: {e}")
114
-
115
- # Function to download and extract the custom model
116
- def download_and_extract_zip(url, extract_to='.'):
117
- try:
118
- os.makedirs(extract_to, exist_ok=True)
119
- zip_path = os.path.join(extract_to, 'model.zip')
120
-
121
- with tqdm(unit='B', unit_scale=True, miniters=1, desc="Downloading Model") as t:
122
- def reporthook(blocknum, blocksize, totalsize):
123
- t.total = totalsize
124
- t.update(blocknum * blocksize - t.n)
125
- urllib.request.urlretrieve(url, zip_path, reporthook=reporthook)
126
- print(f"Downloaded zip file to {zip_path}")
127
-
128
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
129
- files = zip_ref.namelist()
130
- with tqdm(total=len(files), unit="file", desc="Extracting Files") as t:
131
- for file in files:
132
- if not file.endswith('/'):
133
- extracted_path = zip_ref.extract(file, extract_to)
134
- base_file_path = os.path.join(extract_to, os.path.basename(file))
135
- os.rename(extracted_path, base_file_path)
136
- t.update(1)
137
-
138
- os.remove(zip_path)
139
- for root, dirs, files in os.walk(extract_to, topdown=False):
140
- for name in dirs:
141
- os.rmdir(os.path.join(root, name))
142
- print(f"Extracted files to {extract_to}")
143
-
144
- required_files = ['model.pth', 'config.json', 'vocab.json_']
145
- missing_files = [file for file in required_files if not os.path.exists(os.path.join(extract_to, file))]
146
-
147
- if not missing_files:
148
- print("All required files (model.pth, config.json, vocab.json_) found.")
149
- else:
150
- print(f"Missing files: {', '.join(missing_files)}")
151
-
152
- except Exception as e:
153
- print(f"Failed to download or extract zip file: {e}")
154
-
155
- # Function to check if a folder is empty
156
- def is_folder_empty(folder_path):
157
- if os.path.exists(folder_path) and os.path.isdir(folder_path):
158
- return not os.listdir(folder_path)
159
- else:
160
- print(f"The path {folder_path} is not a valid folder.")
161
- return None
162
-
163
- # Function to remove a folder and its contents
164
- def remove_folder_with_contents(folder_path):
165
- try:
166
- shutil.rmtree(folder_path)
167
- print(f"Successfully removed {folder_path} and all of its contents.")
168
- except Exception as e:
169
- print(f"Error removing {folder_path}: {e}")
170
-
171
- # Function to wipe the contents of a folder
172
- def wipe_folder(folder_path):
173
- if not os.path.exists(folder_path):
174
- print(f"The folder {folder_path} does not exist.")
175
- return
176
-
177
- for item in os.listdir(folder_path):
178
- item_path = os.path.join(folder_path, item)
179
- if os.path.isfile(item_path):
180
- os.remove(item_path)
181
- print(f"Removed file: {item_path}")
182
- elif os.path.isdir(item_path):
183
- shutil.rmtree(item_path)
184
- print(f"Removed directory and its contents: {item_path}")
185
-
186
- print(f"All contents wiped from {folder_path}.")
187
-
188
- # Function to create M4B from chapters
189
- def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
190
- def sort_key(chapter_file):
191
- numbers = re.findall(r'\d+', chapter_file)
192
- return int(numbers[0]) if numbers else 0
193
-
194
- def extract_metadata_and_cover(ebook_path):
195
- try:
196
- cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
197
- subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
198
- if os.path.exists(cover_path):
199
- return cover_path
200
- except Exception as e:
201
- print(f"Error extracting eBook metadata or cover: {e}")
202
- return None
203
-
204
- def combine_wav_files(chapter_files, output_path):
205
- combined_audio = AudioSegment.empty()
206
- for chapter_file in chapter_files:
207
- audio_segment = AudioSegment.from_wav(chapter_file)
208
- combined_audio += audio_segment
209
- combined_audio.export(output_path, format='wav')
210
- print(f"Combined audio saved to {output_path}")
211
-
212
- def generate_ffmpeg_metadata(chapter_files, metadata_file):
213
- with open(metadata_file, 'w') as file:
214
- file.write(';FFMETADATA1\n')
215
- start_time = 0
216
- for index, chapter_file in enumerate(chapter_files):
217
- duration_ms = len(AudioSegment.from_wav(chapter_file))
218
- file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
219
- file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
220
- start_time += duration_ms
221
-
222
- def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
223
- os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
224
-
225
- ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
226
- if cover_image:
227
- ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
228
- else:
229
- ffmpeg_cmd += ['-map', '0:a']
230
-
231
- ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
232
- if cover_image:
233
- ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
234
- ffmpeg_cmd += [output_m4b]
235
-
236
- subprocess.run(ffmpeg_cmd, check=True)
237
-
238
- chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
239
- temp_dir = tempfile.gettempdir()
240
- temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
241
- metadata_file = os.path.join(temp_dir, 'metadata.txt')
242
- cover_image = extract_metadata_and_cover(ebook_file)
243
- output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
244
-
245
- combine_wav_files(chapter_files, temp_combined_wav)
246
- generate_ffmpeg_metadata(chapter_files, metadata_file)
247
- create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
248
-
249
- if os.path.exists(temp_combined_wav):
250
- os.remove(temp_combined_wav)
251
- if os.path.exists(metadata_file):
252
- os.remove(metadata_file)
253
- if cover_image and os.path.exists(cover_image):
254
- os.remove(cover_image)
255
-
256
- # Function to create chapter-labeled book
257
- def create_chapter_labeled_book(ebook_file_path):
258
- def ensure_directory(directory_path):
259
- if not os.path.exists(directory_path):
260
- os.makedirs(directory_path)
261
- print(f"Created directory: {directory_path}")
262
-
263
- ensure_directory(os.path.join(".", 'Working_files', 'Book'))
264
-
265
- def convert_to_epub(input_path, output_path):
266
- try:
267
- subprocess.run(['ebook-convert', input_path, output_path], check=True)
268
- except subprocess.CalledProcessError as e:
269
- print(f"An error occurred while converting the eBook: {e}")
270
- return False
271
- return True
272
-
273
- def save_chapters_as_text(epub_path):
274
- directory = os.path.join(".", "Working_files", "temp_ebook")
275
- ensure_directory(directory)
276
-
277
- book = epub.read_epub(epub_path)
278
-
279
- previous_chapter_text = ''
280
- previous_filename = ''
281
- chapter_counter = 0
282
-
283
- for item in book.get_items():
284
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
285
- soup = BeautifulSoup(item.get_content(), 'html.parser')
286
- text = soup.get_text()
287
-
288
- if text.strip():
289
- if len(text) < 2300 and previous_filename:
290
- with open(previous_filename, 'a', encoding='utf-8') as file:
291
- file.write('\n' + text)
292
- else:
293
- previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
294
- chapter_counter += 1
295
- with open(previous_filename, 'w', encoding='utf-8') as file:
296
- file.write(text)
297
- print(f"Saved chapter: {previous_filename}")
298
-
299
- input_ebook = ebook_file_path
300
- output_epub = os.path.join(".", "Working_files", "temp.epub")
301
-
302
- if os.path.exists(output_epub):
303
- os.remove(output_epub)
304
- print(f"File {output_epub} has been removed.")
305
- else:
306
- print(f"The file {output_epub} does not exist.")
307
-
308
- if convert_to_epub(input_ebook, output_epub):
309
- save_chapters_as_text(output_epub)
310
-
311
- nltk.download('punkt')
312
-
313
- def process_chapter_files(folder_path, output_csv):
314
- with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
315
- writer = csv.writer(csvfile)
316
- writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
317
-
318
- chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
319
- for filename in chapter_files:
320
- if filename.startswith('chapter_') and filename.endswith('.txt'):
321
- chapter_number = int(filename.split('_')[1].split('.')[0])
322
- file_path = os.path.join(folder_path, filename)
323
-
324
- try:
325
- with open(file_path, 'r', encoding='utf-8') as file:
326
- text = file.read()
327
- if text:
328
- text = "NEWCHAPTERABC" + text
329
- sentences = nltk.tokenize.sent_tokenize(text)
330
- for sentence in sentences:
331
- start_location = text.find(sentence)
332
- end_location = start_location + len(sentence)
333
- writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
334
- except Exception as e:
335
- print(f"Error processing file {filename}: {e}")
336
-
337
- folder_path = os.path.join(".", "Working_files", "temp_ebook")
338
- output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
339
-
340
- process_chapter_files(folder_path, output_csv)
341
-
342
- def sort_key(filename):
343
- match = re.search(r'chapter_(\d+)\.txt', filename)
344
- return int(match.group(1)) if match else 0
345
-
346
- def combine_chapters(input_folder, output_file):
347
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
348
-
349
- files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
350
- sorted_files = sorted(files, key=sort_key)
351
-
352
- with open(output_file, 'w', encoding='utf-8') as outfile:
353
- for i, filename in enumerate(sorted_files):
354
- with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile:
355
- outfile.write(infile.read())
356
- if i < len(sorted_files) - 1:
357
- outfile.write("\nNEWCHAPTERABC\n")
358
-
359
- input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
360
- output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
361
-
362
- combine_chapters(input_folder, output_file)
363
- ensure_directory(os.path.join(".", "Working_files", "Book"))
364
-
365
- # Function to combine WAV files
366
- def combine_wav_files(input_directory, output_directory, file_name):
367
- os.makedirs(output_directory, exist_ok=True)
368
- output_file_path = os.path.join(output_directory, file_name)
369
- combined_audio = AudioSegment.empty()
370
- input_file_paths = sorted(
371
- [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith(".wav")],
372
- key=lambda f: int(''.join(filter(str.isdigit, f)))
373
- )
374
- for input_file_path in input_file_paths:
375
- audio_segment = AudioSegment.from_wav(input_file_path)
376
- combined_audio += audio_segment
377
- combined_audio.export(output_file_path, format='wav')
378
- print(f"Combined audio saved to {output_file_path}")
379
-
380
- # Function to split long sentences
381
- def split_long_sentence(sentence, max_length=249, max_pauses=10):
382
- parts = []
383
- while len(sentence) > max_length or sentence.count(',') + sentence.count(';') + sentence.count('.') > max_pauses:
384
- possible_splits = [i for i, char in enumerate(sentence) if char in ',;.' and i < max_length]
385
- if possible_splits:
386
- split_at = possible_splits[-1] + 1
387
- else:
388
- split_at = max_length
389
- parts.append(sentence[:split_at].strip())
390
- sentence = sentence[split_at:].strip()
391
- parts.append(sentence)
392
- return parts
393
-
394
- # Function to convert chapters to audio using custom model
395
- def convert_chapters_to_audio_custom_model(chapters_dir, output_audio_dir, target_voice_path=None, language=None, custom_model=None):
396
- if target_voice_path is None:
397
- target_voice_path = default_target_voice_path
398
- if custom_model:
399
- print("Loading custom model...")
400
- config = XttsConfig()
401
- config.load_json(custom_model['config'])
402
- model = Xtts.init_from_config(config)
403
- model.load_checkpoint(config, checkpoint_path=custom_model['model'], vocab_path=custom_model['vocab'], use_deepspeed=False)
404
- model.device
405
- print("Computing speaker latents...")
406
- gpt_cond_latent, speaker_embedding = model.get_conditioning_latents(audio_path=[target_voice_path])
407
- else:
408
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
409
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
410
-
411
- if not os.path.exists(output_audio_dir):
412
- os.makedirs(output_audio_dir)
413
-
414
- for chapter_file in sorted(os.listdir(chapters_dir)):
415
- if chapter_file.endswith('.txt'):
416
- match = re.search(r"chapter_(\d+).txt", chapter_file)
417
- if match:
418
- chapter_num = int(match.group(1))
419
- else:
420
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
421
- continue
422
-
423
- chapter_path = os.path.join(chapters_dir, chapter_file)
424
- output_file_name = f"audio_chapter_{chapter_num}.wav"
425
- output_file_path = os.path.join(output_audio_dir, output_file_name)
426
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
427
- os.makedirs(temp_audio_directory, exist_ok=True)
428
- temp_count = 0
429
-
430
- with open(chapter_path, 'r', encoding='utf-8') as file:
431
- chapter_text = file.read()
432
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
433
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
434
- fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
435
- for fragment in fragments:
436
- if fragment != "":
437
- print(f"Generating fragment: {fragment}...")
438
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
439
- if custom_model:
440
- out = model.inference(fragment, language, gpt_cond_latent, speaker_embedding, temperature=0.7)
441
- torchaudio.save(fragment_file_path, torch.tensor(out["wav"]).unsqueeze(0), 24000)
442
- else:
443
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
444
- language_code = language if language else default_language_code
445
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
446
- temp_count += 1
447
-
448
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
449
- wipe_folder(temp_audio_directory)
450
- print(f"Converted chapter {chapter_num} to audio.")
451
-
452
- # Function to convert chapters to audio using standard model
453
- def convert_chapters_to_audio_standard_model(chapters_dir, output_audio_dir, target_voice_path=None, language=None):
454
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
455
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
456
-
457
- if not os.path.exists(output_audio_dir):
458
- os.makedirs(output_audio_dir)
459
-
460
- for chapter_file in sorted(os.listdir(chapters_dir)):
461
- if chapter_file.endswith('.txt'):
462
- match = re.search(r"chapter_(\d+).txt", chapter_file)
463
- if match:
464
- chapter_num = int(match.group(1))
465
- else:
466
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
467
- continue
468
-
469
- chapter_path = os.path.join(chapters_dir, chapter_file)
470
- output_file_name = f"audio_chapter_{chapter_num}.wav"
471
- output_file_path = os.path.join(output_audio_dir, output_file_name)
472
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
473
- os.makedirs(temp_audio_directory, exist_ok=True)
474
- temp_count = 0
475
-
476
- with open(chapter_path, 'r', encoding='utf-8') as file:
477
- chapter_text = file.read()
478
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
479
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
480
- fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
481
- for fragment in fragments:
482
- if fragment != "":
483
- print(f"Generating fragment: {fragment}...")
484
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
485
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
486
- language_code = language if language else default_language_code
487
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
488
- temp_count += 1
489
-
490
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
491
- wipe_folder(temp_audio_directory)
492
- print(f"Converted chapter {chapter_num} to audio.")
493
-
494
- # Function to handle the processing of an eBook to an audiobook
495
- def process_request(ebook_file, target_voice, language, email, use_custom_model, custom_model):
496
- working_files = os.path.join(".", "Working_files", "temp_ebook")
497
- full_folder_working_files = os.path.join(".", "Working_files")
498
- chapters_directory = os.path.join(".", "Working_files", "temp_ebook")
499
- output_audio_directory = os.path.join(".", 'Chapter_wav_files')
500
- remove_folder_with_contents(full_folder_working_files)
501
- remove_folder_with_contents(output_audio_directory)
502
-
503
- create_chapter_labeled_book(ebook_file.name)
504
- audiobook_output_path = os.path.join(".", "Audiobooks")
505
-
506
- if use_custom_model:
507
- convert_chapters_to_audio_custom_model(chapters_directory, output_audio_directory, target_voice, language, custom_model)
508
- else:
509
- convert_chapters_to_audio_standard_model(chapters_directory, output_audio_directory, target_voice, language)
510
-
511
- create_m4b_from_chapters(output_audio_directory, ebook_file.name, audiobook_output_path)
512
-
513
- m4b_filepath = os.path.join(audiobook_output_path, os.path.splitext(os.path.basename(ebook_file.name))[0] + '.m4b')
514
-
515
- # Upload the final audiobook to file.io
516
- with open(m4b_filepath, 'rb') as f:
517
- response = requests.post('https://file.io', files={'file': f})
518
- download_link = response.json().get('link', '')
519
-
520
- # Send the download link to the user's email
521
- if email and download_link:
522
- send_email(email, download_link)
523
-
524
- return download_link
525
-
526
- # Function to manage the queue and process each request sequentially
527
- def handle_queue():
528
- while True:
529
- ebook_file, target_voice, language, email, use_custom_model, custom_model = queue.get()
530
- process_request(ebook_file, target_voice, language, email, use_custom_model, custom_model)
531
- queue.task_done()
532
-
533
- # Start the queue handler thread
534
- thread = Thread(target=handle_queue, daemon=True)
535
- thread.start()
536
-
537
- # Gradio function to add a request to the queue
538
- def enqueue_request(ebook_file, target_voice_file, language, email, use_custom_model, custom_model_file, custom_config_file, custom_vocab_file, custom_model_url=None):
539
- target_voice = target_voice_file.name if target_voice_file else None
540
- custom_model = None
541
-
542
- if use_custom_model and custom_model_file and custom_config_file and custom_vocab_file:
543
- custom_model = {
544
- 'model': custom_model_file.name,
545
- 'config': custom_config_file.name,
546
- 'vocab': custom_vocab_file.name
547
- }
548
- if use_custom_model and custom_model_url:
549
- download_dir = os.path.join(".", "Working_files", "custom_model")
550
- download_and_extract_zip(custom_model_url, download_dir)
551
- custom_model = {
552
- 'model': os.path.join(download_dir, 'model.pth'),
553
- 'config': os.path.join(download_dir, 'config.json'),
554
- 'vocab': os.path.join(download_dir, 'vocab.json_')
555
- }
556
-
557
- # Add request to the queue
558
- queue_lock.acquire()
559
- queue.put((ebook_file, target_voice, language, email, use_custom_model, custom_model))
560
- position = queue.qsize()
561
- queue_lock.release()
562
- return f"Your request has been added to the queue. You are number {position} in line."
563
-
564
- # Gradio UI setup
565
- language_options = [
566
- "en", "es", "fr", "de", "it", "pt", "pl", "tr", "ru", "nl", "cs", "ar", "zh-cn", "ja", "hu", "ko"
567
- ]
568
-
569
- theme = gr.themes.Soft(
570
- primary_hue="blue",
571
- secondary_hue="blue",
572
- neutral_hue="blue",
573
- text_size=gr.themes.sizes.text_md,
574
- )
575
-
576
- with gr.Blocks(theme=theme) as demo:
577
- gr.Markdown(
578
- """
579
- # eBook to Audiobook Converter
580
-
581
- Transform your eBooks into immersive audiobooks with optional custom TTS models.
582
- """
583
- )
584
-
585
- with gr.Row():
586
- with gr.Column(scale=3):
587
- ebook_file = gr.File(label="eBook File")
588
- target_voice_file = gr.File(label="Target Voice File (Optional)")
589
- language = gr.Dropdown(label="Language", choices=language_options, value="en")
590
- email = gr.Textbox(label="Email Address")
591
-
592
- with gr.Column(scale=3):
593
- use_custom_model = gr.Checkbox(label="Use Custom Model")
594
- custom_model_file = gr.File(label="Custom Model File (Optional)", visible=False)
595
- custom_config_file = gr.File(label="Custom Config File (Optional)", visible=False)
596
- custom_vocab_file = gr.File(label="Custom Vocab File (Optional)", visible=False)
597
- custom_model_url = gr.Textbox(label="Custom Model Zip URL (Optional)", visible=False)
598
-
599
- convert_btn = gr.Button("Convert to Audiobook", variant="primary")
600
- queue_status = gr.Textbox(label="Queue Status")
601
-
602
- convert_btn.click(
603
- enqueue_request,
604
- inputs=[ebook_file, target_voice_file, language, email, use_custom_model, custom_model_file, custom_config_file, custom_vocab_file, custom_model_url],
605
- outputs=[queue_status]
606
- )
607
-
608
- use_custom_model.change(
609
- lambda x: [gr.update(visible=x)] * 4,
610
- inputs=[use_custom_model],
611
- outputs=[custom_model_file, custom_config_file, custom_vocab_file, custom_model_url]
612
- )
613
-
614
- demo.launch(share=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobookXTTS/import_all_files.py DELETED
@@ -1,5 +0,0 @@
1
- import import_nltk_files
2
-
3
- import import_locally_stored_tts_model_files
4
-
5
- #import download_tos_agreed_file
 
 
 
 
 
 
ebook2audiobookXTTS/import_locally_stored_tts_model_files.py DELETED
@@ -1,23 +0,0 @@
1
- import os
2
- import shutil
3
-
4
- print("Importing locally stored coqui tts models...")
5
-
6
- # Define the source directory and the destination base path
7
- tts_folder = "/home/user/app/Base_XTTS_Model"
8
- destination_base = '/home/user/.local/share/'
9
-
10
- # Define the destination path for the tts folder
11
- destination_path = os.path.join(destination_base, 'tts')
12
-
13
- # Move the entire tts folder
14
- if os.path.exists(tts_folder):
15
- # Remove the destination folder if it exists
16
- if os.path.exists(destination_path):
17
- shutil.rmtree(destination_path) # Remove the existing folder
18
- shutil.move(tts_folder, destination_path)
19
- print(f'Moved: {tts_folder} to {destination_path}')
20
- print("Locally stored base coqui XTTS tts model imported!")
21
- print(os.listdir('/home/user/.local/share/tts'))
22
- else:
23
- print(f'Source path does not exist: {tts_folder}')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobookXTTS/import_nltk_files.py DELETED
@@ -1,24 +0,0 @@
1
- import shutil
2
- import os
3
-
4
- try:
5
- print("Importing nltk files...")
6
-
7
- # Define source and destination paths
8
- source_folder = '/home/user/app/nltk_data'
9
- destination_folder = '/home/user/nltk_data'
10
-
11
- # Ensure the destination folder exists, create if it doesn't
12
- os.makedirs(destination_folder, exist_ok=True)
13
-
14
- # Move the source folder to the destination
15
- shutil.move(source_folder, destination_folder)
16
-
17
- print(f"NLTK folder moved to {destination_folder}")
18
-
19
- except FileNotFoundError as fnf_error:
20
- print(f"Error: Source folder not found. {fnf_error}")
21
- except PermissionError as perm_error:
22
- print(f"Error: Permission denied. {perm_error}")
23
- except Exception as e:
24
- print(f"An unexpected error occurred: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebook2audiobookXTTS/trash.py DELETED
@@ -1,366 +0,0 @@
1
- print("starting...")
2
-
3
- import os
4
- import shutil
5
- import subprocess
6
- import re
7
- from pydub import AudioSegment
8
- import tempfile
9
- from tqdm import tqdm
10
- import gradio as gr
11
- import nltk
12
- import ebooklib
13
- import bs4
14
- from ebooklib import epub
15
- from bs4 import BeautifulSoup
16
- from gradio import Progress
17
- import sys
18
- from nltk.tokenize import sent_tokenize
19
- import csv
20
-
21
-
22
- # Ensure necessary models are downloaded
23
- # nltk.download('punkt')
24
-
25
- def is_folder_empty(folder_path):
26
- if os.path.exists(folder_path) and os.path.isdir(folder_path):
27
- return not os.listdir(folder_path)
28
- else:
29
- print(f"The path {folder_path} is not a valid folder.")
30
- return None
31
-
32
- def remove_folder_with_contents(folder_path):
33
- try:
34
- shutil.rmtree(folder_path)
35
- print(f"Successfully removed {folder_path} and all of its contents.")
36
- except Exception as e:
37
- print(f"Error removing {folder_path}: {e}")
38
-
39
- def wipe_folder(folder_path):
40
- if not os.path.exists(folder_path):
41
- print(f"The folder {folder_path} does not exist.")
42
- return
43
- for item in os.listdir(folder_path):
44
- item_path = os.path.join(folder_path, item)
45
- if os.path.isfile(item_path):
46
- os.remove(item_path)
47
- elif os.path.isdir(item_path):
48
- shutil.rmtree(item_path)
49
-
50
- print(f"All contents wiped from {folder_path}.")
51
-
52
- def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
53
- def sort_key(chapter_file):
54
- numbers = re.findall(r'\d+', chapter_file)
55
- return int(numbers[0]) if numbers else 0
56
-
57
- def extract_metadata_and_cover(ebook_path):
58
- try:
59
- cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
60
- subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
61
- if (os.path.exists(cover_path)):
62
- return cover_path
63
- except Exception as e:
64
- print(f"Error extracting eBook metadata or cover: {e}")
65
- return None
66
-
67
- def combine_wav_files(chapter_files, output_path):
68
- combined_audio = AudioSegment.empty()
69
-
70
- for chapter_file in chapter_files:
71
- audio_segment = AudioSegment.from_wav(chapter_file)
72
- combined_audio += audio_segment
73
- combined_audio.export(output_path, format='wav')
74
- print(f"Combined audio saved to {output_path}")
75
-
76
- def generate_ffmpeg_metadata(chapter_files, metadata_file):
77
- with open(metadata_file, 'w') as file:
78
- file.write(';FFMETADATA1\n')
79
- start_time = 0
80
- for index, chapter_file in enumerate(chapter_files):
81
- duration_ms = len(AudioSegment.from_wav(chapter_file))
82
- file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
83
- file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
84
- start_time += duration_ms
85
-
86
- def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
87
- os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
88
-
89
- ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
90
- if cover_image:
91
- ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
92
- else:
93
- ffmpeg_cmd += ['-map', '0:a']
94
-
95
- ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
96
- if cover_image:
97
- ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
98
- ffmpeg_cmd += [output_m4b]
99
-
100
- subprocess.run(ffmpeg_cmd, check=True)
101
-
102
- chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
103
- temp_dir = tempfile.gettempdir()
104
- temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
105
- metadata_file = os.path.join(temp_dir, 'metadata.txt')
106
- cover_image = extract_metadata_and_cover(ebook_file)
107
- output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
108
-
109
- combine_wav_files(chapter_files, temp_combined_wav)
110
- generate_ffmpeg_metadata(chapter_files, metadata_file)
111
- create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
112
-
113
- if os.path.exists(temp_combined_wav):
114
- os.remove(temp_combined_wav)
115
- if os.path.exists(metadata_file):
116
- os.remove(metadata_file)
117
- if cover_image and os.path.exists(cover_image):
118
- os.remove(cover_image)
119
-
120
- def create_chapter_labeled_book(ebook_file_path):
121
- def ensure_directory(directory_path):
122
- if not os.path.exists(directory_path):
123
- os.makedirs(directory_path)
124
- print(f"Created directory: {directory_path}")
125
-
126
- ensure_directory(os.path.join(".", 'Working_files', 'Book'))
127
-
128
- def convert_to_epub(input_path, output_path):
129
- try:
130
- subprocess.run(['ebook-convert', input_path, output_path], check=True)
131
- except subprocess.CalledProcessError as e:
132
- print(f"An error occurred while converting the eBook: {e}")
133
- return False
134
- return True
135
-
136
- def save_chapters_as_text(epub_path):
137
- directory = os.path.join(".", "Working_files", "temp_ebook")
138
- ensure_directory(directory)
139
-
140
- book = epub.read_epub(epub_path)
141
-
142
- previous_filename = ''
143
- chapter_counter = 0
144
-
145
- for item in book.get_items():
146
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
147
- soup = BeautifulSoup(item.get_content(), 'html.parser')
148
- text = soup.get_text()
149
-
150
- if text.strip():
151
- if len(text) < 2300 and previous_filename:
152
- with open(previous_filename, 'a', encoding='utf-8') as file:
153
- file.write('\n' + text)
154
- else:
155
- previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
156
- chapter_counter += 1
157
- with open(previous_filename, 'w', encoding='utf-8') as file:
158
- file.write(text)
159
- print(f"Saved chapter: {previous_filename}")
160
-
161
- input_ebook = ebook_file_path
162
- output_epub = os.path.join(".", "Working_files", "temp.epub")
163
-
164
- if os.path.exists(output_epub):
165
- os.remove(output_epub)
166
- print(f"File {output_epub} has been removed.")
167
- else:
168
- print(f"The file {output_epub} does not exist.")
169
-
170
- if convert_to_epub(input_ebook, output_epub):
171
- save_chapters_as_text(output_epub)
172
-
173
- # nltk.download('punkt')
174
-
175
- def process_chapter_files(folder_path, output_csv):
176
- with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
177
- writer = csv.writer(csvfile)
178
- writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
179
-
180
- chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
181
- for filename in chapter_files:
182
- if filename.startswith('chapter_') and filename.endswith('.txt'):
183
- chapter_number = int(filename.split('_')[1].split('.')[0])
184
- file_path = os.path.join(folder_path, filename)
185
-
186
- try:
187
- with open(file_path, 'r', encoding='utf-8') as file:
188
- text = file.read()
189
- if text:
190
- text = "NEWCHAPTERABC" + text
191
- sentences = nltk.tokenize.sent_tokenize(text)
192
- for sentence in sentences:
193
- start_location = text.find(sentence)
194
- end_location = start_location + len(sentence)
195
- writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
196
- except Exception as e:
197
- print(f"Error processing file {filename}: {e}")
198
-
199
- folder_path = os.path.join(".", "Working_files", "temp_ebook")
200
- output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
201
-
202
- process_chapter_files(folder_path, output_csv)
203
-
204
- def sort_key(filename):
205
- match = re.search(r'chapter_(\d+)\.txt', filename)
206
- return int(match.group(1)) if match else 0
207
-
208
- def combine_chapters(input_folder, output_file):
209
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
210
-
211
- files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
212
- sorted_files = sorted(files, key=sort_key)
213
-
214
- with open(output_file, 'w', encoding='utf-8') as outfile:
215
- for i, filename in enumerate(sorted_files):
216
- with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile:
217
- outfile.write(infile.read())
218
- if i < len(sorted_files) - 1:
219
- outfile.write("\nNEWCHAPTERABC\n")
220
-
221
- input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
222
- output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
223
-
224
- combine_chapters(input_folder, output_file)
225
-
226
- ensure_directory(os.path.join(".", "Working_files", "Book"))
227
-
228
- def convert_chapters_to_audio_espeak(chapters_dir, output_audio_dir, speed="170", pitch="50", voice="en"):
229
- if not os.path.exists(output_audio_dir):
230
- os.makedirs(output_audio_dir)
231
-
232
- for chapter_file in sorted(os.listdir(chapters_dir)):
233
- if chapter_file.endswith('.txt'):
234
- match = re.search(r"chapter_(\d+).txt", chapter_file)
235
- if match:
236
- chapter_num = int(match.group(1))
237
- else:
238
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
239
- continue
240
-
241
- chapter_path = os.path.join(chapters_dir, chapter_file)
242
- output_file_name = f"audio_chapter_{chapter_num}.wav"
243
- output_file_path = os.path.join(output_audio_dir, output_file_name)
244
-
245
- with open(chapter_path, 'r', encoding='utf-8') as file:
246
- chapter_text = file.read()
247
- sentences = nltk.tokenize.sent_tokenize(chapter_text)
248
- combined_audio = AudioSegment.empty()
249
-
250
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
251
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_wav:
252
- subprocess.run(["espeak-ng", "-v", voice, "-w", temp_wav.name, f"-s{speed}", f"-p{pitch}", sentence])
253
- combined_audio += AudioSegment.from_wav(temp_wav.name)
254
- os.remove(temp_wav.name)
255
-
256
- combined_audio.export(output_file_path, format='wav')
257
- print(f"Converted chapter {chapter_num} to audio.")
258
-
259
- def convert_ebook_to_audio(ebook_file, speed, pitch, voice, progress=gr.Progress()):
260
- ebook_file_path = ebook_file.name
261
- working_files = os.path.join(".", "Working_files", "temp_ebook")
262
- full_folder_working_files = os.path.join(".", "Working_files")
263
- chapters_directory = os.path.join(".", "Working_files", "temp_ebook")
264
- output_audio_directory = os.path.join(".", 'Chapter_wav_files')
265
- remove_folder_with_contents(full_folder_working_files)
266
- remove_folder_with_contents(output_audio_directory)
267
-
268
- try:
269
- progress(0.1, desc="Creating chapter-labeled book")
270
- except Exception as e:
271
- print(f"Error updating progress: {e}")
272
-
273
- create_chapter_labeled_book(ebook_file_path)
274
- audiobook_output_path = os.path.join(".", "Audiobooks")
275
-
276
- try:
277
- progress(0.3, desc="Converting chapters to audio")
278
- except Exception as e:
279
- print(f"Error updating progress: {e}")
280
-
281
- convert_chapters_to_audio_espeak(chapters_directory, output_audio_directory, speed, pitch, voice.split()[0])
282
-
283
- try:
284
- progress(0.9, desc="Creating M4B from chapters")
285
- except Exception as e:
286
- print(f"Error updating progress: {e}")
287
-
288
- create_m4b_from_chapters(output_audio_directory, ebook_file_path, audiobook_output_path)
289
-
290
- m4b_filename = os.path.splitext(os.path.basename(ebook_file_path))[0] + '.m4b'
291
- m4b_filepath = os.path.join(audiobook_output_path, m4b_filename)
292
-
293
- try:
294
- progress(1.0, desc="Conversion complete")
295
- except Exception as e:
296
- print(f"Error updating progress: {e}")
297
- print(f"Audiobook created at {m4b_filepath}")
298
- return f"Audiobook created at {m4b_filepath}", m4b_filepath
299
-
300
- def list_audiobook_files(audiobook_folder):
301
- files = []
302
- for filename in os.listdir(audiobook_folder):
303
- if filename.endswith('.m4b'):
304
- files.append(os.path.join(audiobook_folder, filename))
305
- return files
306
-
307
- def download_audiobooks():
308
- audiobook_output_path = os.path.join(".", "Audiobooks")
309
- return list_audiobook_files(audiobook_output_path)
310
-
311
- def get_available_voices():
312
- result = subprocess.run(['espeak-ng', '--voices'], stdout=subprocess.PIPE, text=True)
313
- lines = result.stdout.splitlines()[1:] # Skip the header line
314
- voices = []
315
- for line in lines:
316
- parts = line.split()
317
- if len(parts) > 1:
318
- voice_name = parts[1]
319
- description = ' '.join(parts[2:])
320
- voices.append((voice_name, description))
321
- return voices
322
-
323
- theme = gr.themes.Soft(
324
- primary_hue="blue",
325
- secondary_hue="blue",
326
- neutral_hue="blue",
327
- text_size=gr.themes.sizes.text_md,
328
- )
329
-
330
- # Gradio UI setup
331
- with gr.Blocks(theme=theme) as demo:
332
- gr.Markdown(
333
- """
334
- # eBook to Audiobook Converter
335
-
336
- Convert your eBooks into audiobooks using eSpeak-NG.
337
- """
338
- )
339
-
340
- with gr.Row():
341
- with gr.Column(scale=3):
342
- ebook_file = gr.File(label="eBook File")
343
- speed = gr.Slider(minimum=80, maximum=450, value=170, step=1, label="Speed")
344
- pitch = gr.Slider(minimum=0, maximum=99, value=50, step=1, label="Pitch")
345
- voices = get_available_voices()
346
- voice_choices = [f"{voice} ({desc})" for voice, desc in voices]
347
- voice_dropdown = gr.Dropdown(choices=voice_choices, label="Select Voice", value=voice_choices[0])
348
-
349
- convert_btn = gr.Button("Convert to Audiobook", variant="primary")
350
- output = gr.Textbox(label="Conversion Status")
351
- audio_player = gr.Audio(label="Audiobook Player", type="filepath")
352
- download_btn = gr.Button("Download Audiobook Files")
353
- download_files = gr.File(label="Download Files", interactive=False)
354
-
355
- convert_btn.click(
356
- convert_ebook_to_audio,
357
- inputs=[ebook_file, speed, pitch, voice_dropdown],
358
- outputs=[output, audio_player]
359
- )
360
-
361
- download_btn.click(
362
- download_audiobooks,
363
- outputs=[download_files]
364
- )
365
-
366
- demo.launch(share=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
import_all_files.py DELETED
@@ -1,5 +0,0 @@
1
- import import_nltk_files
2
-
3
- import import_locally_stored_tts_model_files
4
-
5
- #import download_tos_agreed_file
 
 
 
 
 
 
import_locally_stored_tts_model_files.py DELETED
@@ -1,23 +0,0 @@
1
- import os
2
- import shutil
3
-
4
- print("Importing locally stored coqui tts models...")
5
-
6
- # Define the source directory and the destination base path
7
- tts_folder = "/home/user/app/Base_XTTS_Model"
8
- destination_base = '/home/user/.local/share/'
9
-
10
- # Define the destination path for the tts folder
11
- destination_path = os.path.join(destination_base, 'tts')
12
-
13
- # Move the entire tts folder
14
- if os.path.exists(tts_folder):
15
- # Remove the destination folder if it exists
16
- if os.path.exists(destination_path):
17
- shutil.rmtree(destination_path) # Remove the existing folder
18
- shutil.move(tts_folder, destination_path)
19
- print(f'Moved: {tts_folder} to {destination_path}')
20
- print("Locally stored base coqui XTTS tts model imported!")
21
- print(os.listdir('/home/user/.local/share/tts'))
22
- else:
23
- print(f'Source path does not exist: {tts_folder}')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
import_nltk_files.py DELETED
@@ -1,24 +0,0 @@
1
- import shutil
2
- import os
3
-
4
- try:
5
- print("Importing nltk files...")
6
-
7
- # Define source and destination paths
8
- source_folder = '/home/user/app/nltk_data'
9
- destination_folder = '/home/user/nltk_data'
10
-
11
- # Ensure the destination folder exists, create if it doesn't
12
- os.makedirs(destination_folder, exist_ok=True)
13
-
14
- # Move the source folder to the destination
15
- shutil.move(source_folder, destination_folder)
16
-
17
- print(f"NLTK folder moved to {destination_folder}")
18
-
19
- except FileNotFoundError as fnf_error:
20
- print(f"Error: Source folder not found. {fnf_error}")
21
- except PermissionError as perm_error:
22
- print(f"Error: Permission denied. {perm_error}")
23
- except Exception as e:
24
- print(f"An unexpected error occurred: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
input_folder/test.txt DELETED
@@ -1,2 +0,0 @@
1
- this is a story about joe, joe was Joe... you could say
2
- Sometimes I go down to the river and just, idk eat sandwiches
 
 
 
mini_story_long - Drew.epub DELETED
Binary file (415 kB)
 
mini_story_long - Drew.m4b DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:83fef2cb1249fafd74f313a56733cad359328e3312dd7a5e026f68b8e244c7a2
3
- size 9666050
 
 
 
 
nltk_data/tokenizers/punkt.zip DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:51c3078994aeaf650bfc8e028be4fb42b4a0d177d41c012b6a983979653660ec
3
- size 13905355
 
 
 
 
nltk_data/tokenizers/punkt/PY3/README DELETED
@@ -1,98 +0,0 @@
1
- Pretrained Punkt Models -- Jan Strunk (New version trained after issues 313 and 514 had been corrected)
2
-
3
- Most models were prepared using the test corpora from Kiss and Strunk (2006). Additional models have
4
- been contributed by various people using NLTK for sentence boundary detection.
5
-
6
- For information about how to use these models, please confer the tokenization HOWTO:
7
- http://nltk.googlecode.com/svn/trunk/doc/howto/tokenize.html
8
- and chapter 3.8 of the NLTK book:
9
- http://nltk.googlecode.com/svn/trunk/doc/book/ch03.html#sec-segmentation
10
-
11
- There are pretrained tokenizers for the following languages:
12
-
13
- File Language Source Contents Size of training corpus(in tokens) Model contributed by
14
- =======================================================================================================================================================================
15
- czech.pickle Czech Multilingual Corpus 1 (ECI) Lidove Noviny ~345,000 Jan Strunk / Tibor Kiss
16
- Literarni Noviny
17
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
18
- danish.pickle Danish Avisdata CD-Rom Ver. 1.1. 1995 Berlingske Tidende ~550,000 Jan Strunk / Tibor Kiss
19
- (Berlingske Avisdata, Copenhagen) Weekend Avisen
20
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
21
- dutch.pickle Dutch Multilingual Corpus 1 (ECI) De Limburger ~340,000 Jan Strunk / Tibor Kiss
22
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
23
- english.pickle English Penn Treebank (LDC) Wall Street Journal ~469,000 Jan Strunk / Tibor Kiss
24
- (American)
25
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
26
- estonian.pickle Estonian University of Tartu, Estonia Eesti Ekspress ~359,000 Jan Strunk / Tibor Kiss
27
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
28
- finnish.pickle Finnish Finnish Parole Corpus, Finnish Books and major national ~364,000 Jan Strunk / Tibor Kiss
29
- Text Bank (Suomen Kielen newspapers
30
- Tekstipankki)
31
- Finnish Center for IT Science
32
- (CSC)
33
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
34
- french.pickle French Multilingual Corpus 1 (ECI) Le Monde ~370,000 Jan Strunk / Tibor Kiss
35
- (European)
36
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
37
- german.pickle German Neue Zürcher Zeitung AG Neue Zürcher Zeitung ~847,000 Jan Strunk / Tibor Kiss
38
- (Switzerland) CD-ROM
39
- (Uses "ss"
40
- instead of "ß")
41
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
42
- greek.pickle Greek Efstathios Stamatatos To Vima (TO BHMA) ~227,000 Jan Strunk / Tibor Kiss
43
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
44
- italian.pickle Italian Multilingual Corpus 1 (ECI) La Stampa, Il Mattino ~312,000 Jan Strunk / Tibor Kiss
45
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
46
- norwegian.pickle Norwegian Centre for Humanities Bergens Tidende ~479,000 Jan Strunk / Tibor Kiss
47
- (Bokmål and Information Technologies,
48
- Nynorsk) Bergen
49
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
50
- polish.pickle Polish Polish National Corpus Literature, newspapers, etc. ~1,000,000 Krzysztof Langner
51
- (http://www.nkjp.pl/)
52
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
53
- portuguese.pickle Portuguese CETENFolha Corpus Folha de São Paulo ~321,000 Jan Strunk / Tibor Kiss
54
- (Brazilian) (Linguateca)
55
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
56
- slovene.pickle Slovene TRACTOR Delo ~354,000 Jan Strunk / Tibor Kiss
57
- Slovene Academy for Arts
58
- and Sciences
59
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
60
- spanish.pickle Spanish Multilingual Corpus 1 (ECI) Sur ~353,000 Jan Strunk / Tibor Kiss
61
- (European)
62
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
63
- swedish.pickle Swedish Multilingual Corpus 1 (ECI) Dagens Nyheter ~339,000 Jan Strunk / Tibor Kiss
64
- (and some other texts)
65
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
66
- turkish.pickle Turkish METU Turkish Corpus Milliyet ~333,000 Jan Strunk / Tibor Kiss
67
- (Türkçe Derlem Projesi)
68
- University of Ankara
69
- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
70
-
71
- The corpora contained about 400,000 tokens on average and mostly consisted of newspaper text converted to
72
- Unicode using the codecs module.
73
-
74
- Kiss, Tibor and Strunk, Jan (2006): Unsupervised Multilingual Sentence Boundary Detection.
75
- Computational Linguistics 32: 485-525.
76
-
77
- ---- Training Code ----
78
-
79
- # import punkt
80
- import nltk.tokenize.punkt
81
-
82
- # Make a new Tokenizer
83
- tokenizer = nltk.tokenize.punkt.PunktSentenceTokenizer()
84
-
85
- # Read in training corpus (one example: Slovene)
86
- import codecs
87
- text = codecs.open("slovene.plain","Ur","iso-8859-2").read()
88
-
89
- # Train tokenizer
90
- tokenizer.train(text)
91
-
92
- # Dump pickled tokenizer
93
- import pickle
94
- out = open("slovene.pickle","wb")
95
- pickle.dump(tokenizer, out)
96
- out.close()
97
-
98
- ---------