layouts, mutagen id3 tags, table drag & drop, double click to play

This commit is contained in:
billypom on debian 2024-01-28 09:59:51 -05:00
parent b85cd47844
commit 76c1aa554b
13 changed files with 552 additions and 353 deletions

View File

@ -1,10 +1,17 @@
from PyQt5 import QtWidgets, QtGui, QtCore from PyQt5 import QtWidgets
import numpy as np import numpy as np
from .fft_analyser import FFTAnalyser from utils import FFTAnalyser
class AudioVisualizer(QtWidgets.QWidget): class AudioVisualizer(QtWidgets.QWidget):
"""_Audio Visualizer component_
Args:
QtWidgets (_type_): _description_
Returns:
_type_: _description_
"""
def __init__(self, media_player): def __init__(self, media_player):
super().__init__() super().__init__()
self.media_player = media_player self.media_player = media_player

View File

@ -1,16 +1,17 @@
import DBA import DBA
from PyQt5.QtGui import QStandardItem, QStandardItemModel from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import QTableView, QApplication from PyQt5.QtWidgets import QTableView
from PyQt5.QtCore import QTimer from PyQt5.QtCore import QTimer
from tinytag import TinyTag from tinytag import TinyTag
from utils import add_files_to_library
from utils import get_id3_tags
import logging
class MusicTable(QTableView): class MusicTable(QTableView):
def __init__(self, parent=None, qapp=None): def __init__(self, parent=None, qapp=None):
QTableView.__init__(self, parent) QTableView.__init__(self, parent)
self.headers = ['title', 'artist', 'album', 'genre', 'codec', 'year', 'path'] self.headers = ['title', 'artist', 'album', 'genre', 'codec', 'year', 'path']
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(self.headers)
self.songChanged = None self.songChanged = None
self.selected_song_filepath = None self.selected_song_filepath = None
self.current_song_filepath = None self.current_song_filepath = None
@ -56,6 +57,9 @@ class MusicTable(QTableView):
"""Sets the filepath of the currently selected song""" """Sets the filepath of the currently selected song"""
self.selected_song_filepath = self.currentIndex().siblingAtColumn(self.headers.index('path')).data() self.selected_song_filepath = self.currentIndex().siblingAtColumn(self.headers.index('path')).data()
print(f'Selected song: {self.selected_song_filepath}') 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): def set_current_song_filepath(self):
"""Sets the filepath of the currently playing/chosen song""" """Sets the filepath of the currently playing/chosen song"""
@ -80,6 +84,9 @@ class MusicTable(QTableView):
def fetch_library(self): def fetch_library(self):
"""Initialize the tableview model"""
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(self.headers)
# Fetch library data # Fetch library data
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
data = db.query('SELECT title, artist, album, genre, codec, album_date, filepath FROM library;', ()) 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) self.model.appendRow(items)
# Set the model to the tableView (we are the tableview) # Set the model to the tableView (we are the tableview)
self.setModel(self.model) 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): def load_qapp(self, qapp):
self.qapp = qapp self.qapp = qapp

View File

@ -1 +1,2 @@
from .MusicTable import MusicTable from .MusicTable import MusicTable
from .AudioVisualizer import AudioVisualizer

52
main.py
View File

