Merge branch 'main' of github.com:billypom/musicpom
This commit is contained in:
commit
26bd852a3f
44
components/AddToPlaylistWindow.py
Normal file
44
components/AddToPlaylistWindow.py
Normal 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()
|
||||||
@ -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:
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
2
main.py
2
main.py
@ -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}]")
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
32
utils/init.sql
Normal 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)
|
||||||
|
);
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user