Compare commits

...

3 Commits

Author SHA1 Message Date
Keith Edmunds
d9ccaf7caa Allow in-playist editing of title, artist and notes
Fixes #27 #23
2021-08-22 13:52:22 +01:00
Keith Edmunds
d767c879c6 Improve track info dialog box 2021-08-22 13:02:03 +01:00
Keith Edmunds
0caf48919c Implement database search by artist
Fixes #31
2021-08-22 09:53:54 +01:00
6 changed files with 189 additions and 32 deletions

View File

@ -574,6 +574,14 @@ class Tracks(Base):
return q.all()
@staticmethod
def search_artists(session, text):
return (
session.query(Tracks)
.filter(Tracks.artist.ilike(f"%{text}%"))
.order_by(Tracks.title)
).all()
@staticmethod
def search_titles(session, text):
return (
@ -590,5 +598,17 @@ class Tracks(Base):
def update_lastplayed(self):
self.lastplayed = datetime.now()
@staticmethod
def update_artist(session, track_id, artist):
track = session.query(Tracks).filter(Tracks.id == track_id).one()
track.artist = artist
session.commit()
@staticmethod
def update_title(session, track_id, title):
track = session.query(Tracks).filter(Tracks.id == track_id).one()
track.title = title
session.commit()
def update_path(self, newpath):
self.path = newpath

View File

@ -744,12 +744,13 @@ class DbDialog(QDialog):
self.session = session
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.ui.searchString.textEdited.connect(self.chars_typed)
self.ui.matchList.itemDoubleClicked.connect(self.double_click)
self.ui.btnAdd.clicked.connect(self.add_selected)
self.ui.btnAddClose.clicked.connect(self.add_selected_and_close)
self.ui.btnClose.clicked.connect(self.close)
self.ui.matchList.itemDoubleClicked.connect(self.double_click)
self.ui.matchList.itemSelectionChanged.connect(self.selection_changed)
self.ui.radioTitle.toggled.connect(self.radio_toggle)
self.ui.searchString.textEdited.connect(self.chars_typed)
record = Settings.get_int(self.session, "dbdialog_width")
width = record.f_int or 800
@ -778,9 +779,21 @@ class DbDialog(QDialog):
self.add_selected()
self.close()
def radio_toggle(self):
"""
Handle switching between searching for artists and searching for
titles
"""
# Logic is handled already in chars_typed(), so just call that.
self.chars_typed(self.ui.searchString.text())
def chars_typed(self, s):
if len(s) > 0:
matches = Tracks.search_titles(self.session, s)
if self.ui.radioTitle.isChecked():
matches = Tracks.search_titles(self.session, s)
else:
matches = Tracks.search_artists(self.session, s)
self.ui.matchList.clear()
if matches:
for track in matches:

View File

@ -22,10 +22,14 @@ from log import DEBUG, ERROR
from model import (
Notes, Playdates, Playlists, PlaylistTracks, Session, Settings, Tracks
)
from songdb import create_track_from_file
from songdb import create_track_from_file, update_meta
class PlaylistTab(QTableWidget):
cellEditingStarted = QtCore.pyqtSignal(int, int)
cellEditingEnded = QtCore.pyqtSignal()
# Column names
COL_INDEX = 0
COL_MSS = 1
@ -93,6 +97,12 @@ class PlaylistTab(QTableWidget):
self.itemSelectionChanged.connect(self._select_event)
self.editing_cell = False
self.cellChanged.connect(self._cell_changed)
self.doubleClicked.connect(self._edit_cell)
self.cellEditingStarted.connect(self._cell_edit_started)
self.cellEditingEnded.connect(self._cell_edit_ended)
self.current_track_start_time = None
self.played_tracks = []
@ -142,6 +152,16 @@ class PlaylistTab(QTableWidget):
self._save_playlist(session)
self._repaint()
def edit(self, index, trigger, event):
result = super(PlaylistTab, self).edit(index, trigger, event)
if result:
self.cellEditingStarted.emit(index.row(), index.column())
return result
def closeEditor(self, editor, hint):
super(PlaylistTab, self).closeEditor(editor, hint)
self.cellEditingEnded.emit()
def eventFilter(self, source, event):
"Used to process context (right-click) menu"
@ -571,6 +591,39 @@ class PlaylistTab(QTableWidget):
cb.clear(mode=cb.Clipboard)
cb.setText(path, mode=cb.Clipboard)
def _cell_changed(self, row, column):
"Called when cell content has changed"
if not self.editing_cell:
return
new = self.item(row, column).text()
DEBUG(f"_cell_changed({row=}, {column=}, {new=}")
row_id = self._get_row_id(row)
with Session() as session:
if row in self._meta_get_notes():
Notes.update_note(session, row_id, row, new)
else:
track = Tracks.get_track(session, row_id)
if column == self.COL_ARTIST:
update_meta(session, track, artist=new)
elif column == self.COL_TITLE:
update_meta(session, track, title=new)
else:
ERROR("_cell_changed(): unrecognised column")
def _cell_edit_started(self, row, column):
DEBUG(f"_cell_edit_started({row=}, {column=})")
self.editing_cell = True
self.master_process.disable_play_next_controls()
def _cell_edit_ended(self):
DEBUG("_cell_edit_ended()")
self.editing_cell = False
self.master_process.enable_play_next_controls()
def _delete_row(self, row):
"Delete row"
@ -655,8 +708,12 @@ class PlaylistTab(QTableWidget):
txt = (
f"Title: {track.title}\n"
f"Artist: {track.artist}\n"
f"Track ID: {track.id}\n"
f"Track duration: {helpers.ms_to_mmss(track.duration)}\n"
f"Track fade at: {helpers.ms_to_mmss(track.fade_at)}\n"
f"Track silence at: {helpers.ms_to_mmss(track.silence_at)}"
"\n\n"
f"Path: {track.path}\n"
f"Track ID: {track.id}"
)
info = QMessageBox(self)
info.setIcon(QMessageBox.Information)
@ -678,6 +735,16 @@ class PlaylistTab(QTableWidget):
and pos.y() >= rect.center().y() # noqa W503
)
def _edit_cell(self, mi):
"Called when table is double-clicked"
row = mi.row()
column = mi.column()
item = self.item(row, column)
if column in [self.COL_TITLE, self.COL_ARTIST]:
self.editItem(item)
def _find_next_track_row(self):
"""
Find next track to play.

View File

@ -349,5 +349,36 @@ def update_db(session):
Tracks.remove_path(session, path)
def update_meta(session, track, artist=None, title=None):
"""
Updates both the tag info in the file and the database entry with passed
artist and tag details.
"""
DEBUG(f"songdb.update_meta({session=}, {track=}, {artist=}, {title=})")
if not artist and not title:
return
ftype = os.path.splitext(track.path)[1][1:]
if ftype == 'flac':
tag_handler = FLAC
elif ftype == 'mp3':
tag_handler = MP3
else:
INFO(f"File type {ftype} not implemented")
return
f = tag_handler(track.path)
with Session() as session:
if artist:
f["artist"] = artist
Tracks.update_artist(session, track.id, artist)
if title:
f["title"] = title
Tracks.update_title(session, track.id, title)
f.save()
if __name__ == '__main__' and '__file__' in globals():
main()

View File

@ -6,15 +6,15 @@
<rect>
<x>0</x>
<y>0</y>
<width>383</width>
<height>270</height>
<width>584</width>
<height>377</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
@ -28,26 +28,36 @@
</item>
</layout>
</item>
<item>
<item row="1" column="0">
<widget class="QListWidget" name="matchList"/>
</item>
<item>
<widget class="QLabel" name="dbPath">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<widget class="QRadioButton" name="radioTitle">
<property name="text">
<string>&amp;Title</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioArtist">
<property name="text">
<string>&amp;Artist</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>88</width>
<width>40</width>
<height>20</height>
</size>
</property>
@ -79,6 +89,13 @@
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="dbPath">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'app/ui/dlg_SearchDatabase.ui'
# Form implementation generated from reading ui file 'ui/dlg_SearchDatabase.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
@ -14,9 +14,9 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(383, 270)
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout")
Dialog.resize(584, 377)
self.gridLayout_2 = QtWidgets.QGridLayout(Dialog)
self.gridLayout_2.setObjectName("gridLayout_2")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.label = QtWidgets.QLabel(Dialog)
@ -25,17 +25,20 @@ class Ui_Dialog(object):
self.searchString = QtWidgets.QLineEdit(Dialog)
self.searchString.setObjectName("searchString")
self.gridLayout.addWidget(self.searchString, 0, 1, 1, 1)
self.verticalLayout.addLayout(self.gridLayout)
self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)
self.matchList = QtWidgets.QListWidget(Dialog)
self.matchList.setObjectName("matchList")
self.verticalLayout.addWidget(self.matchList)
self.dbPath = QtWidgets.QLabel(Dialog)
self.dbPath.setText("")
self.dbPath.setObjectName("dbPath")
self.verticalLayout.addWidget(self.dbPath)
self.gridLayout_2.addWidget(self.matchList, 1, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
spacerItem = QtWidgets.QSpacerItem(88, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.radioTitle = QtWidgets.QRadioButton(Dialog)
self.radioTitle.setChecked(True)
self.radioTitle.setObjectName("radioTitle")
self.horizontalLayout.addWidget(self.radioTitle)
self.radioArtist = QtWidgets.QRadioButton(Dialog)
self.radioArtist.setObjectName("radioArtist")
self.horizontalLayout.addWidget(self.radioArtist)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.btnAdd = QtWidgets.QPushButton(Dialog)
self.btnAdd.setDefault(True)
@ -47,7 +50,11 @@ class Ui_Dialog(object):
self.btnClose = QtWidgets.QPushButton(Dialog)
self.btnClose.setObjectName("btnClose")
self.horizontalLayout.addWidget(self.btnClose)
self.verticalLayout.addLayout(self.horizontalLayout)
self.gridLayout_2.addLayout(self.horizontalLayout, 2, 0, 1, 1)
self.dbPath = QtWidgets.QLabel(Dialog)
self.dbPath.setText("")
self.dbPath.setObjectName("dbPath")
self.gridLayout_2.addWidget(self.dbPath, 3, 0, 1, 1)
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
@ -56,6 +63,8 @@ class Ui_Dialog(object):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(_translate("Dialog", "Title:"))
self.radioTitle.setText(_translate("Dialog", "&Title"))
self.radioArtist.setText(_translate("Dialog", "&Artist"))
self.btnAdd.setText(_translate("Dialog", "&Add"))
self.btnAddClose.setText(_translate("Dialog", "A&dd and close"))
self.btnClose.setText(_translate("Dialog", "&Close"))