@ -2,15 +2,16 @@ import DBA
from ui import Ui_MainWindow from ui import Ui_MainWindow
from PyQt5.QtWidgets import QMainWindow, QApplication from PyQt5.QtWidgets import QMainWindow, QApplication
import qdarktheme 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.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from utils import scan_for_music from utils import scan_for_music
from utils import initialize_library_database from utils import initialize_library_database
from utils import AudioVisualizer from components import AudioVisualizer
from pyqtgraph import mkBrush 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): class ApplicationWindow(QMainWindow, Ui_MainWindow):
def __init__(self, qapp): def __init__(self, qapp):
@ -23,6 +24,8 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
self.qapp = qapp self.qapp = qapp
print(f'ApplicationWindow self.qapp: {self.qapp}') print(f'ApplicationWindow self.qapp: {self.qapp}')
self.tableView.load_qapp(self.qapp) self.tableView.load_qapp(self.qapp)
self.config = configparser.ConfigParser()
self.config.read('config.ini')
global stopped global stopped
stopped = False stopped = False
@ -64,12 +67,51 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# self.pauseButton.clicked.connect(self.on_pause_clicked) # self.pauseButton.clicked.connect(self.on_pause_clicked)
self.previousButton.clicked.connect(self.on_previous_clicked) # Click to previous song 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.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.actionPreferences.triggered.connect(self.actionPreferencesClicked) # Open preferences menu
self.actionScanLibraries.triggered.connect(self.scan_libraries) # Scan library self.actionScanLibraries.triggered.connect(self.scan_libraries) # Scan library
self.actionClearDatabase.triggered.connect(initialize_library_database) # Clear database 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.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): def play_audio_file(self):
"""Start playback of selected track & moves playback slider""" """Start playback of selected track & moves playback slider"""

View File

@ -1,4 +1,4 @@
tinytag mutagen
matplotlib matplotlib
pyqt5 pyqt5
pydub pydub

View File

@ -10,3 +10,7 @@ reorganize_destination = /where/to/reorganize/to
[settings] [settings]
# Which file types are scanned # Which file types are scanned
extensions = mp3,wav,ogg,flac extensions = mp3,wav,ogg,flac
[table]
# Music table column widths
column_widths = 181,116,222,76,74,72,287

158
ui.py
View File

