From 863c3d417b8431b15dd88cebb4d083549f51fa53 Mon Sep 17 00:00:00 2001 From: tsi-billypom Date: Mon, 3 Jun 2024 15:27:34 -0400 Subject: [PATCH 1/2] refactor sql database for song, playlist, and song_playlist --- components/MusicTable.py | 6 +++--- main.py | 2 +- utils/add_files_to_library.py | 4 ++-- utils/delete_and_create_library.sql | 4 ++-- utils/init.sql | 32 +++++++++++++++++++++++++++++ utils/update_song_in_library.py | 14 +++++++++---- 6 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 utils/init.sql diff --git a/components/MusicTable.py b/components/MusicTable.py index cc5d121..63c5286 100644 --- a/components/MusicTable.py +++ b/components/MusicTable.py @@ -113,7 +113,7 @@ class MusicTable(QTableView): selected_indices = self.get_selected_rows() for file in selected_filepaths: with DBA.DBAccess() as db: - db.execute("DELETE FROM library WHERE filepath = ?", (file,)) + db.execute("DELETE FROM song WHERE filepath = ?", (file,)) for index in selected_indices: self.model.removeRow(index) @@ -264,7 +264,7 @@ class MusicTable(QTableView): # Update the db with DBA.DBAccess() as db: db.query( - "UPDATE library SET filepath = ? WHERE filepath = ?", + "UPDATE song SET filepath = ? WHERE filepath = ?", (new_path, filepath), ) print(f"Moved: {filepath} -> {new_path}") @@ -296,7 +296,7 @@ class MusicTable(QTableView): try: with DBA.DBAccess() as db: data = db.query( - "SELECT id, title, artist, album, genre, codec, album_date, filepath FROM library;", + "SELECT id, title, artist, album, genre, codec, album_date, filepath FROM song;", (), ) except Exception as e: diff --git a/main.py b/main.py index 07f79ed..df7fee2 100644 --- a/main.py +++ b/main.py @@ -384,7 +384,7 @@ if __name__ == "__main__": os.makedirs(path_as_string) # Create database on first run with DBA.DBAccess() as db: - with open("utils/delete_and_create_library.sql", "r") as file: + with open("utils/init.sql", "r") as file: lines = file.read() for statement in lines.split(";"): print(f"executing [{statement}]") diff --git a/utils/add_files_to_library.py b/utils/add_files_to_library.py index 8026673..5200d67 100644 --- a/utils/add_files_to_library.py +++ b/utils/add_files_to_library.py @@ -64,7 +64,7 @@ def add_files_to_library(files): if len(insert_data) >= 1000: with DBA.DBAccess() as db: db.executemany( - "INSERT OR IGNORE INTO library (filepath, title, album, artist, genre, codec, album_date, bitrate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + "INSERT OR IGNORE INTO song (filepath, title, album, artist, genre, codec, album_date, bitrate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", insert_data, ) insert_data = [] # Reset the insert_data list @@ -72,7 +72,7 @@ def add_files_to_library(files): if insert_data: with DBA.DBAccess() as db: db.executemany( - "INSERT OR IGNORE INTO library (filepath, title, album, artist, genre, codec, album_date, bitrate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + "INSERT OR IGNORE INTO song (filepath, title, album, artist, genre, codec, album_date, bitrate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", insert_data, ) return True diff --git a/utils/delete_and_create_library.sql b/utils/delete_and_create_library.sql index 34ed8ea..1703f62 100644 --- a/utils/delete_and_create_library.sql +++ b/utils/delete_and_create_library.sql @@ -1,6 +1,6 @@ -DROP TABLE IF EXISTS library; +DROP TABLE IF EXISTS song; -CREATE TABLE library( +CREATE TABLE song( -- In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID (except in WITHOUT ROWID tables) which is always a 64-bit signed integer. id integer primary key, filepath varchar(511) UNIQUE, diff --git a/utils/init.sql b/utils/init.sql new file mode 100644 index 0000000..9865b5e --- /dev/null +++ b/utils/init.sql @@ -0,0 +1,32 @@ +DROP TABLE IF EXISTS song_playlist; +DROP TABLE IF EXISTS song; +DROP TABLE IF EXISTS playlist; + +CREATE TABLE song( + -- In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID (except in WITHOUT ROWID tables) which is always a 64-bit signed integer. + id integer primary key, + filepath varchar(511) UNIQUE, + title varchar(255), + album varchar(255), + artist varchar(255), + genre varchar(255), + codec varchar(15), + album_date date, + bitrate int, + date_added TIMESTAMP default CURRENT_TIMESTAMP +); + +CREATE TABLE playlist( + id integer primary key, + name varchar(64), + date_created TIMESTAMP default CURRENT_TIMESTAMP +); + +CREATE TABLE song_playlist( + playlist_id integer, + song_id integer, + date_created TIMESTAMP default CURRENT_TIMESTAMP, + primary key (playlist_id, song_id), + foreign key (playlist_id) references playlist(id), + foreign key (song_id) references song(id) +); diff --git a/utils/update_song_in_library.py b/utils/update_song_in_library.py index d857bb1..fc310cb 100644 --- a/utils/update_song_in_library.py +++ b/utils/update_song_in_library.py @@ -2,7 +2,9 @@ import DBA from components.ErrorDialog import ErrorDialog -def update_song_in_library(library_id: int, edited_column_name: str, user_input_data: str): +def update_song_in_library( + library_id: int, edited_column_name: str, user_input_data: str +): """Updates a field in the library database based on an ID Args: @@ -15,10 +17,14 @@ def update_song_in_library(library_id: int, edited_column_name: str, user_input_ try: with DBA.DBAccess() as db: # yeah yeah this is bad... the column names are defined in the program by me so im ok with it because it works - db.execute(f'UPDATE library SET {edited_column_name} = ? WHERE id = ?', (user_input_data, library_id)) + db.execute( + f"UPDATE song SET {edited_column_name} = ? WHERE id = ?", + (user_input_data, library_id), + ) except Exception as e: - dialog = ErrorDialog(f'Unable to update [{edited_column_name}] to [{user_input_data}]. ID: {library_id} | {e}') + dialog = ErrorDialog( + f"Unable to update [{edited_column_name}] to [{user_input_data}]. ID: {library_id} | {e}" + ) dialog.exec_() return False return True - From 2da146ff8c6830b689cb4ea4792a724cf6cf42bd Mon Sep 17 00:00:00 2001 From: tsi-billypom Date: Mon, 3 Jun 2024 16:46:00 -0400 Subject: [PATCH 2/2] playlist choice component qlistwidget functional? --- components/AddToPlaylistWindow.py | 44 +++++++++++++++++++++++++++++++ components/MusicTable.py | 24 +++++++++++++---- components/PreferencesWindow.py | 10 ++++++- components/__init__.py | 1 + utils/add_files_to_library.py | 2 +- utils/scan_for_music.py | 5 +--- 6 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 components/AddToPlaylistWindow.py diff --git a/components/AddToPlaylistWindow.py b/components/AddToPlaylistWindow.py new file mode 100644 index 0000000..f09eb03 --- /dev/null +++ b/components/AddToPlaylistWindow.py @@ -0,0 +1,44 @@ +from PyQt5.QtWidgets import ( + QWidget, + QListWidget, + QFrame, + QVBoxLayout, + QLabel, + QLineEdit, + QPushButton, +) +from PyQt5.QtGui import QFont + + +class AddToPlaylistWindow(QListWidget): + def __init__(self, list_options: dict): + super(AddToPlaylistWindow, self).__init__() + # self.setWindowTitle("Choose") + # self.setMinimumSize(400, 400) + for k, v in list_options: + self.addItem(f"{k} | {v}") + # layout = QVBoxLayout() + # + # label = QLabel("Playlists") + # label.setFont(QFont("Sans", weight=QFont.Bold)) + # layout.addWidget(label) + # layout.addWidget(self) + + # Save button + # save_button = QPushButton("Add") + # save_button.clicked.connect(self.save) + # layout.addWidget(save_button) + self.show() + + def save(self): + # Upcate the config fields + for key in self.input_fields: + for category in self.config.sections(): + if key in self.config[category]: + self.config[category][key] = self.input_fields[key].text() + + # Write the config file + with open("config.ini", "w") as configfile: + self.config.write(configfile) + + self.close() diff --git a/components/MusicTable.py b/components/MusicTable.py index 63c5286..810331b 100644 --- a/components/MusicTable.py +++ b/components/MusicTable.py @@ -18,6 +18,7 @@ from PyQt5.QtWidgets import ( ) from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt, pyqtSignal, QTimer from components.LyricsWindow import LyricsWindow +from components.AddToPlaylistWindow import AddToPlaylistWindow from utils import add_files_to_library from utils import update_song_in_library from utils import get_id3_tags @@ -81,12 +82,11 @@ class MusicTable(QTableView): self.model.layoutChanged.connect(self.restore_scroll_position) def contextMenuEvent(self, event): - """Show a context menu when you right-click a row""" + """Right-click context menu for rows in Music Table""" menu = QMenu(self) - # delete song - delete_action = QAction("Delete", self) - delete_action.triggered.connect(self.delete_songs) - menu.addAction(delete_action) + add_to_playlist_action = QAction("Add to playlist", self) + add_to_playlist_action.triggered.connect(self.add_selected_files_to_playlist) + menu.addAction(add_to_playlist_action) # lyrics lyrics_menu = QAction("Lyrics (View/Edit)", self) lyrics_menu.triggered.connect(self.show_lyrics_menu) @@ -95,6 +95,10 @@ class MusicTable(QTableView): open_containing_folder_action = QAction("Open in system file manager", self) open_containing_folder_action.triggered.connect(self.open_directory) menu.addAction(open_containing_folder_action) + # delete song + delete_action = QAction("Delete", self) + delete_action.triggered.connect(self.delete_songs) + menu.addAction(delete_action) # show self.set_selected_song_filepath() menu.exec_(event.globalPos()) @@ -133,6 +137,16 @@ class MusicTable(QTableView): path = "/".join(filepath) Popen(["xdg-open", path]) + def add_selected_files_to_playlist(self): + """Opens a playlist choice menu and adds the currently selected files to the chosen playlist""" + playlist_dict = {} + with DBA.DBAccess() as db: + data = db.query("SELECT id, name from playlist", ()) + for row in data: + playlist_dict[row[0][0]] = row[0][1] + playlist_choice_window = AddToPlaylistWindow(playlist_dict) + playlist_choice_window.exec_() + def show_lyrics_menu(self): """Shows the lyrics for the currently selected song""" selected_song_filepath = self.get_selected_song_filepath() diff --git a/components/PreferencesWindow.py b/components/PreferencesWindow.py index 578005b..7c9c035 100644 --- a/components/PreferencesWindow.py +++ b/components/PreferencesWindow.py @@ -1,4 +1,12 @@ -from PyQt5.QtWidgets import QDialog, QFrame, QVBoxLayout, QLabel, QLineEdit, QPushButton +from PyQt5.QtWidgets import ( + QDialog, + QFrame, + QVBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QDial, +) from PyQt5.QtGui import QFont diff --git a/components/__init__.py b/components/__init__.py index 60c5353..96919cf 100644 --- a/components/__init__.py +++ b/components/__init__.py @@ -4,3 +4,4 @@ from .AudioVisualizer import AudioVisualizer from .PreferencesWindow import PreferencesWindow from .ErrorDialog import ErrorDialog from .LyricsWindow import LyricsWindow +from .AddToPlaylistWindow import AddToPlaylistWindow diff --git a/utils/add_files_to_library.py b/utils/add_files_to_library.py index 5200d67..4545cd5 100644 --- a/utils/add_files_to_library.py +++ b/utils/add_files_to_library.py @@ -1,6 +1,6 @@ import DBA from configparser import ConfigParser -from utils import get_id3_tags, id3_timestamp_to_datetime, safe_get +from utils import get_id3_tags, id3_timestamp_to_datetime config = ConfigParser() config.read("config.ini") diff --git a/utils/scan_for_music.py b/utils/scan_for_music.py index 1462095..330c35d 100644 --- a/utils/scan_for_music.py +++ b/utils/scan_for_music.py @@ -1,8 +1,6 @@ import os -import DBA from configparser import ConfigParser -from utils import add_files_to_library, get_id3_tags -from utils import safe_get +from utils import add_files_to_library config = ConfigParser() config.read("config.ini") @@ -10,7 +8,6 @@ config.read("config.ini") def scan_for_music(): root_dir = config.get("directories", "library") - # for dirpath, dirnames, filenames ... for _, _, filenames in os.walk(root_dir): add_files_to_library(filenames)