Folder based audiobooks added. Still a little fixing of some malfunctioning navigation keys needed.
This commit is contained in:
+81
-28
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user