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
import os.path
import sqlalchemy
from datetime import datetime
@ -79,7 +80,7 @@ class Notes(Base):
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.row = row
@ -500,8 +501,7 @@ class Tracks(Base):
DEBUG(f"Tracks.get_track_from_filename({filename=})")
try:
track = session.query(Tracks).filter(Tracks.path.ilike(
# TODO: filename separator is hardcoded here
f'%/{filename}')).one()
f'%{os.path.sep}{filename}')).one()
return track
except (NoResultFound, MultipleResultsFound):
return None

View File

@ -132,7 +132,9 @@ class Window(QMainWindow, Ui_MainWindow):
if self.music.playing():
DEBUG("closeEvent() ignored as music is playing")
event.ignore()
# TODO notify user
helpers.show_warning(
"Track playing",
"Can't close application while track is playing")
else:
DEBUG("closeEvent() accepted")
@ -153,7 +155,12 @@ class Window(QMainWindow, Ui_MainWindow):
if record.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()
@ -511,12 +518,6 @@ class Window(QMainWindow, Ui_MainWindow):
silence_at - self.current_track.fade_at
))
def play_previous(self):
"Resume playing last track"
# TODO
pass
def search_database(self):
with Session() as session:
dlg = DbDialog(self, session)

View File

@ -17,7 +17,7 @@ import os
from config import Config
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 model import (
Notes, Playdates, Playlists, PlaylistTracks, Session, Settings, Tracks
@ -172,23 +172,31 @@ class PlaylistTab(QTableWidget):
if item is not None:
row = item.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)
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():
act_setnext = self.menu.addAction("Set next")
act_setnext.triggered.connect(lambda: self._set_next(row))
if not current and not next:
act_setnext = self.menu.addAction("Set next")
act_setnext.triggered.connect(
lambda: self._set_next(row))
act_copypath = self.menu.addAction("Copy track path")
act_copypath.triggered.connect(
lambda: self._copy_path(row))
act_rescan = self.menu.addAction("Rescan track")
act_rescan.triggered.connect(lambda: self._rescan(row))
act_audacity = self.menu.addAction(
"Open track in Audacity")
act_audacity.triggered.connect(lambda: self._audacity(row))
if not current:
act_rescan = self.menu.addAction("Rescan track")
act_rescan.triggered.connect(lambda: self._rescan(row))
act_audacity = self.menu.addAction(
"Open track in Audacity")
act_audacity.triggered.connect(
lambda: self._audacity(row))
if not current and not next:
self.menu.addSeparator()
act_delete = self.menu.addAction('Delete')
act_delete.triggered.connect(self._delete_rows)
act_info = self.menu.addAction('Info')
act_info.triggered.connect(lambda: self._info_row(row))
act_delete = self.menu.addAction('Delete')
act_delete.triggered.connect(self._delete_rows)
return super(PlaylistTab, self).eventFilter(source, event)
@ -224,10 +232,12 @@ class PlaylistTab(QTableWidget):
start_time = None
try:
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:
DEBUG(
f"Note on row {row} ('{note.note}') "
f"playlist.inset_note(): Note on row {row} ('{note.note}') "
"does not contain valid time"
)
@ -613,6 +623,9 @@ class PlaylistTab(QTableWidget):
if not self.editing_cell:
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()
@ -621,7 +634,28 @@ class PlaylistTab(QTableWidget):
row_id = self._get_row_id(row)
with Session() as session:
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)
# 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:
track = Tracks.get_track(session, row_id)
if column == self.COL_ARTIST:
@ -639,6 +673,11 @@ class PlaylistTab(QTableWidget):
def _cell_edit_ended(self):
DEBUG("_cell_edit_ended()")
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()
def _delete_rows(self):
@ -652,22 +691,14 @@ class PlaylistTab(QTableWidget):
with Session() as session:
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()
msg = QMessageBox(self)
msg.setIcon(QMessageBox.Warning)
msg.setText(f"Delete '{title}'?")
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
msg.setDefaultButton(QMessageBox.Cancel)
msg.setWindowTitle("Delete row")
# Store list of notes
# Store list of rows to delete
if msg.exec() == QMessageBox.Yes:
rows_to_delete.append(row)
@ -963,6 +994,18 @@ class PlaylistTab(QTableWidget):
# Set colours and start times
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
for row in range(self.rowCount()):
# We can't calculate start times until next_start_time is
@ -1043,12 +1086,16 @@ class PlaylistTab(QTableWidget):
self._set_row_not_bold(row)
else:
# 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)
next_start_time = self._calculate_next_start_time(
session, row, next_start_time)
# Set end 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
self._set_row_bold(row)

View File

@ -6,6 +6,7 @@ import shutil
import tempfile
from config import Config
from helpers import show_warning
from log import DEBUG, INFO
from model import Notes, Playdates, PlaylistTracks, Session, Tracks
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")
return
# Update tags
f = tag_handler(track.path)
with Session() as session:
try:
if artist:
f["artist"] = artist
Tracks.update_artist(session, track.id, artist)
if title:
f["title"] = title
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)
f.save()
if __name__ == '__main__' and '__file__' in globals():