file>open files dialog and lyrics testing

This commit is contained in:
tsi-billypom 2024-05-30 16:30:30 -04:00
parent 767ce3fa9c
commit a78b5c1a51
11 changed files with 185 additions and 49 deletions

View File

@ -0,0 +1,49 @@
from PyQt5.QtWidgets import (
QDialog,
QPlainTextEdit,
QVBoxLayout,
QLabel,
QPushButton,
)
from PyQt5.QtGui import QFont
from utils import set_id3_tag
class LyricsWindow(QDialog):
def __init__(self, song_filepath, lyrics):
super(LyricsWindow, self).__init__()
self.setWindowTitle("Lyrics")
self.lyrics = lyrics
self.song_filepath = song_filepath
self.input_field = ""
layout = QVBoxLayout()
# label = QLabel("Lyrics")
# layout.addWidget(label)
# Labels & input fields
self.input_fields = {}
lyrics_label = QLabel("Lyrics")
lyrics_label.setFont(QFont("Sans", weight=QFont.Bold)) # bold category
lyrics_label.setStyleSheet("text-transform:uppercase;") # uppercase category
layout.addWidget(lyrics_label)
self.input_field = QPlainTextEdit(self.lyrics)
layout.addWidget(self.input_field)
# Save button
save_button = QPushButton("Save")
save_button.clicked.connect(self.save)
layout.addWidget(save_button)
self.setLayout(layout)
def save(self):
"""Saves the current lyrics text to the USLT/lyrics ID3 tag"""
success = set_id3_tag(
filepath=self.song_filepath,
tag_name="lyrics",
value=self.input_field.toPlainText(),
)
if success:
print("success! yay")
else:
print("NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN")
self.close()

View File

@ -1,6 +1,7 @@
from mutagen.easyid3 import EasyID3
import DBA
from PyQt5.QtGui import (
QDragMoveEvent,
QStandardItem,
QStandardItemModel,
QKeySequence,
@ -16,6 +17,7 @@ from PyQt5.QtWidgets import (
QAbstractItemView,
)
from PyQt5.QtCore import QModelIndex, Qt, pyqtSignal, QTimer
from components.LyricsWindow import LyricsWindow
from utils import add_files_to_library
from utils import update_song_in_library
from utils import get_id3_tags
@ -107,45 +109,79 @@ class MusicTable(QTableView):
QMessageBox.Yes,
)
if reply:
# selected_rows = self.get_selected_rows()
selected_filepaths = self.get_selected_songs_filepaths()
# for index in selected_rows:
# self.model().removeRow(index)
selected_indices = self.get_selected_rows()
for file in selected_filepaths:
with DBA.DBAccess() as db:
db.execute("DELETE FROM library WHERE filepath = ?", (file,))
for index in selected_indices:
self.model.removeRow(index)
def open_directory(self):
"""Opens the currently selected song in the system file manager"""
if self.get_selected_song_filepath() is None:
QMessageBox.warning(
self,
"File does not exist",
"No file is selected, or the file does not exist",
QMessageBox.Ok,
QMessageBox.Ok,
)
return
filepath = self.get_selected_song_filepath().split("/")
filepath.pop()
path = "/".join(filepath)
Popen(["xdg-open", path])
def show_lyrics_menu(self):
pass
"""Shows the lyrics for the currently selected song"""
selected_song_filepath = self.get_selected_song_filepath()
if selected_song_filepath is None:
return
current_song = self.get_selected_song_metadata()
print(f"MusicTable.py | show_lyrics_menu | current song: {current_song}")
try:
lyrics = current_song["lyrics"]
except Exception:
pass
try:
lyrics = current_song["USLT"]
except Exception:
lyrics = ""
lyrics_window = LyricsWindow(selected_song_filepath, lyrics)
lyrics_window.exec_()
def dragEnterEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls():
event.accept()
def dragEnterEvent(self, e: QDragEnterEvent | None):
if e is None:
return
data = e.mimeData()
if data and data.hasUrls():
e.accept()
else:
event.ignore()
e.ignore()
def dragMoveEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls():
event.accept()
def dragMoveEvent(self, e: QDragMoveEvent | None):
if e is None:
return
data = e.mimeData()
if data and data.hasUrls():
e.accept()
else:
event.ignore()
e.ignore()
def dropEvent(self, event: QDropEvent):
if event.mimeData().hasUrls():
def dropEvent(self, e: QDropEvent | None):
if e is None:
return
data = e.mimeData()
if data and data.hasUrls():
files = []
for url in event.mimeData().urls():
for url in data.urls():
if url.isLocalFile():
files.append(url.path())
self.add_files(files)
event.accept()
e.accept()
else:
event.ignore()
e.ignore()
def setup_keyboard_shortcuts(self):
"""Setup shortcuts here"""

View File

