2

我基本上是用 pyqt5 构建一个 GUI,应该包含两个视频。为此,我将 QMediaPlayer 与 QVideoWidget 结合使用,每个类一个。关键是:当第一个视频按预期播放时,第二个视频拒绝播放。它使用与第一个完全相同的框架(一个用于播放/暂停的按钮和一个滑动条),以及相同的代码结构,但在尝试播放时屏幕仍然非常黑。

更糟糕的是,如果我评论第一个视频的代码,第二个现在可以正常播放。这是否意味着两个 QMedialPlayer 之间存在一些冲突?我无法理解这一点。

任何帮助将不胜感激。

这是我的代码(GUI 看起来很奇怪,因为为了清楚起见我已经删除了大部分):

from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QPushButton, QLineEdit, QFrame, QHBoxLayout, QCheckBox, QRadioButton, QButtonGroup, QStyle, QSlider, QStackedLayout
import sys
from tkinter import Tk
from PyQt5.QtCore import pyqtSlot, QRect, Qt, QRunnable, QThreadPool, QThread, QObject, QUrl, QSize
import time
from PyQt5 import QtMultimedia
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtGui import QFont
from PyQt5.QtGui import QImage, QPalette, QBrush, QIcon, QPixmap


class DNN_Viewer(QWidget):             
    def __init__(self, n_filters=2):
        super(DNN_Viewer, self).__init__()

        # initialise GUI
        self.init_gui()

        # initialise videos to display images
        self.mp1.play()
        self.mp1.pause()
        self.mp2.play()
        self.mp2.pause()


    def init_gui(self):

        # main window
        root = Tk()
        screen_width = root.winfo_screenwidth()                                # screen width
        screen_height = root.winfo_screenheight()                              # screen heigth
        self.width = 1900                                                      # interface width
        self.heigth = 1000                                                     # interface height
        self.left = (screen_width - self.width) / 2                            # left-center interface
        self.top = (screen_height - self.heigth) / 2                           # top-center interface
        self.setFixedSize(self.width, self.heigth)
        self.move(self.left, self.top) 
        self.setStyleSheet("background: white");                               # interface background color        



        # bottom left frame
        self.fm2 = QFrame(self)                                                # creation        
        self.fm2.setGeometry(30, 550, 850, 430)                                # left, top, width, height  
        self.fm2.setFrameShape(QFrame.Panel);                                  # use panel style for frame           
        self.fm2.setLineWidth(1)                                               # frame line width

        # video for weights and gradients
        self.vw1 = QVideoWidget(self)                                          # declare video widget
        self.vw1.move(50,555)                                                  # left, top
        self.vw1.resize(542,380)                                               # width, height
        self.vw1.setStyleSheet("background-color:black;");                     # set black background

        # wrapper for the video
        self.mp1 = QMediaPlayer(self)                                          # declare QMediaPlayer
        self.mp1.setVideoOutput(self.vw1)                                      # use video widget vw1 as output
        fileName = "path_to_video_1"                                           # local path to video
        self.mp1.setMedia(QMediaContent(QUrl.fromLocalFile(fileName)))         # path to video
        self.mp1.stateChanged.connect(self.cb_mp1_1)                           # callback on change state (play, pause, stop)
        self.mp1.positionChanged.connect(self.cb_mp1_2)                        # callback to move slider cursor
        self.mp1.durationChanged.connect(self.cb_mp1_3)                        # callback to update slider range

        # play button for video
        self.pb2 = QPushButton(self)                                           # creation 
        self.pb2.move(50,940)                                                  # left, top     
        self.pb2.resize(40,30)                                                 # width, height
        self.pb2.setIconSize(QSize(18,18))                                     # button text
        self.pb2.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))       # standard triangle icon for play
        self.pb2.clicked.connect(self.cb_pb2)                                  # callback on click (play/pause)

        # position slider for video
        self.sld1 = QSlider(Qt.Horizontal,self)                                # creation
        self.sld1.setGeometry( 110, 940, 482, 30)                              # left, top, width, height  
        self.sld1.sliderMoved.connect(self.cb_sld1)                            # callback on move                    

        # title label
        self.lb23 = QLabel(self)                                               # creation                                     
        self.lb23.setText("Loss and accuracy")                                 # label text
        self.lb23.move(980,10)                                                 # left, top
        self.lb23.setStyleSheet("font-size: 30px; font-family: \
        FreeSans; font-weight: bold")                                          # set font and size

        # top right frame
        self.fm3 = QFrame(self)                                                # creation        
        self.fm3.setGeometry(980, 50, 850, 430)                                # left, top, width, height  
        self.fm3.setFrameShape(QFrame.Panel);                                  # use panel style for frame           
        self.fm3.setLineWidth(1)                                               # frame line width

        # video for loss and accuracy
        self.vw2 = QVideoWidget(self)                                          # declare video widget
        self.vw2.move(1000,55)                                                  # left, top
        self.vw2.resize(542,380)                                               # width, height
        self.vw2.setStyleSheet("background-color:black;");                     # set black background

        # wrapper for the video
        self.mp2 = QMediaPlayer(self)                                          # declare QMediaPlayer
        self.mp2.setVideoOutput(self.vw2)                                      # use video widget vw1 as output

        fileName2 = "path_to_video_2"                                          # local path to video
        self.mp2.setMedia(QMediaContent(QUrl.fromLocalFile(fileName2)))        # path to video
        self.mp2.stateChanged.connect(self.cb_mp2_1)                           # callback on change state (play, pause, stop)
        self.mp2.positionChanged.connect(self.cb_mp2_2)                        # callback to move slider cursor
        self.mp2.durationChanged.connect(self.cb_mp2_3)                        # callback to update slider range

        # play button for video
        self.pb3 = QPushButton(self)                                           # creation 
        self.pb3.move(1000,440)                                                  # left, top     
        self.pb3.resize(40,30)                                                 # width, height
        self.pb3.setIconSize(QSize(18,18))                                     # button text
        self.pb3.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))       # standard triangle icon for play
        self.pb3.clicked.connect(self.cb_pb3)                                  # callback on click (play/pause)

        # position slider for video
        self.sld2 = QSlider(Qt.Horizontal,self)                                # creation
        self.sld2.setGeometry(1060, 440, 482, 30)                              # left, top, width, height  
        self.sld2.sliderMoved.connect(self.cb_sld2)                            # callback on move 




    def cb_mp1_1(self, state):
        if self.mp1.state() == QMediaPlayer.PlayingState:                      # if playing, switch button icon to pause
            self.pb2.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
        elif self.mp1.state() == QMediaPlayer.StoppedState:                    # if stopped, rewind to first image
            self.mp1.play()
            self.mp1.pause()
        else:
            self.pb2.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))   # if paused, switch button icon to play

    def cb_mp1_2(self, position):
        self.sld1.setValue(position)                                           # set slider position to video position     

    def cb_mp1_3(self, duration):
        self.sld1.setRange(0, duration)                                        # set slider range to video position 

    def cb_pb2(self):
        if self.mp1.state() == QMediaPlayer.PlayingState:                      # set to pause if playing
            self.mp1.pause()
        else:
            self.mp1.play()                                                    # set to play if in pause

    def cb_sld1(self, position):            
        self.mp1.setPosition(position)                                         # set video position to slider position



    def cb_mp2_1(self, state):
        if self.mp2.state() == QMediaPlayer.PlayingState:                      # if playing, switch button icon to pause
            self.pb3.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
        elif self.mp2.state() == QMediaPlayer.StoppedState:                    # if stopped, rewind to first image
            self.mp2.play()
            self.mp2.pause()
        else:
            self.pb3.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))   # if paused, switch button icon to play

    def cb_mp2_2(self, position):
        self.sld2.setValue(position)                                           # set slider position to video position     

    def cb_mp2_3(self, duration):
        self.sld2.setRange(0, duration)                                        # set slider range to video position         

    def cb_pb3(self):
        if self.mp2.state() == QMediaPlayer.PlayingState:                      # set to pause if playing
            self.mp2.pause()
        else:
            self.mp2.play()                                                    # set to play if in pause

    def cb_sld2(self, position):            
        self.mp2.setPosition(position)                                         # set video position to slider position


