Bookmarks improved. Removed old terminal based entry method, now using the pygame version.
This commit is contained in:
@@ -36,77 +36,6 @@ class BookSelector:
|
||||
self.inBrowser = False
|
||||
self.items = []
|
||||
|
||||
def select_book_interactive(self):
|
||||
"""
|
||||
Interactive book selection with directory navigation
|
||||
|
||||
Returns:
|
||||
Selected book path or None if cancelled
|
||||
"""
|
||||
while True:
|
||||
print(f"\nCurrent directory: {self.currentDir}")
|
||||
print("-" * 60)
|
||||
|
||||
# List directories and supported files
|
||||
items = self._list_items()
|
||||
|
||||
if not items:
|
||||
print("No books or directories found")
|
||||
print("\nCommands:")
|
||||
print(" .. - Go to parent directory")
|
||||
print(" q - Cancel")
|
||||
print()
|
||||
|
||||
choice = input("Select> ").strip()
|
||||
if choice == 'q':
|
||||
return None
|
||||
elif choice == '..':
|
||||
self._go_parent()
|
||||
continue
|
||||
|
||||
# Display items
|
||||
for idx, item in enumerate(items):
|
||||
prefix = "[DIR]" if item['isDir'] else "[BOOK]"
|
||||
print(f"{idx + 1}. {prefix} {item['name']}")
|
||||
|
||||
print("-" * 60)
|
||||
print("\nCommands:")
|
||||
print(" <number> - Select item")
|
||||
print(" .. - Go to parent directory")
|
||||
print(" q - Cancel")
|
||||
print()
|
||||
|
||||
try:
|
||||
choice = input("Select> ").strip()
|
||||
|
||||
if choice == 'q':
|
||||
return None
|
||||
|
||||
elif choice == '..':
|
||||
self._go_parent()
|
||||
|
||||
else:
|
||||
# Select item by number
|
||||
try:
|
||||
itemNum = int(choice)
|
||||
if 1 <= itemNum <= len(items):
|
||||
selectedItem = items[itemNum - 1]
|
||||
|
||||
if selectedItem['isDir']:
|
||||
# Navigate into directory
|
||||
self.currentDir = selectedItem['path']
|
||||
else:
|
||||
# Return selected book
|
||||
return str(selectedItem['path'])
|
||||
else:
|
||||
print(f"Invalid number. Choose 1-{len(items)}")
|
||||
|
||||
except ValueError:
|
||||
print("Invalid input. Enter a number, '..' for parent, or 'q' to cancel")
|
||||
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print("\nCancelled")
|
||||
return None
|
||||
|
||||
def _list_items(self):
|
||||
"""
|
||||
@@ -161,14 +90,6 @@ class BookSelector:
|
||||
|
||||
return items
|
||||
|
||||
def _go_parent(self):
|
||||
"""Navigate to parent directory"""
|
||||
parent = self.currentDir.parent
|
||||
if parent != self.currentDir: # Not at root
|
||||
self.currentDir = parent
|
||||
else:
|
||||
print("Already at root directory")
|
||||
|
||||
def _is_daisy_zip(self, zipPath):
|
||||
"""
|
||||
Check if a zip file contains a DAISY book
|
||||
|
||||
@@ -272,6 +272,20 @@ class BookmarkManager:
|
||||
cursor.execute('DELETE FROM named_bookmarks WHERE id = ?', (bookmarkId,))
|
||||
conn.commit()
|
||||
|
||||
def delete_all_named_bookmarks(self, bookPath):
|
||||
"""
|
||||
Delete all named bookmarks for a specific book
|
||||
|
||||
Args:
|
||||
bookPath: Path to book file
|
||||
"""
|
||||
bookId = self._get_book_id(bookPath)
|
||||
|
||||
with sqlite3.connect(self.dbPath) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('DELETE FROM named_bookmarks WHERE book_id = ?', (bookId,))
|
||||
conn.commit()
|
||||
|
||||
def get_named_bookmark_by_id(self, bookmarkId):
|
||||
"""
|
||||
Get a named bookmark by ID
|
||||
|
||||
+46
-11
@@ -382,8 +382,9 @@ class MpvPlayer:
|
||||
return False
|
||||
|
||||
try:
|
||||
# Seek to start position
|
||||
if startPosition > 0:
|
||||
# Seek to start position (including 0)
|
||||
# Always seek to ensure we're at the right position, even for position 0
|
||||
if startPosition >= 0:
|
||||
self.player.seek(startPosition, reference='absolute')
|
||||
|
||||
# Start playback
|
||||
@@ -459,6 +460,24 @@ class MpvPlayer:
|
||||
position = 0.0
|
||||
|
||||
if position > 0:
|
||||
# Validate position against file duration
|
||||
duration = None
|
||||
try:
|
||||
# pylint: disable=no-member
|
||||
duration = self.player.duration
|
||||
|
||||
if duration is None:
|
||||
# mpv hasn't loaded duration yet - wait a bit
|
||||
import time
|
||||
time.sleep(0.2)
|
||||
duration = self.player.duration
|
||||
|
||||
if duration and position > duration:
|
||||
position = max(0, duration - 1.0) # Seek to 1 second before end
|
||||
except:
|
||||
# If we can't get duration, DON'T seek - it will likely fail
|
||||
return False
|
||||
|
||||
self.player.seek(position, reference='absolute')
|
||||
|
||||
return True
|
||||
@@ -511,12 +530,14 @@ class MpvPlayer:
|
||||
|
||||
return self.currentPlaylistIndex
|
||||
|
||||
def seek_to_playlist_index(self, index):
|
||||
def seek_to_playlist_index(self, index, waitForLoad=True):
|
||||
"""
|
||||
Seek to a specific file in the playlist
|
||||
|
||||
Args:
|
||||
index: File index in playlist
|
||||
waitForLoad: If True, wait for file to fully load before returning.
|
||||
Set to False if you're immediately calling play_audio_file() after.
|
||||
|
||||
Returns:
|
||||
True if seek successful
|
||||
@@ -547,16 +568,30 @@ class MpvPlayer:
|
||||
# Now set the playlist position
|
||||
self.player.playlist_pos = index
|
||||
|
||||
# Wait for mpv to switch files
|
||||
import time
|
||||
time.sleep(0.1)
|
||||
|
||||
# Update our internal tracking AFTER mpv has switched
|
||||
# Update our internal tracking
|
||||
self.currentPlaylistIndex = index
|
||||
|
||||
# Optionally wait for mpv to load the new file
|
||||
# Skip this if caller will immediately play (play_audio_file will trigger load)
|
||||
if waitForLoad:
|
||||
import time
|
||||
maxWait = 2.0 # Maximum 2 seconds
|
||||
elapsed = 0.0
|
||||
interval = 0.05 # Check every 50ms
|
||||
|
||||
while elapsed < maxWait:
|
||||
time.sleep(interval)
|
||||
elapsed += interval
|
||||
|
||||
try:
|
||||
# Check if file has loaded by seeing if duration is available
|
||||
duration = self.player.duration
|
||||
if duration is not None:
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception seeking to playlist index {index}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print(f"Error seeking to playlist index {index}: {e}")
|
||||
return False
|
||||
@@ -88,65 +88,6 @@ class VoiceSelector:
|
||||
"""
|
||||
return self.voices
|
||||
|
||||
def select_voice_interactive(self):
|
||||
"""
|
||||
Interactive voice selection
|
||||
|
||||
Returns:
|
||||
Selected voice path or None if cancelled
|
||||
"""
|
||||
if not self.voices:
|
||||
print("No voices found in", self.voiceDir)
|
||||
return None
|
||||
|
||||
print("\nAvailable Voices:")
|
||||
print("-" * 60)
|
||||
|
||||
for idx, voice in enumerate(self.voices):
|
||||
print(f"{idx + 1}. {voice['name']}")
|
||||
|
||||
print("-" * 60)
|
||||
print("\nCommands:")
|
||||
print(" <number> - Select voice")
|
||||
print(" t <number> - Test voice")
|
||||
print(" q - Cancel")
|
||||
print()
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = input("Select voice> ").strip().lower()
|
||||
|
||||
if choice == 'q':
|
||||
return None
|
||||
|
||||
# Test voice
|
||||
if choice.startswith('t '):
|
||||
try:
|
||||
voiceNum = int(choice[2:])
|
||||
if 1 <= voiceNum <= len(self.voices):
|
||||
self._test_voice(self.voices[voiceNum - 1])
|
||||
else:
|
||||
print(f"Invalid voice number. Choose 1-{len(self.voices)}")
|
||||
except ValueError:
|
||||
print("Invalid input. Use: t <number>")
|
||||
continue
|
||||
|
||||
# Select voice
|
||||
try:
|
||||
voiceNum = int(choice)
|
||||
if 1 <= voiceNum <= len(self.voices):
|
||||
selectedVoice = self.voices[voiceNum - 1]
|
||||
print(f"Selected: {selectedVoice['name']}")
|
||||
return selectedVoice['path']
|
||||
else:
|
||||
print(f"Invalid voice number. Choose 1-{len(self.voices)}")
|
||||
except ValueError:
|
||||
print("Invalid input. Enter a number, 't <number>' to test, or 'q' to cancel")
|
||||
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print("\nCancelled")
|
||||
return None
|
||||
|
||||
def _test_voice(self, voice):
|
||||
"""
|
||||
Test a voice by playing sample text
|
||||
|
||||
Reference in New Issue
Block a user