Slideshow: video support, seek keys, fix double audio

- Slideshow mode now supports video (webm/mp4) and GIFs
- Arrow keys navigate posts in both preview and slideshow (including videos)
- , and . seek 5s back/forward in videos
- Main preview video pauses when slideshow opens (no double audio)
- Fix focus stealing by video player widgets in slideshow
This commit is contained in:
pax 2026-04-04 19:33:24 -05:00
parent 238df9cf3e
commit d275809c6b
2 changed files with 56 additions and 4 deletions

View File

@ -703,8 +703,9 @@ class BooruApp(QMainWindow):
idx = self._grid.selected_index idx = self._grid.selected_index
if 0 <= idx < len(self._grid._thumbs): if 0 <= idx < len(self._grid._thumbs):
self._grid._thumbs[idx]._cached_path = path self._grid._thumbs[idx]._cached_path = path
# Update fullscreen if open # Update fullscreen if open, and mute the main player
if self._fullscreen_window and self._fullscreen_window.isVisible(): if self._fullscreen_window and self._fullscreen_window.isVisible():
self._preview._video_player.stop()
self._fullscreen_window.set_media(path, info) self._fullscreen_window.set_media(path, info)
def _on_favorite_selected(self, fav) -> None: def _on_favorite_selected(self, fav) -> None:
@ -802,15 +803,22 @@ class BooruApp(QMainWindow):
path = self._preview._current_path path = self._preview._current_path
if not path: if not path:
return return
# Pause the main preview's video player
self._preview._video_player.stop()
from .preview import FullscreenPreview from .preview import FullscreenPreview
self._fullscreen_window = FullscreenPreview(parent=self) self._fullscreen_window = FullscreenPreview(parent=self)
self._fullscreen_window.navigate.connect(self._navigate_fullscreen) self._fullscreen_window.navigate.connect(self._navigate_fullscreen)
self._fullscreen_window.destroyed.connect(self._on_fullscreen_closed)
self._fullscreen_window.set_media(path, self._preview._info_label.text()) self._fullscreen_window.set_media(path, self._preview._info_label.text())
def _on_fullscreen_closed(self) -> None:
self._fullscreen_window = None
def _navigate_fullscreen(self, direction: int) -> None: def _navigate_fullscreen(self, direction: int) -> None:
self._navigate_preview(direction) self._navigate_preview(direction)
# For synchronous loads (cached/favorites), update immediately # For synchronous loads (cached/favorites), update immediately
if self._fullscreen_window and self._preview._current_path: if self._fullscreen_window and self._preview._current_path:
self._preview._video_player.stop()
self._fullscreen_window.set_media( self._fullscreen_window.set_media(
self._preview._current_path, self._preview._current_path,
self._preview._info_label.text(), self._preview._info_label.text(),

View File

@ -23,26 +23,52 @@ def _is_video(path: str) -> bool:
class FullscreenPreview(QMainWindow): class FullscreenPreview(QMainWindow):
"""Fullscreen image viewer window with navigation.""" """Fullscreen media viewer with navigation — images, GIFs, and video."""
navigate = Signal(int) # -1 = prev, +1 = next navigate = Signal(int) # -1 = prev, +1 = next
def __init__(self, parent=None) -> None: def __init__(self, parent=None) -> None:
super().__init__(parent, Qt.WindowType.Window) super().__init__(parent, Qt.WindowType.Window)
self.setWindowTitle("booru-viewer — Fullscreen") self.setWindowTitle("booru-viewer — Fullscreen")
self._stack = QStackedWidget()
self.setCentralWidget(self._stack)
self._viewer = ImageViewer() self._viewer = ImageViewer()
self._viewer.close_requested.connect(self.close) self._viewer.close_requested.connect(self.close)
self.setCentralWidget(self._viewer) self._stack.addWidget(self._viewer)
self._video = VideoPlayer()
self._video.setFocusPolicy(Qt.FocusPolicy.NoFocus)
for child in self._video.findChildren(QWidget):
child.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self._stack.addWidget(self._video)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.setFocus()
self.showFullScreen() self.showFullScreen()
def set_media(self, path: str, info: str = "") -> None: def set_media(self, path: str, info: str = "") -> None:
ext = Path(path).suffix.lower() ext = Path(path).suffix.lower()
if ext == ".gif": if _is_video(path):
self._viewer.clear()
self._video.stop()
self._video.play_file(path, info)
self._stack.setCurrentIndex(1)
elif ext == ".gif":
self._video.stop()
self._viewer.set_gif(path, info) self._viewer.set_gif(path, info)
self._stack.setCurrentIndex(0)
else: else:
self._video.stop()
pix = QPixmap(path) pix = QPixmap(path)
if not pix.isNull(): if not pix.isNull():
self._viewer.set_image(pix, info) self._viewer.set_image(pix, info)
self._stack.setCurrentIndex(0)
def closeEvent(self, event) -> None:
self._video.stop()
super().closeEvent(event)
def keyPressEvent(self, event: QKeyEvent) -> None: def keyPressEvent(self, event: QKeyEvent) -> None:
if event.key() in (Qt.Key.Key_Escape, Qt.Key.Key_Q): if event.key() in (Qt.Key.Key_Escape, Qt.Key.Key_Q):
@ -51,6 +77,12 @@ class FullscreenPreview(QMainWindow):
self.navigate.emit(-1) self.navigate.emit(-1)
elif event.key() in (Qt.Key.Key_Right, Qt.Key.Key_L): elif event.key() in (Qt.Key.Key_Right, Qt.Key.Key_L):
self.navigate.emit(1) self.navigate.emit(1)
elif event.key() == Qt.Key.Key_Space and self._stack.currentIndex() == 1:
self._video._toggle_play()
elif event.key() == Qt.Key.Key_Period and self._stack.currentIndex() == 1:
self._video._seek_relative(5000)
elif event.key() == Qt.Key.Key_Comma and self._stack.currentIndex() == 1:
self._video._seek_relative(-5000)
else: else:
super().keyPressEvent(event) super().keyPressEvent(event)
@ -294,6 +326,10 @@ class VideoPlayer(QWidget):
def _seek(self, pos: int) -> None: def _seek(self, pos: int) -> None:
self._player.setPosition(pos) self._player.setPosition(pos)
def _seek_relative(self, ms: int) -> None:
pos = max(0, self._player.position() + ms)
self._player.setPosition(pos)
def _set_volume(self, val: int) -> None: def _set_volume(self, val: int) -> None:
self._audio.setVolume(val / 100.0) self._audio.setVolume(val / 100.0)
@ -497,6 +533,14 @@ class ImagePreview(QWidget):
self._image_viewer.keyPressEvent(event) self._image_viewer.keyPressEvent(event)
elif event.key() == Qt.Key.Key_Space: elif event.key() == Qt.Key.Key_Space:
self._video_player._toggle_play() self._video_player._toggle_play()
elif event.key() == Qt.Key.Key_Period:
self._video_player._seek_relative(5000)
elif event.key() == Qt.Key.Key_Comma:
self._video_player._seek_relative(-5000)
elif event.key() in (Qt.Key.Key_Left, Qt.Key.Key_H):
self.navigate.emit(-1)
elif event.key() in (Qt.Key.Key_Right, Qt.Key.Key_L):
self.navigate.emit(1)
def resizeEvent(self, event) -> None: def resizeEvent(self, event) -> None:
super().resizeEvent(event) super().resizeEvent(event)