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.chosen_list_widget_item: QListWidgetItem | None = (
|
||||||
self.playlist_listWidget.currentItem()
|
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:
|
if self.chosen_list_widget_item is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -146,6 +146,7 @@ class ExportPlaylistWindow(QDialog):
|
|||||||
)
|
)
|
||||||
error_dialog.exec()
|
error_dialog.exec()
|
||||||
self.close()
|
self.close()
|
||||||
|
return
|
||||||
|
|
||||||
# Gather playlist song paths
|
# Gather playlist song paths
|
||||||
write_paths = []
|
write_paths = []
|
||||||
@ -167,7 +168,9 @@ class ExportPlaylistWindow(QDialog):
|
|||||||
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
||||||
with open(filename, "w") as f:
|
with open(filename, "w") as f:
|
||||||
f.writelines(write_paths)
|
f.writelines(write_paths)
|
||||||
|
|
||||||
self.close()
|
self.close()
|
||||||
|
return
|
||||||
|
|
||||||
def cancel(self) -> None:
|
def cancel(self) -> None:
|
||||||
self.close()
|
self.close()
|
||||||
|
|||||||
@ -24,6 +24,7 @@ from components.AddToPlaylistWindow import AddToPlaylistWindow
|
|||||||
from components.MetadataWindow import MetadataWindow
|
from components.MetadataWindow import MetadataWindow
|
||||||
from utils.delete_song_id_from_database import delete_song_id_from_database
|
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.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.update_song_in_database import update_song_in_database
|
||||||
from utils.get_id3_tags import get_id3_tags
|
from utils.get_id3_tags import get_id3_tags
|
||||||
from utils.get_album_art import get_album_art
|
from utils.get_album_art import get_album_art
|
||||||
@ -311,21 +312,7 @@ class MusicTable(QTableView):
|
|||||||
for filepath in filepaths:
|
for filepath in filepaths:
|
||||||
try:
|
try:
|
||||||
# Read file metadata
|
# Read file metadata
|
||||||
audio = ID3(filepath)
|
artist, album = get_reorganize_vars(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"
|
|
||||||
|
|
||||||
# Determine the new path that needs to be made
|
# Determine the new path that needs to be made
|
||||||
new_path = os.path.join(
|
new_path = os.path.join(
|
||||||
target_dir, artist, album, os.path.basename(filepath)
|
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 ui import Ui_MainWindow
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
|
QLabel,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
QApplication,
|
QApplication,
|
||||||
QGraphicsScene,
|
QGraphicsScene,
|
||||||
QHeaderView,
|
QHeaderView,
|
||||||
QGraphicsPixmapItem,
|
QGraphicsPixmapItem,
|
||||||
QMessageBox,
|
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.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe
|
||||||
from PyQt5.QtGui import QCloseEvent, QPixmap
|
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 utils import scan_for_music, delete_and_create_library_database, initialize_db
|
||||||
from components import (
|
from components import (
|
||||||
PreferencesWindow,
|
PreferencesWindow,
|
||||||
@ -35,6 +47,51 @@ from components import (
|
|||||||
# pyuic5 ui.ui -o ui.py
|
# 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):
|
class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||||
playlistCreatedSignal = pyqtSignal()
|
playlistCreatedSignal = pyqtSignal()
|
||||||
|
|
||||||
@ -42,8 +99,16 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
|||||||
super(ApplicationWindow, self).__init__()
|
super(ApplicationWindow, self).__init__()
|
||||||
global stopped
|
global stopped
|
||||||
stopped = False
|
stopped = False
|
||||||
|
# Multithreading stuff...
|
||||||
|
self.workers = dict[UUID, WorkerThread] = {}
|
||||||
|
self.threadpool = QThreadPool()
|
||||||
|
# UI
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.setWindowTitle("MusicPom")
|
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.selected_song_filepath: str | None = None
|
||||||
self.current_song_filepath: str | None = None
|
self.current_song_filepath: str | None = None
|
||||||
self.current_song_metadata: ID3 | dict | 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().setSectionResizeMode(QHeaderView.Stretch)
|
||||||
self.tableView.horizontalHeader().setStretchLastSection(False)
|
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:
|
def closeEvent(self, a0: QCloseEvent | None) -> None:
|
||||||
"""Save settings when closing the application"""
|
"""Save settings when closing the application"""
|
||||||
# MusicTable/tableView column widths
|
# MusicTable/tableView column widths
|
||||||
@ -160,6 +264,21 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
|||||||
self.config.write(configfile)
|
self.config.write(configfile)
|
||||||
super().closeEvent(a0)
|
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:
|
def play_audio_file(self) -> None:
|
||||||
"""Start playback of tableView.current_song_filepath track & moves playback slider"""
|
"""Start playback of tableView.current_song_filepath track & moves playback slider"""
|
||||||
self.current_song_metadata = (
|
self.current_song_metadata = (
|
||||||
@ -346,6 +465,10 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
|||||||
def on_next_clicked(self) -> None:
|
def on_next_clicked(self) -> None:
|
||||||
print("next")
|
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:
|
def open_files(self) -> None:
|
||||||
"""Opens the open files window"""
|
"""Opens the open files window"""
|
||||||
open_files_window = QFileDialog(
|
open_files_window = QFileDialog(
|
||||||
@ -357,16 +480,14 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
|||||||
filenames = open_files_window.selectedFiles()
|
filenames = open_files_window.selectedFiles()
|
||||||
self.tableView.add_files(filenames)
|
self.tableView.add_files(filenames)
|
||||||
|
|
||||||
|
# File
|
||||||
|
|
||||||
def create_playlist(self) -> None:
|
def create_playlist(self) -> None:
|
||||||
"""Creates a database record for a playlist, given a name"""
|
"""Creates a database record for a playlist, given a name"""
|
||||||
window = CreatePlaylistWindow(self.playlistCreatedSignal)
|
window = CreatePlaylistWindow(self.playlistCreatedSignal)
|
||||||
window.playlistCreatedSignal.connect(self.add_latest_playlist_to_tree)
|
window.playlistCreatedSignal.connect(self.add_latest_playlist_to_tree)
|
||||||
window.exec_()
|
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:
|
def import_playlist(self) -> None:
|
||||||
"""
|
"""
|
||||||
Imports a .m3u file, given a base path attempts to match playlist files to
|
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 = ExportPlaylistWindow()
|
||||||
export_playlist_window.exec_()
|
export_playlist_window.exec_()
|
||||||
|
|
||||||
|
# Edit
|
||||||
|
|
||||||
def open_preferences(self) -> None:
|
def open_preferences(self) -> None:
|
||||||
"""Opens the preferences window"""
|
"""Opens the preferences window"""
|
||||||
preferences_window = PreferencesWindow(self.config)
|
preferences_window = PreferencesWindow(self.config)
|
||||||
preferences_window.exec_() # Display the preferences window modally
|
preferences_window.exec_() # Display the preferences window modally
|
||||||
|
|
||||||
|
# Quick Actions
|
||||||
|
|
||||||
def scan_libraries(self) -> None:
|
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()
|
scan_for_music()
|
||||||
self.tableView.load_music_table()
|
self.tableView.load_music_table()
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import DBA
|
|||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from utils import get_id3_tags, id3_timestamp_to_datetime
|
from utils import get_id3_tags, id3_timestamp_to_datetime
|
||||||
import logging
|
import logging
|
||||||
|
from PyQt5.QtCore import pyqtSignal
|
||||||
|
|
||||||
config = ConfigParser()
|
config = ConfigParser()
|
||||||
config.read("config.ini")
|
config.read("config.ini")
|
||||||
@ -12,6 +13,7 @@ def add_files_to_library(files):
|
|||||||
files = list() of fully qualified paths to audio file(s)
|
files = list() of fully qualified paths to audio file(s)
|
||||||
Returns a list of dictionaries of metadata
|
Returns a list of dictionaries of metadata
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# print("Running add_files_to_library.py")
|
# print("Running add_files_to_library.py")
|
||||||
if not files:
|
if not files:
|
||||||
return []
|
return []
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtSignal
|
||||||
from utils.add_files_to_library import add_files_to_library
|
from utils.add_files_to_library import add_files_to_library
|
||||||
|
|
||||||
config = ConfigParser()
|
config = ConfigParser()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user