@ -18,32 +18,13 @@ class Ui_MainWindow(object):
MainWindow.setStatusTip("") MainWindow.setStatusTip("")
self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget") self.centralwidget.setObjectName("centralwidget")
self.frame = QtWidgets.QFrame(self.centralwidget) self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget)
self.frame.setGeometry(QtCore.QRect(0, 160, 1061, 461)) self.verticalLayout_3.setObjectName("verticalLayout_3")
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) self.frame_6 = QtWidgets.QFrame(self.centralwidget)
sizePolicy.setHorizontalStretch(0) self.frame_6.setFrameShape(QtWidgets.QFrame.StyledPanel)
sizePolicy.setVerticalStretch(0) self.frame_6.setFrameShadow(QtWidgets.QFrame.Raised)
sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) self.frame_6.setObjectName("frame_6")
self.frame.setSizePolicy(sizePolicy) self.frame_2 = QtWidgets.QFrame(self.frame_6)
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.frame_2.setGeometry(QtCore.QRect(0, 0, 161, 161)) self.frame_2.setGeometry(QtCore.QRect(0, 0, 161, 161))
self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
@ -51,26 +32,7 @@ class Ui_MainWindow(object):
self.albumGraphicsView = QtWidgets.QGraphicsView(self.frame_2) self.albumGraphicsView = QtWidgets.QGraphicsView(self.frame_2)
self.albumGraphicsView.setGeometry(QtCore.QRect(0, 0, 161, 161)) self.albumGraphicsView.setGeometry(QtCore.QRect(0, 0, 161, 161))
self.albumGraphicsView.setObjectName("albumGraphicsView") self.albumGraphicsView.setObjectName("albumGraphicsView")
self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget) self.frame_4 = QtWidgets.QFrame(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.frame_4 = QtWidgets.QFrame(self.centralwidget)
self.frame_4.setGeometry(QtCore.QRect(540, 40, 521, 121)) self.frame_4.setGeometry(QtCore.QRect(540, 40, 521, 121))
self.frame_4.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_4.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame_4.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_4.setFrameShadow(QtWidgets.QFrame.Raised)
@ -78,18 +40,7 @@ class Ui_MainWindow(object):
self.PlotWidget = PlotWidget(self.frame_4) self.PlotWidget = PlotWidget(self.frame_4)
self.PlotWidget.setGeometry(QtCore.QRect(0, 0, 521, 121)) self.PlotWidget.setGeometry(QtCore.QRect(0, 0, 521, 121))
self.PlotWidget.setObjectName("PlotWidget") self.PlotWidget.setObjectName("PlotWidget")
self.frame_3 = QtWidgets.QFrame(self.centralwidget) self.frame_5 = QtWidgets.QFrame(self.frame_6)
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.setGeometry(QtCore.QRect(160, 0, 381, 161)) self.frame_5.setGeometry(QtCore.QRect(160, 0, 381, 161))
self.frame_5.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_5.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame_5.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_5.setFrameShadow(QtWidgets.QFrame.Raised)
@ -123,30 +74,99 @@ class Ui_MainWindow(object):
self.albumLabel.setFont(font) self.albumLabel.setFont(font)
self.albumLabel.setObjectName("albumLabel") self.albumLabel.setObjectName("albumLabel")
self.verticalLayout.addWidget(self.albumLabel) self.verticalLayout.addWidget(self.albumLabel)
self.horizontalLayoutWidget_2 = QtWidgets.QWidget(self.centralwidget) self.horizontalLayoutWidget = QtWidgets.QWidget(self.frame_6)
self.horizontalLayoutWidget_2.setGeometry(QtCore.QRect(0, 620, 1061, 60)) self.horizontalLayoutWidget.setGeometry(QtCore.QRect(540, 0, 521, 41))
self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2") self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget_2) self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) 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.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.previousButton = QtWidgets.QPushButton(self.horizontalLayoutWidget_2) self.previousButton = QtWidgets.QPushButton(self.centralwidget)
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(28) font.setPointSize(28)
self.previousButton.setFont(font) self.previousButton.setFont(font)
self.previousButton.setObjectName("previousButton") self.previousButton.setObjectName("previousButton")
self.horizontalLayout_2.addWidget(self.previousButton) self.horizontalLayout_2.addWidget(self.previousButton)
self.playButton = QtWidgets.QPushButton(self.horizontalLayoutWidget_2) self.playButton = QtWidgets.QPushButton(self.centralwidget)
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(28) font.setPointSize(28)
self.playButton.setFont(font) self.playButton.setFont(font)
self.playButton.setObjectName("playButton") self.playButton.setObjectName("playButton")
self.horizontalLayout_2.addWidget(self.playButton) self.horizontalLayout_2.addWidget(self.playButton)
self.nextButton = QtWidgets.QPushButton(self.horizontalLayoutWidget_2) self.nextButton = QtWidgets.QPushButton(self.centralwidget)
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(28) font.setPointSize(28)
self.nextButton.setFont(font) self.nextButton.setFont(font)
self.nextButton.setObjectName("nextButton") self.nextButton.setObjectName("nextButton")
self.horizontalLayout_2.addWidget(self.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) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1062, 24)) self.menubar.setGeometry(QtCore.QRect(0, 0, 1062, 24))
@ -183,12 +203,12 @@ class Ui_MainWindow(object):
def retranslateUi(self, MainWindow): def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) 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.artistLabel.setText(_translate("MainWindow", "artist"))
self.titleLabel.setText(_translate("MainWindow", "song title")) self.titleLabel.setText(_translate("MainWindow", "song title"))
self.albumLabel.setText(_translate("MainWindow", "album")) 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.previousButton.setText(_translate("MainWindow", "⏮️"))
self.playButton.setText(_translate("MainWindow", "▶️")) self.playButton.setText(_translate("MainWindow", "▶️"))
self.nextButton.setText(_translate("MainWindow", "⏭️")) self.nextButton.setText(_translate("MainWindow", "⏭️"))

281
ui.ui
View File

