diff --git a/app.py b/app.py index 6bee8ec..e6c3e99 100755 --- a/app.py +++ b/app.py @@ -12,6 +12,10 @@ from PyQt5.uic import loadUi from threading import Timer from time import sleep from timeloop import Timeloop +from tinytag import TinyTag + +from main_window_ui import Ui_MainWindow +from dlg_search_database_ui import Ui_Dialog class RepeatedTimer: @@ -20,7 +24,7 @@ class RepeatedTimer: self.interval = interval self.function = function self.args = args - self.kwargs = kwargs + self.kwargs = kwargsdlg_search_database_ui self.is_running = False self.start() @@ -39,23 +43,28 @@ class RepeatedTimer: self._timer.cancel() self.is_running = False - class Track: def __init__(self, path): self.path = path audio = self.get_audio_segment(path) - self.length = int(len(audio)) self.start_gap = self.leading_silence(audio) self.fade_at = self.fade_point(audio) self.silence_at = self.trailing_silence(audio) + tag = TinyTag.get(path) + self.title = tag.title + self.artist = tag.artist + self.length = tag.duration * 1000 + print( f" path={self.path}" f" length={self.length}" f" start_gap={self.start_gap}" f" fade_at={self.fade_at}" - f" silence_at={self.silence_at}" + f" silence_at={sedlg_search_database_uilf.silence_at}" + f" title={self.title}" + f" artist={self.artist}" ) def get_audio_segment(self, path): @@ -74,7 +83,7 @@ class Track: audio_segment - the segment to find silence in silence_threshold - the upper bound for how quiet is silent in dFBS chunk_size - chunk size for interating over the segment in ms - +dlg_search_database_ui https://github.com/jiaaro/pydub/blob/master/pydub/silence.py """ @@ -92,7 +101,7 @@ class Track: """ Returns the millisecond/index of the point where the fade is down to fade_threshold and doesn't get louder again. - audio_segment - the segment to find silence in + audio_segment - the sdlg_search_database_uiegment to find silence in fade_threshold - the upper bound for how quiet is silent in dFBS chunk_size - chunk size for interating over the segment in ms """ @@ -115,20 +124,106 @@ class Track: return self.fade_point(audio_segment, silence_threshold, chunk_size) +ROOT = "/home/kae/music/" + + + +class Window(QMainWindow, Ui_MainWindow): + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(self) + self.connectSignalsSlots() + + self.resize(1599, 981) + + self.playlist.setColumnWidth(0, 0) + self.playlist.setColumnWidth(1, 70) + self.playlist.setColumnWidth(2, 381) + self.playlist.setColumnWidth(3, 322) + self.playlist.setColumnWidth(4, 78) + self.playlist.setColumnWidth(5, 68) + self.playlist.setColumnWidth(6, 47) + self.playlist.setColumnWidth(7, 577) + + def __del__(self): + for column in range(self.playlist.columnCount()): + print(f"Column {column}: {self.playlist.columnWidth(column)}") + print(f"Window height: {self.height()} Window width: {self.width()}") + + def ms_to_mmss(self, ms, decimals=0): + if not ms: + return "-" + if ms < 0: + sign = "-" + else: + sign = "" + + minutes, remainder = divmod(ms, 60 * 1000) + seconds = remainder / 1000 + + return f"{sign}{minutes:.0f}:{seconds:02.{decimals}f}" + + def connectSignalsSlots(self): + self.fileButton.clicked.connect(self.selectFile) + self.databaseButton.clicked.connect(self.selectDatabase) + # import ipdb; ipdb.set_trace() + # self.playlist.columnResized.connect(self.playlistresize) + # self.playlist.horizontalHeader().sectionResized.connect(self.kae) + # import ipdb; ipdb.set_trace() + # self.playlist.viewport().installEventFilter(self) + # self.playlist.horizontalHeader().sectionClicked.connect(self.kae2) + # x = self.playlist.horizontalHeader() + # import ipdb; ipdb.set_trace() + + def selectDatabase(self): + dlg = DbDialog() + dlg.exec() + + def selectFile(self): + dlg = QFileDialog() + dlg.setFileMode(QFileDialog.ExistingFile) + dlg.setViewMode(QFileDialog.Detail) + Gdlg.setDirectory(ROOT) + dlg.setNameFilter("Music files (*.flac *.mp3)") + + if dlg.exec_(): + for fname in dlg.selectedFiles(): + track = Track(fname) + self.add_to_playlist(track) + + def add_to_playlist(self, track): + pl = self.playlist + row = pl.rowCount() + pl.insertRow(row) + item = QTableWidgetItem(track.title) + pl.setItem(row, 1, item) + item = QTableWidgetItem(track.artist) + pl.setItem(row, 2, item) + item = QTableWidgetItem(self.ms_to_mmss(track.length)) + pl.setItem(row, 3, item) + item = QTableWidgetItem(track.path) + pl.setItem(row, 6, item) + +class DbDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.ui = Ui_Dialog() + self.ui.setupUi(self) + import ipdb; ipdb.set_trace() + # self.lineEdit.clicked.connect(self.selectFile) + + + + +if __name__ == "__main__": + app = QApplication(sys.argv) + win = Window() + win.show() + sys.exit(app.exec()) + + # tl = Timeloop() # -#def ms_to_mmss(ms, decimals=0): -# if not ms: -# return "-" -# if ms < 0: -# sign = "-" -# else: -# sign = "" -# -# minutes, remainder = divmod(ms, 60 * 1000) -# seconds = remainder / 1000 -# -# return f"{sign}{minutes:.0f}:{seconds:02.{decimals}f}" # # ## @tl.job(interval=timedelta(seconds=1)) @@ -174,45 +269,6 @@ class Track: # rt.stop() # better in a try/finally block to make sure the program ends! # print("End") - - -ROOT = "/home/kae/music/" - -from main_window_ui import Ui_MainWindow - - -class Window(QMainWindow, Ui_MainWindow): - def __init__(self, parent=None): - super().__init__(parent) - self.setupUi(self) - self.connectSignalsSlots() - - self.resize(1599, 981) - - self.playlist.setColumnWidth(0, 0) - self.playlist.setColumnWidth(1, 381) - self.playlist.setColumnWidth(2, 322) - self.playlist.setColumnWidth(3, 78) - self.playlist.setColumnWidth(4, 68) - self.playlist.setColumnWidth(5, 47) - self.playlist.setColumnWidth(6, 577) - - def __del__(self): - for column in range(self.playlist.columnCount()): - print(f"Column {column}: {self.playlist.columnWidth(column)}") - print(f"Window height: {self.height()} Window width: {self.width()}") - - def connectSignalsSlots(self): - self.pushButton.clicked.connect(self.btnpush) - # import ipdb; ipdb.set_trace() - # self.playlist.columnResized.connect(self.playlistresize) - # self.playlist.horizontalHeader().sectionResized.connect(self.kae) - # import ipdb; ipdb.set_trace() - # self.playlist.viewport().installEventFilter(self) - # self.playlist.horizontalHeader().sectionClicked.connect(self.kae2) - # x = self.playlist.horizontalHeader() - # import ipdb; ipdb.set_trace() - #def kae2(self, index): # print(f"table header click, index={index}") @@ -244,20 +300,3 @@ class Window(QMainWindow, Ui_MainWindow): # # regardless, just do the default # return super().eventFilter(obj, event) - def btnpush(self): - dlg = QFileDialog() - dlg.setFileMode(QFileDialog.ExistingFile) - dlg.setViewMode(QFileDialog.Detail) - dlg.setDirectory(ROOT) - dlg.setNameFilter("Music files (*.flac *.mp3)") - - if dlg.exec_(): - for fname in dlg.selectedFiles(): - track = Track(fname) - - -if __name__ == "__main__": - app = QApplication(sys.argv) - win = Window() - win.show() - sys.exit(app.exec()) diff --git a/app.py.reference b/app.py.reference new file mode 100644 index 0000000..79edb06 --- /dev/null +++ b/app.py.reference @@ -0,0 +1,45 @@ +import sys + +from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow, QMessageBox +from PyQt5.uic import loadUi + +from main_window_ui import Ui_MainWindow + + +class Window(QMainWindow, Ui_MainWindow): + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(self) + self.connectSignalsSlots() + + def connectSignalsSlots(self): + self.action_Exit.triggered.connect(self.close) + self.action_Find_Replace.triggered.connect(self.findAndReplace) + self.action_About.triggered.connect(self.about) + + def findAndReplace(self): + dialog = FindReplaceDialog(self) + dialog.exec() + + def about(self): + QMessageBox.about( + self, + "About Sample Editor", + "
A sample text editor app built with:
" + "- PyQt
" + "- Qt Designer
" + "- Python
", + ) + + +class FindReplaceDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + loadUi("ui/find_replace.ui", self) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + win = Window() + win.show() + sys.exit(app.exec()) diff --git a/audactious-control.sh b/audactious-control.sh new file mode 100755 index 0000000..b7e868e --- /dev/null +++ b/audactious-control.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +a="/usr/bin/audtool" + +# Advance playlist when track stops +while :; do + $a playback-stopped + if [ $? -eq 0 ]; then + break + fi + sleep 1 +done +$a playlist-advance +exit 0 + diff --git a/dlg_search_database_ui.py b/dlg_search_database_ui.py new file mode 100644 index 0000000..29bd471 --- /dev/null +++ b/dlg_search_database_ui.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/dlg_SearchDatabase.ui' +# +# Created by: PyQt5 UI code generator 5.11.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_Dialog(object): + def setupUi(self, Dialog): + Dialog.setObjectName("Dialog") + Dialog.resize(383, 272) + self.widget = QtWidgets.QWidget(Dialog) + self.widget.setGeometry(QtCore.QRect(10, 20, 361, 242)) + self.widget.setObjectName("widget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.widget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setObjectName("gridLayout") + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.lineEdit = QtWidgets.QLineEdit(self.widget) + self.lineEdit.setObjectName("lineEdit") + self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1) + self.verticalLayout.addLayout(self.gridLayout) + self.listWidget = QtWidgets.QListWidget(self.widget) + self.listWidget.setObjectName("listWidget") + self.verticalLayout.addWidget(self.listWidget) + self.buttonBox = QtWidgets.QDialogButtonBox(self.widget) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(Dialog) + self.buttonBox.accepted.connect(Dialog.accept) + self.buttonBox.rejected.connect(Dialog.reject) + QtCore.QMetaObject.connectSlotsByName(Dialog) + + def retranslateUi(self, Dialog): + _translate = QtCore.QCoreApplication.translate + Dialog.setWindowTitle(_translate("Dialog", "Dialog")) + self.label.setText(_translate("Dialog", "Title:")) + diff --git a/main_window_ui.py b/main_window_ui.py new file mode 100644 index 0000000..68b5ed1 --- /dev/null +++ b/main_window_ui.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/main_window.ui' +# +# Created by: PyQt5 UI code generator 5.11.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(768, 600) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout.setObjectName("verticalLayout") + self.gridLayout_5 = QtWidgets.QGridLayout() + self.gridLayout_5.setHorizontalSpacing(10) + self.gridLayout_5.setObjectName("gridLayout_5") + self.start_box = QtWidgets.QGroupBox(self.centralwidget) + self.start_box.setObjectName("start_box") + self.gridLayout = QtWidgets.QGridLayout(self.start_box) + self.gridLayout.setObjectName("gridLayout") + self.label_elapsed_timer = QtWidgets.QLabel(self.start_box) + font = QtGui.QFont() + font.setFamily("Sans") + font.setPointSize(40) + font.setBold(True) + font.setWeight(75) + self.label_elapsed_timer.setFont(font) + self.label_elapsed_timer.setObjectName("label_elapsed_timer") + self.gridLayout.addWidget(self.label_elapsed_timer, 0, 0, 1, 1) + self.label_start_tod = QtWidgets.QLabel(self.start_box) + font = QtGui.QFont() + font.setFamily("DejaVu Sans") + font.setPointSize(16) + self.label_start_tod.setFont(font) + self.label_start_tod.setScaledContents(False) + self.label_start_tod.setObjectName("label_start_tod") + self.gridLayout.addWidget(self.label_start_tod, 1, 0, 1, 1) + self.gridLayout_5.addWidget(self.start_box, 0, 0, 1, 1) + self.fade_box = QtWidgets.QGroupBox(self.centralwidget) + self.fade_box.setObjectName("fade_box") + self.gridLayout_2 = QtWidgets.QGridLayout(self.fade_box) + self.gridLayout_2.setObjectName("gridLayout_2") + self.label_fade_timer = QtWidgets.QLabel(self.fade_box) + font = QtGui.QFont() + font.setFamily("Sans") + font.setPointSize(40) + font.setBold(True) + font.setWeight(75) + self.label_fade_timer.setFont(font) + self.label_fade_timer.setObjectName("label_fade_timer") + self.gridLayout_2.addWidget(self.label_fade_timer, 0, 0, 1, 1) + self.label_fade_tod = QtWidgets.QLabel(self.fade_box) + font = QtGui.QFont() + font.setFamily("DejaVu Sans") + font.setPointSize(16) + self.label_fade_tod.setFont(font) + self.label_fade_tod.setScaledContents(False) + self.label_fade_tod.setObjectName("label_fade_tod") + self.gridLayout_2.addWidget(self.label_fade_tod, 1, 0, 1, 1) + self.gridLayout_5.addWidget(self.fade_box, 0, 1, 1, 1) + self.silent_box = QtWidgets.QGroupBox(self.centralwidget) + self.silent_box.setStyleSheet("background-color: rgb(252, 233, 79);") + self.silent_box.setObjectName("silent_box") + self.gridLayout_3 = QtWidgets.QGridLayout(self.silent_box) + self.gridLayout_3.setObjectName("gridLayout_3") + self.label_silent_timer = QtWidgets.QLabel(self.silent_box) + font = QtGui.QFont() + font.setFamily("Sans") + font.setPointSize(40) + font.setBold(True) + font.setWeight(75) + self.label_silent_timer.setFont(font) + self.label_silent_timer.setObjectName("label_silent_timer") + self.gridLayout_3.addWidget(self.label_silent_timer, 0, 0, 1, 1) + self.label_silent_tod = QtWidgets.QLabel(self.silent_box) + font = QtGui.QFont() + font.setFamily("DejaVu Sans") + font.setPointSize(16) + self.label_silent_tod.setFont(font) + self.label_silent_tod.setScaledContents(False) + self.label_silent_tod.setObjectName("label_silent_tod") + self.gridLayout_3.addWidget(self.label_silent_tod, 1, 0, 1, 1) + self.gridLayout_5.addWidget(self.silent_box, 0, 2, 1, 1) + self.end_box = QtWidgets.QGroupBox(self.centralwidget) + self.end_box.setObjectName("end_box") + self.gridLayout_4 = QtWidgets.QGridLayout(self.end_box) + self.gridLayout_4.setObjectName("gridLayout_4") + self.label_end_timer = QtWidgets.QLabel(self.end_box) + font = QtGui.QFont() + font.setFamily("Sans") + font.setPointSize(40) + font.setBold(True) + font.setWeight(75) + self.label_end_timer.setFont(font) + self.label_end_timer.setObjectName("label_end_timer") + self.gridLayout_4.addWidget(self.label_end_timer, 0, 0, 1, 1) + self.label_end_tod = QtWidgets.QLabel(self.end_box) + font = QtGui.QFont() + font.setFamily("DejaVu Sans") + font.setPointSize(16) + self.label_end_tod.setFont(font) + self.label_end_tod.setScaledContents(False) + self.label_end_tod.setObjectName("label_end_tod") + self.gridLayout_4.addWidget(self.label_end_tod, 1, 0, 1, 1) + self.gridLayout_5.addWidget(self.end_box, 0, 3, 1, 1) + self.verticalLayout.addLayout(self.gridLayout_5) + self.current_track = QtWidgets.QLabel(self.centralwidget) + font = QtGui.QFont() + font.setFamily("Sans") + font.setPointSize(20) + self.current_track.setFont(font) + self.current_track.setStyleSheet("background-color: rgb(138, 226, 52);\n" +"border: 1px solid rgb(85, 87, 83);") + self.current_track.setObjectName("current_track") + self.verticalLayout.addWidget(self.current_track) + self.playlist = QtWidgets.QTableWidget(self.centralwidget) + self.playlist.setAlternatingRowColors(True) + self.playlist.setSelectionMode(QtWidgets.QAbstractItemView.ContiguousSelection) + self.playlist.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.playlist.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) + self.playlist.setRowCount(0) + self.playlist.setColumnCount(8) + self.playlist.setObjectName("playlist") + item = QtWidgets.QTableWidgetItem() + self.playlist.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.playlist.setHorizontalHeaderItem(1, item) + item = QtWidgets.QTableWidgetItem() + self.playlist.setHorizontalHeaderItem(2, item) + item = QtWidgets.QTableWidgetItem() + self.playlist.setHorizontalHeaderItem(3, item) + item = QtWidgets.QTableWidgetItem() + self.playlist.setHorizontalHeaderItem(4, item) + item = QtWidgets.QTableWidgetItem() + self.playlist.setHorizontalHeaderItem(5, item) + item = QtWidgets.QTableWidgetItem() + self.playlist.setHorizontalHeaderItem(6, item) + item = QtWidgets.QTableWidgetItem() + self.playlist.setHorizontalHeaderItem(7, item) + self.verticalLayout.addWidget(self.playlist) + self.fileButton = QtWidgets.QPushButton(self.centralwidget) + self.fileButton.setObjectName("fileButton") + self.verticalLayout.addWidget(self.fileButton) + self.databaseButton = QtWidgets.QPushButton(self.centralwidget) + self.databaseButton.setObjectName("databaseButton") + self.verticalLayout.addWidget(self.databaseButton) + self.current_time = QtWidgets.QLabel(self.centralwidget) + font = QtGui.QFont() + font.setFamily("DejaVu Sans") + font.setPointSize(25) + font.setBold(True) + font.setWeight(75) + self.current_time.setFont(font) + self.current_time.setObjectName("current_time") + self.verticalLayout.addWidget(self.current_time) + self.playlist.raise_() + self.current_time.raise_() + self.fileButton.raise_() + self.current_track.raise_() + self.databaseButton.raise_() + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 768, 18)) + self.menubar.setObjectName("menubar") + self.menuFile = QtWidgets.QMenu(self.menubar) + self.menuFile.setObjectName("menuFile") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setStyleSheet("background-color: rgb(211, 215, 207);") + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + self.menubar.addAction(self.menuFile.menuAction()) + + self.retranslateUi(MainWindow) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.start_box.setTitle(_translate("MainWindow", "Elapsed")) + self.label_elapsed_timer.setText(_translate("MainWindow", "2:46")) + self.label_start_tod.setText(_translate("MainWindow", "10:17:37")) + self.fade_box.setTitle(_translate("MainWindow", "Fade at")) + self.label_fade_timer.setText(_translate("MainWindow", "0:53")) + self.label_fade_tod.setText(_translate("MainWindow", "10:21:23")) + self.silent_box.setTitle(_translate("MainWindow", "Silent at")) + self.label_silent_timer.setText(_translate("MainWindow", "0:58")) + self.label_silent_tod.setText(_translate("MainWindow", "10:21:28")) + self.end_box.setTitle(_translate("MainWindow", "End at")) + self.label_end_timer.setText(_translate("MainWindow", "1:00")) + self.label_end_tod.setText(_translate("MainWindow", "10:21:30")) + self.current_track.setText(_translate("MainWindow", "After the goldrush - Neil Young [3:46]")) + item = self.playlist.horizontalHeaderItem(0) + item.setText(_translate("MainWindow", "Index")) + item = self.playlist.horizontalHeaderItem(1) + item.setText(_translate("MainWindow", "Skip silence")) + item = self.playlist.horizontalHeaderItem(2) + item.setText(_translate("MainWindow", "Title")) + item = self.playlist.horizontalHeaderItem(3) + item.setText(_translate("MainWindow", "Artist")) + item = self.playlist.horizontalHeaderItem(4) + item.setText(_translate("MainWindow", "Duration")) + item = self.playlist.horizontalHeaderItem(5) + item.setText(_translate("MainWindow", "End time")) + item = self.playlist.horizontalHeaderItem(6) + item.setText(_translate("MainWindow", "Autoplay next")) + item = self.playlist.horizontalHeaderItem(7) + item.setText(_translate("MainWindow", "Path")) + self.fileButton.setText(_translate("MainWindow", "Select file")) + self.databaseButton.setText(_translate("MainWindow", "Database")) + self.current_time.setText(_translate("MainWindow", "10:20:30")) + self.menuFile.setTitle(_translate("MainWindow", "Fi&le")) + diff --git a/notes.otl b/notes.otl new file mode 100644 index 0000000..2e18900 --- /dev/null +++ b/notes.otl @@ -0,0 +1,17 @@ +Playlist columns + Silence skip + Title + Artist + Duration to silence + End time + Autoplay next + Path +Names (app and db) + Beacon + Music Muster + Music Tap + Oryx + Accord + PlayStack +Status bar + Total remaining playlist time diff --git a/songdb.py b/songdb.py index e28f52e..6cd5198 100755 --- a/songdb.py +++ b/songdb.py @@ -105,7 +105,7 @@ def update_db(session): tag = TinyTag.get(path) track.title = tag.title track.artist = tag.artist - track.length = tag.duration * 1000 + track.length = int(tag.duration * 1000) track.mtime = os.path.getmtime(path) elif ext not in [".jpg"]: print(f"Unrecognised file type: {path}") diff --git a/sys b/sys new file mode 100644 index 0000000..e69de29 diff --git a/ui/dlg_SearchDatabase.ui b/ui/dlg_SearchDatabase.ui new file mode 100644 index 0000000..d303eec --- /dev/null +++ b/ui/dlg_SearchDatabase.ui @@ -0,0 +1,91 @@ + +