@ -1,23 +1,25 @@
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QLineEdit, QPushButton
from PyQt5.QtGui import QFont
import configparser
class PreferencesWindow(QDialog):
def __init__(self, config):
super(PreferencesWindow, self).__init__()
self.setWindowTitle('Preferences')
self.setWindowTitle("Preferences")
self.config = config
layout = QVBoxLayout()
label = QLabel('Preferences Window')
label = QLabel("Preferences Window")
layout.addWidget(label)
# Labels & input fields
self.input_fields = {}
for category in self.config.sections():
category_label = QLabel(f'{category}')
category_label.setFont(QFont('', weight=QFont.Bold)) # bold category
category_label.setStyleSheet("text-transform:uppercase;") # uppercase category
category_label = QLabel(f"{category}")
category_label.setFont(QFont("", weight=QFont.Bold)) # bold category
category_label.setStyleSheet(
"text-transform:uppercase;"
) # uppercase category
layout.addWidget(category_label)
for key in self.config[category]:
label = QLabel(key)
@ -27,7 +29,7 @@ class PreferencesWindow(QDialog):
self.input_fields[key] = input_field
# Save button
save_button = QPushButton('Save')
save_button = QPushButton("Save")
save_button.clicked.connect(self.save_preferences)
layout.addWidget(save_button)
self.setLayout(layout)
@ -40,8 +42,7 @@ class PreferencesWindow(QDialog):
self.config[category][key] = self.input_fields[key].text()
# Write the config file
with open('config.ini', 'w') as configfile:
with open("config.ini", "w") as configfile:
self.config.write(configfile)
self.close()

View File

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

45
main.py
View File

@ -10,19 +10,20 @@ from configparser import ConfigParser
import DBA
from ui import Ui_MainWindow
from PyQt5.QtWidgets import (
QFileDialog,
QMainWindow,
QApplication,
QGraphicsScene,
QHeaderView,
QGraphicsPixmapItem,
QMessageBox,
)
from PyQt5.QtCore import QUrl, QTimer, QEvent, Qt, QModelIndex
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe
from PyQt5.QtGui import QPixmap, QStandardItemModel
from utils import scan_for_music
from utils import delete_and_create_library_database
from components import AudioVisualizer
from components import PreferencesWindow
from components import PreferencesWindow, AudioVisualizer
# Create ui.py file from Qt Designer
# pyuic5 ui.ui -o ui.py
@ -89,14 +90,19 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.on_previous_clicked
) # Click to previous song
self.nextButton.clicked.connect(self.on_next_clicked) # Click to next song
# FILE MENU
self.actionOpenFiles.triggered.connect(self.open_files) # Open files window
# EDIT MENU
# VIEW MENU
self.actionPreferences.triggered.connect(
self.actionPreferencesClicked
self.open_preferences
) # Open preferences menu
# QUICK ACTIONS MENU
self.actionScanLibraries.triggered.connect(self.scan_libraries) # Scan library
self.actionClearDatabase.triggered.connect(
self.clear_database
) # Clear database
## tableView
## tableView triggers
self.tableView.doubleClicked.connect(
self.play_audio_file
) # Listens for the double click event, then plays the song
@ -322,17 +328,42 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
def on_next_clicked(self) -> None:
print("next")
def actionPreferencesClicked(self) -> None:
def open_files(self) -> None:
"""Opens the open files window"""
open_files_window = QFileDialog(
self, "Open file(s)", ".", "Audio files (*.mp3)"
)
# QFileDialog.FileMode enum { AnyFile, ExistingFile, Directory, ExistingFiles }
open_files_window.setFileMode(QFileDialog.ExistingFiles)
open_files_window.exec_()
filenames = open_files_window.selectedFiles()
print("file names chosen")
print(filenames)
self.tableView.add_files(filenames)
def open_preferences(self) -> None:
"""Opens the preferences window"""
preferences_window = PreferencesWindow(self.config)
preferences_window.exec_() # Display the preferences window modally
def scan_libraries(self) -> None:
"""Scans for new files in the configured library folder
Refreshes the datagridview"""
scan_for_music()
self.tableView.fetch_library()
def clear_database(self) -> None:
delete_and_create_library_database()
self.tableView.fetch_library()
"""Clears all songs from the database"""
reply = QMessageBox.question(
self,
"Confirmation",
"Clear all songs from database?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes,
)
if reply:
delete_and_create_library_database()
self.tableView.fetch_library()
def process_probe(self, buff) -> None:
buff.startTime()

6
ui.py
View File

