Почему не работает 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 шт):

Автор решения: S. Nick

Потоки - дело тонкое :) Пока по другому не получается. Пробуйте.

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_())
→ Ссылка