Skip to content

Commit

Permalink
add multiprocessing
Browse files Browse the repository at this point in the history
  • Loading branch information
kovaacs committed Jan 23, 2025
1 parent cf0d0aa commit b62e85c
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 30 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ usage: main.py [-h] [--tts {azure,openai,edge,piper}]
[--chapter_start CHAPTER_START] [--chapter_end CHAPTER_END]
[--output_text] [--remove_endnotes]
[--search_and_replace_file SEARCH_AND_REPLACE_FILE]
[--worker_count WORKER_COUNT]
[--voice_name VOICE_NAME] [--output_format OUTPUT_FORMAT]
[--model_name MODEL_NAME] [--voice_rate VOICE_RATE]
[--voice_volume VOICE_VOLUME] [--voice_pitch VOICE_PITCH]
Expand Down Expand Up @@ -164,6 +165,14 @@ options:
is: <search>==<replace> Note that you may have to
specify word boundaries, to avoid replacing parts of
words.
--worker_count WORKER_COUNT
Specifies the number of parallel workers to use for
audiobook generation. Increasing this value can
significantly speed up the process by processing
multiple chapters simultaneously. Note: Chapters may
not be processed in sequential order, but this will
not affect the final audiobook.

--voice_name VOICE_NAME
Various TTS providers has different voice names, look
up for your provider settings.
Expand Down
3 changes: 2 additions & 1 deletion audiobook_generator/config/general_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def __init__(self, args):
self.log = args.log
self.no_prompt = args.no_prompt
self.title_mode = args.title_mode
self.worker_count = args.worker_count

# Book parser specific arguments
self.newline_mode = args.newline_mode
Expand Down Expand Up @@ -39,4 +40,4 @@ def __init__(self, args):
self.piper_length_scale = args.piper_length_scale

def __str__(self):
return ', '.join(f"{key}={value}" for key, value in self.__dict__.items())
return ", ".join(f"{key}={value}" for key, value in self.__dict__.items())
76 changes: 47 additions & 29 deletions audiobook_generator/core/audiobook_generator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import multiprocessing
import os

from audiobook_generator.book_parsers.base_book_parser import get_book_parser
Expand Down Expand Up @@ -31,6 +32,36 @@ def __init__(self, config: GeneralConfig):
def __str__(self) -> str:
return f"{self.config}"

def process_chapter(self, idx, title, text, book_parser, tts_provider):
"""Process a single chapter: write text (if needed) and convert to audio."""
try:
logger.info(f"Processing chapter {idx}: {title}")

# Save chapter text if required
if self.config.output_text:
text_file = os.path.join(self.config.output_folder, f"{idx:04d}_{title}.txt")
with open(text_file, "w", encoding="utf-8") as f:
f.write(text)

# Skip audio generation in preview mode
if self.config.preview:
return

# Generate audio file
output_file = os.path.join(
self.config.output_folder,
f"{idx:04d}_{title}.{tts_provider.get_output_file_extension()}",
)
audio_tags = AudioTags(
title, book_parser.get_book_author(), book_parser.get_book_title(), idx
)
tts_provider.text_to_speech(text, output_file, audio_tags)

logger.info(f"✅ Converted chapter {idx}: {title}")
except Exception:
logger.exception(f"Error processing chapter {idx}")
raise

def run(self):
try:
book_parser = get_book_parser(self.config)
Expand Down Expand Up @@ -59,10 +90,14 @@ def run(self):
f"Chapter start index {self.config.chapter_start} is larger than chapter end index {self.config.chapter_end}. Check your input."
)

logger.info(f"Converting chapters from {self.config.chapter_start} to {self.config.chapter_end}.")
logger.info(
f"Converting chapters from {self.config.chapter_start} to {self.config.chapter_end}."
)

# Initialize total_characters to 0
total_characters = get_total_chars(chapters[self.config.chapter_start - 1:self.config.chapter_end])
total_characters = get_total_chars(
chapters[self.config.chapter_start - 1 : self.config.chapter_end]
)
logger.info(f"✨ Total characters in selected book chapters: {total_characters} ✨")
rough_price = tts_provider.estimate_cost(total_characters)
print(f"Estimate book voiceover would cost you roughly: ${rough_price:.2f}\n")
Expand All @@ -75,36 +110,19 @@ def run(self):
else:
confirm_conversion()

# Loop through each chapter and convert it to speech using the provided TTS provider
for idx, (title, text) in enumerate(chapters, start=1):
if idx < self.config.chapter_start:
continue
if idx > self.config.chapter_end:
break
logger.info(
f"Converting chapter {idx}/{len(chapters)}: {title}, characters: {len(text)}"
# Prepare chapters for processing
chapters_to_process = chapters[self.config.chapter_start - 1 : self.config.chapter_end]
tasks = (
(idx, title, text, book_parser, tts_provider)
for idx, (title, text) in enumerate(
chapters_to_process, start=self.config.chapter_start
)
)

if self.config.output_text:
text_file = os.path.join(self.config.output_folder, f"{idx:04d}_{title}.txt")
with open(text_file, "w", encoding='utf-8') as file:
file.write(text)

if self.config.preview:
continue
# Use multiprocessing to process chapters in parallel
with multiprocessing.Pool(processes=self.config.worker_count) as pool:
pool.starmap(self.process_chapter, tasks)

output_file = os.path.join(self.config.output_folder,
f"{idx:04d}_{title}.{tts_provider.get_output_file_extension()}")

audio_tags = AudioTags(title, book_parser.get_book_author(), book_parser.get_book_title(), idx)
tts_provider.text_to_speech(
text,
output_file,
audio_tags,
)
logger.info(
f"✅ Converted chapter {idx}/{len(chapters)}: {title}"
)
logger.info(f"All chapters converted. 🎉🎉🎉")

except KeyboardInterrupt:
Expand Down
9 changes: 9 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ def handle_args():
""",
)

parser.add_argument(
"--worker_count",
type=int,
default=1,
help="Specifies the number of parallel workers to use for audiobook generation. "
"Increasing this value can significantly speed up the process by processing multiple chapters simultaneously. "
"Note: Chapters may not be processed in sequential order, but this will not affect the final audiobook.",
)

parser.add_argument(
"--voice_name",
help="Various TTS providers has different voice names, look up for your provider settings.",
Expand Down

0 comments on commit b62e85c

Please sign in to comment.