WIP: playlists.py refactoring

This commit is contained in:
Keith Edmunds 2023-03-04 23:02:21 +00:00
parent 4a6ce3b4ee
commit 530ee60015
3 changed files with 211 additions and 370 deletions

View File

@ -52,11 +52,11 @@ class Carts(Base):
__tablename__ = 'carts'
id: int = Column(Integer, primary_key=True, autoincrement=True)
cart_number = Column(Integer, nullable=False, unique=True)
cart_number: int = Column(Integer, nullable=False, unique=True)
name = Column(String(256), index=True)
duration = Column(Integer, index=True)
path = Column(String(2048), index=False)
enabled = Column(Boolean, default=False, nullable=False)
enabled: bool = Column(Boolean, default=False, nullable=False)
def __repr__(self) -> str:
return (
@ -192,13 +192,13 @@ class Playlists(Base):
__tablename__ = "playlists"
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
name = Column(String(32), nullable=False, unique=True)
name: str = Column(String(32), nullable=False, unique=True)
last_used = Column(DateTime, default=None, nullable=True)
tab = Column(Integer, default=None, nullable=True, unique=True)
sort_column = Column(Integer, default=None, nullable=True, unique=False)
is_template = Column(Boolean, default=False, nullable=False)
is_template: bool = Column(Boolean, default=False, nullable=False)
query = Column(String(256), default=None, nullable=True, unique=False)
deleted = Column(Boolean, default=False, nullable=False)
deleted: bool = Column(Boolean, default=False, nullable=False)
rows: List["PlaylistRows"] = relationship(
"PlaylistRows",
back_populates="playlist",
@ -371,14 +371,15 @@ class Playlists(Base):
class PlaylistRows(Base):
__tablename__ = 'playlist_rows'
id = Column(Integer, primary_key=True, autoincrement=True)
row_number = Column(Integer, nullable=False)
note = Column(String(2048), index=False)
playlist_id = Column(Integer, ForeignKey('playlists.id'), nullable=False)
id: int = Column(Integer, primary_key=True, autoincrement=True)
row_number: int = Column(Integer, nullable=False)
note: str = Column(String(2048), index=False, default="", nullable=False)
playlist_id: int = Column(Integer, ForeignKey('playlists.id'),
nullable=False)
playlist: Playlists = relationship(Playlists, back_populates="rows")
track_id = Column(Integer, ForeignKey('tracks.id'), nullable=True)
track: "Tracks" = relationship("Tracks", back_populates="playlistrows")
played = Column(Boolean, nullable=False, index=False, default=False)
played: bool = Column(Boolean, nullable=False, index=False, default=False)
def __repr__(self) -> str:
return (
@ -392,7 +393,7 @@ class PlaylistRows(Base):
playlist_id: int,
track_id: Optional[int],
row_number: int,
note: Optional[str] = None
note: str = ""
) -> None:
"""Create PlaylistRows object"""
@ -479,8 +480,7 @@ class PlaylistRows(Base):
cls.note.endswith("-") |
cls.note.endswith("+")
)
)
.order_by(cls.row_number)).scalars().all()
).order_by(cls.row_number)).scalars().all()
return plrs
@ -531,7 +531,7 @@ class PlaylistRows(Base):
def get_rows_with_tracks(
cls, session: scoped_session, playlist_id: int,
from_row: Optional[int] = None,
to_row: Optional[int] = None) -> List["PlaylistRows"]:
to_row: Optional[int] = None) -> List["PlaylistRows"]:
"""
For passed playlist, return a list of rows that
contain tracks
@ -546,7 +546,9 @@ class PlaylistRows(Base):
if to_row is not None:
query = query.where(cls.row_number <= to_row)
plrs = session.execute((query).order_by(cls.row_number)).scalars().all()
plrs = (
session.execute((query).order_by(cls.row_number)).scalars().all()
)
return plrs
@ -661,7 +663,7 @@ class Tracks(Base):
start_gap = Column(Integer, index=False)
fade_at = Column(Integer, index=False)
silence_at = Column(Integer, index=False)
path = Column(String(2048), index=False, nullable=False, unique=True)
path: str = Column(String(2048), index=False, nullable=False, unique=True)
mtime = Column(Float, index=True)
bitrate = Column(Integer, nullable=True, default=None)
playlistrows: PlaylistRows = relationship("PlaylistRows",

View File

@ -447,6 +447,7 @@ class Window(QMainWindow, Ui_MainWindow):
"""
self.next_track = PlaylistTrack()
self.update_headers()
def clear_selection(self) -> None:
""" Clear selected row"""
@ -598,12 +599,13 @@ class Window(QMainWindow, Ui_MainWindow):
def create_playlist(self,
session: scoped_session,
playlist_name: Optional[str] = None) -> Playlists:
playlist_name: Optional[str] = None) \
-> Optional[Playlists]:
"""Create new playlist"""
playlist_name = self.solicit_playlist_name()
if not playlist_name:
return
return None
playlist = Playlists(session, playlist_name)
return playlist

