diff --git a/app/playlists.py b/app/playlists.py index 5305e44..eb9f9dc 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -18,6 +18,7 @@ from PyQt5.QtWidgets import ( import helpers import os +import re from config import Config from datetime import datetime, timedelta @@ -33,6 +34,8 @@ from models import ( ) from dbconfig import Session +start_time_re = re.compile(r"@\d\d:\d\d\:\d\d") + class RowMeta: CLEAR = 0 @@ -269,7 +272,7 @@ class PlaylistTab(QTableWidget): """ Create note - If a row is selected, set note row to be rows above. Otherwise, + If a row is selected, set note row to be row above. Otherwise, set note row to be end of playlist. """ @@ -679,6 +682,8 @@ class PlaylistTab(QTableWidget): note_text: str row: int row_time: Optional[datetime] + section_start_row: Optional[int] = None + section_time: int = 0 start_time: Optional[datetime] start_times_row: Optional[int] track: Optional[Tracks] @@ -700,12 +705,22 @@ class PlaylistTab(QTableWidget): # Render notes in correct colour if row in notes: - # Extract note text - note_text = self.item(row, self.COL_TITLE).text() + # Extract note text from database to ignore section timings + note_text = self._get_row_notes_object(row, session).note # Does the note have a start time? row_time = self._get_note_text_time(note_text) if row_time: next_start_time = row_time + # Does it delimit a section? + if section_start_row is not None: + if note_text.endswith("-"): + self._set_timed_section(session, section_start_row, + section_time) + section_start_row = None + section_time = 0 + elif note_text.endswith("+"): + section_start_row = row + section_time = 0 # Set colour note_colour = NoteColours.get_colour(session, note_text) if not note_colour: @@ -715,16 +730,23 @@ class PlaylistTab(QTableWidget): ) # Notes are always bold self._set_row_bold(row) + continue # Render unplayable tracks in correct colour - elif row in unreadable: + if row in unreadable: self._set_row_colour( row, QColor(Config.COLOUR_UNREADABLE) ) self._set_row_bold(row) + continue + # Current row is a track row + track = self._get_row_track_object(row, session) + # Add track time to section time if in timed section + if section_start_row is not None: + section_time += track.duration # Render current track - elif row == current_row: + if row == current_row: # Set start time self._set_row_start_time( row, self.current_track_start_time) @@ -736,7 +758,6 @@ class PlaylistTab(QTableWidget): last_played_str) # Calculate next_start_time - track = self._get_row_track_object(row, session) next_start_time = self._calculate_track_end_time( track, self.current_track_start_time) @@ -749,9 +770,10 @@ class PlaylistTab(QTableWidget): # Make bold self._set_row_bold(row) + continue # Render next track - elif row == next_row: + if row == next_row: # if there's a track playing, set start time from that if current_row: start_time = self.current_track_start_time @@ -764,7 +786,6 @@ class PlaylistTab(QTableWidget): self._set_row_start_time(row, start_time) # Set end time - track = self._get_row_track_object(row, session) next_start_time = self._calculate_track_end_time( track, start_time) self._set_row_end_time(row, next_start_time) @@ -778,7 +799,6 @@ class PlaylistTab(QTableWidget): else: # This is a track row other than next or current - track = self._get_row_track_object(row, session) if row in played: # Played today, so update last played column last_playedtime = Playdates.last_played( @@ -809,6 +829,11 @@ class PlaylistTab(QTableWidget): self._set_row_colour( row, QColor(Config.COLOUR_EVEN_PLAYLIST)) + # Have we had a section start but not end? + if section_start_row is not None: + self._set_timed_section( + session, section_start_row, section_time, no_end=True) + # ########## Internally called functions ########## def _audacity(self, row: int) -> None: @@ -874,7 +899,7 @@ class PlaylistTab(QTableWidget): if row in self._get_notes_rows(): # Save change to database DEBUG(f"Notes.update_note: saving new note text '{new_text=}'") - note: Notes = self._get_notes_row_object(row, session) + note: Notes = self._get_row_notes_object(row, session) note.update_note(session, row, new_text) # Set/clear row start time accordingly start_time = self._get_note_text_time(new_text) @@ -892,7 +917,7 @@ class PlaylistTab(QTableWidget): "start time" ) else: - track: Tracks = self._get_track_row_object(row, session) + track: Tracks = self._get_row_track_object(row, session) if column == self.COL_ARTIST: track.update_artist(session, artist=new_text) elif column == self.COL_TITLE: @@ -926,6 +951,19 @@ class PlaylistTab(QTableWidget): # Disable play controls so that keyboard input doesn't disturb playing self.musicmuster.disable_play_next_controls() + # If this is a note cell and it's a section start, we need to + # remove any existing section timing so user can't edit that. + # Section timing is only in display of item, not in note text in + # database. Keep it simple: if this is a note, pull text from + # database. + + if self._is_note_row(row): + item = self.item(row, self.COL_TITLE) + with Session() as session: + note_object = self._get_row_notes_object(row, session) + if note_object: + item.setText(note_object.note) + def _clear_current_track_row(self) -> None: """ Clear current row if there is one. @@ -1066,14 +1104,15 @@ class PlaylistTab(QTableWidget): @staticmethod def _get_note_text_time(text: str) -> Optional[datetime]: - """Return time specified at the end of text""" + """Return time specified as @hh:mm:ss in text""" + + match = start_time_re.search(text) + if not match: + return None try: - time_length = len(Config.NOTE_TIME_FORMAT) - return datetime.strptime( - text[(-time_length):], - Config.NOTE_TIME_FORMAT - ) + return datetime.strptime(match.group(0)[1:], + Config.NOTE_TIME_FORMAT) except ValueError: return None @@ -1231,6 +1270,16 @@ class PlaylistTab(QTableWidget): and pos.y() >= rect.center().y() # noqa W503 ) + def _is_note_row(self, row: int) -> bool: + """ + Return True if passed row is a note row, else False + """ + + if self._meta_get(row): + if self._meta_get(row) & (1 << RowMeta.NOTE): + return True + return False + def _meta_clear_attribute(self, row: int, attribute: int) -> None: """Clear given metadata for row""" @@ -1502,6 +1551,21 @@ class PlaylistTab(QTableWidget): item: QTableWidgetItem = QTableWidgetItem(time_str) self.setItem(row, self.COL_START_TIME, item) + def _set_timed_section(self, session, start_row, ms, no_end=False): + """Add duration to a marked section""" + + duration = helpers.ms_to_mmss(ms) + note_object = self._get_row_notes_object(start_row, session) + if not note_object: + ERROR("Can't get note_object in playlists._set_timed_section") + note_text = note_object.note + caveat = "" + if no_end: + caveat = " (to end of playlist)" + display_text = note_text + ' [' + duration + caveat + ']' + item = self.item(start_row, self.COL_TITLE) + item.setText(display_text) + def _update_row(self, session, row: int, track: Tracks) -> None: """ Update the passed row with info from the passed track.