# run GUI


def dnn_viewer():
    app = QApplication(sys.argv)                  # initiate app; sys.argv argument is only for OS-specific settings
    viewer = DNN_Viewer()                         # create instance of Fil_Rouge_Dashboard class
    viewer.show()                                 # display dashboard
    sys.exit(app.exec_())                         # allow exit of the figure by clicking on the top right cross


# call window function
dnn_viewer()
4

2 回答 2

2

tl:博士;

使用布局管理器。

解释

好吧,您似乎通过做一些真正错误的事情意外发现了一个(可能的)错误。

QVideoWidget 是一个比看起来更复杂的小部件,因为它与操作系统的底层图形系统交互,并且为了正确显示其内容(视频),必须主动通知它的几何形状。

简单来说,QVideoWidget 并不直接显示 QMediaPlayer 显示的视频的“图片”,而是告诉操作系统这样做(嗯,不完全是,但我们不会在这里讨论)。这是因为视频显示可能会利用一些硬件加速,或者需要一些处理(例如,对于 HDR 视频),类似于 3D/OpenGL 图形所做的。

当程序要显示一些(系统管理的)视频时,它必须告诉操作系统该视频的可用几何形状,以便操作系统能够在正确的坐标处显示它,并可能应用调整大小,某种形式的“剪辑”(例如,如果覆盖了另一个窗口)或任何其他级别的 [后] 处理。


