Folder based audiobooks added. Still a little fixing of some malfunctioning navigation keys needed.

This commit is contained in:
Storm Dragon
2025-10-13 18:50:35 -04:00
parent 57e4dceabf
commit 2106143768
4 changed files with 605 additions and 44 deletions
+81 -28
View File
@@ -35,6 +35,7 @@ from src.epub_parser import EpubParser
from src.pdf_parser import PdfParser
from src.txt_parser import TxtParser
from src.audio_parser import AudioParser
from src.folder_audiobook_parser import FolderAudiobookParser
from src.bookmark_manager import BookmarkManager
from src.tts_engine import TtsEngine
from src.config_manager import ConfigManager
@@ -158,31 +159,41 @@ class BookReader:
def load_book(self):
"""Load and parse the book"""
message = f"Loading book {self.bookPath.stem}"
print(message)
self.speechEngine.speak(message)
# Check if bookPath is a directory (folder audiobook)
if self.bookPath.is_dir():
message = f"Loading audiobook folder {self.bookPath.name}"
print(message)
self.speechEngine.speak(message)
# Detect format and create appropriate parser
suffix = self.bookPath.suffix.lower()
if suffix in ['.epub']:
self.parser = EpubParser()
self.book = self.parser.parse(self.bookPath)
elif suffix in ['.zip']:
# Assume DAISY format for zip files
self.parser = DaisyParser()
self.book = self.parser.parse(self.bookPath)
elif suffix in ['.pdf']:
self.parser = PdfParser()
self.book = self.parser.parse(self.bookPath)
elif suffix in ['.txt']:
self.parser = TxtParser()
self.book = self.parser.parse(self.bookPath)
elif suffix in ['.m4b', '.m4a', '.mp3']:
# Audio book file
self.parser = AudioParser()
# Use folder audiobook parser
self.parser = FolderAudiobookParser()
self.book = self.parser.parse(self.bookPath)
else:
raise ValueError(f"Unsupported book format: {self.bookPath.suffix}")
message = f"Loading book {self.bookPath.stem}"
print(message)
self.speechEngine.speak(message)
# Detect format and create appropriate parser
suffix = self.bookPath.suffix.lower()
if suffix in ['.epub']:
self.parser = EpubParser()
self.book = self.parser.parse(self.bookPath)
elif suffix in ['.zip']:
# Assume DAISY format for zip files
self.parser = DaisyParser()
self.book = self.parser.parse(self.bookPath)
elif suffix in ['.pdf']:
self.parser = PdfParser()
self.book = self.parser.parse(self.bookPath)
elif suffix in ['.txt']:
self.parser = TxtParser()
self.book = self.parser.parse(self.bookPath)
elif suffix in ['.m4b', '.m4a', '.mp3']:
# Audio book file
self.parser = AudioParser()
self.book = self.parser.parse(self.bookPath)
else:
raise ValueError(f"Unsupported book format: {self.bookPath.suffix}")
print(f"Loaded: {self.book.title}")
print(f"Chapters: {self.book.get_total_chapters()}")
@@ -191,8 +202,16 @@ class BookReader:
if hasattr(self.book, 'isAudioBook') and self.book.isAudioBook:
# Get saved playback speed from config
playbackSpeed = self.config.get_playback_speed()
if not self.audioPlayer.load_audio_file(self.book.audioPath, playbackSpeed=playbackSpeed):
raise Exception("Failed to load audio file")
# Check if multi-file audiobook (folder)
if hasattr(self.book, 'isMultiFile') and self.book.isMultiFile:
# Multi-file audiobook - load playlist into player
if not self.audioPlayer.load_audio_playlist(self.book.audioFiles, playbackSpeed=playbackSpeed):
raise Exception("Failed to load audio playlist")
else:
# Single-file audiobook
if not self.audioPlayer.load_audio_file(self.book.audioPath, playbackSpeed=playbackSpeed):
raise Exception("Failed to load audio file")
# Inform user about navigation capabilities
if self.book.get_total_chapters() == 1:
@@ -345,6 +364,11 @@ class BookReader:
# For audio books, calculate current playback position
audioPosition = 0.0
if hasattr(self.book, 'isAudioBook') and self.book.isAudioBook:
# For multi-file audiobooks, sync currentChapter with playlist position
if hasattr(self.book, 'isMultiFile') and self.book.isMultiFile:
playlistIndex = self.audioPlayer.get_current_playlist_index()
self.currentChapter = playlistIndex
# Get current chapter start time
chapter = self.book.get_chapter(self.currentChapter)
if chapter and hasattr(chapter, 'startTime'):
@@ -1099,7 +1123,7 @@ class BookReader:
positionInChapter = audioPosition - chapter.startTime
# Seek to position
if self.audioPlayer.is_audio_file_loaded():
self.audioPlayer.seek_audio_position(audioPosition)
self.audioPlayer.seek_audio(audioPosition)
break
# Speak feedback
@@ -1849,10 +1873,15 @@ class BookReader:
self.displayText = f"Playing: {chapter.title}"
self.statusText = f"Ch {self.currentChapter + 1}/{self.book.get_total_chapters()}: {chapter.title}"
# Check if multi-file audiobook (each chapter is a separate file)
isMultiFile = hasattr(self.book, 'isMultiFile') and self.book.isMultiFile
# Determine start position
# If we have a saved audio position and we're on the saved chapter, use it
if self.savedAudioPosition > 0.0:
startTime = self.savedAudioPosition
# Save the position before clearing it (we'll need it later for multi-file)
resumePosition = self.savedAudioPosition
if resumePosition > 0.0:
startTime = resumePosition
# Clear saved position so we don't use it again (only for initial resume)
self.savedAudioPosition = 0.0
minutes = int(startTime // 60)
@@ -1867,7 +1896,31 @@ class BookReader:
# Seek to position and play
if self.audioPlayer.audioFileLoaded:
self.audioPlayer.play_audio_file(startPosition=startTime)
if isMultiFile:
# For multi-file audiobooks, seek to the correct file in playlist
# Chapter index = file index (each file is a chapter)
if self.audioPlayer.seek_to_playlist_index(self.currentChapter):
# Calculate position within current file
# If resuming (saved position > 0), use position relative to chapter start
positionInFile = 0.0
if resumePosition > 0.0:
# Get current chapter's start time to calculate position within file
chapter = self.book.get_chapter(self.currentChapter)
if chapter and hasattr(chapter, 'startTime'):
positionInFile = resumePosition - chapter.startTime
# Ensure position is within file bounds
if positionInFile < 0:
positionInFile = 0.0
elif hasattr(chapter, 'duration') and positionInFile > chapter.duration:
positionInFile = chapter.duration
# Start playback at calculated position
self.audioPlayer.play_audio_file(startPosition=positionInFile)
else:
print(f"ERROR: Could not seek to playlist index {self.currentChapter}")
self.isPlaying = False
else:
# Single-file audiobook with chapter markers
self.audioPlayer.play_audio_file(startPosition=startTime)
else:
print("ERROR: Audio file not loaded!")
self.isPlaying = False