diff --git a/tagstudio/src/qt/helpers/qslider_wrapper.py b/tagstudio/src/qt/helpers/qslider_wrapper.py new file mode 100644 index 000000000..50357b061 --- /dev/null +++ b/tagstudio/src/qt/helpers/qslider_wrapper.py @@ -0,0 +1,44 @@ +# Copyright (C) 2024 Travis Abendshien (CyanVoxel). +# Licensed under the GPL-3.0 License. +# Created for TagStudio: https://github.com/CyanVoxel/TagStudio + +from PySide6.QtWidgets import QSlider, QStyle, QStyleOptionSlider + + +class QClickSlider(QSlider): + """Custom QSlider wrapper. + + The purpose of this wrapper is to allow us to set slider positions + based on click events. + """ + + mouse_pressed = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def mousePressEvent(self, event): # noqa: N802 + """Overide to handle mouse clicks. + + Overriding the mousePressEvent allows us to seek + directly to the position the user clicked instead + of stepping. + """ + opt = QStyleOptionSlider() + self.initStyleOption(opt) + opt.subControls = QStyle.SubControl.SC_SliderGroove | QStyle.SubControl.SC_SliderHandle + handle_rect = self.style().subControlRect( + QStyle.ComplexControl.CC_Slider, opt, QStyle.SubControl.SC_SliderHandle, self + ) + + was_slider_clicked = handle_rect.contains(event.position().x(), event.position().y()) + + if was_slider_clicked: + super().mousePressEvent(event) + else: + self.setValue( + QStyle.sliderValueFromPosition( + self.minimum(), self.maximum(), event.x(), self.width() + ) + ) + self.mouse_pressed = True diff --git a/tagstudio/src/qt/widgets/media_player.py b/tagstudio/src/qt/widgets/media_player.py index b1d86071b..ce5272068 100644 --- a/tagstudio/src/qt/widgets/media_player.py +++ b/tagstudio/src/qt/widgets/media_player.py @@ -2,41 +2,120 @@ # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging import typing from pathlib import Path from time import gmtime -from typing import Any -from PySide6.QtCore import Qt, QUrl -from PySide6.QtGui import QIcon, QPixmap +from PIL import Image, ImageDraw +from PySide6.QtCore import QEvent, QObject, QRectF, Qt, QUrl, QVariantAnimation +from PySide6.QtGui import QAction, QBitmap, QBrush, QColor, QFont, QPen, QRegion, QResizeEvent from PySide6.QtMultimedia import QAudioOutput, QMediaDevices, QMediaPlayer +from PySide6.QtMultimediaWidgets import QGraphicsVideoItem +from PySide6.QtSvgWidgets import QSvgWidget from PySide6.QtWidgets import ( - QGridLayout, - QHBoxLayout, - QLabel, - QPushButton, - QSizePolicy, + QGraphicsScene, + QGraphicsView, QSlider, - QWidget, ) +from src.core.enums import SettingItems +from src.qt.helpers.file_opener import FileOpenerHelper +from src.qt.helpers.qslider_wrapper import QClickSlider +from src.qt.platform_strings import open_file_str +from src.qt.translations import Translations if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver -class MediaPlayer(QWidget): +class MediaPlayer(QGraphicsView): """A basic media player widget. Gives a basic control set to manage media playback. """ + # These mouse_over_* variables are used to help + # determine if a mouse click should be handled + # by the media player or by some parent widget. + mouse_over_volume_slider = False + mouse_over_play_pause = False + mouse_over_mute_unmute = False + + video_preview = None + def __init__(self, driver: "QtDriver") -> None: super().__init__() self.driver = driver - self.setFixedHeight(50) + slider_style = """ + QSlider { + background: transparent; + } + + QSlider::groove:horizontal { + border: 1px solid #999999; + height: 2px; + margin: 2px 0; + border-radius: 2px; + } + + QSlider::handle:horizontal { + background: #6ea0ff; + border: 1px solid #5c5c5c; + width: 12px; + height: 12px; + margin: -6px 0; + border-radius: 6px; + } + + QSlider::add-page:horizontal { + background: #3f4144; + height: 2px; + margin: 2px 0; + border-radius: 2px; + } + + QSlider::sub-page:horizontal { + background: #6ea0ff; + height: 2px; + margin: 2px 0; + border-radius: 2px; + } + """ + + # setup the scene + self.installEventFilter(self) + self.setScene(QGraphicsScene(self)) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.setCursor(Qt.CursorShape.PointingHandCursor) + self.setStyleSheet(""" + QGraphicsView { + background: transparent; + } + """) + self.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.video_preview = VideoPreview() + self.video_preview.setAcceptHoverEvents(True) + self.video_preview.setAcceptedMouseButtons(Qt.MouseButton.RightButton) + self.video_preview.setAcceptedMouseButtons(Qt.MouseButton.LeftButton) + self.video_preview.installEventFilter(self) + + # animation + self.animation = QVariantAnimation(self) + self.animation.valueChanged.connect(lambda value: self.set_tint_opacity(value)) + + # Set up the tint. + self.tint = self.scene().addRect( + 0, + 0, + self.size().width(), + self.size().height(), + QPen(QColor(0, 0, 0, 0)), + QBrush(QColor(0, 0, 0, 0)), + ) + # setup the player self.filepath: Path | None = None self.player = QMediaPlayer() self.player.setAudioOutput(QAudioOutput(QMediaDevices().defaultAudioOutput(), self.player)) @@ -52,58 +131,184 @@ def __init__(self, driver: "QtDriver") -> None: self.player.positionChanged.connect(self.player_position_changed) self.player.mediaStatusChanged.connect(self.media_status_changed) self.player.playingChanged.connect(self.playing_changed) + self.player.hasVideoChanged.connect(self.has_video_changed) self.player.audioOutput().mutedChanged.connect(self.muted_changed) # Media controls - self.base_layout = QGridLayout(self) - self.base_layout.setContentsMargins(0, 0, 0, 0) - self.base_layout.setSpacing(0) - - self.pslider = QSlider(self) + self.pslider = QClickSlider() self.pslider.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.pslider.setTickPosition(QSlider.TickPosition.NoTicks) self.pslider.setSingleStep(1) self.pslider.setOrientation(Qt.Orientation.Horizontal) - + self.pslider.setStyleSheet(slider_style) self.pslider.sliderReleased.connect(self.slider_released) self.pslider.valueChanged.connect(self.slider_value_changed) - - self.media_btns_layout = QHBoxLayout() - - policy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) - - self.play_pause = QPushButton("", self) - self.play_pause.setFlat(True) - self.play_pause.setSizePolicy(policy) - self.play_pause.clicked.connect(self.toggle_pause) - - self.load_play_pause_icon(playing=False) - - self.media_btns_layout.addWidget(self.play_pause) - - self.mute = QPushButton("", self) - self.mute.setFlat(True) - self.mute.setSizePolicy(policy) - self.mute.clicked.connect(self.toggle_mute) - + self.pslider.hide() + + self.play_pause = QSvgWidget() + self.play_pause.setCursor(Qt.CursorShape.PointingHandCursor) + self.play_pause.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, on=True) + self.play_pause.setMouseTracking(True) + self.play_pause.installEventFilter(self) + self.load_toggle_play_icon(playing=False) + self.play_pause.resize(24, 24) + self.play_pause.hide() + + self.mute_unmute = QSvgWidget() + self.mute_unmute.setCursor(Qt.CursorShape.PointingHandCursor) + self.mute_unmute.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, on=True) + self.mute_unmute.setMouseTracking(True) + self.mute_unmute.installEventFilter(self) self.load_mute_unmute_icon(muted=False) + self.mute_unmute.resize(24, 24) + self.mute_unmute.hide() - self.media_btns_layout.addWidget(self.mute) - - self.volume_slider = QSlider() + self.volume_slider = QClickSlider() self.volume_slider.setOrientation(Qt.Orientation.Horizontal) - # set slider value to current volume self.volume_slider.setValue(int(self.player.audioOutput().volume() * 100)) self.volume_slider.valueChanged.connect(self.volume_slider_changed) + self.volume_slider.setMaximumWidth(100) + self.volume_slider.setStyleSheet(slider_style) + self.volume_slider.hide() + + self.position_label = self.scene().addText("0:00") + self.position_label.hide() + + font = QFont() + font.setPointSize(11) + self.position_label.setFont(font) + self.position_label.setDefaultTextColor(QColor(255, 255, 255, 255)) + self.position_label.hide() + + self.scene().addWidget(self.pslider) + self.scene().addWidget(self.play_pause) + self.scene().addWidget(self.mute_unmute) + self.scene().addWidget(self.volume_slider) + + self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) + self.opener = FileOpenerHelper(filepath=self.filepath) + autoplay_action = QAction(self) + Translations.translate_qobject(autoplay_action, "media_player.autoplay") + autoplay_action.setCheckable(True) + self.addAction(autoplay_action) + autoplay_action.setChecked( + self.driver.settings.value(SettingItems.AUTOPLAY, defaultValue=True, type=bool) # type: ignore + ) + autoplay_action.triggered.connect(lambda: self.toggle_autoplay()) + self.autoplay = autoplay_action + + open_file_action = QAction(self) + Translations.translate_qobject(open_file_action, "file.open_file") + open_file_action.triggered.connect(self.opener.open_file) + + open_explorer_action = QAction(open_file_str(), self) + + open_explorer_action.triggered.connect(self.opener.open_explorer) + self.addAction(open_file_action) + self.addAction(open_explorer_action) + + def set_video_output(self, video: QGraphicsVideoItem): + self.player.setVideoOutput(video) + + def toggle_autoplay(self) -> None: + """Toggle the autoplay state of the video.""" + self.driver.settings.setValue(SettingItems.AUTOPLAY, self.autoplay.isChecked()) + self.driver.settings.sync() + + def apply_rounded_corners(self) -> None: + """Apply a rounded corner effect to the video player.""" + width: int = int(max(self.contentsRect().size().width(), 0)) + height: int = int(max(self.contentsRect().size().height(), 0)) + mask = Image.new( + "RGBA", + ( + width, + height, + ), + (0, 0, 0, 255), + ) + draw = ImageDraw.Draw(mask) + draw.rounded_rectangle( + (0, 0) + (width, height), + radius=8, + fill=(0, 0, 0, 0), + ) + final_mask = mask.getchannel("A").toqpixmap() + self.setMask(QRegion(QBitmap(final_mask))) - self.media_btns_layout.addWidget(self.volume_slider) + def set_tint_opacity(self, opacity: int) -> None: + """Set the opacity of the video player's tint. - self.position_label = QLabel("0:00") - self.position_label.setAlignment(Qt.AlignmentFlag.AlignRight) + Args: + opacity(int): The opacity value, from 0-255. + """ + self.tint.setBrush(QBrush(QColor(0, 0, 0, opacity))) + + def underMouse(self) -> bool: # noqa: N802 + self.animation.setStartValue(self.tint.brush().color().alpha()) + self.animation.setEndValue(100) + self.animation.setDuration(250) + self.animation.start() + self.pslider.show() + self.play_pause.show() + self.mute_unmute.show() + self.volume_slider.show() + self.position_label.show() + + return super().underMouse() + + def releaseMouse(self) -> None: # noqa: N802 + self.animation.setStartValue(self.tint.brush().color().alpha()) + self.animation.setEndValue(0) + self.animation.setDuration(500) + self.animation.start() + self.pslider.hide() + self.play_pause.hide() + self.mute_unmute.hide() + self.volume_slider.hide() + self.position_label.hide() + + return super().releaseMouse() + + def mouse_over_elements(self) -> bool: + return ( + self.mouse_over_play_pause + or self.mouse_over_mute_unmute + or self.mouse_over_volume_slider + ) - self.base_layout.addWidget(self.pslider, 0, 0, 1, 2) - self.base_layout.addLayout(self.media_btns_layout, 1, 0) - self.base_layout.addWidget(self.position_label, 1, 1) + def eventFilter(self, obj: QObject, event: QEvent) -> bool: # noqa: N802 + """Manage events for the media player.""" + if ( + event.type() == QEvent.Type.MouseButtonPress + and event.button() == Qt.MouseButton.LeftButton # type: ignore + ): + if obj == self.play_pause: + self.toggle_play() + elif obj == self.mute_unmute: + self.toggle_mute() + elif self.mouse_over_elements() is False: + self.toggle_play() + elif event.type() is QEvent.Type.Enter: + if obj == self or obj == self.video_preview: + self.underMouse() + elif obj == self.mute_unmute: + self.mouse_over_mute_unmute = True + elif obj == self.play_pause: + self.mouse_over_play_pause = True + elif obj == self.volume_slider: + self.mouse_over_volume_slider = True + elif event.type() == QEvent.Type.Leave: + if obj == self or obj == self.video_preview: + self.releaseMouse() + elif obj == self.mute_unmute: + self.mouse_over_mute_unmute = False + elif obj == self.play_pause: + self.mouse_over_play_pause = False + elif obj == self.volume_slider: + self.mouse_over_volume_slider = False + + return super().eventFilter(obj, event) def format_time(self, ms: int) -> str: """Format the given time. @@ -128,8 +333,8 @@ def format_time(self, ms: int) -> str: else f"{time.tm_min}:{time.tm_sec:02}" ) - def toggle_pause(self) -> None: - """Toggle the pause state of the media.""" + def toggle_play(self) -> None: + """Toggle the playing state of the media.""" if self.player.isPlaying(): self.player.pause() self.is_paused = True @@ -145,11 +350,19 @@ def toggle_mute(self) -> None: self.player.audioOutput().setMuted(True) def playing_changed(self, playing: bool) -> None: - self.load_play_pause_icon(playing) + self.load_toggle_play_icon(playing) def muted_changed(self, muted: bool) -> None: self.load_mute_unmute_icon(muted) + def has_video_changed(self, video_available: bool) -> None: + if video_available: + self.scene().addItem(self.video_preview) + self.video_preview.setZValue(-1) + self.player.setVideoOutput(self.video_preview) + else: + self.scene().removeItem(self.video_preview) + def stop(self) -> None: """Clear the filepath and stop the player.""" self.filepath = None @@ -161,29 +374,28 @@ def play(self, filepath: Path) -> None: if not self.is_paused: self.player.stop() self.player.setSource(QUrl.fromLocalFile(self.filepath)) - self.player.play() + + if self.autoplay.isChecked(): + self.player.play() else: self.player.setSource(QUrl.fromLocalFile(self.filepath)) - def load_play_pause_icon(self, playing: bool) -> None: + self.opener.set_filepath(self.filepath) + + def load_toggle_play_icon(self, playing: bool) -> None: icon = self.driver.rm.pause_icon if playing else self.driver.rm.play_icon - self.set_icon(self.play_pause, icon) + self.play_pause.load(icon) + # self.set_icon(self.toggle_play, icon) def load_mute_unmute_icon(self, muted: bool) -> None: icon = self.driver.rm.volume_mute_icon if muted else self.driver.rm.volume_icon - self.set_icon(self.mute, icon) - - def set_icon(self, btn: QPushButton, icon: Any) -> None: - pix_map = QPixmap() - if pix_map.loadFromData(icon): - btn.setIcon(QIcon(pix_map)) - else: - logging.error("failed to load svg file") + self.mute_unmute.load(icon) def slider_value_changed(self, value: int) -> None: current = self.format_time(value) duration = self.format_time(self.player.duration()) - self.position_label.setText(f"{current} / {duration}") + self.position_label.setPlainText(f"{current} / {duration}") + self._move_position_label() def slider_released(self) -> None: was_playing = self.player.isPlaying() @@ -195,12 +407,16 @@ def slider_released(self) -> None: self.player.pause() def player_position_changed(self, position: int) -> None: - if not self.pslider.isSliderDown(): + if self.pslider.mouse_pressed: + self.player.setPosition(self.pslider.value()) + self.pslider.mouse_pressed = False + elif not self.pslider.isSliderDown(): # User isn't using the slider, so update position in widgets. self.pslider.setValue(position) current = self.format_time(self.player.position()) duration = self.format_time(self.player.duration()) - self.position_label.setText(f"{current} / {duration}") + self.position_label.setPlainText(f"{current} / {duration}") + self._move_position_label() if self.player.duration() == position: self.player.pause() @@ -214,7 +430,61 @@ def media_status_changed(self, status: QMediaPlayer.MediaStatus) -> None: current = self.format_time(self.player.position()) duration = self.format_time(self.player.duration()) - self.position_label.setText(f"{current} / {duration}") + self.position_label.setPlainText(f"{current} / {duration}") + self._move_position_label() + + def resizeEvent(self, event: QResizeEvent) -> None: # noqa: N802 + size = event.size() + + self.scene().setSceneRect(0, 0, size.width(), size.height()) + + self.play_pause.move(0, int(self.scene().height() - self.play_pause.height())) + self.mute_unmute.move( + self.play_pause.width(), int(self.scene().height() - self.mute_unmute.height()) + ) + + self._move_position_label() + + self.pslider.setMinimumWidth(self.size().width() - 10) + self.pslider.setMaximumWidth(self.size().width() - 10) + self.pslider.move( + 3, int(self.scene().height() - self.play_pause.height() - self.pslider.height()) + ) + + pos_w = int(self.play_pause.width() + self.mute_unmute.width()) + pos_h = int(size.height() - self.mute_unmute.height() + 5) + self.volume_slider.move(pos_w, pos_h) + + self.video_preview.setSize(self.size()) + if self.player.hasVideo(): + self.centerOn(self.video_preview) + + self.tint.setRect(0, 0, self.size().width(), self.size().height()) + self.apply_rounded_corners() + + def _move_position_label(self): + """Convenience function for repositioning the position label. + + This is needed because the position label is not automatically + resized after changing the text. + """ + rect = self.position_label.boundingRect() + pos_w = int(self.size().width() - rect.width() - 2) + pos_h = int(self.size().height() - self.mute_unmute.size().height() - 2) + self.position_label.setPos(pos_w, pos_h) def volume_slider_changed(self, position: int) -> None: self.player.audioOutput().setVolume(position / 100) + + +class VideoPreview(QGraphicsVideoItem): + def boundingRect(self): # noqa: N802 + return QRectF(0, 0, self.size().width(), self.size().height()) + + def paint(self, painter, option, widget=None) -> None: + # painter.brush().setColor(QColor(0, 0, 0, 255)) + # You can set any shape you want here. + # RoundedRect is the standard rectangle with rounded corners. + # With 2nd and 3rd parameter you can tweak the curve until you get what you expect + + super().paint(painter, option, widget) diff --git a/tagstudio/src/qt/widgets/preview/preview_thumb.py b/tagstudio/src/qt/widgets/preview/preview_thumb.py index 137b2dea7..7902ee93b 100644 --- a/tagstudio/src/qt/widgets/preview/preview_thumb.py +++ b/tagstudio/src/qt/widgets/preview/preview_thumb.py @@ -17,6 +17,7 @@ from PySide6.QtWidgets import ( QHBoxLayout, QLabel, + QStackedLayout, QWidget, ) from src.core.library.alchemy.library import Library @@ -30,7 +31,6 @@ from src.qt.translations import Translations from src.qt.widgets.media_player import MediaPlayer from src.qt.widgets.thumb_renderer import ThumbRenderer -from src.qt.widgets.video_player import VideoPlayer if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver @@ -51,7 +51,9 @@ def __init__(self, library: Library, driver: "QtDriver"): self.img_button_size: tuple[int, int] = (266, 266) self.image_ratio: float = 1.0 - image_layout = QHBoxLayout(self) + image_layout = QStackedLayout(self) + image_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + image_layout.setStackingMode(QStackedLayout.StackingMode.StackAll) image_layout.setContentsMargins(0, 0, 0, 0) self.open_file_action = QAction(self) @@ -70,19 +72,24 @@ def __init__(self, library: Library, driver: "QtDriver"): self.preview_img.addAction(self.open_explorer_action) self.preview_img.addAction(self.delete_action) + # In testing, it didn't seem possible to center the widgets directly + # on the QStackedLayout. Adding sublayouts allows us to center the widgets. + self.preview_img_page = QWidget() + self._stacked_page_setup(self.preview_img_page, self.preview_img) + self.preview_gif = QLabel() self.preview_gif.setMinimumSize(*self.img_button_size) self.preview_gif.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) self.preview_gif.setCursor(Qt.CursorShape.ArrowCursor) self.preview_gif.addAction(self.open_file_action) self.preview_gif.addAction(self.open_explorer_action) - self.preview_gif.addAction(self.delete_action) self.preview_gif.hide() self.gif_buffer: QBuffer = QBuffer() - self.preview_vid = VideoPlayer(driver) - self.preview_vid.addAction(self.delete_action) - self.preview_vid.hide() + self.preview_gif_page = QWidget() + self.preview_img_page.setContentsMargins(0, 0, 0, 0) + self._stacked_page_setup(self.preview_gif_page, self.preview_gif) + self.thumb_renderer = ThumbRenderer(self.lib) self.thumb_renderer.updated.connect(lambda ts, i, s: (self.preview_img.setIcon(i))) self.thumb_renderer.updated_ratio.connect( @@ -101,13 +108,23 @@ def __init__(self, library: Library, driver: "QtDriver"): self.media_player = MediaPlayer(driver) self.media_player.hide() - image_layout.addWidget(self.preview_img) - image_layout.setAlignment(self.preview_img, Qt.AlignmentFlag.AlignCenter) - image_layout.addWidget(self.preview_gif) - image_layout.setAlignment(self.preview_gif, Qt.AlignmentFlag.AlignCenter) - image_layout.addWidget(self.preview_vid) - image_layout.setAlignment(self.preview_vid, Qt.AlignmentFlag.AlignCenter) + self.media_player_page = QWidget() + self.preview_img_page.setContentsMargins(0, 0, 0, 0) + self._stacked_page_setup(self.media_player_page, self.media_player) + + image_layout.addWidget(self.preview_img_page) + image_layout.addWidget(self.preview_gif_page) + image_layout.addWidget(self.media_player_page) + self.setMinimumSize(*self.img_button_size) + image_layout.setCurrentWidget(self.media_player_page) + + def _stacked_page_setup(self, page: QWidget, widget: QWidget): + layout = QHBoxLayout(page) + layout.addWidget(widget) + layout.setAlignment(widget, Qt.AlignmentFlag.AlignCenter) + layout.setContentsMargins(0, 0, 0, 0) + page.setLayout(layout) def set_image_ratio(self, ratio: float): self.image_ratio = ratio @@ -133,17 +150,18 @@ def update_image_size(self, size: tuple[int, int], ratio: float = None): adj_height = size[1] adj_size = QSize(int(adj_width), int(adj_height)) + self.img_button_size = (int(adj_width), int(adj_height)) self.preview_img.setMaximumSize(adj_size) self.preview_img.setIconSize(adj_size) - self.preview_vid.resize_video(adj_size) - self.preview_vid.setMaximumSize(adj_size) - self.preview_vid.setMinimumSize(adj_size) self.preview_gif.setMaximumSize(adj_size) self.preview_gif.setMinimumSize(adj_size) + + self.media_player.setMaximumSize(adj_size) + self.media_player.setMinimumSize(adj_size) proxy_style = RoundedPixmapStyle(radius=8) self.preview_gif.setStyle(proxy_style) - self.preview_vid.setStyle(proxy_style) + self.media_player.setStyle(proxy_style) m = self.preview_gif.movie() if m: m.setScaledSize(adj_size) @@ -158,11 +176,7 @@ def switch_preview(self, preview: str): if preview != "image" and preview != "media": self.preview_img.hide() - if preview != "video_legacy": - self.preview_vid.stop() - self.preview_vid.hide() - - if preview != "media": + if preview not in ["media", "video_legacy"]: self.media_player.stop() self.media_player.hide() @@ -294,7 +308,7 @@ def _update_video_legacy(self, filepath: Path) -> dict: stats["width"] = image.width stats["height"] = image.height if success: - self.preview_vid.play(filepath_, QSize(image.width, image.height)) + self.media_player.show() self.update_image_size((image.width, image.height), image.width / image.height) self.resizeEvent( QResizeEvent( @@ -302,7 +316,7 @@ def _update_video_legacy(self, filepath: Path) -> dict: QSize(image.width, image.height), ) ) - self.preview_vid.show() + self.media_player.play(filepath) stats["duration"] = video.get(cv2.CAP_PROP_FRAME_COUNT) / video.get(cv2.CAP_PROP_FPS) except cv2.error as e: @@ -398,8 +412,8 @@ def stop_file_use(self): logger.info("[PreviewThumb] Stopping file use in video playback...") # This swaps the video out for a placeholder so the previous video's file # is no longer in use by this object. - self.preview_vid.play(str(ResourceManager.get_path("placeholder_mp4")), QSize(8, 8)) - self.preview_vid.hide() + self.media_player.play(ResourceManager.get_path("placeholder_mp4")) + self.media_player.hide() def resizeEvent(self, event: QResizeEvent) -> None: # noqa: N802 self.update_image_size((self.size().width(), self.size().height())) diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index ab1791a95..60784d1c9 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -118,7 +118,7 @@ def __init__(self, library: Library, driver: "QtDriver"): add_buttons_layout.addWidget(self.add_field_button) preview_layout.addWidget(self.thumb) - preview_layout.addWidget(self.thumb.media_player) + # preview_layout.addWidget(self.thumb.media_player) info_layout.addWidget(self.file_attrs) info_layout.addWidget(self.fields) @@ -211,4 +211,9 @@ def update_add_tag_button(self, entry_id: int = None): ) ) - self.add_tag_button.clicked.connect(self.add_tag_modal.show) + self.add_tag_button.clicked.connect( + lambda: ( + self.tag_search_panel.update_tags(), + self.add_tag_modal.show(), + ) + )