From 76c1aa554b0ab8ba3cf04e2302d7827fe160d243 Mon Sep 17 00:00:00 2001 From: billypom on debian Date: Sun, 28 Jan 2024 09:59:51 -0500 Subject: [PATCH] layouts, mutagen id3 tags, table drag & drop, double click to play --- .../AudioVisualizer.py | 11 +- components/MusicTable.py | 27 +- components/__init__.py | 3 +- main.py | 52 +- requirements.txt | 2 +- sample_config.ini | 6 +- ui.py | 158 ++--- ui.ui | 551 +++++++++--------- utils/__init__.py | 5 +- utils/add_files_to_library.py | 58 ++ utils/create_waveform_from_file.py | 7 +- utils/get_id3_tags.py | 21 + utils/scan_for_music.py | 4 +- 13 files changed, 552 insertions(+), 353 deletions(-) rename utils/audio_visualizer.py => components/AudioVisualizer.py (71%) create mode 100644 utils/add_files_to_library.py create mode 100644 utils/get_id3_tags.py diff --git a/utils/audio_visualizer.py b/components/AudioVisualizer.py similarity index 71% rename from utils/audio_visualizer.py rename to components/AudioVisualizer.py index 7313938..417018a 100644 --- a/utils/audio_visualizer.py +++ b/components/AudioVisualizer.py @@ -1,10 +1,17 @@ -from PyQt5 import QtWidgets, QtGui, QtCore +from PyQt5 import QtWidgets import numpy as np -from .fft_analyser import FFTAnalyser +from utils import FFTAnalyser class AudioVisualizer(QtWidgets.QWidget): + """_Audio Visualizer component_ + Args: + QtWidgets (_type_): _description_ + + Returns: + _type_: _description_ + """ def __init__(self, media_player): super().__init__() self.media_player = media_player diff --git a/components/MusicTable.py b/components/MusicTable.py index 2ca97c2..715038e 100644 --- a/components/MusicTable.py +++ b/components/MusicTable.py @@ -1,16 +1,17 @@ import DBA from PyQt5.QtGui import QStandardItem, QStandardItemModel -from PyQt5.QtWidgets import QTableView, QApplication +from PyQt5.QtWidgets import QTableView from PyQt5.QtCore import QTimer from tinytag import TinyTag +from utils import add_files_to_library +from utils import get_id3_tags +import logging class MusicTable(QTableView): def __init__(self, parent=None, qapp=None): QTableView.__init__(self, parent) self.headers = ['title', 'artist', 'album', 'genre', 'codec', 'year', 'path'] - self.model = QStandardItemModel() - self.model.setHorizontalHeaderLabels(self.headers) self.songChanged = None self.selected_song_filepath = None self.current_song_filepath = None @@ -56,6 +57,9 @@ class MusicTable(QTableView): """Sets the filepath of the currently selected song""" self.selected_song_filepath = self.currentIndex().siblingAtColumn(self.headers.index('path')).data() print(f'Selected song: {self.selected_song_filepath}') + print('TAGS:') + print(get_id3_tags(self.selected_song_filepath)) + print('END TAGS') def set_current_song_filepath(self): """Sets the filepath of the currently playing/chosen song""" @@ -80,6 +84,9 @@ class MusicTable(QTableView): def fetch_library(self): + """Initialize the tableview model""" + self.model = QStandardItemModel() + self.model.setHorizontalHeaderLabels(self.headers) # Fetch library data with DBA.DBAccess() as db: data = db.query('SELECT title, artist, album, genre, codec, album_date, filepath FROM library;', ()) @@ -89,6 +96,20 @@ class MusicTable(QTableView): self.model.appendRow(items) # Set the model to the tableView (we are the tableview) self.setModel(self.model) + self.update() + + def add_files(self, files): + """When song(s) added to the library, update the tableview model + - Drag & Drop song(s) on tableView + - File > Open > List of song(s) + """ + print(f'tableView - adding files: {files}') + response = add_files_to_library(files) + if response: + self.fetch_library() + else: + logging.warning('MusicTable.add_files | failed to add files to library') + def load_qapp(self, qapp): self.qapp = qapp diff --git a/components/__init__.py b/components/__init__.py index 706d493..dbbdfbf 100644 --- a/components/__init__.py +++ b/components/__init__.py @@ -1 +1,2 @@ -from .MusicTable import MusicTable \ No newline at end of file +from .MusicTable import MusicTable +from .AudioVisualizer import AudioVisualizer \ No newline at end of file diff --git a/main.py b/main.py index 5d29ef6..c1b1976 100644 --- a/main.py +++ b/main.py @@ -2,15 +2,16 @@ import DBA from ui import Ui_MainWindow from PyQt5.QtWidgets import QMainWindow, QApplication import qdarktheme -from PyQt5.QtCore import QUrl, QTimer, QFile, QTextStream +from PyQt5.QtCore import QUrl, QTimer, QEvent from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe -from PyQt5.QtGui import QStandardItem, QStandardItemModel from utils import scan_for_music from utils import initialize_library_database -from utils import AudioVisualizer +from components import AudioVisualizer from pyqtgraph import mkBrush -from components import MusicTable +import configparser +# Create ui.py file from Qt Designer +# pyuic5 ui.ui -o ui.py class ApplicationWindow(QMainWindow, Ui_MainWindow): def __init__(self, qapp): @@ -23,6 +24,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): self.qapp = qapp print(f'ApplicationWindow self.qapp: {self.qapp}') self.tableView.load_qapp(self.qapp) + self.config = configparser.ConfigParser() + self.config.read('config.ini') global stopped stopped = False @@ -64,12 +67,51 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow): # self.pauseButton.clicked.connect(self.on_pause_clicked) self.previousButton.clicked.connect(self.on_previous_clicked) # Click to previous song self.nextButton.clicked.connect(self.on_next_clicked) # Click to next song - # self.tableView.clicked.connect(self.set_clicked_cell_filepath) self.actionPreferences.triggered.connect(self.actionPreferencesClicked) # Open preferences menu self.actionScanLibraries.triggered.connect(self.scan_libraries) # Scan library self.actionClearDatabase.triggered.connect(initialize_library_database) # Clear database + ## tableView + # self.tableView.clicked.connect(self.set_clicked_cell_filepath) self.tableView.doubleClicked.connect(self.play_audio_file) # Double click to play song + self.tableView.viewport().installEventFilter(self) # for drag & drop functionality + # self.tableView.model.layoutChanged() + ### set column widths + table_view_column_widths = str(self.config['table']['column_widths']).split(',') + for i in range(self.tableView.model.columnCount()): + self.tableView.setColumnWidth(i, int(table_view_column_widths[i])) + def eventFilter(self, source, event): + """Handles events""" + # tableView (drag & drop) + if (source is self.tableView.viewport() and + (event.type() == QEvent.DragEnter or + event.type() == QEvent.DragMove or + event.type() == QEvent.Drop) and + event.mimeData().hasUrls()): + files = [] + if event.type() == QEvent.Drop: + for url in event.mimeData().urls(): + if url.isLocalFile(): + files.append(url.path()) + self.tableView.add_files(files) + event.accept() + return True + return super().eventFilter(source, event) + + def closeEvent(self, event): + """Save settings when closing the application""" + # MusicTable/tableView column widths + list_of_column_widths = [] + for i in range(self.tableView.model.columnCount()): + list_of_column_widths.append(str(self.tableView.columnWidth(i))) + column_widths_as_string = ','.join(list_of_column_widths) + self.config['table']['column_widths'] = column_widths_as_string + + + # Save the config + with open('config.ini', 'w') as configfile: + self.config.write(configfile) + super().closeEvent(event) def play_audio_file(self): """Start playback of selected track & moves playback slider""" diff --git a/requirements.txt b/requirements.txt index 75046f5..51fea21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -tinytag +mutagen matplotlib pyqt5 pydub diff --git a/sample_config.ini b/sample_config.ini index 725aed2..74fa845 100644 --- a/sample_config.ini +++ b/sample_config.ini @@ -9,4 +9,8 @@ reorganize_destination = /where/to/reorganize/to [settings] # Which file types are scanned -extensions = mp3,wav,ogg,flac \ No newline at end of file +extensions = mp3,wav,ogg,flac + +[table] +# Music table column widths +column_widths = 181,116,222,76,74,72,287 \ No newline at end of file diff --git a/ui.py b/ui.py index cbcd2d4..eced834 100644 --- a/ui.py +++ b/ui.py @@ -18,32 +18,13 @@ class Ui_MainWindow(object): MainWindow.setStatusTip("") self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") - self.frame = QtWidgets.QFrame(self.centralwidget) - self.frame.setGeometry(QtCore.QRect(0, 160, 1061, 461)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) - self.frame.setSizePolicy(sizePolicy) - self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame.setObjectName("frame") - self.tableView = MusicTable(self.frame) - self.tableView.setGeometry(QtCore.QRect(0, 0, 1061, 461)) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.tableView.sizePolicy().hasHeightForWidth()) - self.tableView.setSizePolicy(sizePolicy) - self.tableView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) - self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.AnyKeyPressed|QtWidgets.QAbstractItemView.EditKeyPressed) - self.tableView.setAlternatingRowColors(True) - self.tableView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) - self.tableView.setObjectName("tableView") - self.tableView.verticalHeader().setVisible(False) - self.frame_2 = QtWidgets.QFrame(self.centralwidget) + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.frame_6 = QtWidgets.QFrame(self.centralwidget) + self.frame_6.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_6.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_6.setObjectName("frame_6") + self.frame_2 = QtWidgets.QFrame(self.frame_6) self.frame_2.setGeometry(QtCore.QRect(0, 0, 161, 161)) self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) @@ -51,26 +32,7 @@ class Ui_MainWindow(object): self.albumGraphicsView = QtWidgets.QGraphicsView(self.frame_2) self.albumGraphicsView.setGeometry(QtCore.QRect(0, 0, 161, 161)) self.albumGraphicsView.setObjectName("albumGraphicsView") - self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget) - self.horizontalLayoutWidget.setGeometry(QtCore.QRect(540, 0, 521, 41)) - self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget") - self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget) - self.horizontalLayout.setContentsMargins(0, 0, 0, 0) - self.horizontalLayout.setObjectName("horizontalLayout") - self.playbackSlider = QtWidgets.QSlider(self.horizontalLayoutWidget) - self.playbackSlider.setOrientation(QtCore.Qt.Horizontal) - self.playbackSlider.setObjectName("playbackSlider") - self.horizontalLayout.addWidget(self.playbackSlider) - self.startTimeLabel = QtWidgets.QLabel(self.horizontalLayoutWidget) - self.startTimeLabel.setObjectName("startTimeLabel") - self.horizontalLayout.addWidget(self.startTimeLabel) - self.slashLabel = QtWidgets.QLabel(self.horizontalLayoutWidget) - self.slashLabel.setObjectName("slashLabel") - self.horizontalLayout.addWidget(self.slashLabel) - self.endTimeLabel = QtWidgets.QLabel(self.horizontalLayoutWidget) - self.endTimeLabel.setObjectName("endTimeLabel") - self.horizontalLayout.addWidget(self.endTimeLabel) - self.frame_4 = QtWidgets.QFrame(self.centralwidget) + self.frame_4 = QtWidgets.QFrame(self.frame_6) self.frame_4.setGeometry(QtCore.QRect(540, 40, 521, 121)) self.frame_4.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_4.setFrameShadow(QtWidgets.QFrame.Raised) @@ -78,18 +40,7 @@ class Ui_MainWindow(object): self.PlotWidget = PlotWidget(self.frame_4) self.PlotWidget.setGeometry(QtCore.QRect(0, 0, 521, 121)) self.PlotWidget.setObjectName("PlotWidget") - self.frame_3 = QtWidgets.QFrame(self.centralwidget) - self.frame_3.setGeometry(QtCore.QRect(0, 680, 1061, 61)) - self.frame_3.setFrameShape(QtWidgets.QFrame.StyledPanel) - self.frame_3.setFrameShadow(QtWidgets.QFrame.Raised) - self.frame_3.setObjectName("frame_3") - self.volumeSlider = QtWidgets.QSlider(self.frame_3) - self.volumeSlider.setGeometry(QtCore.QRect(10, 10, 181, 31)) - self.volumeSlider.setMaximum(100) - self.volumeSlider.setProperty("value", 50) - self.volumeSlider.setOrientation(QtCore.Qt.Horizontal) - self.volumeSlider.setObjectName("volumeSlider") - self.frame_5 = QtWidgets.QFrame(self.centralwidget) + self.frame_5 = QtWidgets.QFrame(self.frame_6) self.frame_5.setGeometry(QtCore.QRect(160, 0, 381, 161)) self.frame_5.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_5.setFrameShadow(QtWidgets.QFrame.Raised) @@ -123,30 +74,99 @@ class Ui_MainWindow(object): self.albumLabel.setFont(font) self.albumLabel.setObjectName("albumLabel") self.verticalLayout.addWidget(self.albumLabel) - self.horizontalLayoutWidget_2 = QtWidgets.QWidget(self.centralwidget) - self.horizontalLayoutWidget_2.setGeometry(QtCore.QRect(0, 620, 1061, 60)) - self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2") - self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2) - self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) + self.horizontalLayoutWidget = QtWidgets.QWidget(self.frame_6) + self.horizontalLayoutWidget.setGeometry(QtCore.QRect(540, 0, 521, 41)) + self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.playbackSlider = QtWidgets.QSlider(self.horizontalLayoutWidget) + self.playbackSlider.setOrientation(QtCore.Qt.Horizontal) + self.playbackSlider.setObjectName("playbackSlider") + self.horizontalLayout.addWidget(self.playbackSlider) + self.startTimeLabel = QtWidgets.QLabel(self.horizontalLayoutWidget) + self.startTimeLabel.setObjectName("startTimeLabel") + self.horizontalLayout.addWidget(self.startTimeLabel) + self.slashLabel = QtWidgets.QLabel(self.horizontalLayoutWidget) + self.slashLabel.setObjectName("slashLabel") + self.horizontalLayout.addWidget(self.slashLabel) + self.endTimeLabel = QtWidgets.QLabel(self.horizontalLayoutWidget) + self.endTimeLabel.setObjectName("endTimeLabel") + self.horizontalLayout.addWidget(self.endTimeLabel) + self.verticalLayout_3.addWidget(self.frame_6) + self.frame = QtWidgets.QFrame(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) + self.frame.setSizePolicy(sizePolicy) + self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame.setObjectName("frame") + self.verticalLayoutWidget_2 = QtWidgets.QWidget(self.frame) + self.verticalLayoutWidget_2.setGeometry(QtCore.QRect(0, 0, 1061, 461)) + self.verticalLayoutWidget_2.setObjectName("verticalLayoutWidget_2") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_2) + self.verticalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.tableView = MusicTable(self.verticalLayoutWidget_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.tableView.sizePolicy().hasHeightForWidth()) + self.tableView.setSizePolicy(sizePolicy) + self.tableView.setMaximumSize(QtCore.QSize(32000, 32000)) + self.tableView.setAcceptDrops(True) + self.tableView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.AnyKeyPressed|QtWidgets.QAbstractItemView.EditKeyPressed) + self.tableView.setAlternatingRowColors(True) + self.tableView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.tableView.setSortingEnabled(True) + self.tableView.setObjectName("tableView") + self.tableView.horizontalHeader().setCascadingSectionResizes(True) + self.tableView.horizontalHeader().setStretchLastSection(True) + self.tableView.verticalHeader().setVisible(False) + self.verticalLayout_2.addWidget(self.tableView) + self.verticalLayout_3.addWidget(self.frame) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.previousButton = QtWidgets.QPushButton(self.horizontalLayoutWidget_2) + self.previousButton = QtWidgets.QPushButton(self.centralwidget) font = QtGui.QFont() font.setPointSize(28) self.previousButton.setFont(font) self.previousButton.setObjectName("previousButton") self.horizontalLayout_2.addWidget(self.previousButton) - self.playButton = QtWidgets.QPushButton(self.horizontalLayoutWidget_2) + self.playButton = QtWidgets.QPushButton(self.centralwidget) font = QtGui.QFont() font.setPointSize(28) self.playButton.setFont(font) self.playButton.setObjectName("playButton") self.horizontalLayout_2.addWidget(self.playButton) - self.nextButton = QtWidgets.QPushButton(self.horizontalLayoutWidget_2) + self.nextButton = QtWidgets.QPushButton(self.centralwidget) font = QtGui.QFont() font.setPointSize(28) self.nextButton.setFont(font) self.nextButton.setObjectName("nextButton") self.horizontalLayout_2.addWidget(self.nextButton) + self.verticalLayout_3.addLayout(self.horizontalLayout_2) + self.frame_3 = QtWidgets.QFrame(self.centralwidget) + self.frame_3.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.frame_3.setFrameShadow(QtWidgets.QFrame.Raised) + self.frame_3.setObjectName("frame_3") + self.volumeSlider = QtWidgets.QSlider(self.frame_3) + self.volumeSlider.setGeometry(QtCore.QRect(10, 10, 181, 31)) + self.volumeSlider.setMaximum(100) + self.volumeSlider.setProperty("value", 50) + self.volumeSlider.setOrientation(QtCore.Qt.Horizontal) + self.volumeSlider.setObjectName("volumeSlider") + self.verticalLayout_3.addWidget(self.frame_3) + self.verticalLayout_3.setStretch(0, 3) + self.verticalLayout_3.setStretch(1, 8) + self.verticalLayout_3.setStretch(2, 1) + self.verticalLayout_3.setStretch(3, 1) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 1062, 24)) @@ -183,12 +203,12 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.startTimeLabel.setText(_translate("MainWindow", "00:00")) - self.slashLabel.setText(_translate("MainWindow", "/")) - self.endTimeLabel.setText(_translate("MainWindow", "00:00")) self.artistLabel.setText(_translate("MainWindow", "artist")) self.titleLabel.setText(_translate("MainWindow", "song title")) self.albumLabel.setText(_translate("MainWindow", "album")) + self.startTimeLabel.setText(_translate("MainWindow", "00:00")) + self.slashLabel.setText(_translate("MainWindow", "/")) + self.endTimeLabel.setText(_translate("MainWindow", "00:00")) self.previousButton.setText(_translate("MainWindow", "⏮️")) self.playButton.setText(_translate("MainWindow", "▶️")) self.nextButton.setText(_translate("MainWindow", "⏭️")) diff --git a/ui.ui b/ui.ui index 0e7da19..3b7210a 100644 --- a/ui.ui +++ b/ui.ui @@ -17,309 +17,328 @@ - - - - 0 - 160 - 1061 - 461 - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 0 - 0 - 1061 - 461 - - - - - 0 - 0 - - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - false - - - - - - - 0 - 0 - 161 - 161 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 0 - 0 - 161 - 161 - - - - - - - - 540 - 0 - 521 - 41 - - - - - - - Qt::Horizontal + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 0 + 0 + 161 + 161 + - - - - - - 00:00 + + QFrame::StyledPanel - - - - - - / + + QFrame::Raised + + + + 0 + 0 + 161 + 161 + + + - - - - - 00:00 + + + + 540 + 40 + 521 + 121 + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 0 + 0 + 521 + 121 + + + - - - - - - - 540 - 40 - 521 - 121 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 0 - 0 - 521 - 121 - - - - - - - - 0 - 680 - 1061 - 61 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 10 - 10 - 181 - 31 - - - - 100 - - - 50 - - - Qt::Horizontal - - - - - - - 160 - 0 - 381 - 161 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 0 - 0 - 381 - 161 - - - + + + + 160 + 0 + 381 + 161 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 0 + 0 + 381 + 161 + + + + + + + + 24 + 75 + true + + + + artist + + + + + + + + 18 + + + + song title + + + + + + + + 16 + 50 + true + false + + + + album + + + + + + + + + + 540 + 0 + 521 + 41 + + + + + + + Qt::Horizontal + + + + + + + 00:00 + + + + + + + / + + + + + + + 00:00 + + + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 0 + 0 + 1061 + 461 + + + + + QLayout::SetNoConstraint + + + + + + 1 + 1 + + + + + 32000 + 32000 + + + + true + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + true + + + false + + + + + + + + + - + - 24 - 75 - true + 28 - artist + ⏮️ - + - 18 + 28 - song title + ▶️ - + - 16 - 50 - true - false + 28 - album + ⏭️ - - - - - - 0 - 620 - 1061 - 60 - - - - - - - - 28 - + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 10 + 10 + 181 + 31 + - - ⏮️ + + 100 + + + 50 + + + Qt::Horizontal - - - - - - 28 - - - - ▶️ - - - - - - - - 28 - - - - ⏭️ - - - - - + + + diff --git a/utils/__init__.py b/utils/__init__.py index 67af79a..acb4c5b 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,4 +1,5 @@ +from .get_id3_tags import get_id3_tags from .initialize_library_database import initialize_library_database from .scan_for_music import scan_for_music -from .audio_visualizer import AudioVisualizer -from.fft_analyser import FFTAnalyser +from .fft_analyser import FFTAnalyser +from .add_files_to_library import add_files_to_library \ No newline at end of file diff --git a/utils/add_files_to_library.py b/utils/add_files_to_library.py new file mode 100644 index 0000000..e60948b --- /dev/null +++ b/utils/add_files_to_library.py @@ -0,0 +1,58 @@ +import DBA +from configparser import ConfigParser +from utils import get_id3_tags + +config = ConfigParser() +config.read("config.ini") + + +def add_files_to_library(files): + """Adds audio file(s) to the sqllite db + `files` | list() | List of fully qualified paths to audio file(s) + Returns a count of records added + """ + print(f'utils | adding files to library: {files}') + extensions = config.get('settings', 'extensions').split(',') + insert_data = [] # To store data for batch insert + for file in files: + if any(file.lower().endswith(ext) for ext in extensions): + filename = file.split("/")[-1] + audio = get_id3_tags(file) + # Append data tuple to insert_data list + insert_data.append( + ( + file, + audio.title, + audio.album, + audio.artist, + audio.genre, + filename.split(".")[-1], + audio.year, + audio.bitrate, + ) + ) + + # Check if batch size is reached + 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_data) + insert_data = [] # Reset the insert_data list + + # Insert any remaining data + 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_data) + return True + + +# id int unsigned auto_increment, +# title varchar(255), +# album varchar(255), +# artist varchar(255), +# genre varchar(255), +# codec varchar(15), +# album_date date, +# bitrate int unsigned, +# date_added TIMESTAMP default CURRENT_TIMESTAMP, + +# scan_for_music(config.get('directories', 'library1')) diff --git a/utils/create_waveform_from_file.py b/utils/create_waveform_from_file.py index ad33438..b3065e6 100644 --- a/utils/create_waveform_from_file.py +++ b/utils/create_waveform_from_file.py @@ -5,6 +5,11 @@ from pydub import AudioSegment def create_waveform_from_file(file): + """_summary_ + + Args: + file (_type_): _description_ + """ # Read the MP3 file audio = AudioSegment.from_file(file) # Convert to mono and get frame rate and number of channels @@ -43,7 +48,7 @@ def create_waveform_from_file(file): # Graph goes to ends of pic plt.xlim(0, len(waveform)) # Save pic - plt.savefig('now_playing_waveform.png', dpi=64, bbox_inches='tight', pad_inches=0) + plt.savefig('assets/now_playing_waveform.png', dpi=64, bbox_inches='tight', pad_inches=0) # Show me tho # plt.show() diff --git a/utils/get_id3_tags.py b/utils/get_id3_tags.py new file mode 100644 index 0000000..6037290 --- /dev/null +++ b/utils/get_id3_tags.py @@ -0,0 +1,21 @@ +from mutagen.easyid3 import EasyID3 +from mutagen import File + +def get_id3_tags(file): + """Get the ID3 tags for an audio file + # Parameters + `file` | str | Fully qualified path to file + + # Returns + dict of all id3 tags + """ + try: + audio = EasyID3(file) + return audio + except Exception as e: + print(f"Error: {e}") + return {} + +filepath = '/home/billy/Music/songs/meanings/blah99/H.mp3' +id3_tags = get_id3_tags(filepath) +print(id3_tags) \ No newline at end of file diff --git a/utils/scan_for_music.py b/utils/scan_for_music.py index 36423d9..cdb80b5 100644 --- a/utils/scan_for_music.py +++ b/utils/scan_for_music.py @@ -1,7 +1,7 @@ import os import DBA from configparser import ConfigParser -from tinytag import TinyTag +from utils import get_id3_tags config = ConfigParser() config.read('config.ini') @@ -16,7 +16,7 @@ def scan_for_music(): for filename in filenames: if any(filename.lower().endswith(ext) for ext in extensions): filepath = os.path.join(dirpath, filename) - audio = TinyTag.get(filepath) + audio = get_id3_tags(filepath) # Append data tuple to insert_data list insert_data.append((