View File

@ -266,8 +266,7 @@ class PlaylistTab(QTableWidget):
if item is not None:
with Session() as session:
row_number = item.row()
plr_id = self._get_playlistrow_id(row_number)
plr = session.get(PlaylistRows, plr_id)
plr = self._get_row_plr(session, row_number)
track_id = plr.track_id
track_row = track_id is not None
header_row = not track_row
@ -445,7 +444,7 @@ class PlaylistTab(QTableWidget):
# Determine cell type changed
with Session() as session:
# Get playlistrow object
plr_id = self._get_playlistrow_id(row)
plr_id = self._get_row_plr_id(row)
plr_item = session.get(PlaylistRows, plr_id)
if not plr_item:
return
@ -500,16 +499,16 @@ class PlaylistTab(QTableWidget):
play controls and update display.
"""
# Update start times in case a start time in a note has been
# edited
self._update_start_end_times()
self.edit_cell_type = None
self.musicmuster.enable_play_next_controls()
self.musicmuster.actionSetNext.setEnabled(True)
super(PlaylistTab, self).closeEditor(editor, hint)
# Update start times in case a start time in a note has been
# edited
self._update_start_end_times()
def edit(self, index: QModelIndex, # type: ignore # FIXME
trigger: QAbstractItemView.EditTrigger,
event: QEvent) -> bool:
@ -556,8 +555,7 @@ class PlaylistTab(QTableWidget):
# editing a note.
if note_column:
with Session() as session:
plr_id = self._get_playlistrow_id(row)
plr_item = session.get(PlaylistRows, plr_id)
plr_item = self._get_row_plr(session, row)
item = self.item(row, note_column)
if not item:
return False
@ -596,7 +594,7 @@ class PlaylistTab(QTableWidget):
Return a list of PlaylistRow ids of the selected rows
"""
return [self._get_playlistrow_id(a) for a in self._get_selected_rows()]
return [self._get_row_plr_id(a) for a in self._get_selected_rows()]
def get_selected_playlistrows(
self, session: scoped_session) -> List[PlaylistRows]:
@ -648,34 +646,14 @@ class PlaylistTab(QTableWidget):
Insert passed playlist row (plr) into playlist tab.
"""
if plr.row_number is None:
return
row = plr.row_number
self.insertRow(row)
_ = self._set_row_plr_id(row, plr.id)
# Add row metadata to userdata column
self._set_row_userdata(row, self.PLAYLISTROW_ID, plr.id)
if plr.track_id:
_ = self._set_row_userdata(row, self.ROW_TRACK_ID, plr.track_id)
_ = self._set_row_userdata(row, self.TRACK_PATH, plr.track.path)
_ = self._set_row_start_gap(row, plr.track.start_gap)
_ = self._set_row_title(row, plr.track.title)
_ = self._set_row_artist(row, plr.track.artist)
_ = self._set_row_duration(row, plr.track.duration)
_ = self._set_row_start_time(row, None)
_ = self._set_row_end_time(row, None)
_ = self._set_row_bitrate(row, plr.track.bitrate)
_ = self._set_row_note(session, row, plr.note)
_ = self._set_row_last_played(
row, Playdates.last_played(session, plr.track.id))
if not file_is_readable(plr.track.path):
self._set_row_colour(row, QColor(Config.COLOUR_UNREADABLE))
if plr.track:
self._update_row_track_info(session, row, plr.track)
if not played:
self._set_row_bold(row)
else:
# This is a section header so it must have note text
if plr.note is None:
@ -684,34 +662,22 @@ class PlaylistTab(QTableWidget):
)
return
# In order to colour the row, we need items in every column.
# Bug in PyQt5 means that required height of row considers
# text to be wrapped in one column and ignores any spanned
# columns, hence putting notes in HEADER_NOTES_COLUMN which
# is typically reasonably wide and thus minimises
# unneccessary row height increases.
# In order to colour the row, we need items in every column
for i in range(1, len(columns)):
if i == HEADER_NOTES_COLUMN:
continue
self._set_item_text(row, i, None)
self.setSpan(row, HEADER_NOTES_COLUMN, 1, len(columns) - 1)
_ = self._set_item_text(row, HEADER_NOTES_COLUMN, plr.note)
note_colour = NoteColours.get_colour(session, plr.note)
if not note_colour:
note_colour = Config.COLOUR_NOTES_PLAYLIST
self._set_row_colour(row, QColor(note_colour))
_ = self._set_row_note(session, row, plr.note, section_header=True)
# Save (or clear) track_id
_ = self._set_row_userdata(row, self.ROW_TRACK_ID, 0)
_ = self._set_row_track_id(row, 0)
if update_track_times:
# Queue time updates so playlist updates first
QTimer.singleShot(0, lambda: self._update_start_end_times())
def insert_track(self, session: scoped_session, track: Tracks,
note: Optional[str] = None, repaint: bool = True) -> None:
note: str = "", repaint: bool = True) -> None:
"""
Insert track into playlist tab.
@ -762,7 +728,6 @@ class PlaylistTab(QTableWidget):
self.musicmuster.clear_next()
self.clear_selection()
self._set_row_colour(row, None)
self.musicmuster.update_headers()
def play_started(self, session: scoped_session) -> None:
"""
@ -873,7 +838,7 @@ class PlaylistTab(QTableWidget):
# Ensure all row plrs have correct row number and playlist_id
for row in range(self.rowCount()):
plr = self._get_playlistrow_object(session, row)
plr = self._get_row_plr(session, row)
if not plr:
continue
plr.row_number = row
@ -1008,225 +973,14 @@ class PlaylistTab(QTableWidget):
self.resizeRowsToContents()
self.setColumnWidth(len(columns) - 1, 0)
# def update_display(self, session: scoped_session) -> None:
# """
# Set row colours, fonts, etc
# Actions required:
# - Render notes in correct colour
# - Render current, next and unplayable tracks in correct colour
# - Set start and end times
# - Show unplayed tracks in bold
# """
# current_row = self._get_current_track_row_number()
# next_row = self._get_next_track_row_number()
# played = [
# p.row_number for p in PlaylistRows.get_played_rows(
# session, self.playlist_id)
# ]
# # next_start_time = None
# section_start_plr = None
# section_time = 0
# # Start time calculations
# # 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. A note
# # with a start time will reset the next track start time.
# # Cycle through all rows
# for row in range(self.rowCount()):
# # Extract note text from database to ignore section timings
# playlist_row = session.get(PlaylistRows,
# self._get_playlistrow_id(row))
# if not playlist_row:
# continue
# note_text = playlist_row.note
# note_colour = None
# if not note_text:
# note_text = ""
# # Get note colour
# else:
# note_colour = NoteColours.get_colour(session, note_text)
# # Get track if there is one
# track_id = self._get_row_track_id(row)
# track = None
# if track_id:
# track = session.get(Tracks, track_id)
# if not track:
# # We have a track_id but we can't find the track.
# # Update playlist_row accordingly
# missing_track = playlist_row.track_id
# playlist_row.track_id = None
# if note_text:
# note_text += f"track_id {missing_track} not found"
# else:
# note_text = f"track_id {missing_track} not found"
# playlist_row.note = note_text
# session.flush()
# _ = self._set_item_text(row, HEADER_NOTES_COLUMN,
# note_text)
# if track:
# # Reset colour in case it was current/next/unplayable
# self._set_row_colour(row, None)
# # Render unplayable tracks in correct colour
# if not file_is_readable(track.path):
# self._set_row_colour(row,
# QColor(Config.COLOUR_UNREADABLE))
# self._set_row_bold(row)
# continue
# # Add track time to section time if in timed section
# if section_start_plr is not None:
# section_time += track.duration
# # Colour any note
# if note_colour:
# notes_item = self.item(row, ROW_NOTES)
# if notes_item:
# notes_item.setBackground(QColor(note_colour))
# # Highlight low bitrates
# if track.bitrate:
# bitrate_str = str(track.bitrate)
# bitrate_item = self._set_item_text(
# row, BITRATE, str(track.bitrate))
# if bitrate_item:
# if track.bitrate < Config.BITRATE_LOW_THRESHOLD:
# cell_colour = Config.COLOUR_BITRATE_LOW
# elif track.bitrate < Config.BITRATE_OK_THRESHOLD:
# cell_colour = Config.COLOUR_BITRATE_MEDIUM
# else:
# cell_colour = Config.COLOUR_BITRATE_OK
# brush = QBrush(QColor(cell_colour))
# bitrate_item.setBackground(brush)
# # Render playing track
# if row == current_row:
# # Set last played time to "Today"
# self._set_item_text(
# row, LASTPLAYED, Config.LAST_PLAYED_TODAY_STRING)
# # Calculate next_start_time
# # if track.duration:
# # next_start_time = self._calculate_end_time(
# # self.musicmuster.current_track.start_time,
# # track.duration
# # )
# # Set end time
# # self._set_row_end_time(row, next_start_time)
# # Set colour
# self._set_row_colour(row, QColor(
# Config.COLOUR_CURRENT_PLAYLIST))
# # Make bold
# self._set_row_bold(row)
# continue
# # Render next track
# if row == next_row:
# # Set start time
# # if there's a track playing, set start time from
# # that. It may be on a different tab, so we get
# # start time from musicmuster.
# # start_time = self.musicmuster.current_track.end_time
# # if start_time is None:
# # # No current track to base from, but don't change
# # # time if it's already set
# # start_time = self._get_row_start_time(row)
# # if not start_time:
# # start_time = next_start_time
# # self._set_row_start_time(row, start_time)
# # # Calculate next_start_time
# # if track.duration:
# # next_start_time = self._calculate_end_time(
# # start_time, track.duration)
# # # Set end time
# # self._set_row_end_time(row, next_start_time)
# # Set colour
# # self._set_row_colour(
# # row, QColor(Config.COLOUR_NEXT_PLAYLIST))
# # Make bold
# self._set_row_bold(row)
# continue
# if row in played:
# # Played today, so update last played column
# self._set_item_text(
# row, LASTPLAYED, Config.LAST_PLAYED_TODAY_STRING)
# if self.musicmuster.hide_played_tracks:
# self.hideRow(row)
# else:
# self.showRow(row)
# self._set_row_not_bold(row)
# else:
# # Set start/end times as we haven't played it yet
# # if next_start_time:
# # self._set_row_start_time(row, next_start_time)
# # if track.duration:
# # next_start_time = self._calculate_end_time(
# # next_start_time, track.duration)
# # # 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)
# continue
# # No track associated, so this row is a section header
# # 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_plr is not None:
# if note_text.endswith("-"):
# self._update_note_text(
# section_start_plr,
# self._get_section_timing_string(section_time)
# )
# section_start_plr = None
# section_time = 0
# elif note_text.endswith("+"):
# section_start_plr = playlist_row
# section_time = 0
# if not note_colour:
# note_colour = Config.COLOUR_NOTES_PLAYLIST
# self._set_row_colour(row, QColor(note_colour))
# # Section headers are always bold
# self._set_row_bold(row)
# continue
# # Set row heights
# self.resizeRowsToContents()
# # Have we had a section start but not end?
# if section_start_plr is not None:
# self._update_note_text(
# section_start_plr,
# self._get_section_timing_string(section_time, no_end=True)
# )
# # ########## Internally called functions ##########
def _add_track(self, row: int) -> None:
"""Add a track to a section header making it a normal track row"""
with Session() as session:
track = self.musicmuster.get_one_track(session)
if not track:
return
# Add track to playlist row
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
plr = self._get_row_plr(session, row)
if not plr:
return
@ -1234,6 +988,11 @@ class PlaylistTab(QTableWidget):
if plr.track_id is not None:
return
# Get track
track = self.musicmuster.get_one_track(session)
if not track:
return
plr.track_id = track.id
session.flush()
@ -1242,13 +1001,8 @@ class PlaylistTab(QTableWidget):
self.setSpan(row, column, 1, 1)
# Update attributes of row
_ = self._set_row_userdata(row, self.ROW_TRACK_ID, plr.track_id)
_ = self._set_row_last_played(
row, Playdates.last_played(session, plr.track.id))
_ = self._set_row_note(session, row, plr.note)
_ = self._set_row_start_gap(row, plr.track.start_gap)
self._update_row(session, row, track)
self._update_row_track_info(session, row, track)
def _calculate_end_time(self, start: Optional[datetime],
duration: int) -> Optional[datetime]:
@ -1291,27 +1045,21 @@ class PlaylistTab(QTableWidget):
to the clipboard. Otherwise, return None.
"""
track_id = self._get_row_track_id(row)
if track_id is None:
track_path = self._get_row_track_path(row)
if not track_path:
return
with Session() as session:
track = session.get(Tracks, track_id)
if track and track.path:
# Escape single quotes and spaces in name
path = track.path
pathq = path.replace("'", "\\'")
pathqs = pathq.replace(" ", "\\ ")
cb = QApplication.clipboard()
cb.clear(mode=cb.Clipboard)
cb.setText(pathqs, mode=cb.Clipboard)
pathq = track_path.replace("'", "\\'")
pathqs = pathq.replace(" ", "\\ ")
cb = QApplication.clipboard()
cb.clear(mode=cb.Clipboard)
cb.setText(pathqs, mode=cb.Clipboard)
def _deferred_save(self) -> None:
"""
Create session and save playlist
"""
print("_deferred_save() called")
with Session() as session:
self.save_playlist(session)
@ -1322,8 +1070,10 @@ class PlaylistTab(QTableWidget):
Actions required:
- Remove the rows from the display
- Save the playlist
- Update track start/end times
"""
rows_to_delete: List[int] = []
with Session() as session:
plrs = self.get_selected_playlistrows(session)
row_count = len(plrs)
@ -1336,7 +1086,7 @@ class PlaylistTab(QTableWidget):
f"Really delete {row_count} row{plural}?"):
return
rows_to_delete = [a.row_number for a in plrs]
rows_to_delete = [plr.row_number for plr in plrs]
# Delete rows from database. Would be more efficient to
# query then have a single delete.
@ -1349,8 +1099,10 @@ class PlaylistTab(QTableWidget):
# Reset drag mode
self.setDragEnabled(False)
# QTimer.singleShot(0, lambda: self._deferred_save())
self.signals.save_playlist_signal.emit()
self.save_playlist(session)
# Queue time updates so playlist updates first
QTimer.singleShot(0, lambda: self._update_start_end_times())
def _drop_on(self, event):
"""
@ -1390,7 +1142,7 @@ class PlaylistTab(QTableWidget):
for row in range(starting_row, self.rowCount()):
if row not in track_rows or row in played_rows:
continue
plr = self._get_playlistrow_object(session, row)
plr = self._get_row_plr(session, row)
if not plr:
continue
if not file_is_readable(plr.track.path):
@ -1442,7 +1194,7 @@ class PlaylistTab(QTableWidget):
@staticmethod
def _get_note_text_time(text: str) -> Optional[datetime]:
"""Return time specified as @hh:mm:ss in text"""
"""Return datetime specified as @hh:mm:ss in text"""
try:
match = start_time_re.search(text)
@ -1467,30 +1219,12 @@ class PlaylistTab(QTableWidget):
session, self.playlist_id) if p.row_number is not None
]
def _get_playlistrow_id(self, row: int) -> Optional[int]:
"""Return the playlistrow_id associated with this row"""
plrid = self._get_row_userdata(row, self.PLAYLISTROW_ID)
if plrid is None:
return None
return int(plrid)
def _get_playlistrow_object(self, session: scoped_session,
row: int) -> Optional[PlaylistRows]:
"""Return the playlistrow object associated with this row"""
playlistrow_id = self._get_playlistrow_id(row)
if not playlistrow_id:
return None
return session.get(PlaylistRows, playlistrow_id)
def _get_row_artist(self, row: int) -> Optional[str]:
def _get_row_artist(self, row: int) -> str:
"""Return artist on this row or None if none"""
item_artist = self.item(row, ARTIST)
if not item_artist:
return None
return ""
return item_artist.text()
@ -1503,8 +1237,8 @@ class PlaylistTab(QTableWidget):
else:
return int(duration_userdata)
def _get_row_note(self, row: int) -> Optional[str]:
"""Return note on this row or None if none"""
def _get_row_note(self, row: int) -> str:
"""Return note on this row or null string if none"""
track_id = self._get_row_track_id(row)
if track_id:
@ -1512,16 +1246,37 @@ class PlaylistTab(QTableWidget):
else:
item_note = self.item(row, HEADER_NOTES_COLUMN)
if not item_note:
return None
return ""
return item_note.text()
def _get_row_path(self, row: int) -> Optional[str]:
def _get_row_path(self, row: int) -> str:
"""
Return path of track associated with this row or None
Return path of track associated with this row or null string
"""
return str(self._get_row_userdata(row, self.TRACK_PATH))
path = str(self._get_row_userdata(row, self.TRACK_PATH))
if not path:
return ""
return path
def _get_row_plr(self, session: scoped_session,
row: int) -> Optional[PlaylistRows]:
"""
Return PlaylistRows object for this row
"""
return session.get(PlaylistRows, self._get_row_plr_id(row))
def _get_row_plr_id(self, row: int) -> int:
"""Return the plr_id associated with this row or 0"""
plr_id = self._get_row_userdata(row, self.PLAYLISTROW_ID)
if not plr_id:
return 0
else:
return int(plr_id)
def _get_row_start_time(self, row: int) -> Optional[datetime]:
"""Return row start time as string or None"""
@ -1568,6 +1323,15 @@ class PlaylistTab(QTableWidget):
else:
return int(track_id)
def _get_row_track_path(self, row: int) -> str:
"""Return the track path associated with this row or '' """
path = self._get_row_userdata(row, self.TRACK_PATH)
if not path:
return ""
else:
return str(path)
def _get_row_userdata(self, row: int,
role: int) -> Optional[Union[str, int]]:
"""
@ -1580,6 +1344,16 @@ class PlaylistTab(QTableWidget):
return userdata_item.data(role)
def _get_section_timing_string(self, ms: int,
no_end: bool = False) -> str:
"""Return string describing section duration"""
duration = ms_to_mmss(ms)
caveat = ""
if no_end:
caveat = " (to end of playlist)"
return ' [' + duration + caveat + ']'
def _get_selected_row(self) -> Optional[int]:
"""Return row number of first selected row, or None if none selected"""
@ -1714,7 +1488,7 @@ class PlaylistTab(QTableWidget):
"""
for row_number in range(self.rowCount()):
if self._get_playlistrow_id(row_number) == plrid:
if self._get_row_plr_id(row_number) == plrid:
return row_number
return None
@ -1729,7 +1503,7 @@ class PlaylistTab(QTableWidget):
# Update playlist_rows record
with Session() as session:
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
plr = self._get_row_plr(session, row)
if not plr:
return
@ -1766,8 +1540,15 @@ class PlaylistTab(QTableWidget):
row_colour = QColor(Config.COLOUR_UNREADABLE)
else:
set_track_metadata(session, track)
self._update_row(session, row, track)
self._update_row_track_info(session, row, track)
else:
_ = self._set_row_track_id(row, 0)
note_text = self._get_row_note(row)
if note_text is None:
note_text = ""
else:
note_text += f"{track_id=} not found"
self._set_row_note(session, row, note_text)
log.error(
f"playlists._rescan({track_id=}): "
"Track not found"
@ -1970,7 +1751,7 @@ class PlaylistTab(QTableWidget):
self._set_row_colour(next_track_row, None)
# Notify musicmuster
plr = session.get(PlaylistRows, self._get_playlistrow_id(row_number))
plr = self._get_row_plr(session, row_number)
if not plr:
log.debug(f"playists._set_next({row_number=}) can't retrieve plr")
else:
@ -1986,7 +1767,7 @@ class PlaylistTab(QTableWidget):
def _set_played_row(self, session: scoped_session, row: int) -> None:
"""Mark this row as played"""
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
plr = self._get_row_plr(session, row)
if not plr:
return
@ -2101,19 +1882,38 @@ class PlaylistTab(QTableWidget):
self._set_row_bold(row, False)
def _set_row_note(self, session: scoped_session, row: int,
note_text: Optional[str]) -> QTableWidgetItem:
note_text: Optional[str],
section_header: bool = False) -> QTableWidgetItem:
"""Set row note"""
if section_header:
column = HEADER_NOTES_COLUMN
else:
column - ROW_NOTES
if not note_text:
note_text = ""
notes_item = self._set_item_text(row, ROW_NOTES, note_text)
notes_item = self._set_item_text(row, column, note_text)
note_colour = NoteColours.get_colour(session, note_text)
if section_header:
note_colour = Config.COLOUR_NOTES_PLAYLIST
else:
note_colour = NoteColours.get_colour(session, note_text)
if note_colour:
notes_item.setBackground(QColor(note_colour))
return notes_item
def _set_row_plr_id(self, row: int, plr_id: int) -> QTableWidgetItem:
"""
Set PlaylistRows id
"""
return self._set_row_userdata(row, self.PLAYLISTROW_ID, plr_id)
def _set_row_start_gap(self, row: int,
start_gap: Optional[int]) -> QTableWidgetItem:
"""
@ -2147,7 +1947,7 @@ class PlaylistTab(QTableWidget):
return self._set_item_text(row, START_TIME, time_str)
def _set_row_times(self, row: int, start: datetime,
duration: int) -> datetime:
duration: int) -> Optional[datetime]:
"""
Set row start and end times, return end time
"""
@ -2162,8 +1962,6 @@ class PlaylistTab(QTableWidget):
title: Optional[str]) -> QTableWidgetItem:
"""
Set row title.
Return QTableWidgetItem.
"""
if not title:
@ -2171,6 +1969,20 @@ class PlaylistTab(QTableWidget):
return self._set_item_text(row, TITLE, title)
def _set_row_track_id(self, row: int, track_id: int) -> QTableWidgetItem:
"""
Set track id
"""
return self._set_row_userdata(row, self.ROW_TRACK_ID, track_id)
def _set_row_track_path(self, row: int, path: str) -> QTableWidgetItem:
"""
Set track path
"""
return self._set_row_userdata(row, self.TRACK_PATH, path)
def _set_row_userdata(self, row: int, role: int,
value: Optional[Union[str, int]]) \
-> QTableWidgetItem:
@ -2187,16 +1999,6 @@ class PlaylistTab(QTableWidget):
return item
def _get_section_timing_string(self, ms: int,
no_end: bool = False) -> str:
"""Return string describing section duration"""
duration = ms_to_mmss(ms)
caveat = ""
if no_end:
caveat = " (to end of playlist)"
return ' [' + duration + caveat + ']'
def _songfacts(self, row_number: int) -> None:
"""Look up passed row title in songfacts and display info tab"""
@ -2204,6 +2006,23 @@ class PlaylistTab(QTableWidget):
self.musicmuster.tabInfolist.open_in_songfacts(title)
def _track_time_between_rows(self, session: scoped_session,
from_plr: PlaylistRows,
to_plr: PlaylistRows) -> int:
"""
Returns the total duration of all tracks in rows between
from_row and to_row inclusive
"""
plr_tracks = PlaylistRows.get_rows_with_tracks(
session, self.playlist_id, from_plr.row_number, to_plr.row_number)
total_time = 0
total_time = sum([a.track.duration for a in plr_tracks
if a.track.duration])
return total_time
def _update_note_text(self, playlist_row: PlaylistRows,
new_text: str) -> None:
"""Update note text"""
@ -2217,16 +2036,26 @@ class PlaylistTab(QTableWidget):
_ = self._set_item_text(playlist_row.row_number, column, new_text)
def _update_row(self, session, row: int, track: Tracks) -> None:
def _update_row_track_info(self, session: scoped_session, row: int,
track: Tracks) -> None:
"""
Update the passed row with info from the passed track.
"""
_ = self._set_row_start_gap(row, track.start_gap)
_ = self._set_row_title(row, track.title)
_ = self._set_row_artist(row, track.artist)
_ = self._set_row_duration(row, track.duration)
_ = self._set_row_bitrate(row, track.bitrate)
_ = self._set_row_duration(row, track.duration)
_ = self._set_row_end_time(row, None)
_ = self._set_row_last_played(
row, Playdates.last_played(session, track.id))
_ = self._set_row_start_gap(row, track.start_gap)
_ = self._set_row_start_time(row, None)
_ = self._set_row_title(row, track.title)
_ = self._set_row_track_id(row, track.id)
_ = self._set_row_track_path(row, track.path)
if not file_is_readable(track.path):
self._set_row_colour(row, QColor(Config.COLOUR_UNREADABLE))
def _update_section_headers(self, session: scoped_session) -> None:
"""
@ -2240,34 +2069,42 @@ class PlaylistTab(QTableWidget):
for plr in plrs:
if plr.note.endswith("+"):
header_rows.append(plr)
else:
try:
from_plr = header_rows.pop()
except IndexError:
pass
# section runs from from_plr to plr
from_row = from_plr.row_number
plr_tracks = PlaylistRows.get_rows_with_tracks(
session, self.playlist_id, from_row, plr.row_number)
total_time = 0
total_time = sum([a.track.duration for a in plr_tracks])
continue
assert plr.note.endswith("-")
try:
from_plr = header_rows.pop()
to_plr = plr
total_time = self._track_time_between_rows(session,
from_plr, to_plr)
time_str = self._get_section_timing_string(total_time)
self._update_note_text(from_plr, from_plr.note + time_str)
# Update section end
if plr.note.strip() == "-":
if to_plr.note.strip() == "-":
new_text = (
"[End " + from_plr.note.strip()[:-1].strip() + "]"
)
self._update_note_text(plr, new_text)
except IndexError:
continue
# If we still have plrs in header_rows, there isn't an end
# section row for them
possible_plr = self._get_row_plr(session, self.rowCount() - 1)
if possible_plr:
to_plr = possible_plr
for from_plr in header_rows:
total_time = self._track_time_between_rows(session,
from_plr, to_plr)
time_str = self._get_section_timing_string(total_time,
no_end=True)
self._update_note_text(from_plr, from_plr.note + time_str)
def _update_start_end_times(self) -> None:
""" Update track start and end times """
with Session() as session:
section_start_rows = []
section_start_rows: List[PlaylistRows] = []
current_track_end_time = self._get_current_track_end_time()
current_track_row = self._get_current_track_row_number()
current_track_start_time = self._get_current_track_start_time()