drewThomasson commited on
Commit
713312c
1 Parent(s): 33a5446

Update ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS_gradio.py

Browse files
ebook2audiobookXTTS/custom_model_ebook2audiobookXTTS_gradio.py CHANGED
@@ -1,609 +1,611 @@
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
-
22
- import gradio as gr
23
- from gradio import Progress
24
-
25
-
26
- def is_folder_empty(folder_path):
27
- if os.path.exists(folder_path) and os.path.isdir(folder_path):
28
- # List directory contents
29
- if not os.listdir(folder_path):
30
- return True # The folder is empty
31
- else:
32
- return False # The folder is not empty
33
- else:
34
- print(f"The path {folder_path} is not a valid folder.")
35
- return None # The path is not a valid folder
36
-
37
- def remove_folder_with_contents(folder_path):
38
- try:
39
- shutil.rmtree(folder_path)
40
- print(f"Successfully removed {folder_path} and all of its contents.")
41
- except Exception as e:
42
- print(f"Error removing {folder_path}: {e}")
43
-
44
-
45
-
46
-
47
- def wipe_folder(folder_path):
48
- # Check if the folder exists
49
- if not os.path.exists(folder_path):
50
- print(f"The folder {folder_path} does not exist.")
51
- return
52
-
53
- # Iterate over all the items in the given folder
54
- for item in os.listdir(folder_path):
55
- item_path = os.path.join(folder_path, item)
56
- # If it's a file, remove it and print a message
57
- if os.path.isfile(item_path):
58
- os.remove(item_path)
59
- print(f"Removed file: {item_path}")
60
- # If it's a directory, remove it recursively and print a message
61
- elif os.path.isdir(item_path):
62
- shutil.rmtree(item_path)
63
- print(f"Removed directory and its contents: {item_path}")
64
-
65
- print(f"All contents wiped from {folder_path}.")
66
-
67
-
68
- # Example usage
69
- # folder_to_wipe = 'path_to_your_folder'
70
- # wipe_folder(folder_to_wipe)
71
-
72
-
73
- def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
74
- # Function to sort chapters based on their numeric order
75
- def sort_key(chapter_file):
76
- numbers = re.findall(r'\d+', chapter_file)
77
- return int(numbers[0]) if numbers else 0
78
-
79
- # Extract metadata and cover image from the eBook file
80
- def extract_metadata_and_cover(ebook_path):
81
- try:
82
- cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
83
- subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
84
- if os.path.exists(cover_path):
85
- return cover_path
86
- except Exception as e:
87
- print(f"Error extracting eBook metadata or cover: {e}")
88
- return None
89
- # Combine WAV files into a single file
90
- def combine_wav_files(chapter_files, output_path):
91
- # Initialize an empty audio segment
92
- combined_audio = AudioSegment.empty()
93
-
94
- # Sequentially append each file to the combined_audio
95
- for chapter_file in chapter_files:
96
- audio_segment = AudioSegment.from_wav(chapter_file)
97
- combined_audio += audio_segment
98
- # Export the combined audio to the output file path
99
- combined_audio.export(output_path, format='wav')
100
- print(f"Combined audio saved to {output_path}")
101
-
102
- # Function to generate metadata for M4B chapters
103
- def generate_ffmpeg_metadata(chapter_files, metadata_file):
104
- with open(metadata_file, 'w') as file:
105
- file.write(';FFMETADATA1\n')
106
- start_time = 0
107
- for index, chapter_file in enumerate(chapter_files):
108
- duration_ms = len(AudioSegment.from_wav(chapter_file))
109
- file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
110
- file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
111
- start_time += duration_ms
112
-
113
- # Generate the final M4B file using ffmpeg
114
- def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
115
- # Ensure the output directory exists
116
- os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
117
-
118
- ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
119
- if cover_image:
120
- ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
121
- else:
122
- ffmpeg_cmd += ['-map', '0:a']
123
-
124
- ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
125
- if cover_image:
126
- ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
127
- ffmpeg_cmd += [output_m4b]
128
-
129
- subprocess.run(ffmpeg_cmd, check=True)
130
-
131
-
132
-
133
- # Main logic
134
- chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
135
- temp_dir = tempfile.gettempdir()
136
- temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
137
- metadata_file = os.path.join(temp_dir, 'metadata.txt')
138
- cover_image = extract_metadata_and_cover(ebook_file)
139
- output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
140
-
141
- combine_wav_files(chapter_files, temp_combined_wav)
142
- generate_ffmpeg_metadata(chapter_files, metadata_file)
143
- create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
144
-
145
- # Cleanup
146
- if os.path.exists(temp_combined_wav):
147
- os.remove(temp_combined_wav)
148
- if os.path.exists(metadata_file):
149
- os.remove(metadata_file)
150
- if cover_image and os.path.exists(cover_image):
151
- os.remove(cover_image)
152
-
153
- # Example usage
154
- # create_m4b_from_chapters('path_to_chapter_wavs', 'path_to_ebook_file', 'path_to_output_dir')
155
-
156
-
157
-
158
-
159
-
160
-
161
- #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
162
- import os
163
- import subprocess
164
- import ebooklib
165
- from ebooklib import epub
166
- from bs4 import BeautifulSoup
167
- import re
168
- import csv
169
- import nltk
170
-
171
- # Only run the main script if Value is True
172
- def create_chapter_labeled_book(ebook_file_path):
173
- # Function to ensure the existence of a directory
174
- def ensure_directory(directory_path):
175
- if not os.path.exists(directory_path):
176
- os.makedirs(directory_path)
177
- print(f"Created directory: {directory_path}")
178
-
179
- ensure_directory(os.path.join(".", 'Working_files', 'Book'))
180
-
181
- def convert_to_epub(input_path, output_path):
182
- # Convert the ebook to EPUB format using Calibre's ebook-convert
183
- try:
184
- subprocess.run(['ebook-convert', input_path, output_path], check=True)
185
- except subprocess.CalledProcessError as e:
186
- print(f"An error occurred while converting the eBook: {e}")
187
- return False
188
- return True
189
-
190
- def save_chapters_as_text(epub_path):
191
- # Create the directory if it doesn't exist
192
- directory = os.path.join(".", "Working_files", "temp_ebook")
193
- ensure_directory(directory)
194
-
195
- # Open the EPUB file
196
- book = epub.read_epub(epub_path)
197
-
198
- previous_chapter_text = ''
199
- previous_filename = ''
200
- chapter_counter = 0
201
-
202
- # Iterate through the items in the EPUB file
203
- for item in book.get_items():
204
- if item.get_type() == ebooklib.ITEM_DOCUMENT:
205
- # Use BeautifulSoup to parse HTML content
206
- soup = BeautifulSoup(item.get_content(), 'html.parser')
207
- text = soup.get_text()
208
-
209
- # Check if the text is not empty
210
- if text.strip():
211
- if len(text) < 2300 and previous_filename:
212
- # Append text to the previous chapter if it's short
213
- with open(previous_filename, 'a', encoding='utf-8') as file:
214
- file.write('\n' + text)
215
- else:
216
- # Create a new chapter file and increment the counter
217
- previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
218
- chapter_counter += 1
219
- with open(previous_filename, 'w', encoding='utf-8') as file:
220
- file.write(text)
221
- print(f"Saved chapter: {previous_filename}")
222
-
223
- # Example usage
224
- input_ebook = ebook_file_path # Replace with your eBook file path
225
- output_epub = os.path.join(".", "Working_files", "temp.epub")
226
-
227
-
228
- if os.path.exists(output_epub):
229
- os.remove(output_epub)
230
- print(f"File {output_epub} has been removed.")
231
- else:
232
- print(f"The file {output_epub} does not exist.")
233
-
234
- if convert_to_epub(input_ebook, output_epub):
235
- save_chapters_as_text(output_epub)
236
-
237
- # Download the necessary NLTK data (if not already present)
238
- nltk.download('punkt')
239
-
240
- def process_chapter_files(folder_path, output_csv):
241
- with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
242
- writer = csv.writer(csvfile)
243
- # Write the header row
244
- writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
245
-
246
- # Process each chapter file
247
- chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
248
- for filename in chapter_files:
249
- if filename.startswith('chapter_') and filename.endswith('.txt'):
250
- chapter_number = int(filename.split('_')[1].split('.')[0])
251
- file_path = os.path.join(folder_path, filename)
252
-
253
- try:
254
- with open(file_path, 'r', encoding='utf-8') as file:
255
- text = file.read()
256
- # Insert "NEWCHAPTERABC" at the beginning of each chapter's text
257
- if text:
258
- text = "NEWCHAPTERABC" + text
259
- sentences = nltk.tokenize.sent_tokenize(text)
260
- for sentence in sentences:
261
- start_location = text.find(sentence)
262
- end_location = start_location + len(sentence)
263
- writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
264
- except Exception as e:
265
- print(f"Error processing file {filename}: {e}")
266
-
267
- # Example usage
268
- folder_path = os.path.join(".", "Working_files", "temp_ebook")
269
- output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
270
-
271
- process_chapter_files(folder_path, output_csv)
272
-
273
- def sort_key(filename):
274
- """Extract chapter number for sorting."""
275
- match = re.search(r'chapter_(\d+)\.txt', filename)
276
- return int(match.group(1)) if match else 0
277
-
278
- def combine_chapters(input_folder, output_file):
279
- # Create the output folder if it doesn't exist
280
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
281
-
282
- # List all txt files and sort them by chapter number
283
- files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
284
- sorted_files = sorted(files, key=sort_key)
285
-
286
- with open(output_file, 'w', encoding='utf-8') as outfile: # Specify UTF-8 encoding here
287
- for i, filename in enumerate(sorted_files):
288
- with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile: # And here
289
- outfile.write(infile.read())
290
- # Add the marker unless it's the last file
291
- if i < len(sorted_files) - 1:
292
- outfile.write("\nNEWCHAPTERABC\n")
293
-
294
- # Paths
295
- input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
296
- output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
297
-
298
-
299
- # Combine the chapters
300
- combine_chapters(input_folder, output_file)
301
-
302
- ensure_directory(os.path.join(".", "Working_files", "Book"))
303
-
304
-
305
- #create_chapter_labeled_book()
306
-
307
-
308
-
309
-
310
- import os
311
- import subprocess
312
- import sys
313
- import torchaudio
314
-
315
- # Check if Calibre's ebook-convert tool is installed
316
- def calibre_installed():
317
- try:
318
- subprocess.run(['ebook-convert', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
319
- return True
320
- except FileNotFoundError:
321
- print("Calibre is not installed. Please install Calibre for this functionality.")
322
- return False
323
-
324
-
325
- import os
326
- import torch
327
- from TTS.api import TTS
328
- from nltk.tokenize import sent_tokenize
329
- from pydub import AudioSegment
330
- # Assuming split_long_sentence and wipe_folder are defined elsewhere in your code
331
-
332
- default_target_voice_path = "default_voice.wav" # Ensure this is a valid path
333
- default_language_code = "en"
334
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
335
-
336
- def combine_wav_files(input_directory, output_directory, file_name):
337
- # Ensure that the output directory exists, create it if necessary
338
- os.makedirs(output_directory, exist_ok=True)
339
-
340
- # Specify the output file path
341
- output_file_path = os.path.join(output_directory, file_name)
342
-
343
- # Initialize an empty audio segment
344
- combined_audio = AudioSegment.empty()
345
-
346
- # Get a list of all .wav files in the specified input directory and sort them
347
- input_file_paths = sorted(
348
- [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith(".wav")],
349
- key=lambda f: int(''.join(filter(str.isdigit, f)))
350
- )
351
-
352
- # Sequentially append each file to the combined_audio
353
- for input_file_path in input_file_paths:
354
- audio_segment = AudioSegment.from_wav(input_file_path)
355
- combined_audio += audio_segment
356
-
357
- # Export the combined audio to the output file path
358
- combined_audio.export(output_file_path, format='wav')
359
-
360
- print(f"Combined audio saved to {output_file_path}")
361
-
362
- # Function to split long strings into parts
363
- def split_long_sentence(sentence, max_length=249, max_pauses=10):
364
- """
365
- Splits a sentence into parts based on length or number of pauses without recursion.
366
-
367
- :param sentence: The sentence to split.
368
- :param max_length: Maximum allowed length of a sentence.
369
- :param max_pauses: Maximum allowed number of pauses in a sentence.
370
- :return: A list of sentence parts that meet the criteria.
371
- """
372
- parts = []
373
- while len(sentence) > max_length or sentence.count(',') + sentence.count(';') + sentence.count('.') > max_pauses:
374
- possible_splits = [i for i, char in enumerate(sentence) if char in ',;.' and i < max_length]
375
- if possible_splits:
376
- # Find the best place to split the sentence, preferring the last possible split to keep parts longer
377
- split_at = possible_splits[-1] + 1
378
- else:
379
- # If no punctuation to split on within max_length, split at max_length
380
- split_at = max_length
381
-
382
- # Split the sentence and add the first part to the list
383
- parts.append(sentence[:split_at].strip())
384
- sentence = sentence[split_at:].strip()
385
-
386
- # Add the remaining part of the sentence
387
- parts.append(sentence)
388
- return parts
389
-
390
- """
391
- if 'tts' not in locals():
392
- tts = TTS(selected_tts_model, progress_bar=True).to(device)
393
- """
394
- from tqdm import tqdm
395
-
396
- # Convert chapters to audio using XTTS
397
-
398
- def convert_chapters_to_audio_custom_model(chapters_dir, output_audio_dir, target_voice_path=None, language=None, custom_model=None):
399
- if custom_model:
400
- print("Loading custom model...")
401
- config = XttsConfig()
402
- config.load_json(custom_model['config'])
403
- model = Xtts.init_from_config(config)
404
- model.load_checkpoint(config, checkpoint_path=custom_model['model'], vocab_path=custom_model['vocab'], use_deepspeed=False)
405
- model.to(device)
406
- print("Computing speaker latents...")
407
- gpt_cond_latent, speaker_embedding = model.get_conditioning_latents(audio_path=[target_voice_path])
408
- else:
409
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
410
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
411
-
412
- if not os.path.exists(output_audio_dir):
413
- os.makedirs(output_audio_dir)
414
-
415
- for chapter_file in sorted(os.listdir(chapters_dir)):
416
- if chapter_file.endswith('.txt'):
417
- match = re.search(r"chapter_(\d+).txt", chapter_file)
418
- if match:
419
- chapter_num = int(match.group(1))
420
- else:
421
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
422
- continue
423
-
424
- chapter_path = os.path.join(chapters_dir, chapter_file)
425
- output_file_name = f"audio_chapter_{chapter_num}.wav"
426
- output_file_path = os.path.join(output_audio_dir, output_file_name)
427
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
428
- os.makedirs(temp_audio_directory, exist_ok=True)
429
- temp_count = 0
430
-
431
- with open(chapter_path, 'r', encoding='utf-8') as file:
432
- chapter_text = file.read()
433
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
434
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
435
- fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
436
- for fragment in fragments:
437
- if fragment != "":
438
- print(f"Generating fragment: {fragment}...")
439
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
440
- if custom_model:
441
- out = model.inference(fragment, language, gpt_cond_latent, speaker_embedding, temperature=0.7)
442
- torchaudio.save(fragment_file_path, torch.tensor(out["wav"]).unsqueeze(0), 24000)
443
- else:
444
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
445
- language_code = language if language else default_language_code
446
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
447
- temp_count += 1
448
-
449
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
450
- wipe_folder(temp_audio_directory)
451
- print(f"Converted chapter {chapter_num} to audio.")
452
-
453
-
454
-
455
- def convert_chapters_to_audio_standard_model(chapters_dir, output_audio_dir, target_voice_path=None, language=None):
456
- selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
457
- tts = TTS(selected_tts_model, progress_bar=False).to(device)
458
-
459
- if not os.path.exists(output_audio_dir):
460
- os.makedirs(output_audio_dir)
461
-
462
- for chapter_file in sorted(os.listdir(chapters_dir)):
463
- if chapter_file.endswith('.txt'):
464
- match = re.search(r"chapter_(\d+).txt", chapter_file)
465
- if match:
466
- chapter_num = int(match.group(1))
467
- else:
468
- print(f"Skipping file {chapter_file} as it does not match the expected format.")
469
- continue
470
-
471
- chapter_path = os.path.join(chapters_dir, chapter_file)
472
- output_file_name = f"audio_chapter_{chapter_num}.wav"
473
- output_file_path = os.path.join(output_audio_dir, output_file_name)
474
- temp_audio_directory = os.path.join(".", "Working_files", "temp")
475
- os.makedirs(temp_audio_directory, exist_ok=True)
476
- temp_count = 0
477
-
478
- with open(chapter_path, 'r', encoding='utf-8') as file:
479
- chapter_text = file.read()
480
- sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
481
- for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
482
- fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
483
- for fragment in fragments:
484
- if fragment != "":
485
- print(f"Generating fragment: {fragment}...")
486
- fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
487
- speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
488
- language_code = language if language else default_language_code
489
- tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
490
- temp_count += 1
491
-
492
- combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
493
- wipe_folder(temp_audio_directory)
494
- print(f"Converted chapter {chapter_num} to audio.")
495
-
496
-
497
-
498
- # Define the functions to be used in the Gradio interface
499
- 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()):
500
- ebook_file_path = ebook_file.name
501
- target_voice = target_voice_file.name if target_voice_file else None
502
- custom_model = None
503
- if use_custom_model and custom_model_file and custom_config_file and custom_vocab_file:
504
- custom_model = {
505
- 'model': custom_model_file.name,
506
- 'config': custom_config_file.name,
507
- 'vocab': custom_vocab_file.name
508
- }
509
-
510
- try:
511
- progress(0, desc="Starting conversion")
512
- except Exception as e:
513
- print(f"Error updating progress: {e}")
514
-
515
- if not calibre_installed():
516
- return "Calibre is not installed."
517
-
518
- working_files = os.path.join(".", "Working_files", "temp_ebook")
519
- full_folder_working_files = os.path.join(".", "Working_files")
520
- chapters_directory = os.path.join(".", "Working_files", "temp_ebook")
521
- output_audio_directory = os.path.join(".", 'Chapter_wav_files')
522
- remove_folder_with_contents(full_folder_working_files)
523
- remove_folder_with_contents(output_audio_directory)
524
-
525
- try:
526
- progress(0.1, desc="Creating chapter-labeled book")
527
- except Exception as e:
528
- print(f"Error updating progress: {e}")
529
-
530
- create_chapter_labeled_book(ebook_file_path)
531
- audiobook_output_path = os.path.join(".", "Audiobooks")
532
-
533
- try:
534
- progress(0.3, desc="Converting chapters to audio")
535
- except Exception as e:
536
- print(f"Error updating progress: {e}")
537
-
538
- if use_custom_model:
539
- convert_chapters_to_audio_custom_model(chapters_directory, output_audio_directory, target_voice, language, custom_model)
540
- else:
541
- convert_chapters_to_audio_standard_model(chapters_directory, output_audio_directory, target_voice, language)
542
-
543
- try:
544
- progress(0.9, desc="Creating M4B from chapters")
545
- except Exception as e:
546
- print(f"Error updating progress: {e}")
547
-
548
- create_m4b_from_chapters(output_audio_directory, ebook_file_path, audiobook_output_path)
549
-
550
- # Get the name of the created M4B file
551
- m4b_filename = os.path.splitext(os.path.basename(ebook_file_path))[0] + '.m4b'
552
- m4b_filepath = os.path.join(audiobook_output_path, m4b_filename)
553
-
554
- try:
555
- progress(1.0, desc="Conversion complete")
556
- except Exception as e:
557
- print(f"Error updating progress: {e}")
558
-
559
- return f"Audiobook created at {m4b_filepath}", m4b_filepath
560
-
561
- language_options = [
562
- "en", "es", "fr", "de", "it", "pt", "pl", "tr", "ru", "nl", "cs", "ar", "zh-cn", "ja", "hu", "ko"
563
- ]
564
-
565
- theme = gr.themes.Soft(
566
- primary_hue="blue",
567
- secondary_hue="blue",
568
- neutral_hue="blue",
569
- text_size=gr.themes.sizes.text_md,
570
- )
571
-
572
- with gr.Blocks(theme=theme) as demo:
573
- gr.Markdown(
574
- """
575
- # eBook to Audiobook Converter
576
-
577
- Transform your eBooks into immersive audiobooks with optional custom TTS models.
578
- """
579
- )
580
-
581
- with gr.Row():
582
- with gr.Column(scale=3):
583
- ebook_file = gr.File(label="eBook File")
584
- target_voice_file = gr.File(label="Target Voice File (Optional)")
585
- language = gr.Dropdown(label="Language", choices=language_options, value="en")
586
-
587
- with gr.Column(scale=3):
588
- use_custom_model = gr.Checkbox(label="Use Custom Model")
589
- custom_model_file = gr.File(label="Custom Model File (Optional)", visible=False)
590
- custom_config_file = gr.File(label="Custom Config File (Optional)", visible=False)
591
- custom_vocab_file = gr.File(label="Custom Vocab File (Optional)", visible=False)
592
-
593
- convert_btn = gr.Button("Convert to Audiobook", variant="primary")
594
- output = gr.Textbox(label="Conversion Status")
595
- audio_player = gr.Audio(label="Audiobook Player", type="filepath")
596
-
597
- convert_btn.click(
598
- convert_ebook_to_audio,
599
- inputs=[ebook_file, target_voice_file, language, use_custom_model, custom_model_file, custom_config_file, custom_vocab_file],
600
- outputs=[output, audio_player]
601
- )
602
-
603
- use_custom_model.change(
604
- lambda x: [gr.update(visible=x)] * 3,
605
- inputs=[use_custom_model],
606
- outputs=[custom_model_file, custom_config_file, custom_vocab_file]
607
- )
608
-
609
- demo.launch(share=False)
 
 
 
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
+
24
+ import gradio as gr
25
+ from gradio import Progress
26
+
27
+
28
+ def is_folder_empty(folder_path):
29
+ if os.path.exists(folder_path) and os.path.isdir(folder_path):
30
+ # List directory contents
31
+ if not os.listdir(folder_path):
32
+ return True # The folder is empty
33
+ else:
34
+ return False # The folder is not empty
35
+ else:
36
+ print(f"The path {folder_path} is not a valid folder.")
37
+ return None # The path is not a valid folder
38
+
39
+ def remove_folder_with_contents(folder_path):
40
+ try:
41
+ shutil.rmtree(folder_path)
42
+ print(f"Successfully removed {folder_path} and all of its contents.")
43
+ except Exception as e:
44
+ print(f"Error removing {folder_path}: {e}")
45
+
46
+
47
+
48
+
49
+ def wipe_folder(folder_path):
50
+ # Check if the folder exists
51
+ if not os.path.exists(folder_path):
52
+ print(f"The folder {folder_path} does not exist.")
53
+ return
54
+
55
+ # Iterate over all the items in the given folder
56
+ for item in os.listdir(folder_path):
57
+ item_path = os.path.join(folder_path, item)
58
+ # If it's a file, remove it and print a message
59
+ if os.path.isfile(item_path):
60
+ os.remove(item_path)
61
+ print(f"Removed file: {item_path}")
62
+ # If it's a directory, remove it recursively and print a message
63
+ elif os.path.isdir(item_path):
64
+ shutil.rmtree(item_path)
65
+ print(f"Removed directory and its contents: {item_path}")
66
+
67
+ print(f"All contents wiped from {folder_path}.")
68
+
69
+
70
+ # Example usage
71
+ # folder_to_wipe = 'path_to_your_folder'
72
+ # wipe_folder(folder_to_wipe)
73
+
74
+
75
+ def create_m4b_from_chapters(input_dir, ebook_file, output_dir):
76
+ # Function to sort chapters based on their numeric order
77
+ def sort_key(chapter_file):
78
+ numbers = re.findall(r'\d+', chapter_file)
79
+ return int(numbers[0]) if numbers else 0
80
+
81
+ # Extract metadata and cover image from the eBook file
82
+ def extract_metadata_and_cover(ebook_path):
83
+ try:
84
+ cover_path = ebook_path.rsplit('.', 1)[0] + '.jpg'
85
+ subprocess.run(['ebook-meta', ebook_path, '--get-cover', cover_path], check=True)
86
+ if os.path.exists(cover_path):
87
+ return cover_path
88
+ except Exception as e:
89
+ print(f"Error extracting eBook metadata or cover: {e}")
90
+ return None
91
+ # Combine WAV files into a single file
92
+ def combine_wav_files(chapter_files, output_path):
93
+ # Initialize an empty audio segment
94
+ combined_audio = AudioSegment.empty()
95
+
96
+ # Sequentially append each file to the combined_audio
97
+ for chapter_file in chapter_files:
98
+ audio_segment = AudioSegment.from_wav(chapter_file)
99
+ combined_audio += audio_segment
100
+ # Export the combined audio to the output file path
101
+ combined_audio.export(output_path, format='wav')
102
+ print(f"Combined audio saved to {output_path}")
103
+
104
+ # Function to generate metadata for M4B chapters
105
+ def generate_ffmpeg_metadata(chapter_files, metadata_file):
106
+ with open(metadata_file, 'w') as file:
107
+ file.write(';FFMETADATA1\n')
108
+ start_time = 0
109
+ for index, chapter_file in enumerate(chapter_files):
110
+ duration_ms = len(AudioSegment.from_wav(chapter_file))
111
+ file.write(f'[CHAPTER]\nTIMEBASE=1/1000\nSTART={start_time}\n')
112
+ file.write(f'END={start_time + duration_ms}\ntitle=Chapter {index + 1}\n')
113
+ start_time += duration_ms
114
+
115
+ # Generate the final M4B file using ffmpeg
116
+ def create_m4b(combined_wav, metadata_file, cover_image, output_m4b):
117
+ # Ensure the output directory exists
118
+ os.makedirs(os.path.dirname(output_m4b), exist_ok=True)
119
+
120
+ ffmpeg_cmd = ['ffmpeg', '-i', combined_wav, '-i', metadata_file]
121
+ if cover_image:
122
+ ffmpeg_cmd += ['-i', cover_image, '-map', '0:a', '-map', '2:v']
123
+ else:
124
+ ffmpeg_cmd += ['-map', '0:a']
125
+
126
+ ffmpeg_cmd += ['-map_metadata', '1', '-c:a', 'aac', '-b:a', '192k']
127
+ if cover_image:
128
+ ffmpeg_cmd += ['-c:v', 'png', '-disposition:v', 'attached_pic']
129
+ ffmpeg_cmd += [output_m4b]
130
+
131
+ subprocess.run(ffmpeg_cmd, check=True)
132
+
133
+
134
+
135
+ # Main logic
136
+ chapter_files = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.wav')], key=sort_key)
137
+ temp_dir = tempfile.gettempdir()
138
+ temp_combined_wav = os.path.join(temp_dir, 'combined.wav')
139
+ metadata_file = os.path.join(temp_dir, 'metadata.txt')
140
+ cover_image = extract_metadata_and_cover(ebook_file)
141
+ output_m4b = os.path.join(output_dir, os.path.splitext(os.path.basename(ebook_file))[0] + '.m4b')
142
+
143
+ combine_wav_files(chapter_files, temp_combined_wav)
144
+ generate_ffmpeg_metadata(chapter_files, metadata_file)
145
+ create_m4b(temp_combined_wav, metadata_file, cover_image, output_m4b)
146
+
147
+ # Cleanup
148
+ if os.path.exists(temp_combined_wav):
149
+ os.remove(temp_combined_wav)
150
+ if os.path.exists(metadata_file):
151
+ os.remove(metadata_file)
152
+ if cover_image and os.path.exists(cover_image):
153
+ os.remove(cover_image)
154
+
155
+ # Example usage
156
+ # create_m4b_from_chapters('path_to_chapter_wavs', 'path_to_ebook_file', 'path_to_output_dir')
157
+
158
+
159
+
160
+
161
+
162
+
163
+ #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
164
+ import os
165
+ import subprocess
166
+ import ebooklib
167
+ from ebooklib import epub
168
+ from bs4 import BeautifulSoup
169
+ import re
170
+ import csv
171
+ import nltk
172
+
173
+ # Only run the main script if Value is True
174
+ def create_chapter_labeled_book(ebook_file_path):
175
+ # Function to ensure the existence of a directory
176
+ def ensure_directory(directory_path):
177
+ if not os.path.exists(directory_path):
178
+ os.makedirs(directory_path)
179
+ print(f"Created directory: {directory_path}")
180
+
181
+ ensure_directory(os.path.join(".", 'Working_files', 'Book'))
182
+
183
+ def convert_to_epub(input_path, output_path):
184
+ # Convert the ebook to EPUB format using Calibre's ebook-convert
185
+ try:
186
+ subprocess.run(['ebook-convert', input_path, output_path], check=True)
187
+ except subprocess.CalledProcessError as e:
188
+ print(f"An error occurred while converting the eBook: {e}")
189
+ return False
190
+ return True
191
+
192
+ def save_chapters_as_text(epub_path):
193
+ # Create the directory if it doesn't exist
194
+ directory = os.path.join(".", "Working_files", "temp_ebook")
195
+ ensure_directory(directory)
196
+
197
+ # Open the EPUB file
198
+ book = epub.read_epub(epub_path)
199
+
200
+ previous_chapter_text = ''
201
+ previous_filename = ''
202
+ chapter_counter = 0
203
+
204
+ # Iterate through the items in the EPUB file
205
+ for item in book.get_items():
206
+ if item.get_type() == ebooklib.ITEM_DOCUMENT:
207
+ # Use BeautifulSoup to parse HTML content
208
+ soup = BeautifulSoup(item.get_content(), 'html.parser')
209
+ text = soup.get_text()
210
+
211
+ # Check if the text is not empty
212
+ if text.strip():
213
+ if len(text) < 2300 and previous_filename:
214
+ # Append text to the previous chapter if it's short
215
+ with open(previous_filename, 'a', encoding='utf-8') as file:
216
+ file.write('\n' + text)
217
+ else:
218
+ # Create a new chapter file and increment the counter
219
+ previous_filename = os.path.join(directory, f"chapter_{chapter_counter}.txt")
220
+ chapter_counter += 1
221
+ with open(previous_filename, 'w', encoding='utf-8') as file:
222
+ file.write(text)
223
+ print(f"Saved chapter: {previous_filename}")
224
+
225
+ # Example usage
226
+ input_ebook = ebook_file_path # Replace with your eBook file path
227
+ output_epub = os.path.join(".", "Working_files", "temp.epub")
228
+
229
+
230
+ if os.path.exists(output_epub):
231
+ os.remove(output_epub)
232
+ print(f"File {output_epub} has been removed.")
233
+ else:
234
+ print(f"The file {output_epub} does not exist.")
235
+
236
+ if convert_to_epub(input_ebook, output_epub):
237
+ save_chapters_as_text(output_epub)
238
+
239
+ # Download the necessary NLTK data (if not already present)
240
+ #nltk.download('punkt')
241
+
242
+ def process_chapter_files(folder_path, output_csv):
243
+ with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
244
+ writer = csv.writer(csvfile)
245
+ # Write the header row
246
+ writer.writerow(['Text', 'Start Location', 'End Location', 'Is Quote', 'Speaker', 'Chapter'])
247
+
248
+ # Process each chapter file
249
+ chapter_files = sorted(os.listdir(folder_path), key=lambda x: int(x.split('_')[1].split('.')[0]))
250
+ for filename in chapter_files:
251
+ if filename.startswith('chapter_') and filename.endswith('.txt'):
252
+ chapter_number = int(filename.split('_')[1].split('.')[0])
253
+ file_path = os.path.join(folder_path, filename)
254
+
255
+ try:
256
+ with open(file_path, 'r', encoding='utf-8') as file:
257
+ text = file.read()
258
+ # Insert "NEWCHAPTERABC" at the beginning of each chapter's text
259
+ if text:
260
+ text = "NEWCHAPTERABC" + text
261
+ sentences = nltk.tokenize.sent_tokenize(text)
262
+ for sentence in sentences:
263
+ start_location = text.find(sentence)
264
+ end_location = start_location + len(sentence)
265
+ writer.writerow([sentence, start_location, end_location, 'True', 'Narrator', chapter_number])
266
+ except Exception as e:
267
+ print(f"Error processing file {filename}: {e}")
268
+
269
+ # Example usage
270
+ folder_path = os.path.join(".", "Working_files", "temp_ebook")
271
+ output_csv = os.path.join(".", "Working_files", "Book", "Other_book.csv")
272
+
273
+ process_chapter_files(folder_path, output_csv)
274
+
275
+ def sort_key(filename):
276
+ """Extract chapter number for sorting."""
277
+ match = re.search(r'chapter_(\d+)\.txt', filename)
278
+ return int(match.group(1)) if match else 0
279
+
280
+ def combine_chapters(input_folder, output_file):
281
+ # Create the output folder if it doesn't exist
282
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
283
+
284
+ # List all txt files and sort them by chapter number
285
+ files = [f for f in os.listdir(input_folder) if f.endswith('.txt')]
286
+ sorted_files = sorted(files, key=sort_key)
287
+
288
+ with open(output_file, 'w', encoding='utf-8') as outfile: # Specify UTF-8 encoding here
289
+ for i, filename in enumerate(sorted_files):
290
+ with open(os.path.join(input_folder, filename), 'r', encoding='utf-8') as infile: # And here
291
+ outfile.write(infile.read())
292
+ # Add the marker unless it's the last file
293
+ if i < len(sorted_files) - 1:
294
+ outfile.write("\nNEWCHAPTERABC\n")
295
+
296
+ # Paths
297
+ input_folder = os.path.join(".", 'Working_files', 'temp_ebook')
298
+ output_file = os.path.join(".", 'Working_files', 'Book', 'Chapter_Book.txt')
299
+
300
+
301
+ # Combine the chapters
302
+ combine_chapters(input_folder, output_file)
303
+
304
+ ensure_directory(os.path.join(".", "Working_files", "Book"))
305
+
306
+
307
+ #create_chapter_labeled_book()
308
+
309
+
310
+
311
+
312
+ import os
313
+ import subprocess
314
+ import sys
315
+ import torchaudio
316
+
317
+ # Check if Calibre's ebook-convert tool is installed
318
+ def calibre_installed():
319
+ try:
320
+ subprocess.run(['ebook-convert', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
321
+ return True
322
+ except FileNotFoundError:
323
+ print("Calibre is not installed. Please install Calibre for this functionality.")
324
+ return False
325
+
326
+
327
+ import os
328
+ import torch
329
+ from TTS.api import TTS
330
+ from nltk.tokenize import sent_tokenize
331
+ from pydub import AudioSegment
332
+ # Assuming split_long_sentence and wipe_folder are defined elsewhere in your code
333
+
334
+ default_target_voice_path = "default_voice.wav" # Ensure this is a valid path
335
+ default_language_code = "en"
336
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
337
+
338
+ def combine_wav_files(input_directory, output_directory, file_name):
339
+ # Ensure that the output directory exists, create it if necessary
340
+ os.makedirs(output_directory, exist_ok=True)
341
+
342
+ # Specify the output file path
343
+ output_file_path = os.path.join(output_directory, file_name)
344
+
345
+ # Initialize an empty audio segment
346
+ combined_audio = AudioSegment.empty()
347
+
348
+ # Get a list of all .wav files in the specified input directory and sort them
349
+ input_file_paths = sorted(
350
+ [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith(".wav")],
351
+ key=lambda f: int(''.join(filter(str.isdigit, f)))
352
+ )
353
+
354
+ # Sequentially append each file to the combined_audio
355
+ for input_file_path in input_file_paths:
356
+ audio_segment = AudioSegment.from_wav(input_file_path)
357
+ combined_audio += audio_segment
358
+
359
+ # Export the combined audio to the output file path
360
+ combined_audio.export(output_file_path, format='wav')
361
+
362
+ print(f"Combined audio saved to {output_file_path}")
363
+
364
+ # Function to split long strings into parts
365
+ def split_long_sentence(sentence, max_length=249, max_pauses=10):
366
+ """
367
+ Splits a sentence into parts based on length or number of pauses without recursion.
368
+
369
+ :param sentence: The sentence to split.
370
+ :param max_length: Maximum allowed length of a sentence.
371
+ :param max_pauses: Maximum allowed number of pauses in a sentence.
372
+ :return: A list of sentence parts that meet the criteria.
373
+ """
374
+ parts = []
375
+ while len(sentence) > max_length or sentence.count(',') + sentence.count(';') + sentence.count('.') > max_pauses:
376
+ possible_splits = [i for i, char in enumerate(sentence) if char in ',;.' and i < max_length]
377
+ if possible_splits:
378
+ # Find the best place to split the sentence, preferring the last possible split to keep parts longer
379
+ split_at = possible_splits[-1] + 1
380
+ else:
381
+ # If no punctuation to split on within max_length, split at max_length
382
+ split_at = max_length
383
+
384
+ # Split the sentence and add the first part to the list
385
+ parts.append(sentence[:split_at].strip())
386
+ sentence = sentence[split_at:].strip()
387
+
388
+ # Add the remaining part of the sentence
389
+ parts.append(sentence)
390
+ return parts
391
+
392
+ """
393
+ if 'tts' not in locals():
394
+ tts = TTS(selected_tts_model, progress_bar=True).to(device)
395
+ """
396
+ from tqdm import tqdm
397
+
398
+ # Convert chapters to audio using XTTS
399
+
400
+ def convert_chapters_to_audio_custom_model(chapters_dir, output_audio_dir, target_voice_path=None, language=None, custom_model=None):
401
+ if custom_model:
402
+ print("Loading custom model...")
403
+ config = XttsConfig()
404
+ config.load_json(custom_model['config'])
405
+ model = Xtts.init_from_config(config)
406
+ model.load_checkpoint(config, checkpoint_path=custom_model['model'], vocab_path=custom_model['vocab'], use_deepspeed=False)
407
+ model.to(device)
408
+ print("Computing speaker latents...")
409
+ gpt_cond_latent, speaker_embedding = model.get_conditioning_latents(audio_path=[target_voice_path])
410
+ else:
411
+ selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
412
+ tts = TTS(selected_tts_model, progress_bar=False).to(device)
413
+
414
+ if not os.path.exists(output_audio_dir):
415
+ os.makedirs(output_audio_dir)
416
+
417
+ for chapter_file in sorted(os.listdir(chapters_dir)):
418
+ if chapter_file.endswith('.txt'):
419
+ match = re.search(r"chapter_(\d+).txt", chapter_file)
420
+ if match:
421
+ chapter_num = int(match.group(1))
422
+ else:
423
+ print(f"Skipping file {chapter_file} as it does not match the expected format.")
424
+ continue
425
+
426
+ chapter_path = os.path.join(chapters_dir, chapter_file)
427
+ output_file_name = f"audio_chapter_{chapter_num}.wav"
428
+ output_file_path = os.path.join(output_audio_dir, output_file_name)
429
+ temp_audio_directory = os.path.join(".", "Working_files", "temp")
430
+ os.makedirs(temp_audio_directory, exist_ok=True)
431
+ temp_count = 0
432
+
433
+ with open(chapter_path, 'r', encoding='utf-8') as file:
434
+ chapter_text = file.read()
435
+ sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
436
+ for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
437
+ fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
438
+ for fragment in fragments:
439
+ if fragment != "":
440
+ print(f"Generating fragment: {fragment}...")
441
+ fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
442
+ if custom_model:
443
+ out = model.inference(fragment, language, gpt_cond_latent, speaker_embedding, temperature=0.7)
444
+ torchaudio.save(fragment_file_path, torch.tensor(out["wav"]).unsqueeze(0), 24000)
445
+ else:
446
+ speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
447
+ language_code = language if language else default_language_code
448
+ tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
449
+ temp_count += 1
450
+
451
+ combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
452
+ wipe_folder(temp_audio_directory)
453
+ print(f"Converted chapter {chapter_num} to audio.")
454
+
455
+
456
+
457
+ def convert_chapters_to_audio_standard_model(chapters_dir, output_audio_dir, target_voice_path=None, language=None):
458
+ selected_tts_model = "tts_models/multilingual/multi-dataset/xtts_v2"
459
+ tts = TTS(selected_tts_model, progress_bar=False).to(device)
460
+
461
+ if not os.path.exists(output_audio_dir):
462
+ os.makedirs(output_audio_dir)
463
+
464
+ for chapter_file in sorted(os.listdir(chapters_dir)):
465
+ if chapter_file.endswith('.txt'):
466
+ match = re.search(r"chapter_(\d+).txt", chapter_file)
467
+ if match:
468
+ chapter_num = int(match.group(1))
469
+ else:
470
+ print(f"Skipping file {chapter_file} as it does not match the expected format.")
471
+ continue
472
+
473
+ chapter_path = os.path.join(chapters_dir, chapter_file)
474
+ output_file_name = f"audio_chapter_{chapter_num}.wav"
475
+ output_file_path = os.path.join(output_audio_dir, output_file_name)
476
+ temp_audio_directory = os.path.join(".", "Working_files", "temp")
477
+ os.makedirs(temp_audio_directory, exist_ok=True)
478
+ temp_count = 0
479
+
480
+ with open(chapter_path, 'r', encoding='utf-8') as file:
481
+ chapter_text = file.read()
482
+ sentences = sent_tokenize(chapter_text, language='italian' if language == 'it' else 'english')
483
+ for sentence in tqdm(sentences, desc=f"Chapter {chapter_num}"):
484
+ fragments = split_long_sentence(sentence, max_length=249 if language == "en" else 213, max_pauses=10)
485
+ for fragment in fragments:
486
+ if fragment != "":
487
+ print(f"Generating fragment: {fragment}...")
488
+ fragment_file_path = os.path.join(temp_audio_directory, f"{temp_count}.wav")
489
+ speaker_wav_path = target_voice_path if target_voice_path else default_target_voice_path
490
+ language_code = language if language else default_language_code
491
+ tts.tts_to_file(text=fragment, file_path=fragment_file_path, speaker_wav=speaker_wav_path, language=language_code)
492
+ temp_count += 1
493
+
494
+ combine_wav_files(temp_audio_directory, output_audio_dir, output_file_name)
495
+ wipe_folder(temp_audio_directory)
496
+ print(f"Converted chapter {chapter_num} to audio.")
497
+
498
+
499
+
500
+ # Define the functions to be used in the Gradio interface
501
+ 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()):
502
+ ebook_file_path = ebook_file.name
503
+ target_voice = target_voice_file.name if target_voice_file else None
504
+ custom_model = None
505
+ if use_custom_model and custom_model_file and custom_config_file and custom_vocab_file:
506
+ custom_model = {
507
+ 'model': custom_model_file.name,
508
+ 'config': custom_config_file.name,
509
+ 'vocab': custom_vocab_file.name
510
+ }
511
+
512
+ try:
513
+ progress(0, desc="Starting conversion")
514
+ except Exception as e:
515
+ print(f"Error updating progress: {e}")
516
+
517
+ if not calibre_installed():
518
+ return "Calibre is not installed."
519
+
520
+ working_files = os.path.join(".", "Working_files", "temp_ebook")
521
+ full_folder_working_files = os.path.join(".", "Working_files")
522
+ chapters_directory = os.path.join(".", "Working_files", "temp_ebook")
523
+ output_audio_directory = os.path.join(".", 'Chapter_wav_files')
524
+ remove_folder_with_contents(full_folder_working_files)
525
+ remove_folder_with_contents(output_audio_directory)
526
+
527
+ try:
528
+ progress(0.1, desc="Creating chapter-labeled book")
529
+ except Exception as e:
530
+ print(f"Error updating progress: {e}")
531
+
532
+ create_chapter_labeled_book(ebook_file_path)
533
+ audiobook_output_path = os.path.join(".", "Audiobooks")
534
+
535
+ try:
536
+ progress(0.3, desc="Converting chapters to audio")
537
+ except Exception as e:
538
+ print(f"Error updating progress: {e}")
539
+
540
+ if use_custom_model:
541
+ convert_chapters_to_audio_custom_model(chapters_directory, output_audio_directory, target_voice, language, custom_model)
542
+ else:
543
+ convert_chapters_to_audio_standard_model(chapters_directory, output_audio_directory, target_voice, language)
544
+
545
+ try:
546
+ progress(0.9, desc="Creating M4B from chapters")
547
+ except Exception as e:
548
+ print(f"Error updating progress: {e}")
549
+
550
+ create_m4b_from_chapters(output_audio_directory, ebook_file_path, audiobook_output_path)
551
+
552
+ # Get the name of the created M4B file
553
+ m4b_filename = os.path.splitext(os.path.basename(ebook_file_path))[0] + '.m4b'
554
+ m4b_filepath = os.path.join(audiobook_output_path, m4b_filename)
555
+
556
+ try:
557
+ progress(1.0, desc="Conversion complete")
558
+ except Exception as e:
559
+ print(f"Error updating progress: {e}")
560
+
561
+ return f"Audiobook created at {m4b_filepath}", m4b_filepath
562
+
563
+ language_options = [
564
+ "en", "es", "fr", "de", "it", "pt", "pl", "tr", "ru", "nl", "cs", "ar", "zh-cn", "ja", "hu", "ko"
565
+ ]
566
+
567
+ theme = gr.themes.Soft(
568
+ primary_hue="blue",
569
+ secondary_hue="blue",
570
+ neutral_hue="blue",
571
+ text_size=gr.themes.sizes.text_md,
572
+ )
573
+
574
+ with gr.Blocks(theme=theme) as demo:
575
+ gr.Markdown(
576
+ """
577
+ # eBook to Audiobook Converter
578
+
579
+ Transform your eBooks into immersive audiobooks with optional custom TTS models.
580
+ """
581
+ )
582
+
583
+ with gr.Row():
584
+ with gr.Column(scale=3):
585
+ ebook_file = gr.File(label="eBook File")
586
+ target_voice_file = gr.File(label="Target Voice File (Optional)")
587
+ language = gr.Dropdown(label="Language", choices=language_options, value="en")
588
+
589
+ with gr.Column(scale=3):
590
+ use_custom_model = gr.Checkbox(label="Use Custom Model")
591
+ custom_model_file = gr.File(label="Custom Model File (Optional)", visible=False)
592
+ custom_config_file = gr.File(label="Custom Config File (Optional)", visible=False)
593
+ custom_vocab_file = gr.File(label="Custom Vocab File (Optional)", visible=False)
594
+
595
+ convert_btn = gr.Button("Convert to Audiobook", variant="primary")
596
+ output = gr.Textbox(label="Conversion Status")
597
+ audio_player = gr.Audio(label="Audiobook Player", type="filepath")
598
+
599
+ convert_btn.click(
600
+ convert_ebook_to_audio,
601
+ inputs=[ebook_file, target_voice_file, language, use_custom_model, custom_model_file, custom_config_file, custom_vocab_file],
602
+ outputs=[output, audio_player]
603
+ )
604
+
605
+ use_custom_model.change(
606
+ lambda x: [gr.update(visible=x)] * 3,
607
+ inputs=[use_custom_model],
608
+ outputs=[custom_model_file, custom_config_file, custom_vocab_file]
609
+ )
610
+
611
+ demo.launch(share=False)