pyqt5: второе видео не воспроизводится: одновременная проблема QMediaPlayer? - PullRequest
0 голосов
/ 24 апреля 2020

Я в основном строю GUI с pyqt5, который должен включать два видео. Для этого я использую QMediaPlayer в сочетании с QVideoWidget, по одному для каждого класса. Дело в том, что, пока первое видео воспроизводится, как и ожидалось, второе отказывается воспроизводиться. Он использует точно такой же фреймворк, что и первый (одна кнопка для воспроизведения / паузы и одна ползунок), и та же структура кода, но экран остается отчаянно черным при попытке воспроизведения.

Хуже, если я закомментируйте код для первого видео, теперь второе воспроизводится нормально. Может ли это означать, что между двумя QMedialPlayers существует некоторый конфликт? Я не могу понять это.

Любая помощь будет принята с благодарностью.

Вот мой код (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()

Ответы [ 2 ]

0 голосов
/ 25 апреля 2020

Итак, я снова начал пытаться решить проблему с очень исчерпывающим ответом musicamant. Это действительно решило проблему, но меня не порадовало решение, которое работало бы только с адаптивными графическими интерфейсами. Поэтому я снова исследовал проблему, начиная с минимального GUI, где будут присутствовать только два видео. И, к моему величайшему изумлению, два видео воспроизводились нормально, даже с фиксированным размером GUI.

Так что я снова начал раздувать GUI, добавляя все элементы, пока не восстановил свой начальный GUI , И в какой-то момент я снова столкнулся с ошибкой, которая позволила определить истинную причину.

Так что виновник называется ... QFrame. Да, по-настоящему. Qframe вызвал весь этот беспорядок. Сначала я использовал QFrame с setFrameShape (QFrame.Panel), чтобы сразу создать прямоугольный кадр angular. Затем я установил виджет видео внутри рамки. Оказывается, что с некоторыми видео QFrame использует странное поведение и как бы «покрывает» вывод видео, делая экран просмотра видео vani sh. Звук остается неизменным. Это происходит только для определенных видео, а не для других, что не имеет никакого реального смысла. Тем не менее, удаление рамки мгновенно решает проблему, так что это действительно ошибка.

Кажется, что с решением musicamante рамка не принимает это странное поведение, следовательно, рабочее решение. Другое возможное решение с графическим интерфейсом фиксированного размера - использовать кадры, которые не покрывают видео. Конкретнее, вместо использования одного QFrame с setFrameShape (QFrame.Panel), который создает прямоугольник в одном кадре, должен использоваться набор из четырех кадров, два из которых - QFrame с setFrameShape (QFrame.Hline), а два других - QFrame с setFrameShape (QFrame.Vline), организованной для формирования прямоугольника. Я проверил это, и это работает. Рамки покрывают только горизонтальные / вертикальные поверхности, через которые они go проходят, поэтому «внутренняя часть» прямоугольника не является частью какой-либо рамки, что позволяет избежать ошибки.

0 голосов
/ 24 апреля 2020

tl: dr;

Используйте менеджеры по расположению.

Объяснение

Что ж, похоже, вы случайно обнаружили ( возможную ) ошибку, выполнив что-то действительно не так.

QVideoWidget - это виджет, который на более сложен, чем кажется , поскольку он взаимодействует с базовой графической системой ОС и, чтобы правильно отображать его содержимое (видео), он имеет , чтобы быть активно уведомленным о его геометрии.

Проще говоря, QVideoWidget напрямую не показывает «картинки» видео, которое показывает QMediaPlayer, но сообщает операционной системе сделать это (ну, не совсем, но мы не будем обсуждать это здесь). Это связано с тем, что для отображения видео может использоваться некоторое аппаратное ускорение или некоторая обработка (например, для HDR-видео), аналогично тому, что делает графика 3D / OpenGL.

Когда программа собирается отображать некоторые данные ( Управляемое системой) видео, оно имеет , чтобы сообщить ОС о доступной геометрии для этого видео, чтобы ОС могла показывать его в правильных координатах и, возможно, применять изменение размера, некоторую форму «отсечения» (если наложено другое окно, например) или любой другой уровень [post] обработки.


«Что-то действительно неправильное», о котором я говорил ранее, основано на том факте, что вы используете исправленный геометрии (размеры и позиции) для обоих виджетов видео, и я думаю , что Qt не может уведомить систему об этих геометриях сразу для нескольких виджетов, если это происходит за до окно видео фактически отображается (как в «показанном»).

Почему это действительно неправильно, кроме рассматриваемой проблемы?

* 103 1 * Каждое из наших устройств в основном уникально : то, что вы видите на своем устройстве, будет отображаться (возможно, радикально) иначе, чем на других.
Существует множество причин, в том числе:
  • Операционная система (и версии) и ее поведение;
  • Размер экрана и DPI (например, я не смог просмотреть полное окно вашего кода, так как я меньший экран);
  • по умолчанию / настраиваемый размер системного шрифта; наиболее важно, если шрифт по умолчанию очень большой, виджеты могут перекрываться;
  • дальнейшая настройка (например, поля по умолчанию и интервалы);
  • , если интерфейс «адаптивный», пользователь должен иметь возможность изменять размер интерфейса:
    • , если у пользователя экран меньшего размера, пользовательский интерфейс должен быть изменяемого размера, чтобы все было видно, вместо того, чтобы иметь необходимость перемещать окно за пределы экрана (что-то, что иногда невозможно: например, на Windows вы не можете переместить окно выше верхнего поля экрана);
    • , если у пользователя экран большего размера (или используется настройка с очень высоким DPI), интерфейс будет слишком маленьким, а некоторые элементы могут быть трудными для чтения или взаимодействия;

Это причина, по которой почти любой современный веб-сайт использует «отзывчивые» макеты, которые адаптируют содержимое в соответствии с экраном устройства, на котором они будут отображаться.

Решение очень простое, и также решит большой вопрос о GUI: избегайте фиксированной геометрии для GUI и используйте вместо этого менеджеры компоновки.

Обратите внимание, что вы все еще можете использовать фиксированные размеры (не позиции, размеры!): это не такая уж большая проблема, но использование менеджеров компоновки поможет вам с этим, переместив все элементы в соответствии с доступным пространством.
Причина в том, что менеджеры компоновки гарантируют, что любой операция изменения размера (то, что также происходит много раз, как только окно показывается в первый раз) также уведомляется системой, когда это требуется (как, например, адаптация вывода QVideoWidget).

Если вы Если вы хотите сохранить макет «нижний правый / верхний левый», вы все равно можете это сделать: установить основной QGridLayout для виджета (DNN_Viewer), создать другой макет сетки для каждого игрока и добавить этот макет в основной.

Структура будет выглядеть примерно так:

+------------------------- 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)

