drewThomasson commited on
Commit
33a5446
1 Parent(s): 67a6da3

Update ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS.py

Browse files
ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS.py CHANGED
@@ -1,484 +1,486 @@
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
- nltk.download('punkt') # Make sure to download the necessary models
21
- def is_folder_empty(folder_path):
22
- if os.path.exists(folder_path) and os.path.isdir(folder_path):
23
- # List directory contents
24
- if not os.listdir(folder_path):
25
- return True # The folder is empty
26
- else:
27
- return False # The folder is not empty
28
- else:
29
- print(f"The path {folder_path} is not a valid folder.")
30
- return None # The path is not a valid folder
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
-
40
-
41
-
42
- def wipe_folder(folder_path):
43
- # Check if the folder exists
44
- if not os.path.exists(folder_path):
45
- print(f"The folder {folder_path} does not exist.")
46
- return
47
-
48
- # Iterate over all the items in the given folder
49
- for item in os.listdir(folder_path):
50
- item_path = os.path.join(folder_path, item)
51
- # If it's a file, remove it and print a message
52
- if os.path.isfile(item_path):
53
- os.remove(item_path)
54
- print(f"Removed file: {item_path}")
55
- # If it's a directory, remove it recursively and print a message
56
- elif os.path.isdir(item_path):
57
- shutil.rmtree(item_path)
58
- print(f"Removed directory and its contents: {item_path}")
59
-
60
- print(f"All contents wiped from {folder_path}.")
61
-
62
-
63
- # Example usage
64
- # folder_to_wipe = 'path_to_your_folder'
65
- # wipe_folder(folder_to_wipe)
66
-
67
-
68
- def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
69
- # Function to sort chapters based on their numeric order
70
- def sort_key(chapter_file):
71
- numbers = re.findall(r'\d+', chapter_file)
72
- return int(numbers[0]) if numbers else 0
73
-
74
- # Extract metadata and cover image from the eBook file
75
- def extract_metadata_and_cover(ebook_path):
76
- try:
77
- cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
78
- subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
79
- if os.path.exists(cover_path):
80
- return cover_path
81
- except Exception as e:
82
- print(f"Error extracting eBook metadata or cover: {e}")
83
- return None
84
- # Combine WAV files into a single file
85
- def combine_wav_files(chapter_files, output_path):
86
- # Initialize an empty audio segment
87
- combined_audio = AudioSegment.empty()
88
-
89
- # Sequentially append each file to the combined_audio
90
- for chapter_file in chapter_files:
91
- audio_segment = AudioSegment.from_wav(chapter_file)
92
- combined_audio += audio_segment
93
- # Export the combined audio to the output file path
94
- combined_audio.export(output_path, format='wav')
95
- print(f"Combined audio saved to {output_path}")
96
-
97
- # Function to generate metadata for M4B chapters
98
- def generate_ffmpeg_metadata(chapter_files, metadata_file):
99
- with open(metadata_file, 'w') as file:
100
- file.write(';FFMETADATA1\n')
101
- start_time = 0
102
- for index, chapter_file in enumerate(chapter_files):
103
- duration_ms = len(AudioSegment.from_wav(chapter_file))
104
- file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
105
- file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
106
- start_time += duration_ms
107
-
108
- # Generate the final M4B file using ffmpeg
109
- def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
110
- # Ensure the output directory exists
111
- os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
112
-
113
- ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
114
- if cover_image:
115
- ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
116
- else:
117
- ffmpeg_cmd += ['-map', '0:a']
118
-
119
- ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
120
- if cover_image:
121
- ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
122
- ffmpeg_cmd += [output_m4b]
123
-
124
- subprocess.run(ffmpeg_cmd, check=True)
125
-
126
-
127
-
128
- # Main logic
129
- chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
130
- temp_dir = tempfile.gettempdir()
131
- temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
132
- metadata_file = os.path.join(temp_dir, 'metadata.txt')
133
- cover_image = extract_metadata_and_cover(ebook_file)
134
- output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
135
-
136
- combine_wav_files(chapter_files, temp_combined_wav)
137
- generate_ffmpeg_metadata(chapter_files, metadata_file)
138
- create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
139
-
140
- # Cleanup
141
- if os.path.exists(temp_combined_wav):
142
- os.remove(temp_combined_wav)
143
- if os.path.exists(metadata_file):
144
- os.remove(metadata_file)
145
- if cover_image and os.path.exists(cover_image):
146
- os.remove(cover_image)
147
-
148
- # Example usage
149
- # create_m4b_from_chapters('path_to_chapter_wavs', 'path_to_ebook_file', 'path_to_output_dir')
150
-
151
-
152
-
153
-
154
-
155
-
156
- #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
157
- import os
158
- import subprocess
159
- import ebooklib
160
- from ebooklib import epub
161
- from bs4 import BeautifulSoup
162
- import re
163
- import csv
164
- import nltk
165
-
166
- # Only run the main script if Value is True
167
- def create_chapter_labeled_book(ebook_file_path):
168
- # Function to ensure the existence of a directory
169
- def ensure_directory(directory_path):
170
- if not os.path.exists(directory_path):
171
- os.makedirs(directory_path)
172
- print(f"Created directory: {directory_path}")
173
-
174
- ensure_directory(os.path.join(".", 'Working_files', 'Book'))
175
-
176
- def convert_to_epub(input_path, output_path):
177
- # Convert the ebook to EPUB format using Calibre's ebook-convert
178
- try:
179
- subprocess.run(['ebook-convert', input_path, output_path], check=True)
180
- except subprocess.CalledProcessError as e:
181
- print(f"An error occurred while converting the eBook: {e}")
182
- return False
183
- return True
184
-
185
- def save_chapters_as_text(epub_path):
186
- # Create the directory if it doesn't exist
187
- directory = os.path.join(".", "Working_files", "temp_ebook")
188
- ensure_directory(directory)
189
-
190
- # Open the EPUB file
191
- book = epub.read_epub(epub_path)
192
-
193
- previous_chapter_text = ''
194
- previous_filename = ''
195
- chapter_counter = 0
196
-
197
- # Iterate through the items in the EPUB file
198
- for item in book.get_items():
199
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
200
- # Use BeautifulSoup to parse HTML content
201
- soup = BeautifulSoup(item.get_content(), 'html.parser')
202
- text = soup.get_text()
203
-
204
- # Check if the text is not empty
205
- if text.strip():
206
- if len(text) < 2300 and previous_filename:
207
- # Append text to the previous chapter if it's short
208
- with open(previous_filename, 'a', encoding='utf-8') as file:
209
- file.write('\n' + text)
210
- else:
211
- # Create a new chapter file and increment the counter
212
- previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
213
- chapter_counter += 1
214
- with open(previous_filename, 'w', encoding='utf-8') as file:
215
- file.write(text)
216
- print(f"Saved chapter: {previous_filename}")
217
-
218
- # Example usage
219
- input_ebook = ebook_file_path # Replace with your eBook file path
220
- output_epub = os.path.join(".", "Working_files", "temp.epub")
221
-
222
-
223
- if os.path.exists(output_epub):
224
- os.remove(output_epub)
225
- print(f"File {output_epub} has been removed.")
226
- else:
227
- print(f"The file {output_epub} does not exist.")
228
-
229
- if convert_to_epub(input_ebook, output_epub):
230
- save_chapters_as_text(output_epub)
231
-
232
- # Download the necessary NLTK data (if not already present)
233
- nltk.download('punkt')
234
-
235
- def process_chapter_files(folder_path, output_csv):
236
- with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
237
- writer = csv.writer(csvfile)
238
- # Write the header row
239
- writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
240
-
241
- # Process each chapter file
242
- chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
243
- for filename in chapter_files:
244
- if filename.startswith('chapter_') and filename.endswith('.txt'):
245
- chapter_number = int(filename.split('_')[1].split('.')[0])
246
- file_path = os.path.join(folder_path, filename)
247
-
248
- try:
249
- with open(file_path, 'r', encoding='utf-8') as file:
250
- text = file.read()
251
- # Insert "NEWCHAPTERABC" at the beginning of each chapter's text
252
- if text:
253
- text = "NEWCHAPTERABC" + text
254
- sentences = nltk.tokenize.sent_tokenize(text)
255
- for sentence in sentences:
256
- start_location = text.find(sentence)
257
- end_location = start_location + len(sentence)
258
- writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
259
- except Exception as e:
260
- print(f"Error processing file {filename}: {e}")
261
-
262
- # Example usage
263
- folder_path = os.path.join(".", "Working_files", "temp_ebook")
264
- output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
265
-
266
- process_chapter_files(folder_path, output_csv)
267
-
268
- def sort_key(filename):
269
- """Extract chapter number for sorting."""
270
- match = re.search(r'chapter_(\d+)\.txt', filename)
271
- return int(match.group(1)) if match else 0
272
-
273
- def combine_chapters(input_folder, output_file):
274
- # Create the output folder if it doesn't exist
275
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
276
-
277
- # List all txt files and sort them by chapter number
278
- files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
279
- sorted_files = sorted(files, key=sort_key)
280
-
281
- with open(output_file, 'w', encoding='utf-8') as outfile: # Specify UTF-8 encoding here
282
- for i, filename in enumerate(sorted_files):
283
- with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile: # And here
284
- outfile.write(infile.read())
285
- # Add the marker unless it's the last file
286
- if i < len(sorted_files) - 1:
287
- outfile.write("\nNEWCHAPTERABC\n")
288
-
289
- # Paths
290
- input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
291
- output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
292
-
293
-
294
- # Combine the chapters
295
- combine_chapters(input_folder, output_file)
296
-
297
- ensure_directory(os.path.join(".", "Working_files", "Book"))
298
-
299
-
300
- #create_chapter_labeled_book()
301
-
302
-
303
-
304
-
305
- import os
306
- import subprocess
307
- import sys
308
- import torchaudio
309
-
310
- # Check if Calibre's ebook-convert tool is installed
311
- def calibre_installed():
312
- try:
313
- subprocess.run(['ebook-convert', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
314
- return True
315
- except FileNotFoundError:
316
- print("Calibre is not installed. Please install Calibre for this functionality.")
317
- return False
318
-
319
-
320
- import os
321
- import torch
322
- from TTS.api import TTS
323
- from nltk.tokenize import sent_tokenize
324
- from pydub import AudioSegment
325
- # Assuming split_long_sentence and wipe_folder are defined elsewhere in your code
326
-
327
- default_target_voice_path = "default_voice.wav" # Ensure this is a valid path
328
- default_language_code = "en"
329
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
330
-
331
- def combine_wav_files(input_directory, output_directory, file_name):
332
- # Ensure that the output directory exists, create it if necessary
333
- os.makedirs(output_directory, exist_ok=True)
334
-
335
- # Specify the output file path
336
- output_file_path = os.path.join(output_directory, file_name)
337
-
338
- # Initialize an empty audio segment
339
- combined_audio = AudioSegment.empty()
340
-
341
- # Get a list of all .wav files in the specified input directory and sort them
342
- input_file_paths = sorted(
343
- [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith(".wav")],
344
- key=lambda f: int(''.join(filter(str.isdigit, f)))
345
- )
346
-
347
- # Sequentially append each file to the combined_audio
348
- for input_file_path in input_file_paths:
349
- audio_segment = AudioSegment.from_wav(input_file_path)
350
- combined_audio += audio_segment
351
-
352
- # Export the combined audio to the output file path
353
- combined_audio.export(output_file_path, format='wav')
354
-
355
- print(f"Combined audio saved to {output_file_path}")
356
-
357
- # Function to split long strings into parts
358
- def split_long_sentence(sentence, max_length=249, max_pauses=10):
359
- """
360
- Splits a sentence into parts based on length or number of pauses without recursion.
361
-
362
- :param sentence: The sentence to split.
363
- :param max_length: Maximum allowed length of a sentence.
364
- :param max_pauses: Maximum allowed number of pauses in a sentence.
365
- :return: A list of sentence parts that meet the criteria.
366
- """
367
- parts = []
368
- while len(sentence) > max_length or sentence.count(',') + sentence.count(';') + sentence.count('.') > max_pauses:
369
- possible_splits = [i for i, char in enumerate(sentence) if char in ',;.' and i < max_length]
370
- if possible_splits:
371
- # Find the best place to split the sentence, preferring the last possible split to keep parts longer
372
- split_at = possible_splits[-1] + 1
373
- else:
374
- # If no punctuation to split on within max_length, split at max_length
375
- split_at = max_length
376
-
377
- # Split the sentence and add the first part to the list
378
- parts.append(sentence[:split_at].strip())
379
- sentence = sentence[split_at:].strip()
380
-
381
- # Add the remaining part of the sentence
382
- parts.append(sentence)
383
- return parts
384
-
385
- """
386
- if 'tts' not in locals():
387
- tts = TTS(selected_tts_model, progress_bar=True).to(device)
388
- """
389
- from tqdm import tqdm
390
-
391
- # Convert chapters to audio using XTTS
392
- def convert_chapters_to_audio(chapters_dir, output_audio_dir, target_voice_path=None, language=None, custom_model=None):
393
- if custom_model:
394
- print("Loading custom model...")
395
- config = XttsConfig()
396
- config.load_json(custom_model['config'])
397
- model = Xtts.init_from_config(config)
398
- model.load_checkpoint(config, checkpoint_path=custom_model['model'], vocab_path=custom_model['vocab'], use_deepspeed=False)
399
- model.to(device)
400
- print("Computing speaker latents...")
401
- gpt_cond_latent, speaker_embedding = model.get_conditioning_latents(audio_path=[target_voice_path])
402
- else:
403
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
404
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
405
-
406
- if not os.path.exists(output_audio_dir):
407
- os.makedirs(output_audio_dir)
408
-
409
- for chapter_file in sorted(os.listdir(chapters_dir)):
410
- if chapter_file.endswith('.txt'):
411
- match = re.search(r"chapter_(\d+).txt", chapter_file)
412
- if match:
413
- chapter_num = int(match.group(1))
414
- else:
415
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
416
- continue
417
-
418
- chapter_path = os.path.join(chapters_dir, chapter_file)
419
- output_file_name = f"audio_chapter_{chapter_num}.wav"
420
- output_file_path = os.path.join(output_audio_dir, output_file_name)
421
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
422
- os.makedirs(temp_audio_directory, exist_ok=True)
423
- temp_count = 0
424
-
425
- with open(chapter_path, 'r', encoding='utf-8') as file:
426
- chapter_text = file.read()
427
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
428
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
429
- fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
430
- for fragment in fragments:
431
- if fragment != "":
432
- print(f"Generating fragment: {fragment}...")
433
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
434
- if custom_model:
435
- out = model.inference(fragment, language, gpt_cond_latent, speaker_embedding, temperature=0.7)
436
- torchaudio.save(fragment_file_path, torch.tensor(out["wav"]).unsqueeze(0), 24000)
437
- else:
438
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
439
- language_code = language if language else default_language_code
440
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
441
- temp_count += 1
442
-
443
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
444
- wipe_folder(temp_audio_directory)
445
- print(f"Converted chapter {chapter_num} to audio.")
446
-
447
-
448
- # Main execution flow
449
- if __name__ == "__main__":
450
- if len(sys.argv) < 2:
451
- print("Usage: python script.py <ebook_file_path> [target_voice_file_path] [language] [custom_model_path] [custom_config_path] [custom_vocab_path]")
452
- sys.exit(1)
453
-
454
- ebook_file_path = sys.argv[1]
455
- target_voice = sys.argv[2] if len(sys.argv) > 2 else None
456
- language = sys.argv[3] if len(sys.argv) > 3 else None
457
-
458
- custom_model = None
459
- if len(sys.argv) > 6:
460
- custom_model = {
461
- 'model': sys.argv[4],
462
- 'config': sys.argv[5],
463
- 'vocab': sys.argv[6]
464
- }
465
-
466
- if not calibre_installed():
467
- sys.exit(1)
468
-
469
- working_files = os.path.join(".", "Working_files", "temp_ebook")
470
- full_folder_working_files = os.path.join(".", "Working_files")
471
- chapters_directory = os.path.join(".", "Working_files", "temp_ebook")
472
- output_audio_directory = os.path.join(".", 'Chapter_wav_files')
473
-
474
- print("Wiping and removing Working_files folder...")
475
- remove_folder_with_contents(full_folder_working_files)
476
-
477
- print("Wiping and removing chapter_wav_files folder...")
478
- remove_folder_with_contents(output_audio_directory)
479
-
480
- create_chapter_labeled_book(ebook_file_path)
481
- audiobook_output_path = os.path.join(".", "Audiobooks")
482
- print(f"{chapters_directory}||||{output_audio_directory}|||||{target_voice}")
483
- convert_chapters_to_audio(chapters_directory, output_audio_directory, target_voice, language, custom_model)
484
- create_m4b_from_chapters(output_audio_directory, ebook_file_path, audiobook_output_path)
 
 
 
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
+ from .. import import_all_files
21
+
22
+ #nltk.download('punkt') # Make sure to download the necessary models
23
+ def is_folder_empty(folder_path):
24
+ if os.path.exists(folder_path) and os.path.isdir(folder_path):
25
+ # List directory contents
26
+ if not os.listdir(folder_path):
27
+ return True # The folder is empty
28
+ else:
29
+ return False # The folder is not empty
30
+ else:
31
+ print(f"The path {folder_path} is not a valid folder.")
32
+ return None # The path is not a valid folder
33
+
34
+ def remove_folder_with_contents(folder_path):
35
+ try:
36
+ shutil.rmtree(folder_path)
37
+ print(f"Successfully removed {folder_path} and all of its contents.")
38
+ except Exception as e:
39
+ print(f"Error removing {folder_path}: {e}")
40
+
41
+
42
+
43
+
44
+ def wipe_folder(folder_path):
45
+ # Check if the folder exists
46
+ if not os.path.exists(folder_path):
47
+ print(f"The folder {folder_path} does not exist.")
48
+ return
49
+
50
+ # Iterate over all the items in the given folder
51
+ for item in os.listdir(folder_path):
52
+ item_path = os.path.join(folder_path, item)
53
+ # If it's a file, remove it and print a message
54
+ if os.path.isfile(item_path):
55
+ os.remove(item_path)
56
+ print(f"Removed file: {item_path}")
57
+ # If it's a directory, remove it recursively and print a message
58
+ elif os.path.isdir(item_path):
59
+ shutil.rmtree(item_path)
60
+ print(f"Removed directory and its contents: {item_path}")
61
+
62
+ print(f"All contents wiped from {folder_path}.")
63
+
64
+
65
+ # Example usage
66
+ # folder_to_wipe = 'path_to_your_folder'
67
+ # wipe_folder(folder_to_wipe)
68
+
69
+
70
+ def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
71
+ # Function to sort chapters based on their numeric order
72
+ def sort_key(chapter_file):
73
+ numbers = re.findall(r'\d+', chapter_file)
74
+ return int(numbers[0]) if numbers else 0
75
+
76
+ # Extract metadata and cover image from the eBook file
77
+ def extract_metadata_and_cover(ebook_path):
78
+ try:
79
+ cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
80
+ subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
81
+ if os.path.exists(cover_path):
82
+ return cover_path
83
+ except Exception as e:
84
+ print(f"Error extracting eBook metadata or cover: {e}")
85
+ return None
86
+ # Combine WAV files into a single file
87
+ def combine_wav_files(chapter_files, output_path):
88
+ # Initialize an empty audio segment
89
+ combined_audio = AudioSegment.empty()
90
+
91
+ # Sequentially append each file to the combined_audio
92
+ for chapter_file in chapter_files:
93
+ audio_segment = AudioSegment.from_wav(chapter_file)
94
+ combined_audio += audio_segment
95
+ # Export the combined audio to the output file path
96
+ combined_audio.export(output_path, format='wav')
97
+ print(f"Combined audio saved to {output_path}")
98
+
99
+ # Function to generate metadata for M4B chapters
100
+ def generate_ffmpeg_metadata(chapter_files, metadata_file):
101
+ with open(metadata_file, 'w') as file:
102
+ file.write(';FFMETADATA1\n')
103
+ start_time = 0
104
+ for index, chapter_file in enumerate(chapter_files):
105
+ duration_ms = len(AudioSegment.from_wav(chapter_file))
106
+ file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
107
+ file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
108
+ start_time += duration_ms
109
+
110
+ # Generate the final M4B file using ffmpeg
111
+ def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
112
+ # Ensure the output directory exists
113
+ os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
114
+
115
+ ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
116
+ if cover_image:
117
+ ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
118
+ else:
119
+ ffmpeg_cmd += ['-map', '0:a']
120
+
121
+ ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
122
+ if cover_image:
123
+ ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
124
+ ffmpeg_cmd += [output_m4b]
125
+
126
+ subprocess.run(ffmpeg_cmd, check=True)
127
+
128
+
129
+
130
+ # Main logic
131
+ chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
132
+ temp_dir = tempfile.gettempdir()
133
+ temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
134
+ metadata_file = os.path.join(temp_dir, 'metadata.txt')
135
+ cover_image = extract_metadata_and_cover(ebook_file)
136
+ output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
137
+
138
+ combine_wav_files(chapter_files, temp_combined_wav)
139
+ generate_ffmpeg_metadata(chapter_files, metadata_file)
140
+ create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
141
+
142
+ # Cleanup
143
+ if os.path.exists(temp_combined_wav):
144
+ os.remove(temp_combined_wav)
145
+ if os.path.exists(metadata_file):
146
+ os.remove(metadata_file)
147
+ if cover_image and os.path.exists(cover_image):
148
+ os.remove(cover_image)
149
+
150
+ # Example usage
151
+ # create_m4b_from_chapters('path_to_chapter_wavs', 'path_to_ebook_file', 'path_to_output_dir')
152
+
153
+
154
+
155
+
156
+
157
+
158
+ #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
159
+ import os
160
+ import subprocess
161
+ import ebooklib
162
+ from ebooklib import epub
163
+ from bs4 import BeautifulSoup
164
+ import re
165
+ import csv
166
+ import nltk
167
+
168
+ # Only run the main script if Value is True
169
+ def create_chapter_labeled_book(ebook_file_path):
170
+ # Function to ensure the existence of a directory
171
+ def ensure_directory(directory_path):
172
+ if not os.path.exists(directory_path):
173
+ os.makedirs(directory_path)
174
+ print(f"Created directory: {directory_path}")
175
+
176
+ ensure_directory(os.path.join(".", 'Working_files', 'Book'))
177
+
178
+ def convert_to_epub(input_path, output_path):
179
+ # Convert the ebook to EPUB format using Calibre's ebook-convert
180
+ try:
181
+ subprocess.run(['ebook-convert', input_path, output_path], check=True)
182
+ except subprocess.CalledProcessError as e:
183
+ print(f"An error occurred while converting the eBook: {e}")
184
+ return False
185
+ return True
186
+
187
+ def save_chapters_as_text(epub_path):
188
+ # Create the directory if it doesn't exist
189
+ directory = os.path.join(".", "Working_files", "temp_ebook")
190
+ ensure_directory(directory)
191
+
192
+ # Open the EPUB file
193
+ book = epub.read_epub(epub_path)
194
+
195
+ previous_chapter_text = ''
196
+ previous_filename = ''
197
+ chapter_counter = 0
198
+
199
+ # Iterate through the items in the EPUB file
200
+ for item in book.get_items():
201
+ if item.get_type() == ebooklib.ITEM_DOCUMENT:
202
+ # Use BeautifulSoup to parse HTML content
203
+ soup = BeautifulSoup(item.get_content(), 'html.parser')
204
+ text = soup.get_text()
205
+
206
+ # Check if the text is not empty
207
+ if text.strip():
208
+ if len(text) < 2300 and previous_filename:
209
+ # Append text to the previous chapter if it's short
210
+ with open(previous_filename, 'a', encoding='utf-8') as file:
211
+ file.write('\n' + text)
212
+ else:
213
+ # Create a new chapter file and increment the counter
214
+ previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
215
+ chapter_counter += 1
216
+ with open(previous_filename, 'w', encoding='utf-8') as file:
217
+ file.write(text)
218
+ print(f"Saved chapter: {previous_filename}")
219
+
220
+ # Example usage
221
+ input_ebook = ebook_file_path # Replace with your eBook file path
222
+ output_epub = os.path.join(".", "Working_files", "temp.epub")
223
+
224
+
225
+ if os.path.exists(output_epub):
226
+ os.remove(output_epub)
227
+ print(f"File {output_epub} has been removed.")
228
+ else:
229
+ print(f"The file {output_epub} does not exist.")
230
+
231
+ if convert_to_epub(input_ebook, output_epub):
232
+ save_chapters_as_text(output_epub)
233
+
234
+ # Download the necessary NLTK data (if not already present)
235
+ #nltk.download('punkt')
236
+
237
+ def process_chapter_files(folder_path, output_csv):
238
+ with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
239
+ writer = csv.writer(csvfile)
240
+ # Write the header row
241
+ writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
242
+
243
+ # Process each chapter file
244
+ chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
245
+ for filename in chapter_files:
246
+ if filename.startswith('chapter_') and filename.endswith('.txt'):
247
+ chapter_number = int(filename.split('_')[1].split('.')[0])
248
+ file_path = os.path.join(folder_path, filename)
249
+
250
+ try:
251
+ with open(file_path, 'r', encoding='utf-8') as file:
252
+ text = file.read()
253
+ # Insert "NEWCHAPTERABC" at the beginning of each chapter's text
254
+ if text:
255
+ text = "NEWCHAPTERABC" + text
256
+ sentences = nltk.tokenize.sent_tokenize(text)
257
+ for sentence in sentences:
258
+ start_location = text.find(sentence)
259
+ end_location = start_location + len(sentence)
260
+ writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
261
+ except Exception as e:
262
+ print(f"Error processing file {filename}: {e}")
263
+
264
+ # Example usage
265
+ folder_path = os.path.join(".", "Working_files", "temp_ebook")
266
+ output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
267
+
268
+ process_chapter_files(folder_path, output_csv)
269
+
270
+ def sort_key(filename):
271
+ """Extract chapter number for sorting."""
272
+ match = re.search(r'chapter_(\d+)\.txt', filename)
273
+ return int(match.group(1)) if match else 0
274
+
275
+ def combine_chapters(input_folder, output_file):
276
+ # Create the output folder if it doesn't exist
277
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
278
+
279
+ # List all txt files and sort them by chapter number
280
+ files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
281
+ sorted_files = sorted(files, key=sort_key)
282
+
283
+ with open(output_file, 'w', encoding='utf-8') as outfile: # Specify UTF-8 encoding here
284
+ for i, filename in enumerate(sorted_files):
285
+ with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile: # And here
286
+ outfile.write(infile.read())
287
+ # Add the marker unless it's the last file
288
+ if i < len(sorted_files) - 1:
289
+ outfile.write("\nNEWCHAPTERABC\n")
290
+
291
+ # Paths
292
+ input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
293
+ output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
294
+
295
+
296
+ # Combine the chapters
297
+ combine_chapters(input_folder, output_file)
298
+
299
+ ensure_directory(os.path.join(".", "Working_files", "Book"))
300
+
301
+
302
+ #create_chapter_labeled_book()
303
+
304
+
305
+
306
+
307
+ import os
308
+ import subprocess
309
+ import sys
310
+ import torchaudio
311
+
312
+ # Check if Calibre's ebook-convert tool is installed
313
+ def calibre_installed():
314
+ try:
315
+ subprocess.run(['ebook-convert', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
316
+ return True
317
+ except FileNotFoundError:
318
+ print("Calibre is not installed. Please install Calibre for this functionality.")
319
+ return False
320
+
321
+
322
+ import os
323
+ import torch
324
+ from TTS.api import TTS
325
+ from nltk.tokenize import sent_tokenize
326
+ from pydub import AudioSegment
327
+ # Assuming split_long_sentence and wipe_folder are defined elsewhere in your code
328
+
329
+ default_target_voice_path = "default_voice.wav" # Ensure this is a valid path
330
+ default_language_code = "en"
331
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
332
+
333
+ def combine_wav_files(input_directory, output_directory, file_name):
334
+ # Ensure that the output directory exists, create it if necessary
335
+ os.makedirs(output_directory, exist_ok=True)
336
+
337
+ # Specify the output file path
338
+ output_file_path = os.path.join(output_directory, file_name)
339
+
340
+ # Initialize an empty audio segment
341
+ combined_audio = AudioSegment.empty()
342
+
343
+ # Get a list of all .wav files in the specified input directory and sort them
344
+ input_file_paths = sorted(
345
+ [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith(".wav")],
346
+ key=lambda f: int(''.join(filter(str.isdigit, f)))
347
+ )
348
+
349
+ # Sequentially append each file to the combined_audio
350
+ for input_file_path in input_file_paths:
351
+ audio_segment = AudioSegment.from_wav(input_file_path)
352
+ combined_audio += audio_segment
353
+
354
+ # Export the combined audio to the output file path
355
+ combined_audio.export(output_file_path, format='wav')
356
+
357
+ print(f"Combined audio saved to {output_file_path}")
358
+
359
+ # Function to split long strings into parts
360
+ def split_long_sentence(sentence, max_length=249, max_pauses=10):
361
+ """
362
+ Splits a sentence into parts based on length or number of pauses without recursion.
363
+
364
+ :param sentence: The sentence to split.
365
+ :param max_length: Maximum allowed length of a sentence.
366
+ :param max_pauses: Maximum allowed number of pauses in a sentence.
367
+ :return: A list of sentence parts that meet the criteria.
368
+ """
369
+ parts = []
370
+ while len(sentence) > max_length or sentence.count(',') + sentence.count(';') + sentence.count('.') > max_pauses:
371
+ possible_splits = [i for i, char in enumerate(sentence) if char in ',;.' and i < max_length]
372
+ if possible_splits:
373
+ # Find the best place to split the sentence, preferring the last possible split to keep parts longer
374
+ split_at = possible_splits[-1] + 1
375
+ else:
376
+ # If no punctuation to split on within max_length, split at max_length
377
+ split_at = max_length
378
+
379
+ # Split the sentence and add the first part to the list
380
+ parts.append(sentence[:split_at].strip())
381
+ sentence = sentence[split_at:].strip()
382
+
383
+ # Add the remaining part of the sentence
384
+ parts.append(sentence)
385
+ return parts
386
+
387
+ """
388
+ if 'tts' not in locals():
389
+ tts = TTS(selected_tts_model, progress_bar=True).to(device)
390
+ """
391
+ from tqdm import tqdm
392
+
393
+ # Convert chapters to audio using XTTS
394
+ def convert_chapters_to_audio(chapters_dir, output_audio_dir, target_voice_path=None, language=None, custom_model=None):
395
+ if custom_model:
396
+ print("Loading custom model...")
397
+ config = XttsConfig()
398
+ config.load_json(custom_model['config'])
399
+ model = Xtts.init_from_config(config)
400
+ model.load_checkpoint(config, checkpoint_path=custom_model['model'], vocab_path=custom_model['vocab'], use_deepspeed=False)
401
+ model.to(device)
402
+ print("Computing speaker latents...")
403
+ gpt_cond_latent, speaker_embedding = model.get_conditioning_latents(audio_path=[target_voice_path])
404
+ else:
405
+ selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
406
+ tts = TTS(selected_tts_model, progress_bar=False).to(device)
407
+
408
+ if not os.path.exists(output_audio_dir):
409
+ os.makedirs(output_audio_dir)
410
+
411
+ for chapter_file in sorted(os.listdir(chapters_dir)):
412
+ if chapter_file.endswith('.txt'):
413
+ match = re.search(r"chapter_(\d+).txt", chapter_file)
414
+ if match:
415
+ chapter_num = int(match.group(1))
416
+ else:
417
+ print(f"Skipping file {chapter_file} as it does not match the expected format.")
418
+ continue
419
+
420
+ chapter_path = os.path.join(chapters_dir, chapter_file)
421
+ output_file_name = f"audio_chapter_{chapter_num}.wav"
422
+ output_file_path = os.path.join(output_audio_dir, output_file_name)
423
+ temp_audio_directory = os.path.join(".", "Working_files", "temp")
424
+ os.makedirs(temp_audio_directory, exist_ok=True)
425
+ temp_count = 0
426
+
427
+ with open(chapter_path, 'r', encoding='utf-8') as file:
428
+ chapter_text = file.read()
429
+ sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
430
+ for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
431
+ fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
432
+ for fragment in fragments:
433
+ if fragment != "":
434
+ print(f"Generating fragment: {fragment}...")
435
+ fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
436
+ if custom_model:
437
+ out = model.inference(fragment, language, gpt_cond_latent, speaker_embedding, temperature=0.7)
438
+ torchaudio.save(fragment_file_path, torch.tensor(out["wav"]).unsqueeze(0), 24000)
439
+ else:
440
+ speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
441
+ language_code = language if language else default_language_code
442
+ tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
443
+ temp_count += 1
444
+
445
+ combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
446
+ wipe_folder(temp_audio_directory)
447
+ print(f"Converted chapter {chapter_num} to audio.")
448
+
449
+
450
+ # Main execution flow
451
+ if __name__ == "__main__":
452
+ if len(sys.argv) < 2:
453
+ print("Usage: python script.py <ebook_file_path> [target_voice_file_path] [language] [custom_model_path] [custom_config_path] [custom_vocab_path]")
454
+ sys.exit(1)
455
+
456
+ ebook_file_path = sys.argv[1]
457
+ target_voice = sys.argv[2] if len(sys.argv) > 2 else None
458
+ language = sys.argv[3] if len(sys.argv) > 3 else None
459
+
460
+ custom_model = None
461
+ if len(sys.argv) > 6:
462
+ custom_model = {
463
+ 'model': sys.argv[4],
464
+ 'config': sys.argv[5],
465
+ 'vocab': sys.argv[6]
466
+ }
467
+
468
+ if not calibre_installed():
469
+ sys.exit(1)
470
+
471
+ working_files = os.path.join(".", "Working_files", "temp_ebook")
472
+ full_folder_working_files = os.path.join(".", "Working_files")
473
+ chapters_directory = os.path.join(".", "Working_files", "temp_ebook")
474
+ output_audio_directory = os.path.join(".", 'Chapter_wav_files')
475
+
476
+ print("Wiping and removing Working_files folder...")
477
+ remove_folder_with_contents(full_folder_working_files)
478
+
479
+ print("Wiping and removing chapter_wav_files folder...")
480
+ remove_folder_with_contents(output_audio_directory)
481
+
482
+ create_chapter_labeled_book(ebook_file_path)
483
+ audiobook_output_path = os.path.join(".", "Audiobooks")
484
+ print(f"{chapters_directory}||||{output_audio_directory}|||||{target_voice}")
485
+ convert_chapters_to_audio(chapters_directory, output_audio_directory, target_voice, language, custom_model)
486
+ create_m4b_from_chapters(output_audio_directory, ebook_file_path, audiobook_output_path)