mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-01-28 22:01:24 +00:00
Video Player (#149)
* Basic Video Player * Fixes and Comments * Fixed Bug Where Video's Audio did not stop when switching to a Image. * Redo on VideoPlayer. Now with rounded corners. * Fixed size not being correct when first starting video player. * Ruff Check Fix * Fixed Sizing Issue, and added Autoplay option in right click menu. * Autoplay Toggle and Fixed Issue with video not stoping after closing library. * Ruff Format * Suggested Changes Done * Commented out useless code that cause first warning. * Fixed Album Art Error * Might have found solution to Autoplay Inconsistency * Ruff Format * Finally Fixed Autoplay Inconsistency * Fixed Merge Conficts * Requested Changes and Ruff Format * Test for new check * Fix for PySide App Test * More typing fixes and a few other changes. * Ruff Format * MyPy Fix * MyPy Fix * Ruff Format * MyPy Fix * MyPy and Ruff Fix * Code Clean-Up and Requests completed. * Conflict Fixes * MyPy Fix * Confict Fix It appears one of the commits from main fixed the autoplay issue.
This commit is contained in:
3
.github/workflows/apprun.yaml
vendored
3
.github/workflows/apprun.yaml
vendored
@@ -32,7 +32,8 @@ jobs:
|
||||
libxcb-render-util0 \
|
||||
libxcb-xinerama0 \
|
||||
libopengl0 \
|
||||
libxcb-cursor0
|
||||
libxcb-cursor0 \
|
||||
libpulse0
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
1
tagstudio/resources/pause.svg
Normal file
1
tagstudio/resources/pause.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#ffffff"><path d="M560-200v-560h160v560H560Zm-320 0v-560h160v560H240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 172 B |
1
tagstudio/resources/play.svg
Normal file
1
tagstudio/resources/play.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#ffffff"><path d="M320-200v-560l440 280-440 280Z"/></svg>
|
||||
|
After Width: | Height: | Size: 151 B |
1
tagstudio/resources/volume_muted.svg
Normal file
1
tagstudio/resources/volume_muted.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#ffffff"><path d="M792-56 671-177q-25 16-53 27.5T560-131v-82q14-5 27.5-10t25.5-12L480-368v208L280-360H120v-240h128L56-792l56-56 736 736-56 56Zm-8-232-58-58q17-31 25.5-65t8.5-70q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 53-14.5 102T784-288ZM650-422l-90-90v-130q47 22 73.5 66t26.5 96q0 15-2.5 29.5T650-422ZM480-592 376-696l104-104v208Z"/></svg>
|
||||
|
After Width: | Height: | Size: 445 B |
1
tagstudio/resources/volume_unmuted.svg
Normal file
1
tagstudio/resources/volume_unmuted.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#ffffff"><path d="M560-131v-82q90-26 145-100t55-168q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 127-78 224.5T560-131ZM120-360v-240h160l200-200v640L280-360H120Zm440 40v-322q47 22 73.5 66t26.5 96q0 51-26.5 94.5T560-320Z"/></svg>
|
||||
|
After Width: | Height: | Size: 327 B |
@@ -8,6 +8,7 @@ class SettingItems(str, enum.Enum):
|
||||
LAST_LIBRARY = "last_library"
|
||||
LIBS_LIST = "libs_list"
|
||||
WINDOW_SHOW_LIBS = "window_show_libs"
|
||||
AUTOPLAY = "autoplay_videos"
|
||||
|
||||
|
||||
class Theme(str, enum.Enum):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Resource object code (Python 3)
|
||||
# Created by: object code
|
||||
# Created by: The Resource Compiler for Qt version 6.6.3
|
||||
# Created by: The Resource Compiler for Qt version 6.5.1
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PySide6 import QtCore
|
||||
@@ -16232,15 +16232,15 @@ qt_resource_struct = b"\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x96\x00\x00\x00\x00\x00\x01\x00\x03\xca\xbe\
|
||||
\x00\x00\x01\x8a\xfb\xb4\xd6\xbe\
|
||||
\x00\x00\x01\x8f\x10b\x06\xcd\
|
||||
\x00\x00\x00b\x00\x00\x00\x00\x00\x01\x00\x03\xb8\x1a\
|
||||
\x00\x00\x01\x8a\xfb\xc6t\x9f\
|
||||
\x00\x00\x01\x8f\x10b\x06\xca\
|
||||
\x00\x00\x00\xca\x00\x00\x00\x00\x00\x01\x00\x03\xe4\x99\
|
||||
\x00\x00\x01\x8a\xfb\xb4\xc1\x95\
|
||||
\x00\x00\x01\x8f\x10b\x06\xc8\
|
||||
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x01\x8a\xfb\xc6\x86\xda\
|
||||
\x00\x00\x01\x8f\x10b\x06\xce\
|
||||
\x00\x00\x00H\x00\x00\x00\x00\x00\x01\x00\x00\x22\x83\
|
||||
\x00\x00\x01\x8e\xfd%\xc3\xc7\
|
||||
\x00\x00\x01\x8f\x10b\x06\xcc\
|
||||
"
|
||||
|
||||
def qInitResources():
|
||||
|
||||
@@ -41,6 +41,7 @@ from src.qt.widgets.panel import PanelModal
|
||||
from src.qt.widgets.text_box_edit import EditTextBox
|
||||
from src.qt.widgets.text_line_edit import EditTextLine
|
||||
from src.qt.widgets.item_thumb import ItemThumb
|
||||
from src.qt.widgets.video_player import VideoPlayer
|
||||
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
@@ -90,7 +91,8 @@ class PreviewPanel(QWidget):
|
||||
|
||||
self.preview_img.addAction(self.open_file_action)
|
||||
self.preview_img.addAction(self.open_explorer_action)
|
||||
|
||||
self.preview_vid = VideoPlayer(driver)
|
||||
self.preview_vid.hide()
|
||||
self.thumb_renderer = ThumbRenderer()
|
||||
self.thumb_renderer.updated.connect(
|
||||
lambda ts, i, s: (self.preview_img.setIcon(i))
|
||||
@@ -110,7 +112,9 @@ class PreviewPanel(QWidget):
|
||||
|
||||
image_layout.addWidget(self.preview_img)
|
||||
image_layout.setAlignment(self.preview_img, Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
image_layout.addWidget(self.preview_vid)
|
||||
image_layout.setAlignment(self.preview_vid, Qt.AlignmentFlag.AlignCenter)
|
||||
self.image_container.setMinimumSize(*self.img_button_size)
|
||||
self.file_label = FileOpenerLabel("Filename")
|
||||
self.file_label.setWordWrap(True)
|
||||
self.file_label.setTextInteractionFlags(
|
||||
@@ -378,6 +382,9 @@ class PreviewPanel(QWidget):
|
||||
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.resizeVideo(adj_size)
|
||||
self.preview_vid.setMaximumSize(adj_size)
|
||||
self.preview_vid.setMinimumSize(adj_size)
|
||||
# self.preview_img.setMinimumSize(adj_size)
|
||||
|
||||
# if self.preview_img.iconSize().toTuple()[0] < self.preview_img.size().toTuple()[0] + 10:
|
||||
@@ -460,7 +467,9 @@ class PreviewPanel(QWidget):
|
||||
pass
|
||||
for i, c in enumerate(self.containers):
|
||||
c.setHidden(True)
|
||||
|
||||
self.preview_img.show()
|
||||
self.preview_vid.stop()
|
||||
self.preview_vid.hide()
|
||||
self.selected = list(self.driver.selected)
|
||||
self.add_field_button.setHidden(True)
|
||||
|
||||
@@ -468,6 +477,9 @@ class PreviewPanel(QWidget):
|
||||
elif len(self.driver.selected) == 1:
|
||||
# 1 Selected Entry
|
||||
if self.driver.selected[0][0] == ItemType.ENTRY:
|
||||
self.preview_img.show()
|
||||
self.preview_vid.stop()
|
||||
self.preview_vid.hide()
|
||||
item: Entry = self.lib.get_entry(self.driver.selected[0][1])
|
||||
# If a new selection is made, update the thumbnail and filepath.
|
||||
if not self.selected or self.selected != self.driver.selected:
|
||||
@@ -513,6 +525,18 @@ class PreviewPanel(QWidget):
|
||||
success, frame = video.read()
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
image = Image.fromarray(frame)
|
||||
if success:
|
||||
self.preview_img.hide()
|
||||
self.preview_vid.play(
|
||||
filepath, QSize(image.width, image.height)
|
||||
)
|
||||
self.resizeEvent(
|
||||
QResizeEvent(
|
||||
QSize(image.width, image.height),
|
||||
QSize(image.width, image.height),
|
||||
)
|
||||
)
|
||||
self.preview_vid.show()
|
||||
|
||||
# Stats for specific file types are displayed here.
|
||||
if filepath.suffix.lower() in (
|
||||
@@ -579,6 +603,9 @@ class PreviewPanel(QWidget):
|
||||
|
||||
# Multiple Selected Items
|
||||
elif len(self.driver.selected) > 1:
|
||||
self.preview_img.show()
|
||||
self.preview_vid.stop()
|
||||
self.preview_vid.hide()
|
||||
if self.selected != self.driver.selected:
|
||||
self.file_label.setText(f"{len(self.driver.selected)} Items Selected")
|
||||
self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
373
tagstudio/src/qt/widgets/video_player.py
Normal file
373
tagstudio/src/qt/widgets/video_player.py
Normal file
@@ -0,0 +1,373 @@
|
||||
import logging
|
||||
import os
|
||||
import typing
|
||||
|
||||
# os.environ["QT_MEDIA_BACKEND"] = "ffmpeg"
|
||||
|
||||
from PySide6.QtCore import (
|
||||
Qt,
|
||||
QSize,
|
||||
QTimer,
|
||||
QVariantAnimation,
|
||||
QUrl,
|
||||
QObject,
|
||||
QEvent,
|
||||
QRectF,
|
||||
)
|
||||
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput, QMediaDevices
|
||||
from PySide6.QtMultimediaWidgets import QGraphicsVideoItem
|
||||
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene
|
||||
from PySide6.QtGui import (
|
||||
QInputMethodEvent,
|
||||
QPen,
|
||||
QColor,
|
||||
QBrush,
|
||||
QResizeEvent,
|
||||
QWheelEvent,
|
||||
QAction,
|
||||
QRegion,
|
||||
QBitmap,
|
||||
)
|
||||
from PySide6.QtSvgWidgets import QSvgWidget
|
||||
from PIL import Image
|
||||
from src.qt.helpers.file_opener import FileOpenerHelper
|
||||
|
||||
from src.core.constants import VIDEO_TYPES, AUDIO_TYPES
|
||||
from PIL import Image, ImageDraw
|
||||
from src.core.enums import SettingItems
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class VideoPlayer(QGraphicsView):
|
||||
"""A simple video player for the TagStudio application."""
|
||||
|
||||
resolution = QSize(1280, 720)
|
||||
hover_fix_timer = QTimer()
|
||||
video_preview = None
|
||||
play_pause = None
|
||||
mute_button = None
|
||||
content_visible = False
|
||||
filepath = None
|
||||
|
||||
def __init__(self, driver: "QtDriver") -> None:
|
||||
# Set up the base class.
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.animation = QVariantAnimation(self)
|
||||
self.animation.valueChanged.connect(
|
||||
lambda value: self.setTintTransparency(value)
|
||||
)
|
||||
self.hover_fix_timer.timeout.connect(lambda: self.checkIfStillHovered())
|
||||
self.hover_fix_timer.setSingleShot(True)
|
||||
# Set up the video player.
|
||||
self.installEventFilter(self)
|
||||
self.setScene(QGraphicsScene(self))
|
||||
self.player = QMediaPlayer(self)
|
||||
self.player.mediaStatusChanged.connect(
|
||||
lambda: self.checkMediaStatus(self.player.mediaStatus())
|
||||
)
|
||||
self.video_preview = VideoPreview()
|
||||
self.player.setVideoOutput(self.video_preview)
|
||||
self.video_preview.setAcceptHoverEvents(True)
|
||||
self.video_preview.setAcceptedMouseButtons(Qt.MouseButton.RightButton)
|
||||
self.video_preview.installEventFilter(self)
|
||||
self.player.setAudioOutput(
|
||||
QAudioOutput(QMediaDevices().defaultAudioOutput(), self.player)
|
||||
)
|
||||
self.player.audioOutput().setMuted(True)
|
||||
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.scene().addItem(self.video_preview)
|
||||
self.video_preview.setAcceptedMouseButtons(Qt.MouseButton.LeftButton)
|
||||
# Set up the video tint.
|
||||
self.video_tint = self.scene().addRect(
|
||||
0,
|
||||
0,
|
||||
self.video_preview.size().width(),
|
||||
self.video_preview.size().height(),
|
||||
QPen(QColor(0, 0, 0, 0)),
|
||||
QBrush(QColor(0, 0, 0, 0)),
|
||||
)
|
||||
# self.video_tint.setParentItem(self.video_preview)
|
||||
# self.album_art = QGraphicsPixmapItem(self.video_preview)
|
||||
# self.scene().addItem(self.album_art)
|
||||
# self.album_art.setPixmap(
|
||||
# QPixmap("./tagstudio/resources/qt/images/thumb_file_default_512.png")
|
||||
# )
|
||||
# self.album_art.setOpacity(0.0)
|
||||
# Set up the buttons.
|
||||
self.play_pause = QSvgWidget("./tagstudio/resources/pause.svg")
|
||||
self.play_pause.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
|
||||
self.play_pause.setMouseTracking(True)
|
||||
self.play_pause.installEventFilter(self)
|
||||
self.scene().addWidget(self.play_pause)
|
||||
self.play_pause.resize(100, 100)
|
||||
self.play_pause.move(
|
||||
int(self.width() / 2 - self.play_pause.size().width() / 2),
|
||||
int(self.height() / 2 - self.play_pause.size().height() / 2),
|
||||
)
|
||||
self.play_pause.hide()
|
||||
|
||||
self.mute_button = QSvgWidget("./tagstudio/resources/volume_muted.svg")
|
||||
self.mute_button.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
|
||||
self.mute_button.setMouseTracking(True)
|
||||
self.mute_button.installEventFilter(self)
|
||||
self.scene().addWidget(self.mute_button)
|
||||
self.mute_button.resize(40, 40)
|
||||
self.mute_button.move(
|
||||
int(self.width() - self.mute_button.size().width() / 2),
|
||||
int(self.height() - self.mute_button.size().height() / 2),
|
||||
)
|
||||
self.mute_button.hide()
|
||||
# self.fullscreen_button = QSvgWidget('./tagstudio/resources/pause.svg', self)
|
||||
# self.fullscreen_button.setMouseTracking(True)
|
||||
# self.fullscreen_button.installEventFilter(self)
|
||||
# self.scene().addWidget(self.fullscreen_button)
|
||||
# self.fullscreen_button.resize(40, 40)
|
||||
# self.fullscreen_button.move(self.fullscreen_button.size().width()/2, self.height() - self.fullscreen_button.size().height()/2)
|
||||
# self.fullscreen_button.hide()
|
||||
|
||||
self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
self.opener = FileOpenerHelper(filepath=self.filepath)
|
||||
autoplay_action = QAction("Autoplay", self)
|
||||
autoplay_action.setCheckable(True)
|
||||
self.addAction(autoplay_action)
|
||||
autoplay_action.setChecked(
|
||||
self.driver.settings.value(SettingItems.AUTOPLAY, True, bool) # type: ignore
|
||||
)
|
||||
autoplay_action.triggered.connect(lambda: self.toggleAutoplay())
|
||||
self.autoplay = autoplay_action
|
||||
|
||||
open_file_action = QAction("Open file", self)
|
||||
open_file_action.triggered.connect(self.opener.open_file)
|
||||
open_explorer_action = QAction("Open file in explorer", self)
|
||||
open_explorer_action.triggered.connect(self.opener.open_explorer)
|
||||
self.addAction(open_file_action)
|
||||
self.addAction(open_explorer_action)
|
||||
|
||||
def close(self, *args, **kwargs) -> None:
|
||||
self.player.stop()
|
||||
super().close(*args, **kwargs)
|
||||
|
||||
def toggleAutoplay(self) -> None:
|
||||
self.driver.settings.setValue(SettingItems.AUTOPLAY, self.autoplay.isChecked())
|
||||
self.driver.settings.sync()
|
||||
|
||||
def checkMediaStatus(self, media_status: QMediaPlayer.MediaStatus) -> None:
|
||||
# logging.info(media_status)
|
||||
if media_status == QMediaPlayer.MediaStatus.EndOfMedia:
|
||||
# Switches current video to with video at filepath. Reason for this is because Pyside6 is dumb and can't handle setting a new source and freezes.
|
||||
# Even if I stop the player before switching, it breaks.
|
||||
# On the plus side, this adds infinite looping for the video preview.
|
||||
self.player.stop()
|
||||
self.player.setSource(QUrl().fromLocalFile(self.filepath))
|
||||
# logging.info(f'Set source to {self.filepath}.')
|
||||
# self.video_preview.setSize(self.resolution)
|
||||
self.player.setPosition(0)
|
||||
# logging.info(f'Set muted to true.')
|
||||
if self.autoplay.isChecked():
|
||||
# logging.info(self.driver.settings.value("autoplay_videos", True, bool))
|
||||
self.player.play()
|
||||
else:
|
||||
# logging.info("Paused")
|
||||
self.player.pause()
|
||||
self.opener.set_filepath(self.filepath)
|
||||
self.keepControlsInPlace()
|
||||
self.updateControls()
|
||||
|
||||
def updateControls(self) -> None:
|
||||
if self.player.audioOutput().isMuted():
|
||||
self.mute_button.load("./tagstudio/resources/volume_muted.svg")
|
||||
else:
|
||||
self.mute_button.load("./tagstudio/resources/volume_unmuted.svg")
|
||||
|
||||
if self.player.isPlaying():
|
||||
self.play_pause.load("./tagstudio/resources/pause.svg")
|
||||
else:
|
||||
self.play_pause.load("./tagstudio/resources/play.svg")
|
||||
|
||||
def wheelEvent(self, event: QWheelEvent) -> None:
|
||||
return
|
||||
|
||||
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
|
||||
# This chunk of code is for the video controls.
|
||||
if (
|
||||
obj == self.play_pause
|
||||
and event.type() == QEvent.Type.MouseButtonPress
|
||||
and event.button() == Qt.MouseButton.LeftButton # type: ignore
|
||||
):
|
||||
if self.player.hasVideo():
|
||||
self.pauseToggle()
|
||||
|
||||
if (
|
||||
obj == self.mute_button
|
||||
and event.type() == QEvent.Type.MouseButtonPress
|
||||
and event.button() == Qt.MouseButton.LeftButton # type: ignore
|
||||
):
|
||||
if self.player.hasAudio():
|
||||
self.muteToggle()
|
||||
|
||||
if (
|
||||
obj == self.video_preview
|
||||
and event.type() == QEvent.Type.GraphicsSceneHoverEnter
|
||||
or event.type() == QEvent.Type.HoverEnter
|
||||
):
|
||||
if self.video_preview.isUnderMouse():
|
||||
self.underMouse()
|
||||
self.hover_fix_timer.start(10)
|
||||
elif (
|
||||
obj == self.video_preview
|
||||
and event.type() == QEvent.Type.GraphicsSceneHoverLeave
|
||||
or event.type() == QEvent.Type.HoverLeave
|
||||
):
|
||||
if not self.video_preview.isUnderMouse():
|
||||
self.hover_fix_timer.stop()
|
||||
self.releaseMouse()
|
||||
return super().eventFilter(obj, event)
|
||||
|
||||
def checkIfStillHovered(self) -> None:
|
||||
# Yet again, Pyside6 is dumb. I don't know why, but the HoverLeave event is not triggered sometimes and does not hide the controls.
|
||||
# So, this is a workaround. This is called by a QTimer every 10ms to check if the mouse is still in the video preview.
|
||||
if not self.video_preview.isUnderMouse():
|
||||
self.releaseMouse()
|
||||
else:
|
||||
self.hover_fix_timer.start(10)
|
||||
|
||||
def setTintTransparency(self, value) -> None:
|
||||
self.video_tint.setBrush(QBrush(QColor(0, 0, 0, value)))
|
||||
|
||||
def underMouse(self) -> bool:
|
||||
# logging.info("under mouse")
|
||||
self.animation.setStartValue(self.video_tint.brush().color().alpha())
|
||||
self.animation.setEndValue(100)
|
||||
self.animation.setDuration(500)
|
||||
self.animation.start()
|
||||
self.play_pause.show()
|
||||
self.mute_button.show()
|
||||
# self.fullscreen_button.show()
|
||||
self.keepControlsInPlace()
|
||||
self.updateControls()
|
||||
# rcontent = self.contentsRect()
|
||||
# self.setSceneRect(0, 0, rcontent.width(), rcontent.height())
|
||||
return super().underMouse()
|
||||
|
||||
def releaseMouse(self) -> None:
|
||||
# logging.info("release mouse")
|
||||
self.animation.setStartValue(self.video_tint.brush().color().alpha())
|
||||
self.animation.setEndValue(0)
|
||||
self.animation.setDuration(500)
|
||||
self.animation.start()
|
||||
self.play_pause.hide()
|
||||
self.mute_button.hide()
|
||||
# self.fullscreen_button.hide()
|
||||
return super().releaseMouse()
|
||||
|
||||
def resetControlsToDefault(self) -> None:
|
||||
# Resets the video controls to their default state.
|
||||
self.play_pause.load("./tagstudio/resources/pause.svg")
|
||||
self.mute_button.load("./tagstudio/resources/volume_muted.svg")
|
||||
|
||||
def pauseToggle(self) -> None:
|
||||
if self.player.isPlaying():
|
||||
self.player.pause()
|
||||
self.play_pause.load("./tagstudio/resources/play.svg")
|
||||
else:
|
||||
self.player.play()
|
||||
self.play_pause.load("./tagstudio/resources/pause.svg")
|
||||
|
||||
def muteToggle(self) -> None:
|
||||
if self.player.audioOutput().isMuted():
|
||||
self.player.audioOutput().setMuted(False)
|
||||
self.mute_button.load("./tagstudio/resources/volume_unmuted.svg")
|
||||
else:
|
||||
self.player.audioOutput().setMuted(True)
|
||||
self.mute_button.load("./tagstudio/resources/volume_muted.svg")
|
||||
|
||||
def play(self, filepath: str, resolution: QSize) -> None:
|
||||
# Sets the filepath and sends the current player position to the very end, so that the new video can be played.
|
||||
# self.player.audioOutput().setMuted(True)
|
||||
logging.info(f"Playing {filepath}")
|
||||
self.resolution = resolution
|
||||
self.filepath = filepath
|
||||
if self.player.isPlaying():
|
||||
self.player.setPosition(self.player.duration())
|
||||
self.player.play()
|
||||
else:
|
||||
self.checkMediaStatus(QMediaPlayer.MediaStatus.EndOfMedia)
|
||||
# logging.info(f"Successfully stopped.")
|
||||
|
||||
def stop(self) -> None:
|
||||
self.filepath = None
|
||||
self.player.stop()
|
||||
|
||||
def resizeVideo(self, new_size: QSize) -> None:
|
||||
# Resizes the video preview to the new size.
|
||||
self.video_preview.setSize(new_size)
|
||||
self.video_tint.setRect(
|
||||
0, 0, self.video_preview.size().width(), self.video_preview.size().height()
|
||||
)
|
||||
|
||||
rcontent = self.contentsRect()
|
||||
self.centerOn(self.video_preview)
|
||||
self.roundCorners()
|
||||
self.setSceneRect(0, 0, rcontent.width(), rcontent.height())
|
||||
self.keepControlsInPlace()
|
||||
|
||||
def roundCorners(self) -> None:
|
||||
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=12,
|
||||
fill=(0, 0, 0, 0),
|
||||
)
|
||||
final_mask = mask.getchannel("A").toqpixmap()
|
||||
self.setMask(QRegion(QBitmap(final_mask)))
|
||||
|
||||
def keepControlsInPlace(self) -> None:
|
||||
# Keeps the video controls in the places they should be.
|
||||
self.play_pause.move(
|
||||
int(self.width() / 2 - self.play_pause.size().width() / 2),
|
||||
int(self.height() / 2 - self.play_pause.size().height() / 2),
|
||||
)
|
||||
self.mute_button.move(
|
||||
int(self.width() - self.mute_button.size().width() - 10),
|
||||
int(self.height() - self.mute_button.size().height() - 10),
|
||||
)
|
||||
# self.fullscreen_button.move(-self.fullscreen_button.size().width()-10, self.height() - self.fullscreen_button.size().height()-10)
|
||||
|
||||
def resizeEvent(self, event: QResizeEvent) -> None:
|
||||
# Keeps the video preview in the center of the screen.
|
||||
self.centerOn(self.video_preview)
|
||||
self.resizeVideo(
|
||||
QSize(
|
||||
int(self.video_preview.size().width()),
|
||||
int(self.video_preview.size().height()),
|
||||
)
|
||||
)
|
||||
return
|
||||
# return super().resizeEvent(event)\
|
||||
|
||||
|
||||
class VideoPreview(QGraphicsVideoItem):
|
||||
def boundingRect(self):
|
||||
return QRectF(0, 0, self.size().width(), self.size().height())
|
||||
|
||||
def paint(self, painter, option, widget):
|
||||
# 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)
|
||||
Reference in New Issue
Block a user