@ -17,65 +17,15 @@
<string/> <string/>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<widget class="QFrame" name="frame"> <layout class="QVBoxLayout" name="verticalLayout_3" stretch="3,8,1,1">
<property name="geometry"> <item>
<rect> <widget class="QFrame" name="frame_6">
<x>0</x>
<y>160</y>
<width>1061</width>
<height>461</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::StyledPanel</enum> <enum>QFrame::StyledPanel</enum>
</property> </property>
<property name="frameShadow"> <property name="frameShadow">
<enum>QFrame::Raised</enum> <enum>QFrame::Raised</enum>
</property> </property>
<widget class="MusicTable" name="tableView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1061</width>
<height>461</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</widget>
<widget class="QFrame" name="frame_2"> <widget class="QFrame" name="frame_2">
<property name="geometry"> <property name="geometry">
<rect> <rect>
@ -102,46 +52,6 @@
</property> </property>
</widget> </widget>
</widget> </widget>
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>540</x>
<y>0</y>
<width>521</width>
<height>41</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSlider" name="playbackSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="startTimeLabel">
<property name="text">
<string>00:00</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="slashLabel">
<property name="text">
<string>/</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="endTimeLabel">
<property name="text">
<string>00:00</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="frame_4"> <widget class="QFrame" name="frame_4">
<property name="geometry"> <property name="geometry">
<rect> <rect>
@ -168,41 +78,6 @@
</property> </property>
</widget> </widget>
</widget> </widget>
<widget class="QFrame" name="frame_3">
<property name="geometry">
<rect>
<x>0</x>
<y>680</y>
<width>1061</width>
<height>61</height>
</rect>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<widget class="QSlider" name="volumeSlider">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>181</width>
<height>31</height>
</rect>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</widget>
<widget class="QFrame" name="frame_5"> <widget class="QFrame" name="frame_5">
<property name="geometry"> <property name="geometry">
<rect> <rect>
@ -272,15 +147,129 @@
</layout> </layout>
</widget> </widget>
</widget> </widget>
<widget class="QWidget" name="horizontalLayoutWidget_2"> <widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>540</x>
<y>0</y>
<width>521</width>
<height>41</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSlider" name="playbackSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="startTimeLabel">
<property name="text">
<string>00:00</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="slashLabel">
<property name="text">
<string>/</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="endTimeLabel">
<property name="text">
<string>00:00</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget_2">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>620</y> <y>0</y>
<width>1061</width> <width>1061</width>
<height>60</height> <height>461</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<item>
<widget class="MusicTable" name="tableView">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>32000</width>
<height>32000</height>
</size>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item>
<widget class="QPushButton" name="previousButton"> <widget class="QPushButton" name="previousButton">
@ -319,8 +308,38 @@
</widget> </widget>
</item> </item>
</layout> </layout>
</item>
<item>
<widget class="QFrame" name="frame_3">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<widget class="QSlider" name="volumeSlider">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>181</width>
<height>31</height>
</rect>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget> </widget>
</widget> </widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar"> <widget class="QMenuBar" name="menubar">
<property name="geometry"> <property name="geometry">
<rect> <rect>

View File

@ -1,4 +1,5 @@
from .get_id3_tags import get_id3_tags
from .initialize_library_database import initialize_library_database from .initialize_library_database import initialize_library_database
from .scan_for_music import scan_for_music 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

View File

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

View File

@ -5,6 +5,11 @@ from pydub import AudioSegment
def create_waveform_from_file(file): def create_waveform_from_file(file):
"""_summary_
Args:
file (_type_): _description_
"""
# Read the MP3 file # Read the MP3 file
audio = AudioSegment.from_file(file) audio = AudioSegment.from_file(file)
# Convert to mono and get frame rate and number of channels # 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 # Graph goes to ends of pic
plt.xlim(0, len(waveform)) plt.xlim(0, len(waveform))
# Save pic # 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 # Show me tho
# plt.show() # plt.show()

21
utils/get_id3_tags.py Normal file
View File

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

View File

@ -1,7 +1,7 @@
import os import os
import DBA import DBA
from configparser import ConfigParser from configparser import ConfigParser
from tinytag import TinyTag from utils import get_id3_tags
config = ConfigParser() config = ConfigParser()
config.read('config.ini') config.read('config.ini')
@ -16,7 +16,7 @@ def scan_for_music():
for filename in filenames: for filename in filenames:
if any(filename.lower().endswith(ext) for ext in extensions): if any(filename.lower().endswith(ext) for ext in extensions):
filepath = os.path.join(dirpath, filename) filepath = os.path.join(dirpath, filename)
audio = TinyTag.get(filepath) audio = get_id3_tags(filepath)
# Append data tuple to insert_data list # Append data tuple to insert_data list
insert_data.append(( insert_data.append((