WIP: playlists.py refactoring
This commit is contained in:
parent
4a6ce3b4ee
commit
530ee60015
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
541
app/playlists.py
541
app/playlists.py
@ -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()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user