Messages are much less verbose now.
This commit is contained in:
+18
-87
@@ -204,9 +204,7 @@ class BookReader:
|
||||
"""Load and parse the book"""
|
||||
# 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)
|
||||
self.speechEngine.speak(f"Loading audiobook folder {self.bookPath.name}")
|
||||
|
||||
# Use folder audiobook parser
|
||||
self.parser = FolderAudiobookParser()
|
||||
@@ -238,9 +236,6 @@ class BookReader:
|
||||
else:
|
||||
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 hasattr(self.book, 'isAudioBook') and self.book.isAudioBook:
|
||||
# Get saved playback speed from config
|
||||
@@ -262,11 +257,8 @@ class BookReader:
|
||||
|
||||
# Inform user about navigation capabilities
|
||||
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.")
|
||||
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.")
|
||||
|
||||
# Check if this book is linked to Audiobookshelf server
|
||||
@@ -282,10 +274,6 @@ class BookReader:
|
||||
if serverProgress:
|
||||
progressTime = serverProgress.get('currentTime', 0.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
|
||||
if hasattr(self.book, 'isAudioBook') and self.book.isAudioBook:
|
||||
self.savedAudioPosition = progressTime
|
||||
@@ -303,8 +291,8 @@ class BookReader:
|
||||
pass
|
||||
|
||||
serverProgressLoaded = True
|
||||
except Exception as e:
|
||||
print(f"Could not load server progress: {e}")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Fall back to local bookmark if no server progress
|
||||
if not serverProgressLoaded:
|
||||
@@ -313,16 +301,7 @@ class BookReader:
|
||||
self.currentChapter = bookmark['chapterIndex']
|
||||
self.currentParagraph = bookmark['paragraphIndex']
|
||||
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:
|
||||
print("Starting from beginning")
|
||||
self.currentChapter = 0
|
||||
self.currentParagraph = 0
|
||||
self.savedAudioPosition = 0.0
|
||||
@@ -349,10 +328,8 @@ class BookReader:
|
||||
# Generate and play audio (unless muted for Braille-only mode)
|
||||
if not self.brailleOutput.muteVoice:
|
||||
try:
|
||||
print("Generating speech...")
|
||||
wavData = self.ttsEngine.text_to_wav_data(paragraph)
|
||||
if wavData:
|
||||
print("Playing...")
|
||||
completed = self.audioPlayer.play_wav_data(wavData, blocking=True)
|
||||
return completed
|
||||
except Exception as e:
|
||||
@@ -601,14 +578,14 @@ class BookReader:
|
||||
if self.book:
|
||||
self.displayText = "Press SPACE to start reading"
|
||||
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")
|
||||
self.speechEngine.speak("BookStorm ready. Press SPACE to start reading. Press i for info. Press h for help.")
|
||||
|
||||
# Determine book type for message
|
||||
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:
|
||||
self.displayText = "No book loaded"
|
||||
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
|
||||
|
||||
# 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
|
||||
else:
|
||||
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:
|
||||
import resource
|
||||
# pylint: disable=no-member
|
||||
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:
|
||||
memoryWarningShown = True
|
||||
self.speechEngine.speak("Warning: High memory usage detected. Consider restarting BookStorm soon.")
|
||||
print("WARNING: Memory usage exceeds 2GB - consider restarting")
|
||||
except:
|
||||
pass
|
||||
gcCounter = 0
|
||||
@@ -912,9 +885,6 @@ class BookReader:
|
||||
# Common commands (from brltty documentation):
|
||||
# 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
|
||||
# The key format varies by device, but we can check ranges
|
||||
# Typically: 0x04000000 range for panning commands
|
||||
@@ -928,18 +898,13 @@ class BookReader:
|
||||
|
||||
if command == CMD_FWINRT or (keyCode & 0xFF) == 0x01:
|
||||
# Forward panning
|
||||
print("DEBUG: Braille pan forward")
|
||||
self.brailleOutput.pan_forward()
|
||||
elif command == CMD_FWINLT or (keyCode & 0xFF) == 0x02:
|
||||
# Backward panning
|
||||
print("DEBUG: Braille 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:
|
||||
print(f"DEBUG: Error handling Braille key: {e}")
|
||||
except:
|
||||
pass
|
||||
|
||||
def _handle_pygame_key(self, event):
|
||||
"""Handle pygame key event"""
|
||||
@@ -993,8 +958,6 @@ class BookReader:
|
||||
readerEngine = self.config.get_reader_engine()
|
||||
|
||||
if not self.isPlaying:
|
||||
# Speak UI feedback (always safe with separate sessions)
|
||||
self.speechEngine.speak("Starting playback")
|
||||
self.isPlaying = True
|
||||
self._start_paragraph_playback()
|
||||
else:
|
||||
@@ -1002,26 +965,20 @@ class BookReader:
|
||||
if isAudioBook:
|
||||
# Handle audio book pause/resume
|
||||
if self.audioPlayer.is_paused():
|
||||
self.speechEngine.speak("Resuming")
|
||||
self.audioPlayer.resume_audio_file()
|
||||
else:
|
||||
self.speechEngine.speak("Paused")
|
||||
self.audioPlayer.pause_audio_file()
|
||||
elif readerEngine == 'speechd':
|
||||
# Handle speech-dispatcher pause/resume
|
||||
if self.readingEngine.is_reading_paused():
|
||||
self.speechEngine.speak("Resuming")
|
||||
self.readingEngine.resume_reading()
|
||||
else:
|
||||
self.speechEngine.speak("Paused")
|
||||
self.readingEngine.pause_reading()
|
||||
else:
|
||||
# Handle piper-tts pause/resume (now uses audio file methods)
|
||||
if self.audioPlayer.is_paused():
|
||||
self.speechEngine.speak("Resuming")
|
||||
self.audioPlayer.resume_audio_file()
|
||||
else:
|
||||
self.speechEngine.speak("Paused")
|
||||
self.audioPlayer.pause_audio_file()
|
||||
|
||||
elif event.key == pygame.K_n:
|
||||
@@ -1043,8 +1000,6 @@ class BookReader:
|
||||
self._stop_playback()
|
||||
|
||||
if self.next_chapter():
|
||||
# Just say "Next chapter" without title to avoid confusion
|
||||
self.speechEngine.speak("Next chapter")
|
||||
if wasPlaying:
|
||||
self.isPlaying = True
|
||||
self._start_paragraph_playback()
|
||||
@@ -1081,8 +1036,6 @@ class BookReader:
|
||||
self._stop_playback()
|
||||
|
||||
if self.previous_chapter():
|
||||
# Just say "Previous chapter" without title to avoid confusion
|
||||
self.speechEngine.speak("Previous chapter")
|
||||
if wasPlaying:
|
||||
self.isPlaying = True
|
||||
self._start_paragraph_playback()
|
||||
@@ -1339,7 +1292,6 @@ class BookReader:
|
||||
# Shift+Left: Previous chapter
|
||||
self._stop_playback()
|
||||
if self.previous_chapter():
|
||||
self.speechEngine.speak("Previous chapter")
|
||||
if self.isPlaying:
|
||||
self._start_paragraph_playback()
|
||||
else:
|
||||
@@ -1372,7 +1324,6 @@ class BookReader:
|
||||
# Shift+Right: Next chapter
|
||||
self._stop_playback()
|
||||
if self.next_chapter():
|
||||
self.speechEngine.speak("Next chapter")
|
||||
if self.isPlaying:
|
||||
self._start_paragraph_playback()
|
||||
else:
|
||||
@@ -1837,8 +1788,6 @@ class BookReader:
|
||||
author = metadata.get('authorName', '')
|
||||
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)
|
||||
self.speechEngine.speak(f"Loading stream for {title}. Please wait.")
|
||||
@@ -1894,7 +1843,6 @@ class BookReader:
|
||||
# Save server book reference for resume on restart
|
||||
# Use special format: abs://{server_id} so we can detect and resume
|
||||
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)
|
||||
if not self.sessionId:
|
||||
@@ -1925,7 +1873,6 @@ class BookReader:
|
||||
progressTime = serverProgress.get('currentTime', 0.0)
|
||||
minutes = 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
|
||||
self.savedAudioPosition = progressTime
|
||||
@@ -2027,8 +1974,6 @@ class BookReader:
|
||||
duration = media.get('duration', 0.0)
|
||||
numChapters = media.get('numChapters', 0)
|
||||
|
||||
print(f"\nDEBUG: Downloading book ID: {serverId}")
|
||||
print(f"DEBUG: Title from metadata: {title}")
|
||||
|
||||
# Create sanitized filename
|
||||
safeTitle = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).strip()
|
||||
@@ -2150,31 +2095,19 @@ class BookReader:
|
||||
if not isAudioBook:
|
||||
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
|
||||
currentPos = self.audioPlayer.get_audio_position()
|
||||
|
||||
# Calculate new position
|
||||
newPos = max(0.0, currentPos + seconds)
|
||||
|
||||
# Seek to new position
|
||||
if 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
|
||||
# Seek to new position silently (audio feedback is enough)
|
||||
return self.audioPlayer.seek_audio(newPos)
|
||||
|
||||
def _load_new_book(self, bookPath):
|
||||
"""
|
||||
@@ -2359,7 +2292,6 @@ class BookReader:
|
||||
self.savedAudioPosition = 0.0
|
||||
minutes = int(startTime // 60)
|
||||
seconds = int(startTime % 60)
|
||||
print(f"Resuming playback at {minutes}m {seconds}s")
|
||||
else:
|
||||
# Get start time from audio chapter
|
||||
if hasattr(chapter, 'startTime'):
|
||||
@@ -2591,7 +2523,6 @@ def main():
|
||||
if lastBook and lastBook.startswith('abs://'):
|
||||
# Extract server book ID
|
||||
serverId = lastBook[6:] # Remove 'abs://' prefix
|
||||
print(f"Resuming Audiobookshelf book: {serverId}")
|
||||
|
||||
# Try to restore from cached server link
|
||||
from src.server_link_manager import ServerLinkManager
|
||||
|
||||
@@ -294,8 +294,6 @@ class AudiobookshelfClient:
|
||||
downloadUrl = f"{self.serverUrl}/api/items/{itemId}/file"
|
||||
headers = {'Authorization': f'Bearer {self.authToken}'}
|
||||
|
||||
print(f"DEBUG: Downloading from: {downloadUrl}")
|
||||
|
||||
# Download with streaming to handle large files
|
||||
# Use context manager to ensure response cleanup
|
||||
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)
|
||||
if itemDetails:
|
||||
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
|
||||
# This is what the web player uses
|
||||
playUrl = f"{self.serverUrl}/api/items/{itemId}/play"
|
||||
print(f"DEBUG: Requesting play session from: {playUrl}")
|
||||
|
||||
response = requests.post(
|
||||
playUrl,
|
||||
@@ -378,7 +371,6 @@ class AudiobookshelfClient:
|
||||
return None
|
||||
|
||||
playData = response.json()
|
||||
print(f"DEBUG: Play response keys: {list(playData.keys())}")
|
||||
|
||||
# Extract the actual stream URL from the play response
|
||||
# The response contains either 'audioTracks' or direct 'url'
|
||||
@@ -389,15 +381,12 @@ class AudiobookshelfClient:
|
||||
# Multi-file audiobook - use first track or concatenated stream
|
||||
audioTrack = playData['audioTracks'][0]
|
||||
streamUrl = audioTrack.get('contentUrl')
|
||||
print(f"DEBUG: Using audioTrack URL")
|
||||
elif 'url' in playData:
|
||||
# Direct URL
|
||||
streamUrl = playData.get('url')
|
||||
print(f"DEBUG: Using direct URL")
|
||||
elif 'contentUrl' in playData:
|
||||
# Alternative format
|
||||
streamUrl = playData.get('contentUrl')
|
||||
print(f"DEBUG: Using contentUrl")
|
||||
|
||||
if not streamUrl:
|
||||
print(f"ERROR: No stream URL found in play response")
|
||||
@@ -408,7 +397,6 @@ class AudiobookshelfClient:
|
||||
if streamUrl.startswith('/'):
|
||||
streamUrl = f"{self.serverUrl}{streamUrl}"
|
||||
|
||||
print(f"DEBUG: Stream URL: {streamUrl[:100]}...")
|
||||
return streamUrl
|
||||
|
||||
except Exception as e:
|
||||
@@ -551,7 +539,6 @@ class AudiobookshelfClient:
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
sessionId = data.get('id')
|
||||
print(f"DEBUG: Session created successfully: {sessionId}")
|
||||
return sessionId
|
||||
else:
|
||||
# Session creation not critical, just log and continue
|
||||
|
||||
@@ -177,15 +177,6 @@ class AudiobookshelfMenu:
|
||||
elif direction == 'down':
|
||||
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()
|
||||
|
||||
def change_view(self, direction):
|
||||
@@ -375,31 +366,21 @@ class AudiobookshelfMenu:
|
||||
# Book selected
|
||||
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
|
||||
# (they might have incomplete metadata)
|
||||
if not item.get('media'):
|
||||
# Book doesn't have full media details, fetch them
|
||||
bookId = item.get('id') or item.get('libraryItemId')
|
||||
if bookId:
|
||||
print(f"\nFetching full details for book ID: {bookId}")
|
||||
fullDetails = self.absClient.get_library_item_details(bookId)
|
||||
if fullDetails:
|
||||
self.selectedBook = fullDetails
|
||||
item = fullDetails
|
||||
print("Full book details loaded")
|
||||
# Re-extract metadata from full details
|
||||
|
||||
# Get metadata for later use
|
||||
media = item.get('media', {})
|
||||
metadata = media.get('metadata', {})
|
||||
title = metadata.get('title', 'Unknown')
|
||||
print(f"DEBUG SELECT: After fetch, title is: {title}")
|
||||
|
||||
# Check if local copy exists
|
||||
isLocal = self._check_if_local(item)
|
||||
|
||||
@@ -54,8 +54,6 @@ class FolderAudiobookParser:
|
||||
if not audioFiles:
|
||||
raise ValueError(f"No audio files found in: {folderPath}")
|
||||
|
||||
print(f"Found {len(audioFiles)} audio files in folder")
|
||||
|
||||
# Extract metadata from each file
|
||||
fileMetadata = []
|
||||
for audioFile in audioFiles:
|
||||
@@ -98,10 +96,6 @@ class FolderAudiobookParser:
|
||||
book.audioFiles = [fileData['path'] for fileData in sortedFiles]
|
||||
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
|
||||
|
||||
def _find_audio_files(self, folderPath):
|
||||
|
||||
Reference in New Issue
Block a user