Messages are much less verbose now.
This commit is contained in:
+18
-87
@@ -204,9 +204,7 @@ class BookReader:
|
|||||||
"""Load and parse the book"""
|
"""Load and parse the book"""
|
||||||
# Check if bookPath is a directory (folder audiobook)
|
# Check if bookPath is a directory (folder audiobook)
|
||||||
if self.bookPath.is_dir():
|
if self.bookPath.is_dir():
|
||||||
message = f"Loading audiobook folder {self.bookPath.name}"
|
self.speechEngine.speak(f"Loading audiobook folder {self.bookPath.name}")
|
||||||
print(message)
|
|
||||||
self.speechEngine.speak(message)
|
|
||||||
|
|
||||||
# Use folder audiobook parser
|
# Use folder audiobook parser
|
||||||
self.parser = FolderAudiobookParser()
|
self.parser = FolderAudiobookParser()
|
||||||
@@ -238,9 +236,6 @@ class BookReader:
|
|||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported book format: {self.bookPath.suffix}")
|
raise ValueError(f"Unsupported book format: {self.bookPath.suffix}")
|
||||||
|
|
||||||
print(f"Loaded: {self.book.title}")
|
|
||||||
print(f"Chapters: {self.book.get_total_chapters()}")
|
|
||||||
|
|
||||||
# If it's an audio book, load it into the player
|
# If it's an audio book, load it into the player
|
||||||
if hasattr(self.book, 'isAudioBook') and self.book.isAudioBook:
|
if hasattr(self.book, 'isAudioBook') and self.book.isAudioBook:
|
||||||
# Get saved playback speed from config
|
# Get saved playback speed from config
|
||||||
@@ -262,11 +257,8 @@ class BookReader:
|
|||||||
|
|
||||||
# Inform user about navigation capabilities
|
# Inform user about navigation capabilities
|
||||||
if self.book.get_total_chapters() == 1:
|
if self.book.get_total_chapters() == 1:
|
||||||
print("\nNote: This audio file has no chapter markers.")
|
|
||||||
print("Navigation: Only play/pause/stop supported (no chapter jumping)")
|
|
||||||
self.speechEngine.speak("Audio book loaded. No chapter markers found. Only basic playback controls available.")
|
self.speechEngine.speak("Audio book loaded. No chapter markers found. Only basic playback controls available.")
|
||||||
else:
|
else:
|
||||||
print(f"\nChapter navigation: Enabled ({self.book.get_total_chapters()} chapters)")
|
|
||||||
self.speechEngine.speak(f"Audio book loaded with {self.book.get_total_chapters()} chapters. Chapter navigation enabled.")
|
self.speechEngine.speak(f"Audio book loaded with {self.book.get_total_chapters()} chapters. Chapter navigation enabled.")
|
||||||
|
|
||||||
# Check if this book is linked to Audiobookshelf server
|
# Check if this book is linked to Audiobookshelf server
|
||||||
@@ -282,10 +274,6 @@ class BookReader:
|
|||||||
if serverProgress:
|
if serverProgress:
|
||||||
progressTime = serverProgress.get('currentTime', 0.0)
|
progressTime = serverProgress.get('currentTime', 0.0)
|
||||||
if progressTime > 0:
|
if progressTime > 0:
|
||||||
minutes = int(progressTime // 60)
|
|
||||||
seconds = int(progressTime % 60)
|
|
||||||
print(f"Resuming from server progress: {minutes}m {seconds}s")
|
|
||||||
|
|
||||||
# For audio books, save exact position
|
# For audio books, save exact position
|
||||||
if hasattr(self.book, 'isAudioBook') and self.book.isAudioBook:
|
if hasattr(self.book, 'isAudioBook') and self.book.isAudioBook:
|
||||||
self.savedAudioPosition = progressTime
|
self.savedAudioPosition = progressTime
|
||||||
@@ -303,8 +291,8 @@ class BookReader:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
serverProgressLoaded = True
|
serverProgressLoaded = True
|
||||||
except Exception as e:
|
except:
|
||||||
print(f"Could not load server progress: {e}")
|
pass
|
||||||
|
|
||||||
# Fall back to local bookmark if no server progress
|
# Fall back to local bookmark if no server progress
|
||||||
if not serverProgressLoaded:
|
if not serverProgressLoaded:
|
||||||
@@ -313,16 +301,7 @@ class BookReader:
|
|||||||
self.currentChapter = bookmark['chapterIndex']
|
self.currentChapter = bookmark['chapterIndex']
|
||||||
self.currentParagraph = bookmark['paragraphIndex']
|
self.currentParagraph = bookmark['paragraphIndex']
|
||||||
self.savedAudioPosition = bookmark.get('audioPosition', 0.0)
|
self.savedAudioPosition = bookmark.get('audioPosition', 0.0)
|
||||||
|
|
||||||
# For audio books, show resume position
|
|
||||||
if hasattr(self.book, 'isAudioBook') and self.book.isAudioBook and self.savedAudioPosition > 0:
|
|
||||||
minutes = int(self.savedAudioPosition // 60)
|
|
||||||
seconds = int(self.savedAudioPosition % 60)
|
|
||||||
print(f"Resuming from local bookmark: chapter {self.currentChapter + 1} at {minutes}m {seconds}s")
|
|
||||||
else:
|
|
||||||
print(f"Resuming from chapter {self.currentChapter + 1}, paragraph {self.currentParagraph + 1}")
|
|
||||||
else:
|
else:
|
||||||
print("Starting from beginning")
|
|
||||||
self.currentChapter = 0
|
self.currentChapter = 0
|
||||||
self.currentParagraph = 0
|
self.currentParagraph = 0
|
||||||
self.savedAudioPosition = 0.0
|
self.savedAudioPosition = 0.0
|
||||||
@@ -349,10 +328,8 @@ class BookReader:
|
|||||||
# Generate and play audio (unless muted for Braille-only mode)
|
# Generate and play audio (unless muted for Braille-only mode)
|
||||||
if not self.brailleOutput.muteVoice:
|
if not self.brailleOutput.muteVoice:
|
||||||
try:
|
try:
|
||||||
print("Generating speech...")
|
|
||||||
wavData = self.ttsEngine.text_to_wav_data(paragraph)
|
wavData = self.ttsEngine.text_to_wav_data(paragraph)
|
||||||
if wavData:
|
if wavData:
|
||||||
print("Playing...")
|
|
||||||
completed = self.audioPlayer.play_wav_data(wavData, blocking=True)
|
completed = self.audioPlayer.play_wav_data(wavData, blocking=True)
|
||||||
return completed
|
return completed
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -601,14 +578,14 @@ class BookReader:
|
|||||||
if self.book:
|
if self.book:
|
||||||
self.displayText = "Press SPACE to start reading"
|
self.displayText = "Press SPACE to start reading"
|
||||||
self.statusText = f"Book: {self.book.title}"
|
self.statusText = f"Book: {self.book.title}"
|
||||||
print(f"\n{self.book.title} - {self.book.get_total_chapters()} chapters")
|
|
||||||
print("Press SPACE to start reading")
|
# Determine book type for message
|
||||||
self.speechEngine.speak("BookStorm ready. Press SPACE to start reading. Press i for info. Press h for help.")
|
isAudioBook = hasattr(self.book, 'isAudioBook') and self.book.isAudioBook
|
||||||
|
bookType = "Audio book" if isAudioBook else "Book"
|
||||||
|
self.speechEngine.speak(f"{bookType} loaded, press h for help.")
|
||||||
else:
|
else:
|
||||||
self.displayText = "No book loaded"
|
self.displayText = "No book loaded"
|
||||||
self.statusText = "Press A for Audiobookshelf, B for local books, R for recent books"
|
self.statusText = "Press A for Audiobookshelf, B for local books, R for recent books"
|
||||||
print("\nNo book loaded")
|
|
||||||
print("Press A for Audiobookshelf, B for local books, R for recent books")
|
|
||||||
# Speech message already given earlier
|
# Speech message already given earlier
|
||||||
|
|
||||||
# Cached rendered surfaces to prevent memory leak from re-rendering 30 FPS
|
# Cached rendered surfaces to prevent memory leak from re-rendering 30 FPS
|
||||||
@@ -835,18 +812,14 @@ class BookReader:
|
|||||||
gc.collect() # Full collection every 20 seconds
|
gc.collect() # Full collection every 20 seconds
|
||||||
else:
|
else:
|
||||||
gc.collect(generation=0) # Fast collection every 10 seconds
|
gc.collect(generation=0) # Fast collection every 10 seconds
|
||||||
# Debug: Print memory usage every 10 seconds
|
# Memory watchdog: warn if exceeding 2GB (50% on Pi 4GB)
|
||||||
try:
|
try:
|
||||||
import resource
|
import resource
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
memUsage = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024 # MB
|
memUsage = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024 # MB
|
||||||
print(f"DEBUG: Memory usage: {memUsage:.1f} MB")
|
|
||||||
|
|
||||||
# Memory watchdog: warn if exceeding 2GB (50% on Pi 4GB)
|
|
||||||
if memUsage > 2048 and not memoryWarningShown:
|
if memUsage > 2048 and not memoryWarningShown:
|
||||||
memoryWarningShown = True
|
memoryWarningShown = True
|
||||||
self.speechEngine.speak("Warning: High memory usage detected. Consider restarting BookStorm soon.")
|
self.speechEngine.speak("Warning: High memory usage detected. Consider restarting BookStorm soon.")
|
||||||
print("WARNING: Memory usage exceeds 2GB - consider restarting")
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
gcCounter = 0
|
gcCounter = 0
|
||||||
@@ -912,9 +885,6 @@ class BookReader:
|
|||||||
# Common commands (from brltty documentation):
|
# Common commands (from brltty documentation):
|
||||||
# FWINRT (forward) and FWINLT (backward) for panning
|
# FWINRT (forward) and FWINLT (backward) for panning
|
||||||
|
|
||||||
# For debugging, print the key code
|
|
||||||
print(f"DEBUG: Braille key pressed: {keyCode}")
|
|
||||||
|
|
||||||
# Check if it's a panning command
|
# Check if it's a panning command
|
||||||
# The key format varies by device, but we can check ranges
|
# The key format varies by device, but we can check ranges
|
||||||
# Typically: 0x04000000 range for panning commands
|
# Typically: 0x04000000 range for panning commands
|
||||||
@@ -928,18 +898,13 @@ class BookReader:
|
|||||||
|
|
||||||
if command == CMD_FWINRT or (keyCode & 0xFF) == 0x01:
|
if command == CMD_FWINRT or (keyCode & 0xFF) == 0x01:
|
||||||
# Forward panning
|
# Forward panning
|
||||||
print("DEBUG: Braille pan forward")
|
|
||||||
self.brailleOutput.pan_forward()
|
self.brailleOutput.pan_forward()
|
||||||
elif command == CMD_FWINLT or (keyCode & 0xFF) == 0x02:
|
elif command == CMD_FWINLT or (keyCode & 0xFF) == 0x02:
|
||||||
# Backward panning
|
# Backward panning
|
||||||
print("DEBUG: Braille pan backward")
|
|
||||||
self.brailleOutput.pan_backward()
|
self.brailleOutput.pan_backward()
|
||||||
else:
|
|
||||||
# Unknown key - just log it for now
|
|
||||||
print(f"DEBUG: Unknown Braille key: 0x{keyCode:08x}")
|
|
||||||
|
|
||||||
except Exception as e:
|
except:
|
||||||
print(f"DEBUG: Error handling Braille key: {e}")
|
pass
|
||||||
|
|
||||||
def _handle_pygame_key(self, event):
|
def _handle_pygame_key(self, event):
|
||||||
"""Handle pygame key event"""
|
"""Handle pygame key event"""
|
||||||
@@ -993,8 +958,6 @@ class BookReader:
|
|||||||
readerEngine = self.config.get_reader_engine()
|
readerEngine = self.config.get_reader_engine()
|
||||||
|
|
||||||
if not self.isPlaying:
|
if not self.isPlaying:
|
||||||
# Speak UI feedback (always safe with separate sessions)
|
|
||||||
self.speechEngine.speak("Starting playback")
|
|
||||||
self.isPlaying = True
|
self.isPlaying = True
|
||||||
self._start_paragraph_playback()
|
self._start_paragraph_playback()
|
||||||
else:
|
else:
|
||||||
@@ -1002,26 +965,20 @@ class BookReader:
|
|||||||
if isAudioBook:
|
if isAudioBook:
|
||||||
# Handle audio book pause/resume
|
# Handle audio book pause/resume
|
||||||
if self.audioPlayer.is_paused():
|
if self.audioPlayer.is_paused():
|
||||||
self.speechEngine.speak("Resuming")
|
|
||||||
self.audioPlayer.resume_audio_file()
|
self.audioPlayer.resume_audio_file()
|
||||||
else:
|
else:
|
||||||
self.speechEngine.speak("Paused")
|
|
||||||
self.audioPlayer.pause_audio_file()
|
self.audioPlayer.pause_audio_file()
|
||||||
elif readerEngine == 'speechd':
|
elif readerEngine == 'speechd':
|
||||||
# Handle speech-dispatcher pause/resume
|
# Handle speech-dispatcher pause/resume
|
||||||
if self.readingEngine.is_reading_paused():
|
if self.readingEngine.is_reading_paused():
|
||||||
self.speechEngine.speak("Resuming")
|
|
||||||
self.readingEngine.resume_reading()
|
self.readingEngine.resume_reading()
|
||||||
else:
|
else:
|
||||||
self.speechEngine.speak("Paused")
|
|
||||||
self.readingEngine.pause_reading()
|
self.readingEngine.pause_reading()
|
||||||
else:
|
else:
|
||||||
# Handle piper-tts pause/resume (now uses audio file methods)
|
# Handle piper-tts pause/resume (now uses audio file methods)
|
||||||
if self.audioPlayer.is_paused():
|
if self.audioPlayer.is_paused():
|
||||||
self.speechEngine.speak("Resuming")
|
|
||||||
self.audioPlayer.resume_audio_file()
|
self.audioPlayer.resume_audio_file()
|
||||||
else:
|
else:
|
||||||
self.speechEngine.speak("Paused")
|
|
||||||
self.audioPlayer.pause_audio_file()
|
self.audioPlayer.pause_audio_file()
|
||||||
|
|
||||||
elif event.key == pygame.K_n:
|
elif event.key == pygame.K_n:
|
||||||
@@ -1043,8 +1000,6 @@ class BookReader:
|
|||||||
self._stop_playback()
|
self._stop_playback()
|
||||||
|
|
||||||
if self.next_chapter():
|
if self.next_chapter():
|
||||||
# Just say "Next chapter" without title to avoid confusion
|
|
||||||
self.speechEngine.speak("Next chapter")
|
|
||||||
if wasPlaying:
|
if wasPlaying:
|
||||||
self.isPlaying = True
|
self.isPlaying = True
|
||||||
self._start_paragraph_playback()
|
self._start_paragraph_playback()
|
||||||
@@ -1081,8 +1036,6 @@ class BookReader:
|
|||||||
self._stop_playback()
|
self._stop_playback()
|
||||||
|
|
||||||
if self.previous_chapter():
|
if self.previous_chapter():
|
||||||
# Just say "Previous chapter" without title to avoid confusion
|
|
||||||
self.speechEngine.speak("Previous chapter")
|
|
||||||
if wasPlaying:
|
if wasPlaying:
|
||||||
self.isPlaying = True
|
self.isPlaying = True
|
||||||
self._start_paragraph_playback()
|
self._start_paragraph_playback()
|
||||||
@@ -1339,7 +1292,6 @@ class BookReader:
|
|||||||
# Shift+Left: Previous chapter
|
# Shift+Left: Previous chapter
|
||||||
self._stop_playback()
|
self._stop_playback()
|
||||||
if self.previous_chapter():
|
if self.previous_chapter():
|
||||||
self.speechEngine.speak("Previous chapter")
|
|
||||||
if self.isPlaying:
|
if self.isPlaying:
|
||||||
self._start_paragraph_playback()
|
self._start_paragraph_playback()
|
||||||
else:
|
else:
|
||||||
@@ -1372,7 +1324,6 @@ class BookReader:
|
|||||||
# Shift+Right: Next chapter
|
# Shift+Right: Next chapter
|
||||||
self._stop_playback()
|
self._stop_playback()
|
||||||
if self.next_chapter():
|
if self.next_chapter():
|
||||||
self.speechEngine.speak("Next chapter")
|
|
||||||
if self.isPlaying:
|
if self.isPlaying:
|
||||||
self._start_paragraph_playback()
|
self._start_paragraph_playback()
|
||||||
else:
|
else:
|
||||||
@@ -1837,8 +1788,6 @@ class BookReader:
|
|||||||
author = metadata.get('authorName', '')
|
author = metadata.get('authorName', '')
|
||||||
duration = media.get('duration', 0.0)
|
duration = media.get('duration', 0.0)
|
||||||
|
|
||||||
print(f"\nDEBUG: Streaming book ID: {serverId}")
|
|
||||||
print(f"DEBUG: Title from metadata: {title}")
|
|
||||||
|
|
||||||
# Get streaming URL (pass full book details to avoid re-fetching)
|
# Get streaming URL (pass full book details to avoid re-fetching)
|
||||||
self.speechEngine.speak(f"Loading stream for {title}. Please wait.")
|
self.speechEngine.speak(f"Loading stream for {title}. Please wait.")
|
||||||
@@ -1894,7 +1843,6 @@ class BookReader:
|
|||||||
# Save server book reference for resume on restart
|
# Save server book reference for resume on restart
|
||||||
# Use special format: abs://{server_id} so we can detect and resume
|
# Use special format: abs://{server_id} so we can detect and resume
|
||||||
self.config.set_last_book(f"abs://{serverId}")
|
self.config.set_last_book(f"abs://{serverId}")
|
||||||
print(f"DEBUG: Saved last_book as: abs://{serverId}")
|
|
||||||
|
|
||||||
# Create listening session (only if we don't already have one from resume)
|
# Create listening session (only if we don't already have one from resume)
|
||||||
if not self.sessionId:
|
if not self.sessionId:
|
||||||
@@ -1925,7 +1873,6 @@ class BookReader:
|
|||||||
progressTime = serverProgress.get('currentTime', 0.0)
|
progressTime = serverProgress.get('currentTime', 0.0)
|
||||||
minutes = int(progressTime // 60)
|
minutes = int(progressTime // 60)
|
||||||
seconds = int(progressTime % 60)
|
seconds = int(progressTime % 60)
|
||||||
print(f"Resuming from server progress: {minutes}m {seconds}s ({progressTime:.1f}s)")
|
|
||||||
|
|
||||||
# Save the exact position for playback resume
|
# Save the exact position for playback resume
|
||||||
self.savedAudioPosition = progressTime
|
self.savedAudioPosition = progressTime
|
||||||
@@ -2027,8 +1974,6 @@ class BookReader:
|
|||||||
duration = media.get('duration', 0.0)
|
duration = media.get('duration', 0.0)
|
||||||
numChapters = media.get('numChapters', 0)
|
numChapters = media.get('numChapters', 0)
|
||||||
|
|
||||||
print(f"\nDEBUG: Downloading book ID: {serverId}")
|
|
||||||
print(f"DEBUG: Title from metadata: {title}")
|
|
||||||
|
|
||||||
# Create sanitized filename
|
# Create sanitized filename
|
||||||
safeTitle = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).strip()
|
safeTitle = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).strip()
|
||||||
@@ -2150,31 +2095,19 @@ class BookReader:
|
|||||||
if not isAudioBook:
|
if not isAudioBook:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Check if audio is actually loaded/playing
|
||||||
|
if not self.audioPlayer.is_audio_file_loaded():
|
||||||
|
self.speechEngine.speak("Nothing playing")
|
||||||
|
return False
|
||||||
|
|
||||||
# Get current position
|
# Get current position
|
||||||
currentPos = self.audioPlayer.get_audio_position()
|
currentPos = self.audioPlayer.get_audio_position()
|
||||||
|
|
||||||
# Calculate new position
|
# Calculate new position
|
||||||
newPos = max(0.0, currentPos + seconds)
|
newPos = max(0.0, currentPos + seconds)
|
||||||
|
|
||||||
# Seek to new position
|
# Seek to new position silently (audio feedback is enough)
|
||||||
if self.audioPlayer.seek_audio(newPos):
|
return self.audioPlayer.seek_audio(newPos)
|
||||||
# Format time for speech feedback
|
|
||||||
absSeconds = abs(seconds)
|
|
||||||
if absSeconds >= 60:
|
|
||||||
minutes = int(absSeconds // 60)
|
|
||||||
secs = int(absSeconds % 60)
|
|
||||||
if secs > 0:
|
|
||||||
timeStr = f"{minutes} minutes {secs} seconds"
|
|
||||||
else:
|
|
||||||
timeStr = f"{minutes} minutes"
|
|
||||||
else:
|
|
||||||
timeStr = f"{int(absSeconds)} seconds"
|
|
||||||
|
|
||||||
direction = "forward" if seconds > 0 else "backward"
|
|
||||||
self.speechEngine.speak(f"Seek {timeStr} {direction}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _load_new_book(self, bookPath):
|
def _load_new_book(self, bookPath):
|
||||||
"""
|
"""
|
||||||
@@ -2359,7 +2292,6 @@ class BookReader:
|
|||||||
self.savedAudioPosition = 0.0
|
self.savedAudioPosition = 0.0
|
||||||
minutes = int(startTime // 60)
|
minutes = int(startTime // 60)
|
||||||
seconds = int(startTime % 60)
|
seconds = int(startTime % 60)
|
||||||
print(f"Resuming playback at {minutes}m {seconds}s")
|
|
||||||
else:
|
else:
|
||||||
# Get start time from audio chapter
|
# Get start time from audio chapter
|
||||||
if hasattr(chapter, 'startTime'):
|
if hasattr(chapter, 'startTime'):
|
||||||
@@ -2591,7 +2523,6 @@ def main():
|
|||||||
if lastBook and lastBook.startswith('abs://'):
|
if lastBook and lastBook.startswith('abs://'):
|
||||||
# Extract server book ID
|
# Extract server book ID
|
||||||
serverId = lastBook[6:] # Remove 'abs://' prefix
|
serverId = lastBook[6:] # Remove 'abs://' prefix
|
||||||
print(f"Resuming Audiobookshelf book: {serverId}")
|
|
||||||
|
|
||||||
# Try to restore from cached server link
|
# Try to restore from cached server link
|
||||||
from src.server_link_manager import ServerLinkManager
|
from src.server_link_manager import ServerLinkManager
|
||||||
|
|||||||
@@ -294,8 +294,6 @@ class AudiobookshelfClient:
|
|||||||
downloadUrl = f"{self.serverUrl}/api/items/{itemId}/file"
|
downloadUrl = f"{self.serverUrl}/api/items/{itemId}/file"
|
||||||
headers = {'Authorization': f'Bearer {self.authToken}'}
|
headers = {'Authorization': f'Bearer {self.authToken}'}
|
||||||
|
|
||||||
print(f"DEBUG: Downloading from: {downloadUrl}")
|
|
||||||
|
|
||||||
# Download with streaming to handle large files
|
# Download with streaming to handle large files
|
||||||
# Use context manager to ensure response cleanup
|
# Use context manager to ensure response cleanup
|
||||||
with requests.get(downloadUrl, headers=headers, stream=True, timeout=30) as response:
|
with requests.get(downloadUrl, headers=headers, stream=True, timeout=30) as response:
|
||||||
@@ -348,14 +346,9 @@ class AudiobookshelfClient:
|
|||||||
# Validate item exists and has audio content (optional check)
|
# Validate item exists and has audio content (optional check)
|
||||||
if itemDetails:
|
if itemDetails:
|
||||||
media = itemDetails.get('media', {})
|
media = itemDetails.get('media', {})
|
||||||
numAudioFiles = media.get('numAudioFiles', 0)
|
|
||||||
duration = media.get('duration', 0.0)
|
|
||||||
print(f"DEBUG: Item has {numAudioFiles} audio files, duration {duration}s")
|
|
||||||
|
|
||||||
# Use the /play endpoint which creates a playback session and returns stream info
|
# Use the /play endpoint which creates a playback session and returns stream info
|
||||||
# This is what the web player uses
|
# This is what the web player uses
|
||||||
playUrl = f"{self.serverUrl}/api/items/{itemId}/play"
|
playUrl = f"{self.serverUrl}/api/items/{itemId}/play"
|
||||||
print(f"DEBUG: Requesting play session from: {playUrl}")
|
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
playUrl,
|
playUrl,
|
||||||
@@ -378,7 +371,6 @@ class AudiobookshelfClient:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
playData = response.json()
|
playData = response.json()
|
||||||
print(f"DEBUG: Play response keys: {list(playData.keys())}")
|
|
||||||
|
|
||||||
# Extract the actual stream URL from the play response
|
# Extract the actual stream URL from the play response
|
||||||
# The response contains either 'audioTracks' or direct 'url'
|
# The response contains either 'audioTracks' or direct 'url'
|
||||||
@@ -389,15 +381,12 @@ class AudiobookshelfClient:
|
|||||||
# Multi-file audiobook - use first track or concatenated stream
|
# Multi-file audiobook - use first track or concatenated stream
|
||||||
audioTrack = playData['audioTracks'][0]
|
audioTrack = playData['audioTracks'][0]
|
||||||
streamUrl = audioTrack.get('contentUrl')
|
streamUrl = audioTrack.get('contentUrl')
|
||||||
print(f"DEBUG: Using audioTrack URL")
|
|
||||||
elif 'url' in playData:
|
elif 'url' in playData:
|
||||||
# Direct URL
|
# Direct URL
|
||||||
streamUrl = playData.get('url')
|
streamUrl = playData.get('url')
|
||||||
print(f"DEBUG: Using direct URL")
|
|
||||||
elif 'contentUrl' in playData:
|
elif 'contentUrl' in playData:
|
||||||
# Alternative format
|
# Alternative format
|
||||||
streamUrl = playData.get('contentUrl')
|
streamUrl = playData.get('contentUrl')
|
||||||
print(f"DEBUG: Using contentUrl")
|
|
||||||
|
|
||||||
if not streamUrl:
|
if not streamUrl:
|
||||||
print(f"ERROR: No stream URL found in play response")
|
print(f"ERROR: No stream URL found in play response")
|
||||||
@@ -408,7 +397,6 @@ class AudiobookshelfClient:
|
|||||||
if streamUrl.startswith('/'):
|
if streamUrl.startswith('/'):
|
||||||
streamUrl = f"{self.serverUrl}{streamUrl}"
|
streamUrl = f"{self.serverUrl}{streamUrl}"
|
||||||
|
|
||||||
print(f"DEBUG: Stream URL: {streamUrl[:100]}...")
|
|
||||||
return streamUrl
|
return streamUrl
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -551,7 +539,6 @@ class AudiobookshelfClient:
|
|||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
sessionId = data.get('id')
|
sessionId = data.get('id')
|
||||||
print(f"DEBUG: Session created successfully: {sessionId}")
|
|
||||||
return sessionId
|
return sessionId
|
||||||
else:
|
else:
|
||||||
# Session creation not critical, just log and continue
|
# Session creation not critical, just log and continue
|
||||||
|
|||||||
@@ -177,15 +177,6 @@ class AudiobookshelfMenu:
|
|||||||
elif direction == 'down':
|
elif direction == 'down':
|
||||||
self.currentSelection = (self.currentSelection + 1) % len(self.items)
|
self.currentSelection = (self.currentSelection + 1) % len(self.items)
|
||||||
|
|
||||||
# Debug output
|
|
||||||
print(f"DEBUG NAV: {direction} - moved from {oldSelection} to {self.currentSelection} (total items: {len(self.items)})")
|
|
||||||
if self.currentSelection < len(self.items):
|
|
||||||
item = self.items[self.currentSelection]
|
|
||||||
media = item.get('media', {})
|
|
||||||
metadata = media.get('metadata', {})
|
|
||||||
title = metadata.get('title', 'Unknown')
|
|
||||||
print(f"DEBUG NAV: Current item title: {title}")
|
|
||||||
|
|
||||||
self._speak_current_item()
|
self._speak_current_item()
|
||||||
|
|
||||||
def change_view(self, direction):
|
def change_view(self, direction):
|
||||||
@@ -375,31 +366,21 @@ class AudiobookshelfMenu:
|
|||||||
# Book selected
|
# Book selected
|
||||||
self.selectedBook = item
|
self.selectedBook = item
|
||||||
|
|
||||||
# Debug: what book did user select?
|
|
||||||
media = item.get('media', {})
|
|
||||||
metadata = media.get('metadata', {})
|
|
||||||
title = metadata.get('title', 'Unknown')
|
|
||||||
print(f"\nDEBUG SELECT: User pressed ENTER on item {self.currentSelection}")
|
|
||||||
print(f"DEBUG SELECT: Book title: {title}")
|
|
||||||
print(f"DEBUG SELECT: Book ID: {item.get('id', 'NO ID')}")
|
|
||||||
|
|
||||||
# For books from series/collections, fetch full details if needed
|
# For books from series/collections, fetch full details if needed
|
||||||
# (they might have incomplete metadata)
|
# (they might have incomplete metadata)
|
||||||
if not item.get('media'):
|
if not item.get('media'):
|
||||||
# Book doesn't have full media details, fetch them
|
# Book doesn't have full media details, fetch them
|
||||||
bookId = item.get('id') or item.get('libraryItemId')
|
bookId = item.get('id') or item.get('libraryItemId')
|
||||||
if bookId:
|
if bookId:
|
||||||
print(f"\nFetching full details for book ID: {bookId}")
|
|
||||||
fullDetails = self.absClient.get_library_item_details(bookId)
|
fullDetails = self.absClient.get_library_item_details(bookId)
|
||||||
if fullDetails:
|
if fullDetails:
|
||||||
self.selectedBook = fullDetails
|
self.selectedBook = fullDetails
|
||||||
item = fullDetails
|
item = fullDetails
|
||||||
print("Full book details loaded")
|
|
||||||
# Re-extract metadata from full details
|
# Get metadata for later use
|
||||||
media = item.get('media', {})
|
media = item.get('media', {})
|
||||||
metadata = media.get('metadata', {})
|
metadata = media.get('metadata', {})
|
||||||
title = metadata.get('title', 'Unknown')
|
title = metadata.get('title', 'Unknown')
|
||||||
print(f"DEBUG SELECT: After fetch, title is: {title}")
|
|
||||||
|
|
||||||
# Check if local copy exists
|
# Check if local copy exists
|
||||||
isLocal = self._check_if_local(item)
|
isLocal = self._check_if_local(item)
|
||||||
|
|||||||
@@ -54,8 +54,6 @@ class FolderAudiobookParser:
|
|||||||
if not audioFiles:
|
if not audioFiles:
|
||||||
raise ValueError(f"No audio files found in: {folderPath}")
|
raise ValueError(f"No audio files found in: {folderPath}")
|
||||||
|
|
||||||
print(f"Found {len(audioFiles)} audio files in folder")
|
|
||||||
|
|
||||||
# Extract metadata from each file
|
# Extract metadata from each file
|
||||||
fileMetadata = []
|
fileMetadata = []
|
||||||
for audioFile in audioFiles:
|
for audioFile in audioFiles:
|
||||||
@@ -98,10 +96,6 @@ class FolderAudiobookParser:
|
|||||||
book.audioFiles = [fileData['path'] for fileData in sortedFiles]
|
book.audioFiles = [fileData['path'] for fileData in sortedFiles]
|
||||||
book.isMultiFile = True
|
book.isMultiFile = True
|
||||||
|
|
||||||
print(f"Loaded: {book.title} by {book.author}")
|
|
||||||
print(f"Total duration: {totalDuration / 60:.1f} minutes")
|
|
||||||
print(f"Chapters: {len(book.chapters)}")
|
|
||||||
|
|
||||||
return book
|
return book
|
||||||
|
|
||||||
def _find_audio_files(self, folderPath):
|
def _find_audio_files(self, folderPath):
|
||||||
|
|||||||
Reference in New Issue
Block a user