Merge branch 'main' of github.com:billypom/musicpom

This commit is contained in:
billypom on debian 2024-06-27 17:06:58 -04:00
commit 26bd852a3f
10 changed files with 125 additions and 23 deletions

View File

@ -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()

View File

@ -18,6 +18,7 @@ from PyQt5.QtWidgets import (
) )
from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt, pyqtSignal, QTimer from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt, pyqtSignal, QTimer
from components.LyricsWindow import LyricsWindow from components.LyricsWindow import LyricsWindow
from components.AddToPlaylistWindow import AddToPlaylistWindow
from utils import add_files_to_library from utils import add_files_to_library
from utils import update_song_in_library from utils import update_song_in_library
from utils import get_id3_tags from utils import get_id3_tags
@ -81,12 +82,11 @@ class MusicTable(QTableView):
self.model.layoutChanged.connect(self.restore_scroll_position) self.model.layoutChanged.connect(self.restore_scroll_position)
def contextMenuEvent(self, event): 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) menu = QMenu(self)
# delete song add_to_playlist_action = QAction("Add to playlist", self)
delete_action = QAction("Delete", self) add_to_playlist_action.triggered.connect(self.add_selected_files_to_playlist)
delete_action.triggered.connect(self.delete_songs) menu.addAction(add_to_playlist_action)
menu.addAction(delete_action)
# lyrics # lyrics
lyrics_menu = QAction("Lyrics (View/Edit)", self) lyrics_menu = QAction("Lyrics (View/Edit)", self)
lyrics_menu.triggered.connect(self.show_lyrics_menu) 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 = QAction("Open in system file manager", self)
open_containing_folder_action.triggered.connect(self.open_directory) open_containing_folder_action.triggered.connect(self.open_directory)
menu.addAction(open_containing_folder_action) 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 # show
self.set_selected_song_filepath() self.set_selected_song_filepath()
menu.exec_(event.globalPos()) menu.exec_(event.globalPos())
@ -114,7 +118,7 @@ class MusicTable(QTableView):
selected_indices = self.get_selected_rows() selected_indices = self.get_selected_rows()
for file in selected_filepaths: for file in selected_filepaths:
with DBA.DBAccess() as db: 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: for index in selected_indices:
model.removeRow(index) model.removeRow(index)
self.fetch_library() self.fetch_library()
@ -135,6 +139,16 @@ class MusicTable(QTableView):
path = "/".join(filepath) path = "/".join(filepath)
Popen(["xdg-open", path]) 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): def show_lyrics_menu(self):
"""Shows the lyrics for the currently selected song""" """Shows the lyrics for the currently selected song"""
selected_song_filepath = self.get_selected_song_filepath() selected_song_filepath = self.get_selected_song_filepath()
@ -266,7 +280,7 @@ class MusicTable(QTableView):
# Update the db # Update the db
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
db.query( db.query(
"UPDATE library SET filepath = ? WHERE filepath = ?", "UPDATE song SET filepath = ? WHERE filepath = ?",
(new_path, filepath), (new_path, filepath),
) )
print(f"Moved: {filepath} -> {new_path}") print(f"Moved: {filepath} -> {new_path}")
@ -298,7 +312,7 @@ class MusicTable(QTableView):
try: try:
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
data = db.query( 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: except Exception as e:

View File

@ -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 from PyQt5.QtGui import QFont

View File

@ -4,3 +4,4 @@ from .AudioVisualizer import AudioVisualizer
from .PreferencesWindow import PreferencesWindow from .PreferencesWindow import PreferencesWindow
from .ErrorDialog import ErrorDialog from .ErrorDialog import ErrorDialog
from .LyricsWindow import LyricsWindow from .LyricsWindow import LyricsWindow
from .AddToPlaylistWindow import AddToPlaylistWindow

View File

@ -384,7 +384,7 @@ if __name__ == "__main__":
os.makedirs(path_as_string) os.makedirs(path_as_string)
# Create database on first run # Create database on first run
with DBA.DBAccess() as db: 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() lines = file.read()
for statement in lines.split(";"): for statement in lines.split(";"):
print(f"executing [{statement}]") print(f"executing [{statement}]")

View File

@ -1,6 +1,6 @@
import DBA import DBA
from configparser import ConfigParser 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 = ConfigParser()
config.read("config.ini") config.read("config.ini")
@ -64,7 +64,7 @@ def add_files_to_library(files):
if len(insert_data) >= 1000: if len(insert_data) >= 1000:
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
db.executemany( 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,
) )
insert_data = [] # Reset the insert_data list insert_data = [] # Reset the insert_data list
@ -72,7 +72,7 @@ def add_files_to_library(files):
if insert_data: if insert_data:
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
db.executemany( 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,
) )
return True return True

View File

@ -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. -- 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, id integer primary key,
filepath varchar(511) UNIQUE, filepath varchar(511) UNIQUE,

32
utils/init.sql Normal file
View File

@ -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)
);

View File

@ -1,8 +1,6 @@
import os import os
import DBA
from configparser import ConfigParser from configparser import ConfigParser
from utils import add_files_to_library, get_id3_tags from utils import add_files_to_library
from utils import safe_get
config = ConfigParser() config = ConfigParser()
config.read("config.ini") config.read("config.ini")
@ -10,7 +8,6 @@ config.read("config.ini")
def scan_for_music(): def scan_for_music():
root_dir = config.get("directories", "library") root_dir = config.get("directories", "library")
# for dirpath, dirnames, filenames ... # for dirpath, dirnames, filenames ...
for _, _, filenames in os.walk(root_dir): for _, _, filenames in os.walk(root_dir):
add_files_to_library(filenames) add_files_to_library(filenames)

View File

@ -2,7 +2,9 @@ import DBA
from components.ErrorDialog import ErrorDialog 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 """Updates a field in the library database based on an ID
Args: Args:
@ -15,10 +17,14 @@ def update_song_in_library(library_id: int, edited_column_name: str, user_input_
try: try:
with DBA.DBAccess() as db: 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 # 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: 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_() dialog.exec_()
return False return False
return True return True