Epub now uses spine order for book navigation and headings are included in paragraph navigation.

This commit is contained in:
Storm Dragon
2025-10-19 16:02:19 -04:00
parent a934c06f6f
commit 16e01cb1f5
3 changed files with 272 additions and 220 deletions

View File

@@ -50,6 +50,7 @@ from src.audiobookshelf_client import AudiobookshelfClient
from src.audiobookshelf_menu import AudiobookshelfMenu
from src.server_link_manager import ServerLinkManager
from src.bookmarks_menu import BookmarksMenu
from src.wav_exporter import WavExporter
class BookReader:
@@ -2595,7 +2596,8 @@ def main():
# Handle export mode
if args.wav:
return export_to_wav(bookPath, config, args.outputDir)
exporter = WavExporter(config)
return exporter.export(bookPath, args.outputDir)
# Interactive reading mode
config.set_last_book(bookPath)
@@ -2614,110 +2616,5 @@ def main():
return 0
def export_to_wav(bookPath, config, outputDir=None):
"""
Export book to WAV files split by chapter
Args:
bookPath: Path to book file
config: ConfigManager instance
outputDir: Output directory (optional)
Returns:
Exit code
"""
from src.daisy_parser import DaisyParser
from src.epub_parser import EpubParser
from src.pdf_parser import PdfParser
from src.txt_parser import TxtParser
from src.tts_engine import TtsEngine
import wave
print(f"Exporting book to WAV: {bookPath}")
# Parse book using appropriate parser
bookPath = Path(bookPath)
suffix = bookPath.suffix.lower()
if suffix in ['.epub']:
parser = EpubParser()
elif suffix in ['.zip']:
parser = DaisyParser()
elif suffix in ['.pdf']:
parser = PdfParser()
elif suffix in ['.txt']:
parser = TxtParser()
else:
print(f"Error: Unsupported book format: {suffix}")
return 1
try:
book = parser.parse(bookPath)
except Exception as e:
print(f"Error parsing book: {e}")
return 1
# Determine output directory
if outputDir is None:
bookName = Path(bookPath).stem
outputDir = Path(f"./{bookName}_audio")
else:
outputDir = Path(outputDir)
outputDir.mkdir(parents=True, exist_ok=True)
print(f"Output directory: {outputDir}")
# Initialize TTS engine
readerEngine = config.get_reader_engine()
if readerEngine == 'speechd':
print("Error: WAV export requires piper-tts. Set reader_engine=piper in config.")
return 1
voiceModel = config.get_voice_model()
tts = TtsEngine(voiceModel)
print(f"Using voice: {voiceModel}")
print(f"Chapters: {book.get_total_chapters()}")
print()
# Export each chapter
for chapterIdx in range(book.get_total_chapters()):
chapter = book.get_chapter(chapterIdx)
if not chapter:
continue
chapterNum = chapterIdx + 1
print(f"Exporting Chapter {chapterNum}/{book.get_total_chapters()}: {chapter.title}")
# Combine all paragraphs in chapter
chapterText = "\n\n".join(chapter.paragraphs)
# Generate audio
try:
wavData = tts.text_to_wav_data(chapterText)
if not wavData:
print(f" Warning: No audio generated for chapter {chapterNum}")
continue
# Save to file
sanitizedTitle = "".join(c for c in chapter.title if c.isalnum() or c in (' ', '-', '_')).strip()
if not sanitizedTitle:
sanitizedTitle = f"Chapter_{chapterNum}"
outputFile = outputDir / f"{chapterNum:03d}_{sanitizedTitle}.wav"
with open(outputFile, 'wb') as f:
f.write(wavData)
print(f" Saved: {outputFile.name}")
except Exception as e:
print(f" Error generating audio for chapter {chapterNum}: {e}")
continue
parser.cleanup()
print(f"\nExport complete! Files saved to: {outputDir}")
return 0
if __name__ == '__main__':
sys.exit(main())