Это решит вашу главную проблему (и многие другие, которые вы не рассмотрели).


Некоторые дополнительные предложения:

  • использовать более описательные имена переменных; такие вещи, как pb2 или lb23 кажутся более простыми в использовании, и вы можете подумать, что короткие переменные равны меньшему времени, затрачиваемому на набор текста. На самом деле, есть нет заключительного преимущества в этом: хотя может быть верно, что более короткие имена переменных могут улучшить скорость компиляции (особенно для интерпретируемых языков, таких как Python), в конце почти нет никаких преимуществ; напротив, вам придется помнить, что означает «sld2», в то время как что-то вроде «player2Slider» намного более наглядно и проще для чтения (что означает, что вы будете читать и отлаживать быстрее, и люди, читающие ваш код, поймут это и поможет вам гораздо проще)
  • по той же причине, что и выше, используйте более описательные имена функций: имена вроде cb_mp1_3 буквально ничего не значат; наименование действительно важно, и улучшение скорости запуска, о котором говорилось выше, практически невозможно на современных компьютерах; это также помогает вам получить помощь от других: потребовалось больше времени, чтобы понять, в чем заключалась ваша проблема, чем понять, что делает ваш код, поскольку все эти имена были для меня почти бессмысленными; читайте больше на официальном руководстве по стилю для Python кода (он же, PEP 8);
  • используйте комментарии с умом:
    • избегайте чрезмерного комментирования, это отвлекает внимание теряя при этом большую часть своей цели (в то время как «пусть код будет документацией» - хорошая идея, не переоценивайте ее)
    • избегайте «причудливых» отформатированных комментариев: они могут показаться крутыми, но в конец, с которым они раздражают; если вы хотите прокомментировать функцию, чтобы лучше описать, что она делает, используйте функцию тройных кавычек, которую Python уже предоставляет; также учтите, что многие службы совместного использования кода имеют ограничения по столбцам (и среди них StackOverflow): людям нужно будет прокрутить каждую строку , чтобы прочитать соответствующий комментарий;
    • , если вам нужно описание для однострочная функция, возможно, что функция не является описательной, как могла бы или должна быть, как объяснено выше;
  • в большей степени соответствует разделению пустых строк между функциями или классами: Python был создан для удобства чтения, и хорошо следовать этому принципу;
  • не перезаписывать существующие имена атрибутов: self.width() и self.height() являются базовыми свойствами всех QWidgets, и вам, возможно, потребуется часто обращаться к ним;
  • более согласованно с импортом, который вы используете, особенно со сложными модулями, такими как Qt: вам следует либо импортировать субмодули (from PyQt5 import QtWidgets, ... ) или отдельные классы (from PyQt5.QtWidgets import QApplication, ...); обратите внимание, что, хотя последний может считаться более "pythoni c", с Qt это обычно сложно, так как он имеет сотни классов (и вам может понадобиться десятки из них в каждом скрипте), тогда вам всегда нужно помнить, чтобы добавить каждый класс каждый раз, когда вам это нужно, и вы можете импортировать ненужные классы, которые больше не используете; этот подход не сильно улучшит производительность, по крайней мере, с Qt, особенно если вы забудете удалить ненужный импорт (в вашем случае возможное преимущество импорта отдельных классов полностью отменяется тем фактом, что существует как минимум 10 импортированных классов, которые фактически никогда не используется);
  • избегайте ненужного импорта из других платформ, если они не являются абсолютно необходимыми: если вам нужно знать геометрию экрана, используйте QApplication.screens(), не импортируйте Tk просто за это;
...