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.