Compare commits

..

6 Commits

Author SHA1 Message Date
Keith Edmunds
e4fe4b576e Clear start/end time for unplayed tracks above current
Fixes #53
2021-08-23 19:25:47 +01:00
Keith Edmunds
54cfb1191a Set start correctly when note edited 2021-08-23 15:19:52 +01:00
Keith Edmunds
d8072ae73f Remove TODOs from code.
Fixes #57
2021-08-23 09:23:18 +01:00
Keith Edmunds
d2e2144148 Remove inapplicable right-click menu items 2021-08-22 20:40:41 +01:00
Keith Edmunds
9dfc5e50cc Improve tagging on rescan 2021-08-22 20:40:13 +01:00
Keith Edmunds
4267901630 Tweak right-click menu order 2021-08-22 19:13:33 +01:00
4 changed files with 95 additions and 38 deletions

View File

@ -1,5 +1,6 @@
#!/usr/bin/python3 #!/usr/bin/python3
import os.path
import sqlalchemy import sqlalchemy
from datetime import datetime from datetime import datetime
@ -79,7 +80,7 @@ class Notes(Base):
Update note details. If text=None, don't change text. Update note details. If text=None, don't change text.
""" """
DEBUG(f"update_note(id={id}, row={row}, text={text})") DEBUG(f"Notes.update_note(id={id}, row={row}, text={text})")
note = session.query(cls).filter(cls.id == id).one() note = session.query(cls).filter(cls.id == id).one()
note.row = row note.row = row
@ -500,8 +501,7 @@ class Tracks(Base):
DEBUG(f"Tracks.get_track_from_filename({filename=})") DEBUG(f"Tracks.get_track_from_filename({filename=})")
try: try:
track = session.query(Tracks).filter(Tracks.path.ilike( track = session.query(Tracks).filter(Tracks.path.ilike(
# TODO: filename separator is hardcoded here f'%{os.path.sep}{filename}')).one()
f'%/{filename}')).one()
return track return track
except (NoResultFound, MultipleResultsFound): except (NoResultFound, MultipleResultsFound):
return None return None

View File

@ -132,7 +132,9 @@ class Window(QMainWindow, Ui_MainWindow):
if self.music.playing(): if self.music.playing():
DEBUG("closeEvent() ignored as music is playing") DEBUG("closeEvent() ignored as music is playing")
event.ignore() event.ignore()
# TODO notify user helpers.show_warning(
"Track playing",
"Can't close application while track is playing")
else: else:
DEBUG("closeEvent() accepted") DEBUG("closeEvent() accepted")
@ -153,7 +155,12 @@ class Window(QMainWindow, Ui_MainWindow):
if record.f_int != self.y(): if record.f_int != self.y():
record.update(session, {'f_int': self.y()}) record.update(session, {'f_int': self.y()})
self.visible_playlist_tab().close(session) # Find a playlist tab (as opposed to an info tab) and
# save column widths
if self.current_track_playlist_tab:
self.current_track_playlist_tab.close(session)
elif self.next_track_playlist_tab:
self.next_track_playlist_tab.close(session)
event.accept() event.accept()
@ -511,12 +518,6 @@ class Window(QMainWindow, Ui_MainWindow):
silence_at - self.current_track.fade_at silence_at - self.current_track.fade_at
)) ))
def play_previous(self):
"Resume playing last track"
# TODO
pass
def search_database(self): def search_database(self):
with Session() as session: with Session() as session:
dlg = DbDialog(self, session) dlg = DbDialog(self, session)

View File

