0

I am writing a part of a basic FFmpeg GUI application, and this is the installation code. While setting install_progress_bar from the 'download_thread' the terminal is spammed with QObject::setParent: Cannot set parent, new parent is in a different thread. When in the 'install_thread', the terminal is spammed with QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it? so I'm obviously doing something wrong. After some googling, I still cannot figure out what I'm doing wrong. Any help?

The functions in question: (any bad formatting is due to copy and pasting, sorry about that.)

def download_thread(self):
    HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
     size = 0
     with requests.get("https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip", headers = HEADERS, stream = True) as r: // this is not on a seperate line in the actual code
          r.raise_for_status()
          total_size = int(r.headers.get("content-length", 0))
          with open("ffmpeg.zip", "wb") as f:
               for chunk in r.iter_content(chunk_size = 8192):
                   size = size + f.write(chunk)
                   self.install_progress_bar.setValue(int(size / total_size * 100))
                   if threading.main_thread().is_alive() == False:
                      os.remove("ffmpeg.zip")
                      break
      return 0

and

def install_thread(self):
    self.zipFile = zipfile.ZipFile("ffmpeg.zip")
    os.mkdir("ffmpegt/")
    for member in self.zipFile.infolist():
        self.install_progress_bar.setValue(self.install_progress_bar.value() + 1)
        self.zipFile.extract(member, "ffmpegt/")
        if threading.main_thread().is_alive() == False:
            os.remove("ffmpeg.zip")
            os.remove("ffmpegt/")
            break
    self.install_progress_bar.setValue(100)
    return 0

The rest of the code is as follows:

import sys
import os
import subprocess
import requests
from time import sleep
from threading import Thread
import threading

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QGridLayout, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QMessageBox, QProgressBar, QDialog, QDialogButtonBox
from PyQt6.QtGui import QPalette, QColor
import zipfile

with open("stylesheet.qss", "r") as stylesheet:
    dark_stylesheet = stylesheet.read()
with open("stylesheet_bright.qss", "r") as stylesheet:
    light_stylesheet = stylesheet.read()

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("FFmpeg check")
        self.setGeometry(100, 100, 500, 100)
        
        self.label = QLabel("Checking if you have FFmpeg installed...", alignment=Qt.AlignmentFlag.AlignCenter)
        
        self.install_progress_bar = QProgressBar()
        self.install_yes_button = QPushButton("Yes")
        self.install_no_button = QPushButton("No")
        self.install_yes_button.clicked.connect(self.install_yes)
        self.install_no_button.clicked.connect(self.install_no)
        
        self.container = QWidget()
        self.Vlayout = QVBoxLayout()
        self.Vlayout.addWidget(self.label)
        
        self.PlaceholderLayout = QHBoxLayout()
        self.Vlayout.addLayout(self.PlaceholderLayout)
        
        self.Hlayout = QHBoxLayout()
        self.Vlayout.addLayout(self.Hlayout)
        self.container.setLayout(self.Vlayout)
        self.setCentralWidget(self.container)
        try:
            process = subprocess.Popen(["FFmeg", "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            output, error = process.communicate()
            output = output.decode("utf-8")
            error = error.decode("utf-8")
        except FileNotFoundError:
            output = ""
            error = ""
        
        if "ffmpeg version" in output:
            self.label.setText("FFmpeg is installed.")
        else:
            self.label.setText("FFmpeg is not installed. Do you want to install it?")
            self.Hlayout.addWidget(self.install_yes_button)
            self.Hlayout.addWidget(self.install_no_button)
    
    def install_yes(self, event):
        self.PlaceholderLayout.addWidget(self.install_progress_bar)
        self.install_progress_bar.setValue(0)
        self.install_progress_bar.setMaximum(100)
        self.install_progress_bar.setTextVisible(True)
        self.install_progress_bar.setFormat("Downloading... %p%")
        self.install_progress_bar.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.install_progress_bar.setContentsMargins(0, 0, 0, 0)

        self.label.setText("FFmpeg is downloading...")
        download = Thread(target=self.download_thread)
        sleep(0.5)
        download.start()
        while download.is_alive():
            app.processEvents()
        self.install_progress_bar.setFormat("Installing... %p%")
        self.install_progress_bar.setValue(0)
        self.label.setText("FFmpeg is installing...")
        install = Thread(target=self.install_thread)
        sleep(0.5)
        install.start()
        while install.is_alive():
            app.processEvents()
        self.label.setText("FFmpeg is installed.")
        self.dialog = CustomDialog("FFmpeg is installed.")
        self.dialog.exec()
        return 0
            

        
    def install_no(self, event):
        app.quit()
        
    def download_thread(self):
        HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
        size = 0
        with requests.get("https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip", headers = HEADERS, stream = True) as r:
            r.raise_for_status()
            total_size = int(r.headers.get("content-length", 0))
            with open("ffmpeg.zip", "wb") as f:
                for chunk in r.iter_content(chunk_size = 8192):
                    size = size + f.write(chunk)
                    self.install_progress_bar.setValue(int(size / total_size * 100))
                    if threading.main_thread().is_alive() == False:
                        os.remove("ffmpeg.zip")
                        break
        return 0
    
    def install_thread(self):
        self.zipFile = zipfile.ZipFile("ffmpeg.zip")
        os.mkdir("ffmpegt/")
        for member in self.zipFile.infolist():
            self.install_progress_bar.setValue(self.install_progress_bar.value() + 1)
            self.zipFile.extract(member, "ffmpegt/")
            if threading.main_thread().is_alive() == False:
                os.remove("ffmpeg.zip")
                os.remove("ffmpegt/")
                break
        self.install_progress_bar.setValue(100)
        return 0
    
class CustomDialog(QDialog):
    def __init__(self, text, windowTitle = "Dialog", cancelable = True):
        super().__init__()

        self.setWindowTitle(windowTitle)
        if cancelable == True:
            QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
        else:
            QBtn = QDialogButtonBox.StandardButton.Ok

        self.buttonBox = QDialogButtonBox(QBtn)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

        self.layout = QVBoxLayout()
        message = QLabel(text)
        self.layout.addWidget(message)
        self.layout.addWidget(self.buttonBox)
        self.setLayout(self.layout)


app = QApplication(sys.argv)
app.setStyleSheet(dark_stylesheet)
window = MainWindow()

window.show()

app.exec()

Please note the the code works, the progressbar updates as the download and the install completes, it's just that at a random point during this process the application crashes.

5
  • 1
    Haven't read the code completely but... all access/updates of GUI elements must happen on the same thread on which main is running. Commented Nov 22, 2023 at 21:17
  • 1
    UI elements are not thread-safe. They cannot be created and should never be accessed from a thread different than the main one running the application. Use signals. Commented Nov 22, 2023 at 22:59
  • in some GUIs you have to use queue to send message from one thread to main thread and main thread may use timer to check if there is new message and update PostgressBar Commented Nov 23, 2023 at 0:10
  • @musicamante Can you please give me pointers on how to use signals in this situation? I'm new to PyQT (as you can see, lol) Commented Nov 23, 2023 at 3:11
  • @KaneryU Sorry but no. I suggest you to carefully read the posts for which I marked yours as a duplicate, including all their answers and comments, and every related link contained in each of them Then try to learn how threading can be used within Qt (and Python) by creating small and simple tests, so that you can begin to understand the underlying mechanisms. Threading adds a further (and thick) difficulty layer to programming, and you have to be patient enough to learn about its implications. Don't try to rush your learning; it never helps, especially if you're learning on your own. Commented Nov 23, 2023 at 3:20

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.