Search code examples
pythonpyqtpyqt5signals-slotsqthread

Url not defined using Signals and slots PyQt5 in different classes


I am trying to run a Video with its parameter in parallel using Threads so when I click on the SD button its activates TP1 and TP2 buttons, and for each has a different Url.

I want to display the video and start the ProgressBar and show the result at the end.

from multiprocessing import Process

import sys
import json
import shlex
import threading
import subprocess
import webbrowser
from QLed import QLed
from functools import partial
from PyQt5.QtGui import QColor,QFont
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtGui import QPainter, QPen
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtCore import QDir, Qt, QUrl, QSize, QPoint, QTimer
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QMainWindow
from PyQt5 import QtCore, QtGui, QtWidgets, QtMultimedia, QtMultimediaWidgets
from PyQt5.QtWidgets import (QWidget, QPushButton, QApplication,QGridLayout, QLCDNumber)
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QGridLayout, QLCDNumber





class Analyzer(QtCore.QObject):


    result_ready = QtCore.pyqtSignal(object)

    def do_work(self, myurl):


        cmd = "ffprobe -v quiet -print_format json -show_streams"
        args = shlex.split(cmd)

        args.append(myurl)

        ffprobeOutput = subprocess.check_output(args).decode('utf-8')
        ffprobeOutput = json.loads(ffprobeOutput)

        result = ffprobeOutput['streams'][0]
        self.result_ready.emit(result)



class MainProg(QtWidgets.QMainWindow):

    def __init__(self):


        super(MainProg, self).__init__()

        self.resize(870, 525)
        self.centralwidget = QtWidgets.QWidget(self)
        self.centralwidget.setObjectName("centralwidget")

        ############################      The Viedeo and frame  ######

        self.frame = QtWidgets.QFrame(self)
        self.frame.setGeometry(QtCore.QRect(450, 40, 391, 291))
        self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtWidgets.QFrame.Raised)

        self.mediaPlayer = QtMultimedia.QMediaPlayer(self.frame)
        self.viewer1 = QtMultimediaWidgets.QVideoWidget(self.frame)
        self.mediaPlayer.setVideoOutput(self.viewer1)
        layout1 = QtWidgets.QGridLayout(self.frame)
        layout1.addWidget(self.viewer1, 0, 0, 1, 2)
       #############################################################

        self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
        self.progressBar.setGeometry(QtCore.QRect(110, 470, 143, 25))
        self.progressBar.setProperty("value", 0)
        self.progressBar.setTextVisible(True)

        self.lcd = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcd.setGeometry(QtCore.QRect(220, 50, 146, 50))
        self.lcd1 = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcd1.setGeometry(QtCore.QRect(220, 100, 146, 50))
        self.lcd2 = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcd2.setGeometry(QtCore.QRect(220, 150, 146, 50))
        self.lcd3 = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcd3.setGeometry(QtCore.QRect(220, 200, 146, 50))
        self.lcd4 = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcd4.setGeometry(QtCore.QRect(220, 250, 146, 50))
        self.lcd5 = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcd5.setGeometry(QtCore.QRect(220, 300, 146, 50))
        self.lcd6 = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcd6.setGeometry(QtCore.QRect(220, 350, 146, 50))


        self.txtt = QtWidgets.QLabel(self.centralwidget)
        self.txtt.setFont(QFont('Arial', 12))
        self.txtt.setGeometry(QtCore.QRect(20, 0, 300, 400))

        self.txtt.setText("Video"
                          "\nCode Name .................."
                          "\n\nHorizont........................"
                          "\n\nVertical.........................."
                          "\n\nDisplay Aspect Ratio......"
                          "\n\nRefrence........................."
                          "\n\nB frames........................."
                          "\n\nStart Bits......................."
                          "\n\nSample Aspect ratio......."
                          "\n\nBit Rate.........................")

        self.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(self)
        self.statusbar.setObjectName("statusbar")
        self.setStatusBar(self.statusbar)




       #########################The buttons#################################

        self.AASD = QtWidgets.QToolButton(self)
        self.AASD.setGeometry(QtCore.QRect(140, 20, 31, 32))
        self.AASD.setObjectName("AASD")
        self.AASD.setText("SD")
        self.AASD.clicked.connect(self.funcchoos)
        QTimer.singleShot(5000, lambda: self.AASD.setDisabled(False))

        self.Testpunk1 = QtWidgets.QToolButton(self)
        self.Testpunk1.setGeometry(QtCore.QRect(150, 400, 31, 32))

        self.Testpunk2 = QtWidgets.QToolButton(self)
        self.Testpunk2.setGeometry(QtCore.QRect(150, 430, 31, 32))

        self.Testpunk1.setText("TP1")
        self.Testpunk2.setText("TP2")

        self.Testpunk1.setObjectName("TP1")
        self.Testpunk2.setObjectName("TP2")

        self.Tp1 = QLed(self, onColour=QLed.Orange, shape=QLed.Circle)
        self.Tp1.setGeometry(QtCore.QRect(185, 415, 25, 25))
        self.Tp1.value = False
    ########################### the functions############################

    def funcchoos (self):

        QtCore.QTimer.singleShot(500, self.TPLed) # Using timer as QLed uses it in its tests

        if self.sender().objectName() == "AASD":
            self.Testpunk1.clicked.connect(self.MyUrl)
            self.Testpunk2.clicked.connect(self.MyUrl)
            return

    def MyUrl(self):
        TP1 = "293.168.1.6:1115"
        TP2 = "239.168.1.7:1116"


        if self.sender().objectName() == "TP1":
            myurl = TP1
            print("TP1 is playing")
            self.dep3(myurl)

            return
        if self.sender().objectName() == "TP2":
            myurl = TP2
            self.dep3(myurl)

            print(myurl)
            print("TP2 is playing")
            return

