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:
parent
238df9cf3e
commit
d275809c6b
@ -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(),
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user