@ -17,7 +17,7 @@ import os
from config import Config from config import Config
from datetime import datetime, timedelta from datetime import datetime, timedelta
from helpers import get_relative_date, open_in_audacity, show_warning from helpers import get_relative_date, open_in_audacity
from log import DEBUG, ERROR from log import DEBUG, ERROR
from model import ( from model import (
Notes, Playdates, Playlists, PlaylistTracks, Session, Settings, Tracks Notes, Playdates, Playlists, PlaylistTracks, Session, Settings, Tracks
@ -172,23 +172,31 @@ class PlaylistTab(QTableWidget):
if item is not None: if item is not None:
row = item.row() row = item.row()
DEBUG(f"playlist.eventFilter(): Right-click on row {row}") DEBUG(f"playlist.eventFilter(): Right-click on row {row}")
current = row == self._meta_get_current()
next = row == self._meta_get_next()
self.menu = QMenu(self) self.menu = QMenu(self)
act_info = self.menu.addAction('Info')
act_info.triggered.connect(lambda: self._info_row(row))
self.menu.addSeparator()
if row not in self._meta_get_notes(): if row not in self._meta_get_notes():
if not current and not next:
act_setnext = self.menu.addAction("Set next") act_setnext = self.menu.addAction("Set next")
act_setnext.triggered.connect(lambda: self._set_next(row)) act_setnext.triggered.connect(
lambda: self._set_next(row))
act_copypath = self.menu.addAction("Copy track path") act_copypath = self.menu.addAction("Copy track path")
act_copypath.triggered.connect( act_copypath.triggered.connect(
lambda: self._copy_path(row)) lambda: self._copy_path(row))
if not current:
act_rescan = self.menu.addAction("Rescan track") act_rescan = self.menu.addAction("Rescan track")
act_rescan.triggered.connect(lambda: self._rescan(row)) act_rescan.triggered.connect(lambda: self._rescan(row))
act_audacity = self.menu.addAction( act_audacity = self.menu.addAction(
"Open track in Audacity") "Open track in Audacity")
act_audacity.triggered.connect(lambda: self._audacity(row)) act_audacity.triggered.connect(
lambda: self._audacity(row))
if not current and not next:
self.menu.addSeparator() self.menu.addSeparator()
act_delete = self.menu.addAction('Delete') act_delete = self.menu.addAction('Delete')
act_delete.triggered.connect(self._delete_rows) act_delete.triggered.connect(self._delete_rows)
act_info = self.menu.addAction('Info')
act_info.triggered.connect(lambda: self._info_row(row))
return super(PlaylistTab, self).eventFilter(source, event) return super(PlaylistTab, self).eventFilter(source, event)
@ -224,10 +232,12 @@ class PlaylistTab(QTableWidget):
start_time = None start_time = None
try: try:
start_time = datetime.strptime(note.note[-9:], " %H:%M:%S").time() start_time = datetime.strptime(note.note[-9:], " %H:%M:%S").time()
DEBUG(f"Note contains valid time={start_time}") DEBUG(
f"playlist.inset_note(): Note contains valid time={start_time}"
)
except ValueError: except ValueError:
DEBUG( DEBUG(
f"Note on row {row} ('{note.note}') " f"playlist.inset_note(): Note on row {row} ('{note.note}') "
"does not contain valid time" "does not contain valid time"
) )
@ -613,6 +623,9 @@ class PlaylistTab(QTableWidget):
if not self.editing_cell: if not self.editing_cell:
return return
# If we update start time, _cell_changed will be called
if column not in [self.COL_TITLE, self.COL_ARTIST]:
return
new = self.item(row, column).text() new = self.item(row, column).text()
@ -621,7 +634,28 @@ class PlaylistTab(QTableWidget):
row_id = self._get_row_id(row) row_id = self._get_row_id(row)
with Session() as session: with Session() as session:
if row in self._meta_get_notes(): if row in self._meta_get_notes():
# Save change to database
DEBUG(
f"Notes.update_note: saving new note text '{new=}'",
True
)
Notes.update_note(session, row_id, row, new) Notes.update_note(session, row_id, row, new)
# Set/clear row start time accordingly
try:
start_dt = datetime.strptime(new[-9:], " %H:%M:%S")
start_time = start_dt.time()
self._set_row_start_time(row, start_time)
DEBUG(
f"_cell_changed:Note {new} contains valid "
f"time={start_time}"
)
except ValueError:
# Reset row start time in case it used to have one
self._set_row_start_time(row, None)
DEBUG(
f"_cell_changed:Note {new} does not contain "
"start time"
)
else: else:
track = Tracks.get_track(session, row_id) track = Tracks.get_track(session, row_id)
if column == self.COL_ARTIST: if column == self.COL_ARTIST:
@ -639,6 +673,11 @@ class PlaylistTab(QTableWidget):
def _cell_edit_ended(self): def _cell_edit_ended(self):
DEBUG("_cell_edit_ended()") DEBUG("_cell_edit_ended()")
self.editing_cell = False self.editing_cell = False
# Call repaint to update start times, such as when a note has
# been edited
self._repaint()
self.master_process.enable_play_next_controls() self.master_process.enable_play_next_controls()
def _delete_rows(self): def _delete_rows(self):
@ -652,22 +691,14 @@ class PlaylistTab(QTableWidget):
with Session() as session: with Session() as session:
for row in rows: for row in rows:
if row == self._meta_get_current():
show_warning("Silly", "Can't delete playing track")
return
elif row == self._meta_get_next():
show_warning("Safety", "Can't delete next track")
return
title = self.item(row, self.COL_TITLE).text() title = self.item(row, self.COL_TITLE).text()
msg = QMessageBox(self) msg = QMessageBox(self)
msg.setIcon(QMessageBox.Warning) msg.setIcon(QMessageBox.Warning)
msg.setText(f"Delete '{title}'?") msg.setText(f"Delete '{title}'?")
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel) msg.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
msg.setDefaultButton(QMessageBox.Cancel) msg.setDefaultButton(QMessageBox.Cancel)
msg.setWindowTitle("Delete row") msg.setWindowTitle("Delete row")
# Store list of notes # Store list of rows to delete
if msg.exec() == QMessageBox.Yes: if msg.exec() == QMessageBox.Yes:
rows_to_delete.append(row) rows_to_delete.append(row)
@ -963,6 +994,18 @@ class PlaylistTab(QTableWidget):
# Set colours and start times # Set colours and start times
next_start_time = None next_start_time = None
# Don't change start times for tracks that have been played.
# For unplayed tracks, if there's a 'current' or 'next'
# track marked, populate start times from then onwards. If
# neither, populate start times from first note with a start
# time.
if current and next:
start_times_row = min(current, next)
else:
start_times_row = current or next
if not start_times_row:
start_times_row = 0
# Cycle through all rows # Cycle through all rows
for row in range(self.rowCount()): for row in range(self.rowCount()):
# We can't calculate start times until next_start_time is # We can't calculate start times until next_start_time is
@ -1043,12 +1086,16 @@ class PlaylistTab(QTableWidget):
self._set_row_not_bold(row) self._set_row_not_bold(row)
else: else:
# Set start/end times only if we haven't played it yet # Set start/end times only if we haven't played it yet
if next_start_time: if next_start_time and row >= start_times_row:
self._set_row_start_time(row, next_start_time) self._set_row_start_time(row, next_start_time)
next_start_time = self._calculate_next_start_time( next_start_time = self._calculate_next_start_time(
session, row, next_start_time) session, row, next_start_time)
# Set end time # Set end time
self._set_row_end_time(row, next_start_time) self._set_row_end_time(row, next_start_time)
else:
# Clear start and end time
self._set_row_start_time(row, None)
self._set_row_end_time(row, None)
# Don't dim unplayed tracks # Don't dim unplayed tracks
self._set_row_bold(row) self._set_row_bold(row)

View File

@ -6,6 +6,7 @@ import shutil
import tempfile import tempfile
from config import Config from config import Config
from helpers import show_warning
from log import DEBUG, INFO from log import DEBUG, INFO
from model import Notes, Playdates, PlaylistTracks, Session, Tracks from model import Notes, Playdates, PlaylistTracks, Session, Tracks
from mutagen.flac import FLAC from mutagen.flac import FLAC
@ -369,15 +370,23 @@ def update_meta(session, track, artist=None, title=None):
INFO(f"File type {ftype} not implemented") INFO(f"File type {ftype} not implemented")
return return
# Update tags
f = tag_handler(track.path) f = tag_handler(track.path)
with Session() as session: try:
if artist: if artist:
f["artist"] = artist f["artist"] = artist
Tracks.update_artist(session, track.id, artist)
if title: if title:
f["title"] = title f["title"] = title
Tracks.update_title(session, track.id, title)
f.save() f.save()
except TypeError:
show_warning("TAG error", "Can't update tag. Try editing in Audacity")
# Update database
with Session() as session:
if artist:
Tracks.update_artist(session, track.id, artist)
if title:
Tracks.update_title(session, track.id, title)
if __name__ == '__main__' and '__file__' in globals(): if __name__ == '__main__' and '__file__' in globals():