diff --git a/bookstorm.py b/bookstorm.py index e6accf3..5756611 100755 --- a/bookstorm.py +++ b/bookstorm.py @@ -78,6 +78,10 @@ class BookReader: self.speechEngine = SpeechEngine() # UI feedback self.audioPlayer = MpvPlayer() # Used for both audio books and TTS playback + # Configure audio player from saved settings + savedVolume = self.config.get_volume() + self.audioPlayer.set_volume(savedVolume) + # Configure speech engine from saved settings speechRate = self.config.get_speech_rate() self.speechEngine.set_rate(speechRate) @@ -246,6 +250,10 @@ class BookReader: if not self.audioPlayer.load_audio_file(self.book.audioPath, playbackSpeed=playbackSpeed): raise Exception("Failed to load audio file") + # Restore saved volume setting + savedVolume = self.config.get_volume() + self.audioPlayer.set_volume(savedVolume) + # Inform user about navigation capabilities if self.book.get_total_chapters() == 1: print("\nNote: This audio file has no chapter markers.") @@ -1220,20 +1228,32 @@ class BookReader: self.speechEngine.speak("No book loaded") elif event.key == pygame.K_0: - # Increase volume (audio books only - mpv handles volume control) - if self.book and hasattr(self.book, 'isAudioBook') and self.book.isAudioBook: - newVolume = self.audioPlayer.increase_volume(5) - self.speechEngine.speak(f"Volume {newVolume}") + # Increase volume (audio books and Piper-TTS - mpv handles volume control) + if self.book: + isAudioBook = hasattr(self.book, 'isAudioBook') and self.book.isAudioBook + isPiperMode = self.config.get_reader_engine() == 'piper' + if isAudioBook or isPiperMode: + newVolume = self.audioPlayer.increase_volume(5) + self.config.set_volume(newVolume) + self.speechEngine.speak(f"Volume {newVolume}") + else: + self.speechEngine.speak("Volume control only works for audio books and Piper TTS mode") else: - self.speechEngine.speak("Volume control only works for audio books") + self.speechEngine.speak("No book loaded") elif event.key == pygame.K_9: - # Decrease volume (audio books only - mpv handles volume control) - if self.book and hasattr(self.book, 'isAudioBook') and self.book.isAudioBook: - newVolume = self.audioPlayer.decrease_volume(5) - self.speechEngine.speak(f"Volume {newVolume}") + # Decrease volume (audio books and Piper-TTS - mpv handles volume control) + if self.book: + isAudioBook = hasattr(self.book, 'isAudioBook') and self.book.isAudioBook + isPiperMode = self.config.get_reader_engine() == 'piper' + if isAudioBook or isPiperMode: + newVolume = self.audioPlayer.decrease_volume(5) + self.config.set_volume(newVolume) + self.speechEngine.speak(f"Volume {newVolume}") + else: + self.speechEngine.speak("Volume control only works for audio books and Piper TTS mode") else: - self.speechEngine.speak("Volume control only works for audio books") + self.speechEngine.speak("No book loaded") elif event.key == pygame.K_HOME and shiftPressed: # Shift+Home: Clear bookmark and jump to beginning of book @@ -1905,6 +1925,10 @@ class BookReader: print("Make sure mpv is installed: sudo pacman -S mpv") return + # Restore saved volume setting + savedVolume = self.config.get_volume() + self.audioPlayer.set_volume(savedVolume) + # Success! Start playing self.speechEngine.speak(f"Now streaming {title}. Press space to pause.") print(f"\nNow streaming: {title} by {author}") @@ -2462,6 +2486,14 @@ class BookReader: self.serverLinkManager.clear_session(str(self.bookPath)) self.sessionId = None + # Save current volume setting if audio book or Piper-TTS was used + if self.book: + isAudioBook = hasattr(self.book, 'isAudioBook') and self.book.isAudioBook + isPiperMode = self.config.get_reader_engine() == 'piper' + if isAudioBook or isPiperMode: + currentVolume = self.audioPlayer.get_volume() + self.config.set_volume(currentVolume) + self._cancel_buffer() # Cleanup audio player (handles both TTS and audio books) self.audioPlayer.cleanup() diff --git a/src/config_manager.py b/src/config_manager.py index 726d8bc..ea47989 100644 --- a/src/config_manager.py +++ b/src/config_manager.py @@ -57,7 +57,8 @@ class ConfigManager: } self.config['Audio'] = { - 'playback_speed': '1.0' + 'playback_speed': '1.0', + 'volume': '100' } self.config['Paths'] = { @@ -335,6 +336,22 @@ class ConfigManager: self.set('Audio', 'playback_speed', str(speed)) self.save() + def get_volume(self): + """Get audio volume (0-200, 100 is normal)""" + try: + volume = int(self.get('Audio', 'volume', '100')) + # Clamp to valid range + return max(0, min(200, volume)) + except ValueError: + return 100 + + def set_volume(self, volume): + """Set audio volume (0-200, 100 is normal)""" + # Clamp to valid range + volume = max(0, min(200, int(volume))) + self.set('Audio', 'volume', str(volume)) + self.save() + # Braille settings def get_braille_enabled(self): diff --git a/src/mpv_player.py b/src/mpv_player.py index cb77cf7..6d2e36b 100644 --- a/src/mpv_player.py +++ b/src/mpv_player.py @@ -28,6 +28,7 @@ class MpvPlayer: self.isPaused = False self.audioFileLoaded = False # Track if audio file is loaded self.playbackSpeed = 1.0 # Current playback speed + self.volume = 100 # Current volume level (0-200) self.endFileCallback = None # Callback for when file finishes self.playlist = [] # Current playlist (for multi-file audiobooks) self.currentPlaylistIndex = 0 # Current file index in playlist @@ -183,6 +184,7 @@ class MpvPlayer: currentVolume = self.player.volume if self.player.volume is not None else 100 # Allow up to 200% for software amplification (useful for quiet audio) newVolume = min(200, currentVolume + step) + self.volume = newVolume self.player.volume = newVolume return int(newVolume) except Exception as e: @@ -206,6 +208,7 @@ class MpvPlayer: # pylint: disable=no-member currentVolume = self.player.volume if self.player.volume is not None else 100 newVolume = max(0, currentVolume - step) + self.volume = newVolume self.player.volume = newVolume return int(newVolume) except Exception as e: @@ -219,15 +222,30 @@ class MpvPlayer: Returns: Current volume (0-200, 100 is normal, >100 is software amplification) """ + # Return our tracked volume instead of querying mpv + # This is more reliable, especially during cleanup + return self.volume + + def set_volume(self, volume): + """ + Set volume level + + Args: + volume: Volume level (0-200, 100 is normal, >100 is software amplification) + """ if not self.isInitialized or not self.player: - return 100 + # Store it anyway for when player is ready + self.volume = max(0, min(200, int(volume))) + return try: + # Clamp to valid range + volume = max(0, min(200, int(volume))) + self.volume = volume # pylint: disable=no-member - volume = self.player.volume - return int(volume) if volume is not None else 100 - except: - return 100 + self.player.volume = volume + except Exception as e: + print(f"Error setting volume: {e}") def cleanup(self): """Cleanup resources""" @@ -283,6 +301,8 @@ class MpvPlayer: self.player.loadfile(audioPath, 'replace') self.player.pause = True # Keep paused until play_audio_file() is called self.player.speed = self.playbackSpeed + # Restore volume setting (important for TTS paragraphs) + self.player.volume = self.volume self.audioFileLoaded = True self.playlist = [] # Clear playlist (single file mode) self.currentPlaylistIndex = 0 @@ -338,6 +358,8 @@ class MpvPlayer: self.player.pause = True self.player.speed = self.playbackSpeed + # Restore volume setting (important for playlist/folder audiobooks) + self.player.volume = self.volume self.audioFileLoaded = True return True