Messages are much less verbose now.

This commit is contained in:
Storm Dragon
2025-10-22 17:03:09 -04:00
parent b41402b189
commit 7372cbc7ff
4 changed files with 23 additions and 130 deletions
+18 -87
View File
@@ -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
-13
View File
@@ -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
+5 -24
View File
@@ -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)
-6
View File
@@ -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):