##############################################################
    def TPLed(self):
        self.Tp1.setValue(True)  # the LED ON code


#########################################################################

    def dep3(self,myurl):
        # set progress bar to undetermined state and disable button
        self.progressBar.setRange(0,0)
        self.Testpunk1.setEnabled(False)
        self.mediaPlayer.setMedia(QMediaContent(QUrl(myurl)))
        self.mediaPlayer.play()
        # create thread for doing heavy work
        self.thread = QtCore.QThread()
        self.worker = Analyzer()
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.do_work(myurl))
        self.thread.finished.connect(self.worker.deleteLater)
        self.worker.result_ready.connect(self.process_result)
        self.worker.result_ready.connect(self.worker.do_work(myurl))


        self.thread.start()

    def process_result(self, result):

        codec_name = result['codec_name']
        width = result['width']
        height = result['height']
        display_aspect_ratio = result['display_aspect_ratio']
        sample_aspect_ratio = result['sample_aspect_ratio']
        refs = result['refs']
        has_b_frames = result['has_b_frames']

        self.lcd.display(has_b_frames)
        self.lcd1.display(codec_name)
        self.lcd2.display(width)
        self.lcd3.display(height)
        self.lcd4.display(display_aspect_ratio)
        self.lcd5.display(sample_aspect_ratio)
        self.lcd6.display(refs)

        # reset progress bar and push button
        self.progressBar.setRange(0,100)
        self.progressBar.setValue(100)
        self.pushButton.setEnabled(True)



        print("done!!")



if __name__ == "__main__":
    import sys



    app = QtWidgets.QApplication(sys.argv)


    player = MainProg()
    player.show()
    sys.exit(app.exec_())

