trying to multithread

This commit is contained in:
tsi-billypom 2024-09-05 14:37:05 -04:00
parent cc21dd6403
commit 14b3014518
5 changed files with 143 additions and 23 deletions

View File

@ -103,7 +103,7 @@ class ExportPlaylistWindow(QDialog):
self.chosen_list_widget_item: QListWidgetItem | None = (
self.playlist_listWidget.currentItem()
)
# We don't care if its None
# We don't care if nothing is chosen
if self.chosen_list_widget_item is None:
return
@ -146,6 +146,7 @@ class ExportPlaylistWindow(QDialog):
)
error_dialog.exec()
self.close()
return
# Gather playlist song paths
write_paths = []
@ -167,7 +168,9 @@ class ExportPlaylistWindow(QDialog):
os.makedirs(os.path.dirname(filename), exist_ok=True)
with open(filename, "w") as f:
f.writelines(write_paths)
self.close()
return
def cancel(self) -> None:
self.close()

View File

@ -24,6 +24,7 @@ from components.AddToPlaylistWindow import AddToPlaylistWindow
from components.MetadataWindow import MetadataWindow
from utils.delete_song_id_from_database import delete_song_id_from_database
from utils.add_files_to_library import add_files_to_library
from utils.get_reorganize_vars import get_reorganize_vars
from utils.update_song_in_database import update_song_in_database
from utils.get_id3_tags import get_id3_tags
from utils.get_album_art import get_album_art
@ -311,21 +312,7 @@ class MusicTable(QTableView):
for filepath in filepaths:
try:
# Read file metadata
audio = ID3(filepath)
try:
artist = audio["TPE1"].text[0]
if artist == "":
artist = "Unknown Artist"
except KeyError:
artist = "Unknown Artist"
try:
album = audio["TALB"].text[0]
if album == "":
album = "Unknown Album"
except KeyError:
album = "Unknown Album"
artist, album = get_reorganize_vars(filepath)
# Determine the new path that needs to be made
new_path = os.path.join(
target_dir, artist, album, os.path.basename(filepath)

141
main.py
View File

@ -13,16 +13,28 @@ import DBA
from ui import Ui_MainWindow
from PyQt5.QtWidgets import (
QFileDialog,
QLabel,
QMainWindow,
QApplication,
QGraphicsScene,
QHeaderView,
QGraphicsPixmapItem,
QMessageBox,
QStatusBar,
)
from PyQt5.QtCore import (
QUrl,
QTimer,
Qt,
pyqtSignal,
QObject,
pyqtSlot,
QThreadPool,
QRunnable,
)
from PyQt5.QtCore import QUrl, QTimer, Qt, pyqtSignal
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe
from PyQt5.QtGui import QCloseEvent, QPixmap
from uuid import uuid4, UUID
from utils import scan_for_music, delete_and_create_library_database, initialize_db
from components import (
PreferencesWindow,
@ -35,6 +47,51 @@ from components import (
# pyuic5 ui.ui -o ui.py
class WorkerSignals(QObject):
"""
How to use signals for a QRunnable class; unlike most cases where signals
are defined as class attributes directly in the class, here we define a
class that inherits from QObject and define the signals as class
attributes in that class. Then we can instantiate that class and use it
as a signal object.
"""
# 1)
# Use a naming convention for signals that makes it clear that they are signals
# and a corresponding naming convention for the slots that handle them.
# For example signal_* and handle_*.
# 2)
# And try to make the signal content as small as possible. DO NOT pass large objects through signals, like
# pandas DataFrames or numpy arrays. Instead, pass the minimum amount of information needed
# (i.e. lists of filepaths)
signal_started = pyqtSignal(list)
signal_finished = pyqtSignal(UUID)
signal_progress = pyqtSignal(str)
class WorkerThread(QRunnable):
"""
This is the thread that is going to do the work so that the
application doesn't freeze
"""
def __init__(self, worker_id: UUID) -> None:
super().__init__()
# allow for signals to be used
self.worker_id = worker_id
self.signals: WorkerSignals = WorkerSignals()
def run(self):
"""
This is where the work is done.
MUST be called run() in order for QRunnable to work
"""
self.signals.signal_started.emit("start worker")
self.signals.signal_progress.emit("worker progress")
self.signals.signal_finished.emit(self.worker_id)
class ApplicationWindow(QMainWindow, Ui_MainWindow):
playlistCreatedSignal = pyqtSignal()
@ -42,8 +99,16 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
super(ApplicationWindow, self).__init__()
global stopped
stopped = False
# Multithreading stuff...
self.workers = dict[UUID, WorkerThread] = {}
self.threadpool = QThreadPool()
# UI
self.setupUi(self)
self.setWindowTitle("MusicPom")
self.status_bar = QStatusBar()
self.permanent_status_label = QLabel("Status...")
self.status_bar.addPermanentWidget(self.permanent_status_label)
self.setStatusBar(self.status_bar)
self.selected_song_filepath: str | None = None
self.current_song_filepath: str | None = None
self.current_song_metadata: ID3 | dict | None = None
@ -146,6 +211,45 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.tableView.horizontalHeader().setStretchLastSection(False)
def start_worker(self):
worker_id = uuid4() # generate an unique id for the worker
worker = WorkerThread(worker_id=worker_id)
# Connect the signals to the slots
worker.signals.signal_started.connect(self.handle_started)
worker.signals.signal_progress.connect(self.handle_progress)
worker.signals.signal_finished.connect(self.handle_finished)
# Store the worker in a dict so we can keep track of it
self.workers[worker_id] = worker
# Start the worker
self.threadpool.start(worker)
# Slots should have decorators whose arguments match the signals they are connected to
@pyqtSlot(list)
def handle_files_from_child_window(self, files: list[str]):
self.files_to_process = files
print(f"Files to process: {self.files_to_process}")
self.show_status_bar_message(f"Files to process: {self.files_to_process}")
@pyqtSlot(int)
def handle_progress(self, n):
print(f"progress: {n}%")
self.show_status_bar_message(f"progress: {n}%")
@pyqtSlot(UUID)
def handle_finished(self, worker_id):
print(f"worker {worker_id} finished")
self.show_status_bar_message(f"worker {worker_id} finished")
# remove the worker from the dict
self.workers.pop(worker_id)
assert worker_id not in self.workers
@pyqtSlot(list)
def handle_started(self, files: list[str]):
print(f"Started processing files: {files}")
def closeEvent(self, a0: QCloseEvent | None) -> None:
"""Save settings when closing the application"""
# MusicTable/tableView column widths
@ -160,6 +264,21 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.config.write(configfile)
super().closeEvent(a0)
def show_status_bar_message(self, message: str, timeout: int | None = None) -> None:
"""
Show a `message` in the status bar for a length of time - `timeout` in ms
"""
if timeout:
self.status_bar.showMessage(message, timeout)
else:
self.status_bar.showMessage(message)
def set_permanent_status_bar_message(self, message: str) -> None:
"""
Sets the permanent message label in the status bar
"""
self.permanent_status_label.setText(message)
def play_audio_file(self) -> None:
"""Start playback of tableView.current_song_filepath track & moves playback slider"""
self.current_song_metadata = (
@ -346,6 +465,10 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
def on_next_clicked(self) -> None:
print("next")
def add_latest_playlist_to_tree(self) -> None:
"""Refreshes the playlist tree"""
self.playlistTreeView.add_latest_playlist_to_tree()
def open_files(self) -> None:
"""Opens the open files window"""
open_files_window = QFileDialog(
@ -357,16 +480,14 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
filenames = open_files_window.selectedFiles()
self.tableView.add_files(filenames)
# File
def create_playlist(self) -> None:
"""Creates a database record for a playlist, given a name"""
window = CreatePlaylistWindow(self.playlistCreatedSignal)
window.playlistCreatedSignal.connect(self.add_latest_playlist_to_tree)
window.exec_()
def add_latest_playlist_to_tree(self) -> None:
"""Refreshes the playlist tree"""
self.playlistTreeView.add_latest_playlist_to_tree()
def import_playlist(self) -> None:
"""
Imports a .m3u file, given a base path attempts to match playlist files to
@ -382,14 +503,20 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
export_playlist_window = ExportPlaylistWindow()
export_playlist_window.exec_()
# Edit
def open_preferences(self) -> None:
"""Opens the preferences window"""
preferences_window = PreferencesWindow(self.config)
preferences_window.exec_() # Display the preferences window modally
# Quick Actions
def scan_libraries(self) -> None:
"""Scans for new files in the configured library folder
Refreshes the datagridview"""
"""
Scans for new files in the configured library folder
Refreshes the datagridview
"""
scan_for_music()
self.tableView.load_music_table()

View File

@ -2,6 +2,7 @@ import DBA
from configparser import ConfigParser
from utils import get_id3_tags, id3_timestamp_to_datetime
import logging
from PyQt5.QtCore import pyqtSignal
config = ConfigParser()
config.read("config.ini")
@ -12,6 +13,7 @@ def add_files_to_library(files):
files = list() of fully qualified paths to audio file(s)
Returns a list of dictionaries of metadata
"""
# print("Running add_files_to_library.py")
if not files:
return []

View File

@ -1,6 +1,7 @@
import os
from configparser import ConfigParser
from PyQt5.QtCore import pyqtSignal
from utils.add_files_to_library import add_files_to_library
config = ConfigParser()