From dc4f7a4cbcd6bce89ecec0413513b889aa9cc408 Mon Sep 17 00:00:00 2001 From: billy Date: Mon, 29 Sep 2025 22:43:26 -0400 Subject: [PATCH] auto export playlist works!!! --- components/EditPlaylistOptionsWindow.py | 85 +++++++++++++++++++++++++ components/ExportPlaylistWindow.py | 17 ++--- components/PlaylistsPane.py | 12 +++- components/__init__.py | 1 + main.py | 14 +++- sample_config.ini | 2 +- utils/__init__.py | 1 + utils/export_playlist_by_id.py | 69 ++++++++++++++++++++ utils/init.sql | 4 +- 9 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 components/EditPlaylistOptionsWindow.py create mode 100644 utils/export_playlist_by_id.py diff --git a/components/EditPlaylistOptionsWindow.py b/components/EditPlaylistOptionsWindow.py new file mode 100644 index 0000000..c283abc --- /dev/null +++ b/components/EditPlaylistOptionsWindow.py @@ -0,0 +1,85 @@ +import DBA +from PyQt5.QtWidgets import ( + QDialog, + QLineEdit, + QLabel, + QPushButton, + QVBoxLayout, +) +from PyQt5.QtGui import QFont + + +class EditPlaylistOptionsWindow(QDialog): + def __init__(self, playlist_id): + super(EditPlaylistOptionsWindow, self).__init__() + self.setWindowTitle("Playlist options") + self.setMinimumSize(600, 400) + self.playlist_id = playlist_id + # self.playlist_path_prefix: str = self.config.get( + # "settings", "playlist_path_prefix" + # ) + # self.export_path: str = self.config.get("settings", "playlist_export_path") + # self.selected_playlist_name: str = "my-playlist.m3u" + # self.chosen_list_widget_item: QListWidgetItem | None = None + layout = self.setup_ui() + self.setLayout(layout) + self.show() + + def setup_ui(self): + layout = QVBoxLayout() + + # Header label + label = QLabel("Options") + label.setFont(QFont("Sans", weight=QFont.Bold)) + layout.addWidget(label) + + # Get options from db + with DBA.DBAccess() as db: + data = db.query("SELECT auto_export_path, path_prefix from playlist WHERE id = ?;", (self.playlist_id,)) + auto_export_path = data[0][0] + path_prefix = data[0][1] + + # Relative export path label + label = QLabel("Auto export path (../music/playlists/my_playlist.m3u)") + label.setFont(QFont("Sans", weight=QFont.Bold)) # bold category + label.setStyleSheet("text-transform:lowercase;") # uppercase category + layout.addWidget(label) + + # Relative export path line edit widget + self.auto_export_path = QLineEdit(auto_export_path) + layout.addWidget(self.auto_export_path) + + # Playlist file save path label + label = QLabel("Path prefix (/prefix/song.mp3, /prefix/song2.mp3)") + label.setFont(QFont("Sans", weight=QFont.Bold)) + label.setStyleSheet("text-transform:lowercase;") + layout.addWidget(label) + + # Playlist file save path line edit widget + self.path_prefix = QLineEdit(path_prefix) + layout.addWidget(self.path_prefix) + + # Save button + self.save_button = QPushButton("Save") + layout.addWidget(self.save_button) + + # Signals + self.save_button.clicked.connect(self.save) + return layout + + def save(self) -> None: + """ + Updates the options in the db + """ + with DBA.DBAccess() as db: + db.execute(''' + UPDATE playlist SET auto_export_path = ?, path_prefix = ? WHERE id = ? + ''', (self.auto_export_path.text(), self.path_prefix.text(), self.playlist_id) + ) + + self.close() + return + + def cancel(self) -> None: + self.close() + return diff --git a/components/ExportPlaylistWindow.py b/components/ExportPlaylistWindow.py index 8b12019..973ab00 100644 --- a/components/ExportPlaylistWindow.py +++ b/components/ExportPlaylistWindow.py @@ -4,7 +4,6 @@ import os from PyQt5.QtWidgets import ( QCheckBox, QDialog, - QHBoxLayout, QLineEdit, QLabel, QPushButton, @@ -26,8 +25,8 @@ class ExportPlaylistWindow(QDialog): self.setWindowTitle("Export playlist") self.setMinimumSize(600, 400) self.load_config() - self.relative_path: str = self.config.get( - "settings", "playlist_content_relative_path" + self.playlist_path_prefix: str = self.config.get( + "settings", "playlist_path_prefix" ) self.export_path: str = self.config.get("settings", "playlist_export_path") self.selected_playlist_name: str = "my-playlist.m3u" @@ -74,7 +73,7 @@ class ExportPlaylistWindow(QDialog): layout.addWidget(label) # Relative export path line edit widget - self.input_relative_path = QLineEdit(self.relative_path) # not needed + self.input_relative_path = QLineEdit(self.playlist_path_prefix) # not needed layout.addWidget(self.input_relative_path) # Playlist file save path label @@ -112,9 +111,7 @@ class ExportPlaylistWindow(QDialog): Sets the current playlist name, then edits the playlist export path """ # Get the current chosen list item - self.chosen_list_widget_item: QListWidgetItem | None = ( - self.playlist_listWidget.currentItem() - ) + self.chosen_list_widget_item = self.playlist_listWidget.currentItem() # We don't care if nothing is chosen if self.chosen_list_widget_item is None: return @@ -129,13 +126,13 @@ class ExportPlaylistWindow(QDialog): # alter line edit text for playlist path # Make the thing react and show the correct thing or whatever - current_text: list = self.input_m3u_path.text().split("/") + current_text: list = self.export_m3u_path.text().split("/") if "." in current_text[-1]: current_text = current_text[:-1] else: current_text = current_text m3u_text = os.path.join("/", *current_text, self.selected_playlist_name) - self.input_m3u_path.setText(m3u_text) + self.export_m3u_path.setText(m3u_text) def save(self) -> None: """ @@ -145,7 +142,7 @@ class ExportPlaylistWindow(QDialog): if self.chosen_list_widget_item is None: return relative_path = self.input_relative_path.text() - output_filename = self.input_m3u_path.text() + output_filename = self.export_m3u_path.text() # If no output path is provided, just close the window... if output_filename == "" or output_filename is None: diff --git a/components/PlaylistsPane.py b/components/PlaylistsPane.py index 5b26761..7cab894 100644 --- a/components/PlaylistsPane.py +++ b/components/PlaylistsPane.py @@ -6,13 +6,12 @@ from PyQt5.QtWidgets import ( QTreeWidget, QTreeWidgetItem, ) -from PyQt5.QtCore import QThreadPool, pyqtSignal, Qt, QPoint +from PyQt5.QtCore import pyqtSignal, Qt, QPoint import DBA -from logging import debug from components.ErrorDialog import ErrorDialog from utils import Worker -from components import CreatePlaylistWindow +from components import CreatePlaylistWindow, EditPlaylistOptionsWindow class PlaylistWidgetItem(QTreeWidgetItem): @@ -70,10 +69,13 @@ class PlaylistsPane(QTreeWidget): # only allow delete/rename non-root nodes rename_action = QAction("Rename", self) delete_action = QAction("Delete", self) + options_action = QAction("Options", self) rename_action.triggered.connect(self.rename_playlist) delete_action.triggered.connect(self.delete_playlist) + options_action.triggered.connect(self.options) menu.addAction(rename_action) menu.addAction(delete_action) + menu.addAction(options_action) create_action = QAction("New playlist", self) create_action.triggered.connect(self.create_playlist) menu.addAction(create_action) @@ -85,6 +87,10 @@ class PlaylistsPane(QTreeWidget): window.playlistCreatedSignal.connect(self.reload_playlists) window.exec_() + def options(self): + window = EditPlaylistOptionsWindow(self.playlist_db_id_choice) + window.exec_() + def rename_playlist(self, *args): """ Asks user for input diff --git a/components/__init__.py b/components/__init__.py index 5eb37b5..e048de5 100644 --- a/components/__init__.py +++ b/components/__init__.py @@ -6,6 +6,7 @@ from .ErrorDialog import ErrorDialog from .LyricsWindow import LyricsWindow from .DebugWindow import DebugWindow from .AddToPlaylistWindow import AddToPlaylistWindow +from .EditPlaylistOptionsWindow import EditPlaylistOptionsWindow from .CreatePlaylistWindow import CreatePlaylistWindow from .PlaylistsPane import PlaylistsPane from .ExportPlaylistWindow import ExportPlaylistWindow diff --git a/main.py b/main.py index 402bb3d..70c0e1c 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ import logging from PyQt5 import QtCore import qdarktheme import typing -# import DBA +import DBA from subprocess import run # from pyqtgraph import mkBrush from mutagen.id3 import ID3 @@ -60,6 +60,7 @@ from components import ( CreatePlaylistWindow, ExportPlaylistWindow, ) +from utils.export_playlist_by_id import export_playlist_by_id # good help with signals slots in threads # https://stackoverflow.com/questions/52993677/how-do-i-setup-signals-and-slots-in-pyqt-with-qthreads-in-both-directions @@ -246,13 +247,22 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): self.config["settings"]["window_size"] = ( str(self.width()) + "," + str(self.height()) ) - # Save the config try: with open(self.cfg_file, "w") as configfile: self.config.write(configfile) except Exception as e: debug(f"wtf man {e}") + + # auto export any playlists that want it + try: + with DBA.DBAccess() as db: + result = db.query('SELECT id FROM playlist WHERE auto_export_path IS NOT NULL;', ()) + ids = [id[0] for id in result] + for id in ids: + export_playlist_by_id(id) + except Exception: + pass if a0 is not None: super().closeEvent(a0) diff --git a/sample_config.ini b/sample_config.ini index 2091f65..d7c6501 100644 --- a/sample_config.ini +++ b/sample_config.ini @@ -4,7 +4,7 @@ library = /path/to/music reorganize_destination = /where/to/reorganize/to playlist_auto_export_enabled = 0 playlist_export_path = /where/to/export/to -playlist_relative_path = /relative/prefix +playlist_path_prefix = /relative/prefix extensions = mp3,wav,ogg,flac volume = 100 window_size=1152,894 diff --git a/utils/__init__.py b/utils/__init__.py index 59f4199..313d3eb 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -17,3 +17,4 @@ from .convert_date_str_to_tyer_tdat_id3_tag import convert_date_str_to_tyer_tdat from .set_album_art import set_album_art from .delete_album_art import delete_album_art from .Worker import Worker +from .export_playlist_by_id import export_playlist_by_id diff --git a/utils/export_playlist_by_id.py b/utils/export_playlist_by_id.py new file mode 100644 index 0000000..53583ac --- /dev/null +++ b/utils/export_playlist_by_id.py @@ -0,0 +1,69 @@ +import os +from PyQt5.QtCore import QThreadPool +import DBA +import logging +from utils import get_reorganize_vars, Worker + +def export_playlist_by_id(playlist_db_id: int) -> bool: + """ + Exports a playlist to its defined auto_export_path, by database ID + """ + threadpool = QThreadPool() + try: + with DBA.DBAccess() as db: + result = db.query(''' + SELECT auto_export_path, path_prefix FROM playlist WHERE id = ? + ''', (playlist_db_id,) + ) + auto_export_path = result[0][0] + path_prefix = result[0][1] + except Exception as e: + logging.error( + f"export_playlist_by_id.py | Could not contact database: {e}" + ) + return False + + # If the prefix is null, then use an empty string + if not path_prefix: + path_prefix = "" + + # Get filepaths for selected playlist from the database + try: + with DBA.DBAccess() as db: + data = db.query( + """SELECT s.filepath FROM song_playlist as sp + JOIN song as s ON s.id = sp.song_id + WHERE sp.playlist_id = ?;""", + (playlist_db_id,), + ) + db_paths = [path[0] for path in data] + except Exception as e: + logging.error( + f"export_playlist_by_id.py | Could not retrieve records from playlist: {e}" + ) + return False + + # Gather playlist song paths + write_paths = [] + # Relative paths + for song in db_paths: + artist, album = get_reorganize_vars(song) + write_path = os.path.join( + path_prefix, artist, album, song.split("/")[-1] + "\n" + ) + write_paths.append(str(write_path)) + + + worker = Worker(write_to_playlist_file, write_paths, auto_export_path) + # worker.signals.signal_finished.connect(None) + # worker.signals.signal_progress.connect() + threadpool.start(worker) + return True + +def write_to_playlist_file(paths: list[str], outfile: str, progress_callback=None) -> None: + """ + Writes a list of strings to a m3u file + """ + os.makedirs(os.path.dirname(outfile), exist_ok=True) + with open(outfile, "w") as f: + f.writelines(paths) diff --git a/utils/init.sql b/utils/init.sql index 68d6ff2..217a73e 100644 --- a/utils/init.sql +++ b/utils/init.sql @@ -22,7 +22,9 @@ CREATE TABLE song( CREATE TABLE playlist( id integer primary key, name varchar(64), - date_created TIMESTAMP default CURRENT_TIMESTAMP + date_created TIMESTAMP default CURRENT_TIMESTAMP, + auto_export_path varchar(512), + path_prefix varchar(255) ); CREATE TABLE song_playlist(