Solution

  • The main problem is that when you use:

    foo.signal.connect(function(args))
    

    equals

    value = function(args)
    foo.signal.connect(value)
    

    which causes the error since connect expects a callable, and in your case value is None causing the error.

    The solution in general is to use lambda or partials, but the first will cause the function to be executed in the main thread since it is invoked in that thread so it must be discarded, instead partials only add arguments.

    • lambda:
    foo.signal.connect(lambda *_, args=args : function(args))
    
    • functools.partial
    foo.signal.connect(functools.partial(function, args))
    

    In addition you have other errors such as every time you invoke "funcchoos" a new connection is created between the "TestpunkX" and MyUrl causing that every time you press "TestpunkX", "MyUrl" is invoked as many times as "funcchoos is invoked ".

    Considering the above, I rewrote your code as there may be other errors.

    from functools import partial
    import json
    import shlex
    import subprocess
    import sys
    
    
    from QLed import QLed
    
    from PyQt5 import QtCore, QtGui, QtWidgets, QtMultimedia, QtMultimediaWidgets
    
    
    class Analyzer(QtCore.QObject):
        result_ready = QtCore.pyqtSignal(object)
    
        @QtCore.pyqtSlot(str)
        def do_work(self, myurl):
            cmd = "ffprobe -v quiet -print_format json -show_streams"
            args = shlex.split(cmd)
    
            args.append(myurl)
    
            ffprobeOutput = subprocess.check_output(args).decode("utf-8")
            ffprobeOutput = json.loads(ffprobeOutput)
    
            result = ffprobeOutput["streams"][0]
    
            self.result_ready.emit(result)
    
    
    class MainProg(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainProg, self).__init__(parent)
    
            self.setFont(QtGui.QFont("Arial", 12))
    
            central_widget = QtWidgets.QWidget()
            self.setCentralWidget(central_widget)
            hlay = QtWidgets.QHBoxLayout(central_widget)
    
            left_widget = QtWidgets.QWidget()
    
            self.mediaPlayer = QtMultimedia.QMediaPlayer()
            self.video_widget = QtMultimediaWidgets.QVideoWidget()
            self.video_widget.setContentsMargins(30, 30, 30, 30)
            self.mediaPlayer.setVideoOutput(self.video_widget)
    
            self.sd_button = QtWidgets.QToolButton(text="SD", checkable=True)
            self.sd_button.setFixedSize(31, 32)
            self.code_lcd = QtWidgets.QLCDNumber()
            self.horizontal_lcd = QtWidgets.QLCDNumber()
            self.vertical_lcd = QtWidgets.QLCDNumber()
            self.display_aspect_ratio_lcd = QtWidgets.QLCDNumber()
            self.reference_lcd = QtWidgets.QLCDNumber()
            self.b_frames_lcd = QtWidgets.QLCDNumber()
            self.start_bits_lcd = QtWidgets.QLCDNumber()
            self.sample_aspect_ratio_lcd = QtWidgets.QLCDNumber()
            self.bit_rate_lcd = QtWidgets.QLCDNumber()
            self.tp1_button = QtWidgets.QToolButton(text="TP1")
            self.tp2_button = QtWidgets.QToolButton(text="TP2")
            self.led = QLed(self, onColour=QLed.Orange, shape=QLed.Circle)
            self.progressbar = QtWidgets.QProgressBar()
    
            for lcd in (
                self.code_lcd,
                self.horizontal_lcd,
                self.vertical_lcd,
                self.display_aspect_ratio_lcd,
                self.reference_lcd,
                self.b_frames_lcd,
                self.start_bits_lcd,
                self.sample_aspect_ratio_lcd,
                self.bit_rate_lcd,
            ):
                lcd.setFixedSize(146, 50)
    
            hlay.addWidget(left_widget)
            hlay.addWidget(self.video_widget, stretch=1)
    
            lay = QtWidgets.QGridLayout(left_widget)
            lay.setVerticalSpacing(5)
    
            for i, (text, widget) in enumerate(
                zip(
                    (
                        "Video",
                        "Codec Name:",
                        "Horizontal:",
                        "Vertical:",
                        "Display Aspect Ratio:",
                        "Refrence:",
                        "B frames:",
                        "Start Bits:",
                        "Sample Aspect ratio:",
                        "Bit Rate:",
                    ),
                    (
                        self.sd_button,
                        self.code_lcd,
                        self.horizontal_lcd,
                        self.vertical_lcd,
                        self.display_aspect_ratio_lcd,
                        self.reference_lcd,
                        self.b_frames_lcd,
                        self.start_bits_lcd,
                        self.sample_aspect_ratio_lcd,
                        self.bit_rate_lcd,
                    ),
                )
            ):
                label = QtWidgets.QLabel(text)
                lay.addWidget(label, i, 0)
                lay.addWidget(widget, i, 1, alignment=QtCore.Qt.AlignCenter)
    
            vlay = QtWidgets.QVBoxLayout()
            vlay.addWidget(self.tp1_button)
            vlay.addWidget(self.tp2_button)
    
            hlay2 = QtWidgets.QHBoxLayout()
            hlay2.addStretch(0)
            hlay2.addLayout(vlay)
            hlay2.addWidget(self.led)
            hlay2.addStretch(0)
    
            lay.addLayout(hlay2, lay.rowCount(), 0, 1, 2)
            lay.addWidget(self.progressbar, lay.rowCount(), 0, 1, 2)
    
            lay.setRowStretch(lay.rowCount(), 1)
            self.resize(960, 480)
    
            self.sd_button.toggled.connect(self.led.setValue)
            self.tp1_button.clicked.connect(self.on_tp_clicked)
            self.tp2_button.clicked.connect(self.on_tp_clicked)
    
            self.current_button = None
    
            thread = QtCore.QThread(self)
            thread.start()
    
            self.worker = Analyzer()
            self.worker.moveToThread(thread)
            self.worker.result_ready.connect(self.process_result)
    
        @QtCore.pyqtSlot()
        def on_tp_clicked(self):
            if self.sd_button.isChecked():
                urls_map = {
                    self.tp1_button: "293.168.1.6:1115",
                    self.tp2_button: "239.168.1.7:1116",
                }
                url = urls_map.get(self.sender(), "")
                if url:
                    self.play(url)
                    self.current_button = self.sender()
                    self.current_button.setEnabled(False)
    
        def play(self, url):
            self.progressbar.setRange(0, 0)
            self.mediaPlayer.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl(url)))
            self.mediaPlayer.play()
            wrapper = partial(self.worker.do_work, url)
            QtCore.QTimer.singleShot(0, wrapper)
    
        @QtCore.pyqtSlot(object)
        def process_result(self, result):
            self.current_button.setEnabled(True)
            self.current_button = None
            self.progressbar.setRange(0, 1)
            self.progressbar.setValue(0)
    
            for lcd, key in zip(
                (
                    self.code_lcd,
                    self.b_frames_lcd,
                    self.horizontal_lcd,
                    self.vertical_lcd,
                    self.display_aspect_ratio_lcd,
                    self.sample_aspect_ratio_lcd,
                    self.reference_lcd,
                ),
                (
                    "codec_name",
                    "has_b_frames",
                    "width",
                    "height",
                    "display_aspect_ratio",
                    "sample_aspect_ratio",
                    "refs",
                ),
            ):
                value = result.get(key, 0)
                lcd.display(value)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = MainProg()
        w.show()
        sys.exit(app.exec_())
    

    Although I don't see the need to use threads, in this case it is easier to use QProcess.

    from functools import partial
    import json
    import shlex
    import sys
    
    
    from QLed import QLed
    
    from PyQt5 import QtCore, QtGui, QtWidgets, QtMultimedia, QtMultimediaWidgets
    
    
    class MainProg(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainProg, self).__init__(parent)
    
            self.setFont(QtGui.QFont("Arial", 12))
    
            central_widget = QtWidgets.QWidget()
            self.setCentralWidget(central_widget)
            hlay = QtWidgets.QHBoxLayout(central_widget)
    
            left_widget = QtWidgets.QWidget()
    
            self.mediaPlayer = QtMultimedia.QMediaPlayer()
            self.video_widget = QtMultimediaWidgets.QVideoWidget()
            self.video_widget.setContentsMargins(30, 30, 30, 30)
            self.mediaPlayer.setVideoOutput(self.video_widget)
    
            self.sd_button = QtWidgets.QToolButton(text="SD", checkable=True)
            self.sd_button.setFixedSize(31, 32)
            self.code_lcd = QtWidgets.QLCDNumber()
            self.horizontal_lcd = QtWidgets.QLCDNumber()
            self.vertical_lcd = QtWidgets.QLCDNumber()
            self.display_aspect_ratio_lcd = QtWidgets.QLCDNumber()
            self.reference_lcd = QtWidgets.QLCDNumber()
            self.b_frames_lcd = QtWidgets.QLCDNumber()
            self.start_bits_lcd = QtWidgets.QLCDNumber()
            self.sample_aspect_ratio_lcd = QtWidgets.QLCDNumber()
            self.bit_rate_lcd = QtWidgets.QLCDNumber()
            self.tp1_button = QtWidgets.QToolButton(text="TP1")
            self.tp2_button = QtWidgets.QToolButton(text="TP2")
            self.led = QLed(self, onColour=QLed.Orange, shape=QLed.Circle)
            self.progressbar = QtWidgets.QProgressBar()
    
            for lcd in (
                self.code_lcd,
                self.horizontal_lcd,
                self.vertical_lcd,
                self.display_aspect_ratio_lcd,
                self.reference_lcd,
                self.b_frames_lcd,
                self.start_bits_lcd,
                self.sample_aspect_ratio_lcd,
                self.bit_rate_lcd,
            ):
                lcd.setFixedSize(146, 50)
    
            hlay.addWidget(left_widget)
            hlay.addWidget(self.video_widget, stretch=1)
    
            lay = QtWidgets.QGridLayout(left_widget)
            lay.setVerticalSpacing(5)
    
            for i, (text, widget) in enumerate(
                zip(
                    (
                        "Video",
                        "Codec Name:",
                        "Horizontal:",
                        "Vertical:",
                        "Display Aspect Ratio:",
                        "Refrence:",
                        "B frames:",
                        "Start Bits:",
                        "Sample Aspect ratio:",
                        "Bit Rate:",
                    ),
                    (
                        self.sd_button,
                        self.code_lcd,
                        self.horizontal_lcd,
                        self.vertical_lcd,
                        self.display_aspect_ratio_lcd,
                        self.reference_lcd,
                        self.b_frames_lcd,
                        self.start_bits_lcd,
                        self.sample_aspect_ratio_lcd,
                        self.bit_rate_lcd,
                    ),
                )
            ):
                label = QtWidgets.QLabel(text)
                lay.addWidget(label, i, 0)
                lay.addWidget(widget, i, 1, alignment=QtCore.Qt.AlignCenter)
    
            vlay = QtWidgets.QVBoxLayout()
            vlay.addWidget(self.tp1_button)
            vlay.addWidget(self.tp2_button)
    
            hlay2 = QtWidgets.QHBoxLayout()
            hlay2.addStretch(0)
            hlay2.addLayout(vlay)
            hlay2.addWidget(self.led)
            hlay2.addStretch(0)
    
            lay.addLayout(hlay2, lay.rowCount(), 0, 1, 2)
            lay.addWidget(self.progressbar, lay.rowCount(), 0, 1, 2)
    
            lay.setRowStretch(lay.rowCount(), 1)
            self.resize(960, 480)
    
            self.sd_button.toggled.connect(self.led.setValue)
            self.tp1_button.clicked.connect(self.on_tp_clicked)
            self.tp2_button.clicked.connect(self.on_tp_clicked)
    
            self.current_button = None
    
            self.process = QtCore.QProcess(self)
            self.process.finished.connect(self.on_finish)
    
        @QtCore.pyqtSlot()
        def on_tp_clicked(self):
            if self.sd_button.isChecked():
                urls_map = {
                    self.tp1_button: "293.168.1.6:1115",
                    self.tp2_button: "239.168.1.7:1116",
                }
                url = urls_map.get(self.sender(), "")
                if url:
                    self.play(url)
                    self.current_button = self.sender()
                    self.current_button.setEnabled(False)
    
        def play(self, url):
            self.progressbar.setRange(0, 0)
            self.mediaPlayer.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl(url)))
            self.mediaPlayer.play()
    
            cmd = "ffprobe -v quiet -print_format json -show_streams"
            program, *args = shlex.split(cmd)
            args.append(url)
            self.process.start(program, args)
    
        @QtCore.pyqtSlot()
        def on_finish(self):
            data = self.process.readAllStandardOutput().data()
            if data:
                ffprobeOutput = json.loads(data)
                result = ffprobeOutput['streams'][0]
                for lcd, key in zip(
                    (
                        self.code_lcd,
                        self.b_frames_lcd,
                        self.horizontal_lcd,
                        self.vertical_lcd,
                        self.display_aspect_ratio_lcd,
                        self.sample_aspect_ratio_lcd,
                        self.reference_lcd,
                    ),
                    (
                        "codec_name",
                        "has_b_frames",
                        "width",
                        "height",
                        "display_aspect_ratio",
                        "sample_aspect_ratio",
                        "refs",
                    ),
                ):
                    value = result.get(key, 0)
                    lcd.display(value)
    
    
            self.current_button.setEnabled(True)
            self.current_button = None
            self.progressbar.setRange(0, 1)
            self.progressbar.setValue(0)
    
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = MainProg()
        w.show()
        sys.exit(app.exec_())