trying to multithread
This commit is contained in:
parent
cc21dd6403
commit
14b3014518
@ -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()
|
||||
|
||||
@ -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
141
main.py
@ -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()
|
||||
|
||||
|
||||
@ -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 []
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user