@ -154,7 +154,7 @@ class Ui_MainWindow(object):
self.verticalLayout_3.setStretch(3, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1152, 41))
self.menubar.setGeometry(QtCore.QRect(0, 0, 1152, 21))
self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
@ -174,6 +174,9 @@ class Ui_MainWindow(object):
self.actionScanLibraries.setObjectName("actionScanLibraries")
self.actionClearDatabase = QtWidgets.QAction(MainWindow)
self.actionClearDatabase.setObjectName("actionClearDatabase")
self.actionOpenFiles = QtWidgets.QAction(MainWindow)
self.actionOpenFiles.setObjectName("actionOpenFiles")
self.menuFile.addAction(self.actionOpenFiles)
self.menuEdit.addAction(self.actionPreferences)
self.menuQuick_Actions.addAction(self.actionScanLibraries)
self.menuQuick_Actions.addAction(self.actionClearDatabase)
@ -205,5 +208,6 @@ class Ui_MainWindow(object):
self.actionPreferences.setStatusTip(_translate("MainWindow", "Open preferences"))
self.actionScanLibraries.setText(_translate("MainWindow", "Scan libraries"))
self.actionClearDatabase.setText(_translate("MainWindow", "Clear Database"))
self.actionOpenFiles.setText(_translate("MainWindow", "Open file(s)"))
from components import AlbumArtGraphicsView, MusicTable
from pyqtgraph import PlotWidget

8
ui.ui
View File

@ -274,13 +274,14 @@
<x>0</x>
<y>0</y>
<width>1152</width>
<height>41</height>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionOpenFiles"/>
</widget>
<widget class="QMenu" name="menuEdit">
<property name="title">
@ -324,6 +325,11 @@
<string>Clear Database</string>
</property>
</action>
<action name="actionOpenFiles">
<property name="text">
<string>Open file(s)</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -14,15 +14,15 @@ def add_files_to_library(files):
"""
if not files:
return []
print(f"utils/add_files_to_library: {files}")
# print(f"utils/add_files_to_library: {files}")
extensions = config.get("settings", "extensions").split(",")
insert_data = [] # To store data for batch insert
for filepath in files:
if any(filepath.lower().endswith(ext) for ext in extensions):
filename = filepath.split("/")[-1]
audio = get_id3_tags(filepath)
print('add_files_to_library audio:')
print(audio)
# print("add_files_to_library audio:")
# print(audio)
# Skip if no title is found (but should never happen
if "title" not in audio:
continue

View File

@ -0,0 +1,2 @@
#!/bin/bash
ffmpeg -f lavfi -i anullsrc=r=44100:cl=mono -t 5 -q:a 9 -acodec libmp3lame out.mp3

View File

@ -20,24 +20,25 @@ def get_id3_tags(file):
is_easy_id3 = False
audio = {}
print('get_id3_tags audio:')
print(audio)
# print('get_id3_tags audio:')
# print(audio)
# Check if all tags are empty
tags_are_empty = all(not values for values in audio.values())
if tags_are_empty:
# split on / to get just the filename
# os.path.splitext to get name without extension
audio['title'] = [os.path.splitext(file.split('/')[-1])[0]]
audio["title"] = [os.path.splitext(file.split("/")[-1])[0]]
if audio['title'] is None: # I guess a song could have other tags
if audio["title"] is None: # I guess a song could have other tags
# without a title, so i make sure to have title
audio['title'] = [os.path.splitext(file.split('/')[-1])[0]]
audio["title"] = [os.path.splitext(file.split("/")[-1])[0]]
if is_easy_id3: # i can ignore this error because of this check
audio.save() # type: ignore
if is_easy_id3: # i can ignore this error because of this check
audio.save() # type: ignore
return audio
# import sys
# my_file = sys.argv[1]
# data = get_id3_tags(my_file)

View File

@ -84,12 +84,12 @@ def set_id3_tag(filepath: str, tag_name: str, value: str):
Args:
filepath: path to the mp3 file
tag_name: common name of the ID3 tag
value: valut to set for the tag
value: value to set for the tag
Returns:
True / False"""
print(
f"set_id3_tag(): filepath: {filepath} | tag_name: {tag_name} | value: {value}"
f"set_id3_tag.py | filepath: {filepath} | tag_name: {tag_name} | value: {value}"
)
try:
@ -105,11 +105,16 @@ def set_id3_tag(filepath: str, tag_name: str, value: str):
if tdat_tag:
# update TDAT if we have it
audio_file.tags.add(tdat_tag)
elif tag_name == "lyrics":
print("lyrics..........")
audio_file.tags.add(USLT(encoding=3, lang="eng", desc="desc", text=value))
elif tag_name in id3_tag_mapping: # Tag accounted for
tag_class = id3_tag_mapping[tag_name]
print(f"set_id3_tag.py | tag_class: {tag_class}")
# if issubclass(tag_class, EasyID3) or issubclass(tag_class, ID3): # Type safety
if issubclass(tag_class, Frame):
audio_file.tags.add(tag_class(encoding=3, text=value)) # Add the tag
print(f"AAAAAAAAAAAAAA")
else:
# dialog = ErrorDialog(f'ID3 tag not supported.\nTag: {tag_name}\nTag class: {tag_class}\nValue:{value}')
# dialog.exec_()
@ -122,9 +127,9 @@ def set_id3_tag(filepath: str, tag_name: str, value: str):
pass
audio_file.save()
print("ID3 tags updated:")
print("set_id3_tag.py | ID3 tags updated:")
print(get_id3_tags(filepath))
print("-----")
print("set_id3_tag.py | -----")
return True
except Exception as e: