Почему не работает QTimer?(часть 2)
Задавал уже сегодня такой вопрос, но, как оказалось, я, пытаясь сделать свой код более читаемым, опять слишком сильно упростил ситуацию. Сейчас я попытаюсь рассказать обо всем подробнее.
Мой код состоит из двух классов - MyThread и MyWindow.
В MyWindow создается окно приложения с единственной кнопкой btn. btn привязана к методу new_thread, который вызывает метод choose_dir. choose_dir вызывает диалоговое окно выбора директории и возвращает путь к выбранной директории. Если choose_dir вернул путь к директории, new_thread создает новый поток, который является копией класса MyThread.
В новом потоке я планировал извлечь теги каждого MP3-файла в выбранной директории.
Метод choose_files класса MyThread, перебирает все MP3-файлы в выбранной директории, передавая путь к каждому файлу методу extract_tags. extract_tags извлекает разные теги музыкального файла, в числе которых должна быть длительность файла. Чтобы извлечь длительность, я создаю QMediaPlayer, передаю ему путь к файлу, полученный методом extract_tags. После этого создаю QTimer, который должен вызывать метод extract_duration. В extract_duration должна извлекаться длина файла. Но, почему-то код метода extract_duration не выполняется.
Пожалуйста, подскажите, как починить код ?
from PyQt5 import QtCore, QtWidgets, QtMultimedia
from pathlib import Path
class MyThread(QtCore.QThread):
singlTimer = QtCore.pyqtSignal()
def __init__(self, directory, parent=None):
QtCore.QThread.__init__(self, parent)
self.directory = directory
def run(self):
print('THREAD: run')
self.choose_files()
def choose_files(self):
pathlist = Path(self.directory).glob('**/*.mp3')
if pathlist:
for path in pathlist:
self.path = str(path).replace('\\', '/')
print(self.path)
self.extract_tags()
def extract_tags(self):
# тут достаю теги файла self.path
self.player = QtMultimedia.QMediaPlayer()
self.player.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl(self.path)))
self.player.play()
self.player.setVolume(0.0)
QtCore.QTimer.singleShot(150, self.extract_duration)
def extract_duration(self):
d = self.player.duration()
m = d // 1000 // 60
s = d // 1000 % 60
self.length = f'{m:1}:{s:02}'
print(self.length)
class MyWindow(QtWidgets.QWidget):
def __init__(self, parent = None):
super().__init__(parent)
box = QtWidgets.QHBoxLayout(self)
btn = QtWidgets.QPushButton('Button', clicked = self.new_thread)
box.addWidget(btn)
def new_thread(self):
print('CHOOSE DIRECTORY BUTTON: click')
directory = self.choose_dir()
if directory:
self.mythread = MyThread(directory)
self.mythread.started.connect(self.on_started)
self.mythread.finished.connect(self.on_finished)
self.mythread.start()
def choose_dir(self):
# Вызываю диалоговое окно выбора папки
print('dialog window')
drive = Path().absolute().drive + '/'
dir_1 = QtWidgets.QFileDialog.getExistingDirectory(self,
'Open a folder',
drive,
QtWidgets.QFileDialog.ShowDirsOnly)
if dir_1:
return dir_1
def on_started(self):
print('THREAD: start')
def on_finished(self):
print('THREAD: finish')
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.setWindowTitle(' ')
window.show()
sys.exit(app.exec_())
UPD:
from PyQt5 import QtCore, QtWidgets, QtMultimedia
from pathlib import Path
class MyThread(QtCore.QThread):
singlTimer = QtCore.pyqtSignal(str)
def __init__(self, directory, parent=None):
QtCore.QThread.__init__(self, parent)
self.directory = directory
self.length = None
def run(self):
print('THREAD: run\n')
self.choose_files()
def choose_files(self):
pathlist = Path(self.directory).glob('**/*.mp3')
if pathlist:
for path in pathlist:
self.index = 0 # +++
self.path = str(path).replace('\\', '/')
self.singlTimer.emit(self.path)
# update
while self.index != 1:
QtCore.QThread.msleep(20)
print('не хватает времени')
if self.length:
self.extract_duration()
# update
def extract_duration(self): # update
print(self.length) # update
print('a') # update
self.length = None
class MyWindow(QtWidgets.QWidget):
def __init__(self, parent = None):
super().__init__(parent)
box = QtWidgets.QHBoxLayout(self)
btn = QtWidgets.QPushButton('Button', clicked = self.new_thread)
box.addWidget(btn)
def new_thread(self):
print('CHOOSE DIRECTORY BUTTON: click')
directory = self.choose_dir()
if directory:
self.mythread = MyThread(directory)
self.mythread.started.connect(self.on_started)
self.mythread.finished.connect(self.on_finished)
self.mythread.singlTimer.connect(self.on_singlTimer)
self.mythread.start()
def choose_dir(self):
# Вызываю диалоговое окно выбора папки
print('dialog window')
drive = Path().absolute().drive + '/'
dir_1 = QtWidgets.QFileDialog.getExistingDirectory(self,
'Open a folder',
drive,
QtWidgets.QFileDialog.ShowDirsOnly)
if dir_1:
return dir_1
def on_started(self):
print('THREAD: start')
def on_finished(self):
print('\nTHREAD: finish')
def on_singlTimer(self, path):
self.player2 = QtMultimedia.QMediaPlayer()
self.player2.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl(path)))
self.player2.play()
self.player2.setVolume(0.0)
self.player2.durationChanged.connect(self.func_2)
def func_2(self):
d = self.player2.duration()
m = d // 1000 // 60
s = d // 1000 % 60
length = f'{m:1}:{s:02}'
self.mythread.length = length
self.mythread.index = 1 # +++
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.setWindowTitle(' ')
window.show()
sys.exit(app.exec_())
Ответы (1 шт):
Потоки - дело тонкое :) Пока по другому не получается. Пробуйте.
from PyQt5 import QtCore, QtWidgets, QtMultimedia
from pathlib import Path
class MyThread(QtCore.QThread):
singlTimer = QtCore.pyqtSignal(str) # str
def __init__(self, directory, parent=None):
QtCore.QThread.__init__(self, parent)
self.directory = directory
self.length = None # ++
def run(self):
print('THREAD: run\n')
self.choose_files()
def choose_files(self):
pathlist = Path(self.directory).glob('**/*.mp3')
if pathlist:
for path in pathlist:
self.path = str(path).replace('\\', '/')
self.singlTimer.emit(self.path) # +++ self.path
QtCore.QThread.msleep(500) # +++
# self.extract_tags()
if self.length: # +++
self.extract_duration(self.length) # +++
def extract_duration(self, length):
print(f'length !!!!!!!!!!!+++++++++++++++ {length}')
QtCore.QThread.msleep(3000) # <<<--- Запускайте здесь свою длительную задачу
self.length = None
class MyWindow(QtWidgets.QWidget):
def __init__(self, parent = None):
super().__init__(parent)
box = QtWidgets.QHBoxLayout(self)
btn = QtWidgets.QPushButton('Button', clicked = self.new_thread)
box.addWidget(btn)
def new_thread(self):
print('CHOOSE DIRECTORY BUTTON: click')
directory = self.choose_dir()
if directory:
self.mythread = MyThread(directory)
self.mythread.started.connect(self.on_started)
self.mythread.finished.connect(self.on_finished)
self.mythread.singlTimer.connect(self.on_singlTimer) # +++
self.mythread.start()
def choose_dir(self):
# Вызываю диалоговое окно выбора папки
print('dialog window')
drive = Path().absolute().drive + '/'
dir_1 = QtWidgets.QFileDialog.getExistingDirectory(self,
'Open a folder',
drive,
QtWidgets.QFileDialog.ShowDirsOnly)
if dir_1:
return dir_1
def on_started(self):
print('THREAD: start')
def on_finished(self):
print('\nTHREAD: finish')
# ++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
def on_singlTimer(self, path): # +++
# тут достаю теги файла self.path
self.player = QtMultimedia.QMediaPlayer()
self.player.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl(path))) # path
self.player.play()
self.player.setVolume(0.0)
QtCore.QTimer.singleShot(150, self.func_2)
def func_2(self): # +++
d = self.player.duration()
m = d // 1000 // 60
s = d // 1000 % 60
length = f'{m:1}:{s:02}'
self.mythread.length = length # +++ !!!
print(f'This doesn`t work :) ---> {d}')
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.setWindowTitle(' ')
window.show()
sys.exit(app.exec_())
Update
я исправил ваш второй пример и он в принципе работает.
- НЕ НАДО делать атрибутами класса ВРЕМЕННЫЕ переменные, (self.index и self.path)
- не надо делать
while self.index != 1:, делайте такwhile index:илиwhile not index: - смотрите другие комментарии по тексту примера.
from PyQt5 import QtCore, QtWidgets, QtMultimedia
from pathlib import Path
class MyThread(QtCore.QThread):
singlTimer = QtCore.pyqtSignal(str)
def __init__(self, directory, parent=None):
QtCore.QThread.__init__(self, parent)
self.directory = directory
self.length = None
def run(self):
print('THREAD: run\n')
self.choose_files()
def choose_files(self):
pathlist = Path(self.directory).glob('**/*.mp3')
if pathlist:
for path in pathlist:
# self.index = 0 #---
index = True # +++
path = str(path).replace('\\', '/') # - self
self.singlTimer.emit(path) # - self
# while self.index != 1:
while index: # != 1:
QtCore.QThread.msleep(50) # - 20 + 50
# print('не хватает времени', index, self.length, path)
# ----> это должно быть в цикле `while`
if self.length:
self.extract_duration()
index = False # +++ да
# ---->
def extract_duration(self):
print(f' получен length -> {self.length}')
self.length = None
class MyWindow(QtWidgets.QWidget):
def __init__(self, parent = None):
super().__init__(parent)
box = QtWidgets.QHBoxLayout(self)
btn = QtWidgets.QPushButton('Button', clicked = self.new_thread)
box.addWidget(btn)
def new_thread(self):
print('CHOOSE DIRECTORY BUTTON: click')
directory = self.choose_dir()
if directory:
self.mythread = MyThread(directory)
self.mythread.started.connect(self.on_started)
self.mythread.finished.connect(self.on_finished)
self.mythread.singlTimer.connect(self.on_singlTimer)
self.mythread.start()
def choose_dir(self):
# Вызываю диалоговое окно выбора папки
print('dialog window')
drive = Path().absolute().drive + '/'
dir_1 = QtWidgets.QFileDialog.getExistingDirectory(self,
'Open a folder',
drive,
QtWidgets.QFileDialog.ShowDirsOnly)
if dir_1:
return dir_1
def on_started(self):
print('THREAD: start')
def on_finished(self):
print('\nTHREAD: finish')
def on_singlTimer(self, path):
self.player2 = QtMultimedia.QMediaPlayer()
self.player2.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl(path)))
self.player2.play()
self.player2.setVolume(0.0)
# QMediaPlayer::durationChanged(qint64 duration) # !!!
# Notifier signal for property duration. # !!!
self.player2.durationChanged.connect(self.func_2)
def func_2(self, d): # +++ d !!!
# d = self.player2.duration() # ---
m = d // 1000 // 60
s = d // 1000 % 60
length = f'{m:1}:{s:02}'
self.mythread.length = length
# self.mythread.index = 1 # --- нет
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.setWindowTitle(' ')
window.show()
sys.exit(app.exec_())