我之前所说的“真正错误的事情”是基于您对两个视频小部件都使用固定几何形状(大小和位置)的事实,并且我认为Qt 无法将这些几何形状通知系统超过如果在实际映射视频窗口之前发生这种情况(如“所示”中所示),则立即生成一个小部件。

除了手头的问题,为什么它真的错了?

我们的每一个设备大多都是独一无二的:您在设备上看到的内容将以(可能完全)不同的方式显示在其他设备上。
造成这种情况的原因有很多,包括:

  • 操作系统(和版本)及其行为;
  • 屏幕尺寸和 DPI(例如,我无法查看您的代码的完整窗口,因为我的屏幕较小);
  • 默认/自定义系统字体大小;最重要的是,如果默认字体很大,小部件可能会重叠;
  • 进一步定制(例如,默认边距和间距);
  • 如果界面是“自适应”的,用户应该能够调整界面大小:
    • 如果用户的屏幕较小,则 ui 应该可调整大小,以便所有内容都可见,而不需要将窗口移动到屏幕边缘之外(有时这是不可能的:例如在 Windows 上,您不能移动窗口高于屏幕上边距);
    • 如果用户有更大的屏幕(或使用非常高的 DPI 设置),界面会太小,某些元素可能难以阅读或交互;

这就是现在几乎所有网站都使用“响应式”布局的原因,它根据将要显示的设备的屏幕调整内容。

解决方案非常简单,也将解决您的 GUI 的问题:避免为您的 GUI 使用任何固定的几何图形,而是使用布局管理器。

请注意,您仍然可以使用固定大小(而不是位置,大小!):这不是什么大问题,但是使用布局管理器会为您提供很多帮助,方法是根据可用空间重新定位所有元素。
其原因是布局管理器确保任何调整大小的操作(在第一次显示窗口时也会发生多次)也会在需要时通知系统(例如,调整 QVideoWidget输出)。

如果你想保持“右下/左上”布局,你仍然可以这样做:为小部件(DNN_Viewer)设置一个主 QGridLayout,为每个玩家创建另一个网格布局并将这些布局添加到主布局。

结构将是这样的:

+------------------------- DNN_Viewer -------------------------+
|                              | +------ player2Layout ------+ |
|                              | |                           | |
|                              | |            vw2            | |
|                              | |                           | |
|                              | +-------+-------------------+ |
|                              | |  pb2  |        sld1       | |
|                              | +-------+-------------------+ |
+------------------------------+-------------------------------+
| +------ player1Layout------+ |                               |
| |                          | |                               |
| |            vw1           | |                               |
| |                          | |                               |
| +-------+------------------+ |                               |
| |  pb1  |        sld2      | |                               |
| +-------+------------------+ |                               |
+------------------------------+-------------------------------+
class DNN_Viewer(QWidget):             
    # ...
    def init_gui(self):
        # create a grid layout for the widget and automatically set it for it
        layout = QtWidgets.QGridLayout(self)

        player1Layout = QtWidgets.QGridLayout()
        # add the layout to the second row, at the first column
        layout.addLayout(player1Layout, 1, 0)

        # video for weights and gradients
        self.vw1 = QVideoWidget(self)
        # add the video widget at the first row and column, but set its column
        # span to 2: we'll need to add two widgets in the second row, the play
        # button and the slider
        player1Layout.addWidget(self.vw1, 0, 0, 1, 2)

        # ...

        self.pb2 = QPushButton(self)
        # add the button to the layout; if you don't specify rows and columns it
        # normally means that the widget is added to a new grid row
        player1Layout.addWidget(self.pb2)

        # ...

        self.sld1 = QSlider(Qt.Horizontal,self)
        # add the slider to the second row, besides the button
        player1Layout.addWidget(self.sld1, 1, 1)

        # ...

        player2Layout = QtWidgets.QGridLayout()
        # add the second player layout to the first row, second column
        layout.addLayout(player2Layout, 0, 1)

        self.vw2 = QVideoWidget(self)
        # same column span as before
        player2Layout.addWidget(self.vw2, 0, 0, 1, 2)

        # ...

        self.pb3 = QPushButton(self)
        player2Layout.addWidget(self.pb3, 1, 0)

        # ...

        self.sld2 = QSlider(Qt.Horizontal,self)
        player2Layout.addWidget(self.sld2, 1, 1)

这将解决您的主要问题(以及您没有考虑的许多其他问题)。


