Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I am trying to use youtube-dl module to download videos from youtube. I created a simple GUI to do a little job, I need when user clicks start button the thread will be called and download starts and sends data using emit method, when this data arrives to read function in Main class, the thread must stops after call stop function from GUI, I tried to make event loop in qthread using exec_() and stop thread with exit , and I tried too use terminate but the GUI freezes.

The code that I used is:

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from youtube_dl import *
class Worker(QThread):
    data = pyqtSignal(object)
    def __init__(self):
        super(Worker, self).__init__()
        self.flag = True
    def sendHook(self, data, status = {'status':'downloading'}):
        self.data.emit(data)
    def stop(self):
        self.quit()
        self.exit()
    def run(self):
        self.y = YoutubeDL({'progress_hooks':[self.sendHook]})
        self.y.download(['https://www.youtube.com/watch?v=LKIXbNW-B5g'])
        self.exec_()
class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        layout = QVBoxLayout()
        self.l = QLabel("Hello")
        b = QPushButton("Start!")
        b.pressed.connect(self.connectthread)
        layout.addWidget(self.l)
        layout.addWidget(b)
        w = QWidget()
        w.setLayout(layout)
        self.setCentralWidget(w)
        self.show()
    def read(self, data):
        self.thread.stop()
    def connectthread(self):
        self.thread = Worker()
        self.thread.data.connect(self.read)
        self.thread.start()
app = QApplication([])
window = MainWindow()
app.exec_()

By calling self.exec_() in the run() method of your worker you start a new event loop on this thread after the download finished, and this event loop then just keeps running. You don't need an event loop here, you only need a separate event loop if you want to move QObjects to it using their moveToThread() method to decouple them from the main event loop, but that is not needed here, you're not doing anything that requires a Qt event loop. That's also why calling stop() or exit() doesn't do anything, it only affects an event loop. The only way to stop this thread would be its terminate() method, and this also kind of works:

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from youtube_dl import *
class Worker(QThread):
    data = pyqtSignal(object)
    def __init__(self):
        super(Worker, self).__init__()
        self.flag = True
    def sendHook(self, data, status = {'status':'downloading'}):
        self.data.emit(data)
    def stop(self):
        self.terminate()
        print("QThread terminated")
    def run(self):
        self.y = YoutubeDL({'progress_hooks':[self.sendHook]})
        self.y.download(['https://www.youtube.com/watch?v=LKIXbNW-B5g'])
        print("finished")
class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        layout = QVBoxLayout()
        self.l = QLabel("Hello")
        b = QPushButton("Start!")
        b.pressed.connect(self.connectthread)
        layout.addWidget(self.l)
        layout.addWidget(b)
        w = QWidget()
        w.setLayout(layout)
        self.setCentralWidget(w)
        self.thread = None
        self.show()
    def read(self, data):
        print("read:", data)
    def connectthread(self):
        if self.thread is not None:
            # already running
            self.thread.stop()
            self.thread = None
            return
        self.thread = Worker()
        self.thread.data.connect(self.read)
        self.thread.start()
app = QApplication([])
window = MainWindow()
app.exec_()

Here I've altered your program so the first time the button is clicked the worker is started, the second time the thread is terminated and so on.

However terminating a thread this way is dangerous and discouraged. Python threads usually need to cooperate to be stoppable because by design they don't have a way to be interrupted. In this case it only works because PyQt is in control of the threads.

Unfortunately there isn't a way to gracefully stop a youtube-dl download, see this related issue for more information. In general there is no guarantee that killing the thread that called download() will actually stop the download. YoutubeDL supports a plugin system with different downloaders. To download a hls stream for example an external ffmpeg (or avconv) process is started, which would not be stopped by killing the worker thread. The same would be true for a downloader that uses other threads or processes internally, or for the post-processing steps that are also performed using ffmpeg.

If you want to be able to safely stop a download you'd have to use a separate process so you can use the SIGINT signal (same as pressing Ctrl-C) to tell it to stop.

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.