一些进一步的建议:

  • 使用更具描述性的变量名称;类似pb2lb23看起来更容易使用的东西,你可能会认为短变量等于更少的打字时间。实际上,这并没有最终的好处:虽然较短的变量名可能会提高编译速度(尤其是对于像 Python 这样的解释型语言),但最终几乎没有任何优势;相反,您必须记住“sld2”的含义,而“player2Slider”之类的内容更具描述性且更易于阅读(这意味着您将更快地阅读和调试,阅读您的代码的人会理解它并且帮助您更轻松)
  • 出于与上述相同的原因,请使用更具描述性的函数名称:像这样的名称cb_mp1_3实际上没有任何意义;命名真的很重要,上面报告的启动速度改进对于今天的计算机来说几乎可以忽略不计;它还可以帮助您从其他人那里获得帮助:与了解您的代码的作用相比,了解您的实际问题需要更多时间,因为所有这些名称对我来说几乎毫无意义;阅读更多关于 Python 代码的官方样式指南(又名 PEP 8);
  • 明智地使用评论:
    • 避免过度评论,这会使评论分散注意力,同时失去其大部分目的(也就是说,虽然“让代码成为文档”是一个好概念,但不要过分夸大它)
    • 避免“花哨”格式的评论:它们可能看起来很酷,但最终它们只是烦人的处理;如果您想评论一个函数以更好地描述它的作用,请使用 Python 已经提供的三引号功能;还要考虑到许多代码共享服务都有列限制(StackOverflow 就是其中之一):人们需要滚动每一行才能阅读相应的评论;
    • 如果您需要单行函数的描述,则该函数可能不是描述性的,因为它可能或应该是描述性的,如上所述;
  • 与函数或类之间的空行分隔更加一致:Python 的创建考虑到了可读性,遵循该原则是一件好事;
  • 不要覆盖现有的属性名称:self.width()并且self.height()是所有 QWidget 的基本属性,您可能需要经常访问它们;
  • 与您正在使用的导入更加一致,尤其是对于像 Qt 这样的复杂模块:您应该导入子模块 ( from PyQt5 import QtWidgets, ...) 或单个类 ( from PyQt5.QtWidgets import QApplication, ...);请注意,虽然后者可能被认为更“pythonic”,但它通常对 Qt 来说很棘手,因为它有数百个类(每个脚本中可能需要几十个),那么你总是必须记住每个类都添加当您需要它时,您可能最终会导入不再使用的不必要的类;使用这种方法并没有太大的性能改进,至少在 Qt 中,特别是如果您忘记删除不必要的导入(在您的情况下,导入单个类的可能好处被完全取消,因为至少有 10 个导入的类是从未实际使用过);
  • 如果不是绝对必要,请避免从其他框架进行不必要的导入:如果您需要了解屏幕几何形状,请使用QApplication.screens(),不要仅仅为此导入 Tk;
于 2020-04-24T03:54:27.933 回答
1

所以,我再次开始尝试用音乐家非常详尽的答案来解决这个问题。它确实解决了这个问题,但我对只适用于自适应 GUI 的解决方案并不满意。所以我再次调查了这个问题,从一个只有两个视频的最小 GUI 开始。而且,令我惊讶的是,这两个视频播放得很好,即使是固定大小的 GUI。

所以我开始再次膨胀 GUI,添加所有元素,直到我恢复我的初始 GUI。在某个时候,我再次遇到了这个错误,这使得确定实际原因成为可能。

所以罪魁祸首被称为... QFrame。是的,真的。Qframe 造成了所有这些混乱。起初,我使用带有 setFrameShape(QFrame.Panel) 的 QFrame,以便立即创建一个矩形框架。然后我在框架内安装了视频小部件。事实证明,对于某些视频,QFrame 采取了一种奇怪的行为,并“覆盖”了视频输出,使视频查看器屏幕消失了。声音不受影响。它只发生在某些视频而不是其他视频,这没有任何实际意义。尽管如此,移除框架立即解决了问题,所以这确实是一个错误。

似乎使用 musicamante 的解决方案,框架没有采用这种奇怪的行为,因此是一个可行的解决方案。固定尺寸 GUI 的另一种可能解决方案是使用不覆盖视频的帧。具体来说,不是使用带有 setFrameShape(QFrame.Panel) 的单个 QFrame 在一个帧中创建一个矩形,而是必须使用一组四个帧,其中两个是带有 setFrameShape(QFrame.Hline) 的 QFrame,另外两个是QFrame 与 setFrameShape(QFrame.Vline),组织成一个矩形。我测试了它并且它有效。框架仅覆盖它们穿过的水平/垂直表面,因此矩形的“内部”不是任何框架的一部分,从而避免了错误。

于 2020-04-25T08:19:36.897 回答