Rebase dev onto v2_id
This commit is contained in:
parent
a91309477b
commit
bc6a4c11cf
@ -51,8 +51,10 @@ engine = sqlalchemy.create_engine(
|
||||
echo=Config.DISPLAY_SQL,
|
||||
pool_pre_ping=True)
|
||||
|
||||
sm: sessionmaker = sessionmaker(bind=engine)
|
||||
Session = scoped_session(sm)
|
||||
# Create a Session factory
|
||||
Session = scoped_session(sessionmaker(bind=engine))
|
||||
# sm: sessionmaker = sessionmaker(bind=engine) # , expire_on_commit=False)
|
||||
# Session = scoped_session(sm)
|
||||
|
||||
Base: DeclarativeMeta = declarative_base()
|
||||
Base.metadata.create_all(engine)
|
||||
@ -105,7 +107,7 @@ class NoteColours(Base):
|
||||
Optional["NoteColours"]:
|
||||
"""Return record identified by id, or None if not found"""
|
||||
|
||||
return session.query(NoteColours).local_filter(
|
||||
return session.query(NoteColours).filter(
|
||||
NoteColours.id == note_id).first()
|
||||
|
||||
@staticmethod
|
||||
@ -116,7 +118,7 @@ class NoteColours(Base):
|
||||
|
||||
for rec in (
|
||||
session.query(NoteColours)
|
||||
.local_filter(NoteColours.enabled.is_(True))
|
||||
.filter(NoteColours.enabled.is_(True))
|
||||
.order_by(NoteColours.order)
|
||||
.all()
|
||||
):
|
||||
@ -213,7 +215,7 @@ class Playdates(Base):
|
||||
"""Return datetime track last played or None"""
|
||||
|
||||
last_played: Optional[Playdates] = session.query(
|
||||
Playdates.lastplayed).local_filter((Playdates.track_id == track_id)
|
||||
Playdates.lastplayed).filter((Playdates.track_id == track_id)
|
||||
).order_by(Playdates.lastplayed.desc()).first()
|
||||
if last_played:
|
||||
return last_played[0]
|
||||
@ -226,7 +228,7 @@ class Playdates(Base):
|
||||
Remove all records of track_id
|
||||
"""
|
||||
|
||||
session.query(Playdates).local_filter(
|
||||
session.query(Playdates).filter(
|
||||
Playdates.track_id == track_id,
|
||||
).delete()
|
||||
session.commit()
|
||||
@ -293,7 +295,7 @@ class Playlists(Base):
|
||||
|
||||
@classmethod
|
||||
def get_by_id(cls, session: Session, playlist_id: int) -> "Playlists":
|
||||
return (session.query(cls).local_filter(cls.id == playlist_id)).one()
|
||||
return (session.query(cls).filter(cls.id == playlist_id)).one()
|
||||
|
||||
@classmethod
|
||||
def get_closed(cls, session: Session) -> List["Playlists"]:
|
||||
@ -301,7 +303,7 @@ class Playlists(Base):
|
||||
|
||||
return (
|
||||
session.query(cls)
|
||||
.local_filter(cls.loaded.is_(False))
|
||||
.filter(cls.loaded.is_(False))
|
||||
.order_by(cls.last_used.desc())
|
||||
).all()
|
||||
|
||||
@ -313,7 +315,7 @@ class Playlists(Base):
|
||||
|
||||
return (
|
||||
session.query(cls)
|
||||
.local_filter(cls.loaded.is_(True))
|
||||
.filter(cls.loaded.is_(True))
|
||||
.order_by(cls.last_used.desc())
|
||||
).all()
|
||||
|
||||
@ -330,7 +332,7 @@ class Playlists(Base):
|
||||
Remove all tracks from this playlist
|
||||
"""
|
||||
|
||||
session.query(PlaylistTracks).local_filter(
|
||||
session.query(PlaylistTracks).filter(
|
||||
PlaylistTracks.playlist_id == self.id,
|
||||
).delete()
|
||||
session.commit()
|
||||
@ -338,7 +340,7 @@ class Playlists(Base):
|
||||
def remove_track(self, session: Session, row: int) -> None:
|
||||
DEBUG(f"Playlist.remove_track({self.id=}, {row=})")
|
||||
|
||||
session.query(PlaylistTracks).local_filter(
|
||||
session.query(PlaylistTracks).filter(
|
||||
PlaylistTracks.playlist_id == self.id,
|
||||
PlaylistTracks.row == row
|
||||
).delete()
|
||||
@ -389,7 +391,7 @@ class PlaylistTracks(Base):
|
||||
|
||||
new_row: int
|
||||
max_row: Optional[int] = session.query(
|
||||
func.max(PlaylistTracks.row)).local_filter(
|
||||
func.max(PlaylistTracks.row)).filter(
|
||||
PlaylistTracks.playlist_id == to_playlist_id).scalar()
|
||||
if max_row is None:
|
||||
# Destination playlist is empty; use row 0
|
||||
@ -398,7 +400,7 @@ class PlaylistTracks(Base):
|
||||
# Destination playlist has tracks; add to end
|
||||
new_row = max_row + 1
|
||||
try:
|
||||
record: PlaylistTracks = session.query(PlaylistTracks).local_filter(
|
||||
record: PlaylistTracks = session.query(PlaylistTracks).filter(
|
||||
PlaylistTracks.playlist_id == from_playlist_id,
|
||||
PlaylistTracks.row == row).one()
|
||||
except NoResultFound:
|
||||
@ -446,7 +448,7 @@ class Settings(Base):
|
||||
int_setting: Settings
|
||||
|
||||
try:
|
||||
int_setting = session.query(cls).local_filter(
|
||||
int_setting = session.query(cls).filter(
|
||||
cls.name == name).one()
|
||||
except NoResultFound:
|
||||
int_setting = Settings()
|
||||
@ -516,7 +518,7 @@ class Tracks(Base):
|
||||
DEBUG(f"Tracks.get_or_create({path=})")
|
||||
|
||||
try:
|
||||
track = session.query(cls).local_filter(cls.path == path).one()
|
||||
track = session.query(cls).filter(cls.path == path).one()
|
||||
except NoResultFound:
|
||||
track = Tracks(session, path)
|
||||
|
||||
@ -533,7 +535,7 @@ class Tracks(Base):
|
||||
|
||||
DEBUG(f"Tracks.get_track_from_filename({filename=})")
|
||||
try:
|
||||
track = session.query(Tracks).local_filter(Tracks.path.ilike(
|
||||
track = session.query(Tracks).filter(Tracks.path.ilike(
|
||||
f'%{os.path.sep}{filename}')).one()
|
||||
return track
|
||||
except (NoResultFound, MultipleResultsFound):
|
||||
@ -547,7 +549,7 @@ class Tracks(Base):
|
||||
|
||||
DEBUG(f"Tracks.get_track_from_path({path=})")
|
||||
|
||||
return session.query(Tracks).local_filter(Tracks.path == path).first()
|
||||
return session.query(Tracks).filter(Tracks.path == path).first()
|
||||
|
||||
@classmethod
|
||||
def get_by_id(cls, session: Session, track_id: int) -> Optional["Tracks"]:
|
||||
@ -555,7 +557,7 @@ class Tracks(Base):
|
||||
|
||||
try:
|
||||
DEBUG(f"Tracks.get_track(track_id={track_id})")
|
||||
track = session.query(Tracks).local_filter(Tracks.id == track_id).one()
|
||||
track = session.query(Tracks).filter(Tracks.id == track_id).one()
|
||||
return track
|
||||
except NoResultFound:
|
||||
ERROR(f"get_track({track_id}): not found")
|
||||
@ -584,7 +586,7 @@ class Tracks(Base):
|
||||
DEBUG(f"Tracks.remove_path({path=})")
|
||||
|
||||
try:
|
||||
session.query(Tracks).local_filter(Tracks.path == path).delete()
|
||||
session.query(Tracks).filter(Tracks.path == path).delete()
|
||||
session.commit()
|
||||
except IntegrityError as exception:
|
||||
ERROR(f"Can't remove track with {path=} ({exception=})")
|
||||
@ -594,7 +596,7 @@ class Tracks(Base):
|
||||
|
||||
return (
|
||||
session.query(cls)
|
||||
.local_filter(cls.artist.ilike(f"%{text}%"))
|
||||
.filter(cls.artist.ilike(f"%{text}%"))
|
||||
.order_by(cls.title)
|
||||
).all()
|
||||
|
||||
@ -602,7 +604,7 @@ class Tracks(Base):
|
||||
def search_titles(cls, session: Session, text: str) -> List["Tracks"]:
|
||||
return (
|
||||
session.query(cls)
|
||||
.local_filter(cls.title.ilike(f"%{text}%"))
|
||||
.filter(cls.title.ilike(f"%{text}%"))
|
||||
.order_by(cls.title)
|
||||
).all()
|
||||
|
||||
|
||||
@ -86,6 +86,8 @@ class Music:
|
||||
p.stop()
|
||||
DEBUG(f"Releasing player {p=}", True)
|
||||
p.release()
|
||||
# Ensure we don't reference player after release
|
||||
p = None
|
||||
|
||||
self.fading -= 1
|
||||
|
||||
|
||||
@ -412,7 +412,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
rows = []
|
||||
for (row, track) in (
|
||||
self.visible_playlist_tab().get_selected_rows_and_tracks()
|
||||
self.visible_playlist_tab().get_selected_rows_and_tracks(
|
||||
session)
|
||||
):
|
||||
rows.append(row)
|
||||
if destination_visible_playlist_tab:
|
||||
@ -436,8 +437,14 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
Ensure we have info tabs for next and current track titles
|
||||
"""
|
||||
|
||||
title_list: List[str, str] = [self.previous_track.title,
|
||||
self.current_track.title]
|
||||
title_list: List[str] = []
|
||||
|
||||
if self.previous_track:
|
||||
title_list.append(self.previous_track.title)
|
||||
if self.current_track:
|
||||
title_list.append(self.current_track.title)
|
||||
if self.next_track:
|
||||
title_list.append(self.next_track.title)
|
||||
|
||||
for title in title_list:
|
||||
if title in self.info_tabs.keys():
|
||||
@ -533,7 +540,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
Playdates(session, self.current_track)
|
||||
|
||||
# Tell playlist track is now playing
|
||||
self.current_track_playlist_tab.play_started()
|
||||
self.current_track_playlist_tab.play_started(session)
|
||||
|
||||
# Disable play next controls
|
||||
self.disable_play_next_controls()
|
||||
@ -680,38 +687,39 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
"""
|
||||
|
||||
# Clear next track if on another tab
|
||||
if self.next_track_playlist_tab != playlist_tab:
|
||||
# We need to reset the ex-next-track playlist
|
||||
if self.next_track_playlist_tab:
|
||||
self.next_track_playlist_tab.clear_next()
|
||||
with Session() as session:
|
||||
# Clear next track if on another tab
|
||||
if self.next_track_playlist_tab != playlist_tab:
|
||||
# We need to reset the ex-next-track playlist
|
||||
if self.next_track_playlist_tab:
|
||||
self.next_track_playlist_tab.clear_next(session)
|
||||
|
||||
# Reset tab colour if on other tab
|
||||
if (self.next_track_playlist_tab !=
|
||||
self.current_track_playlist_tab):
|
||||
self.set_tab_colour(
|
||||
self.next_track_playlist_tab,
|
||||
QColor(Config.COLOUR_NORMAL_TAB))
|
||||
# Reset tab colour if on other tab
|
||||
if (self.next_track_playlist_tab !=
|
||||
self.current_track_playlist_tab):
|
||||
self.set_tab_colour(
|
||||
self.next_track_playlist_tab,
|
||||
QColor(Config.COLOUR_NORMAL_TAB))
|
||||
|
||||
# Note next playlist tab
|
||||
self.next_track_playlist_tab = playlist_tab
|
||||
# Note next playlist tab
|
||||
self.next_track_playlist_tab = playlist_tab
|
||||
|
||||
# Set next playlist_tab tab colour if it isn't the
|
||||
# currently-playing tab
|
||||
if (self.next_track_playlist_tab !=
|
||||
self.current_track_playlist_tab):
|
||||
self.set_tab_colour(
|
||||
self.next_track_playlist_tab,
|
||||
QColor(Config.COLOUR_NEXT_TAB))
|
||||
# Set next playlist_tab tab colour if it isn't the
|
||||
# currently-playing tab
|
||||
if (self.next_track_playlist_tab !=
|
||||
self.current_track_playlist_tab):
|
||||
self.set_tab_colour(
|
||||
self.next_track_playlist_tab,
|
||||
QColor(Config.COLOUR_NEXT_TAB))
|
||||
|
||||
# Note next track
|
||||
self.next_track = track
|
||||
# Note next track
|
||||
self.next_track = track
|
||||
|
||||
# Update headers
|
||||
self.update_headers()
|
||||
# Update headers
|
||||
self.update_headers()
|
||||
|
||||
# Populate 'info' tabs
|
||||
self.open_info_tabs()
|
||||
# Populate 'info' tabs
|
||||
self.open_info_tabs()
|
||||
|
||||
def tick(self) -> None:
|
||||
"""
|
||||
@ -739,52 +747,55 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
# If track is playing, update track clocks time and colours
|
||||
if self.music.player and self.music.playing():
|
||||
self.playing = True
|
||||
playtime: int = self.music.get_playtime()
|
||||
time_to_fade: int = (self.current_track.fade_at - playtime)
|
||||
time_to_silence: int = (self.current_track.silence_at - playtime)
|
||||
time_to_end: int = (self.current_track.duration - playtime)
|
||||
with Session() as session:
|
||||
self.playing = True
|
||||
if self.current_track not in session:
|
||||
session.add(self.current_track)
|
||||
playtime: int = self.music.get_playtime()
|
||||
time_to_fade: int = (self.current_track.fade_at - playtime)
|
||||
time_to_silence: int = (self.current_track.silence_at - playtime)
|
||||
time_to_end: int = (self.current_track.duration - playtime)
|
||||
|
||||
# Elapsed time
|
||||
if time_to_end < 500:
|
||||
self.label_elapsed_timer.setText(
|
||||
helpers.ms_to_mmss(self.current_track.duration)
|
||||
# Elapsed time
|
||||
if time_to_end < 500:
|
||||
self.label_elapsed_timer.setText(
|
||||
helpers.ms_to_mmss(self.current_track.duration)
|
||||
)
|
||||
else:
|
||||
self.label_elapsed_timer.setText(helpers.ms_to_mmss(playtime))
|
||||
|
||||
# Time to fade
|
||||
self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade))
|
||||
|
||||
# If silent in the next 5 seconds, put warning colour on
|
||||
# time to silence box and enable play controls
|
||||
if time_to_silence <= 5500:
|
||||
self.frame_silent.setStyleSheet(
|
||||
f"background: {Config.COLOUR_ENDING_TIMER}"
|
||||
)
|
||||
self.enable_play_next_controls()
|
||||
# Set warning colour on time to silence box when fade starts
|
||||
elif time_to_fade <= 500:
|
||||
self.frame_silent.setStyleSheet(
|
||||
f"background: {Config.COLOUR_WARNING_TIMER}"
|
||||
)
|
||||
# Five seconds before fade starts, set warning colour on
|
||||
# time to silence box and enable play controls
|
||||
elif time_to_fade <= 5500:
|
||||
self.frame_fade.setStyleSheet(
|
||||
f"background: {Config.COLOUR_WARNING_TIMER}"
|
||||
)
|
||||
self.enable_play_next_controls()
|
||||
else:
|
||||
self.frame_silent.setStyleSheet("")
|
||||
self.frame_fade.setStyleSheet("")
|
||||
|
||||
self.label_silent_timer.setText(
|
||||
helpers.ms_to_mmss(time_to_silence)
|
||||
)
|
||||
else:
|
||||
self.label_elapsed_timer.setText(helpers.ms_to_mmss(playtime))
|
||||
|
||||
# Time to fade
|
||||
self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade))
|
||||
|
||||
# If silent in the next 5 seconds, put warning colour on
|
||||
# time to silence box and enable play controls
|
||||
if time_to_silence <= 5500:
|
||||
self.frame_silent.setStyleSheet(
|
||||
f"background: {Config.COLOUR_ENDING_TIMER}"
|
||||
)
|
||||
self.enable_play_next_controls()
|
||||
# Set warning colour on time to silence box when fade starts
|
||||
elif time_to_fade <= 500:
|
||||
self.frame_silent.setStyleSheet(
|
||||
f"background: {Config.COLOUR_WARNING_TIMER}"
|
||||
)
|
||||
# Five seconds before fade starts, set warning colour on
|
||||
# time to silence box and enable play controls
|
||||
elif time_to_fade <= 5500:
|
||||
self.frame_fade.setStyleSheet(
|
||||
f"background: {Config.COLOUR_WARNING_TIMER}"
|
||||
)
|
||||
self.enable_play_next_controls()
|
||||
else:
|
||||
self.frame_silent.setStyleSheet("")
|
||||
self.frame_fade.setStyleSheet("")
|
||||
|
||||
self.label_silent_timer.setText(
|
||||
helpers.ms_to_mmss(time_to_silence)
|
||||
)
|
||||
|
||||
# Time to end
|
||||
self.label_end_timer.setText(helpers.ms_to_mmss(time_to_end))
|
||||
# Time to end
|
||||
self.label_end_timer.setText(helpers.ms_to_mmss(time_to_end))
|
||||
|
||||
else:
|
||||
if self.playing:
|
||||
|
||||
452
app/playlists.py
452
app/playlists.py
@ -16,6 +16,7 @@ from PyQt5.QtWidgets import (
|
||||
QTableWidgetItem,
|
||||
)
|
||||
from sqlalchemy import inspect
|
||||
from sqlalchemy.orm.exc import DetachedInstanceError
|
||||
|
||||
import helpers
|
||||
import os
|
||||
@ -192,9 +193,9 @@ class PlaylistTab(QTableWidget):
|
||||
f"Moved row(s) {rows} to become row {drop_row}"
|
||||
)
|
||||
|
||||
with Session() as session:
|
||||
with Session() as session: # checked
|
||||
self.save_playlist(session)
|
||||
self.update_display()
|
||||
self.update_display(session)
|
||||
|
||||
def edit(self, index, trigger, event): # review
|
||||
result = super(PlaylistTab, self).edit(index, trigger, event)
|
||||
@ -249,7 +250,7 @@ class PlaylistTab(QTableWidget):
|
||||
def closeEvent(self, event) -> None:
|
||||
"""Save column widths"""
|
||||
|
||||
with Session() as session:
|
||||
with Session() as session: # checked
|
||||
for column in range(self.columnCount()):
|
||||
width = self.columnWidth(column)
|
||||
name = f"playlist_col_{str(column)}_width"
|
||||
@ -259,11 +260,11 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
event.accept()
|
||||
|
||||
def clear_next(self) -> None:
|
||||
def clear_next(self, session) -> None:
|
||||
"""Clear next track"""
|
||||
|
||||
self._meta_clear_next()
|
||||
self.update_display()
|
||||
self.update_display(session)
|
||||
|
||||
def create_note(self) -> None:
|
||||
"""
|
||||
@ -287,7 +288,7 @@ class PlaylistTab(QTableWidget):
|
||||
with Session() as session:
|
||||
note: Notes = Notes(
|
||||
session, self.playlist.id, row, dlg.textValue())
|
||||
self._insert_note(session, note, row, True)
|
||||
self._insert_note(session, note, row, True) # checked
|
||||
|
||||
def get_selected_row(self) -> Optional[int]:
|
||||
"""Return row number of first selected row, or None if none selected"""
|
||||
@ -297,7 +298,7 @@ class PlaylistTab(QTableWidget):
|
||||
else:
|
||||
return self.selectionModel().selectedRows()[0].row()
|
||||
|
||||
def get_selected_rows_and_tracks(self) \
|
||||
def get_selected_rows_and_tracks(self, session: Session) \
|
||||
-> Optional[List[Tuple[int, Tracks]]]:
|
||||
"""Return a list of selected (row-number, track) tuples"""
|
||||
|
||||
@ -307,7 +308,7 @@ class PlaylistTab(QTableWidget):
|
||||
result = []
|
||||
|
||||
for row in [r.row() for r in self.selectionModel().selectedRows()]:
|
||||
result.append((row, self._get_row_object(row)))
|
||||
result.append((row, self._get_row_object(row, session)))
|
||||
|
||||
return result
|
||||
|
||||
@ -378,14 +379,14 @@ class PlaylistTab(QTableWidget):
|
||||
self._set_row_content(row, track)
|
||||
|
||||
# Mark track if file is unreadable
|
||||
if not self._track_is_readable(track):
|
||||
if not self._file_is_readable(track.path):
|
||||
self._meta_set_unreadable(row)
|
||||
# Scroll to new row
|
||||
self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter)
|
||||
|
||||
if repaint:
|
||||
self.save_playlist(session)
|
||||
self.update_display(clear_selection=False)
|
||||
self.update_display(session, clear_selection=False)
|
||||
|
||||
def remove_rows(self, rows) -> None:
|
||||
"""Remove rows passed in rows list"""
|
||||
@ -398,10 +399,9 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
with Session() as session:
|
||||
self.save_playlist(session)
|
||||
self.update_display(session)
|
||||
|
||||
self.update_display()
|
||||
|
||||
def play_started(self) -> None:
|
||||
def play_started(self, session: Session) -> None:
|
||||
"""
|
||||
Notification from musicmuster that track has started playing.
|
||||
|
||||
@ -425,16 +425,17 @@ class PlaylistTab(QTableWidget):
|
||||
self._meta_set_played(current_row)
|
||||
|
||||
# Scroll to put current track in middle
|
||||
scroll_to = self.item(current_row, self.COL_INDEX)
|
||||
scroll_to = self.item(current_row, self.COL_MSS)
|
||||
self.scrollToItem(scroll_to, QAbstractItemView.PositionAtCenter)
|
||||
|
||||
# Set next track
|
||||
search_from = current_row + 1
|
||||
next_row = self._find_next_track_row(search_from)
|
||||
self._set_next(next_row)
|
||||
if next_row:
|
||||
self._set_next(next_row)
|
||||
|
||||
# Update display
|
||||
self.update_display()
|
||||
self.update_display(session)
|
||||
|
||||
def play_stopped(self) -> None:
|
||||
"""
|
||||
@ -448,7 +449,6 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
self._meta_clear_current()
|
||||
self.current_track_start_time = None
|
||||
self.update_display()
|
||||
|
||||
def save_playlist(self, session) -> None:
|
||||
"""
|
||||
@ -474,7 +474,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# PlaylistTab
|
||||
for row in notes_rows:
|
||||
note: Notes = self._get_row_object(row)
|
||||
note: Notes = self._get_row_object(row, session)
|
||||
session.add(note)
|
||||
playlist_notes[note.id] = note
|
||||
|
||||
@ -604,9 +604,9 @@ class PlaylistTab(QTableWidget):
|
||||
if row is None:
|
||||
return None
|
||||
|
||||
self.set_next_track(row)
|
||||
self._set_next(row)
|
||||
|
||||
def update_display(self, clear_selection: bool = True) -> None:
|
||||
def update_display(self, session, clear_selection: bool = True) -> None:
|
||||
"""
|
||||
Set row colours, fonts, etc
|
||||
|
||||
@ -618,6 +618,8 @@ class PlaylistTab(QTableWidget):
|
||||
- Show unplayed tracks in bold
|
||||
"""
|
||||
|
||||
if self.playlist not in session:
|
||||
session.add(self.playlist)
|
||||
DEBUG(f"playlist. update_display [{self.playlist=}]")
|
||||
|
||||
# Clear selection if required
|
||||
@ -654,120 +656,119 @@ class PlaylistTab(QTableWidget):
|
||||
if not start_times_row:
|
||||
start_times_row = 0
|
||||
|
||||
with Session() as session:
|
||||
# Cycle through all rows
|
||||
for row in range(self.rowCount()):
|
||||
# Cycle through all rows
|
||||
for row in range(self.rowCount()):
|
||||
|
||||
# Render notes in correct colour
|
||||
if row in notes:
|
||||
# Extract note text
|
||||
note_text = self.item(row, self.COL_TITLE).text()
|
||||
# Does the note have a start time?
|
||||
row_time = self._get_note_text_time(note_text)
|
||||
if row_time:
|
||||
next_start_time = row_time
|
||||
# Set colour
|
||||
note_colour = NoteColours.get_colour(session, note_text)
|
||||
if not note_colour:
|
||||
note_colour = Config.COLOUR_NOTES_PLAYLIST
|
||||
self._set_row_colour(
|
||||
row, QColor(note_colour)
|
||||
)
|
||||
# Notes are always bold
|
||||
self._set_row_bold(row)
|
||||
# Render notes in correct colour
|
||||
if row in notes:
|
||||
# Extract note text
|
||||
note_text = self.item(row, self.COL_TITLE).text()
|
||||
# Does the note have a start time?
|
||||
row_time = self._get_note_text_time(note_text)
|
||||
if row_time:
|
||||
next_start_time = row_time
|
||||
# Set colour
|
||||
note_colour = NoteColours.get_colour(session, note_text)
|
||||
if not note_colour:
|
||||
note_colour = Config.COLOUR_NOTES_PLAYLIST
|
||||
self._set_row_colour(
|
||||
row, QColor(note_colour)
|
||||
)
|
||||
# Notes are always bold
|
||||
self._set_row_bold(row)
|
||||
|
||||
# Render unplayable tracks in correct colour
|
||||
elif row in unreadable:
|
||||
self._set_row_colour(
|
||||
row, QColor(Config.COLOUR_UNREADABLE)
|
||||
)
|
||||
self._set_row_bold(row)
|
||||
# Render unplayable tracks in correct colour
|
||||
elif row in unreadable:
|
||||
self._set_row_colour(
|
||||
row, QColor(Config.COLOUR_UNREADABLE)
|
||||
)
|
||||
self._set_row_bold(row)
|
||||
|
||||
# Render current track
|
||||
elif row == current_row:
|
||||
# Set start time
|
||||
self._set_row_start_time(
|
||||
row, self.current_track_start_time)
|
||||
# Render current track
|
||||
elif row == current_row:
|
||||
# Set start time
|
||||
self._set_row_start_time(
|
||||
row, self.current_track_start_time)
|
||||
|
||||
# Set last played time
|
||||
last_played_str = get_relative_date(
|
||||
self.current_track_start_time)
|
||||
# Set last played time
|
||||
last_played_str = get_relative_date(
|
||||
self.current_track_start_time)
|
||||
self.item(row, self.COL_LAST_PLAYED).setText(
|
||||
last_played_str)
|
||||
|
||||
# Calculate next_start_time
|
||||
track = self._get_row_object(row, session)
|
||||
next_start_time = self._calculate_track_end_time(
|
||||
track, self.current_track_start_time)
|
||||
|
||||
# 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)
|
||||
|
||||
# Render next track
|
||||
elif row == next_row:
|
||||
# if there's a track playing, set start time from that
|
||||
if current_row:
|
||||
start_time = self.current_track_start_time
|
||||
else:
|
||||
# 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)
|
||||
|
||||
# Set end time
|
||||
track = self._get_row_object(row, session)
|
||||
next_start_time = self._calculate_track_end_time(
|
||||
track, start_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)
|
||||
|
||||
else:
|
||||
# This is a track row other than next or current
|
||||
track = self._get_row_object(row, session)
|
||||
if row in played:
|
||||
# Played today, so update last played column
|
||||
last_playedtime = Playdates.last_played(
|
||||
session, track.id)
|
||||
last_played_str = get_relative_date(last_playedtime)
|
||||
self.item(row, self.COL_LAST_PLAYED).setText(
|
||||
last_played_str)
|
||||
|
||||
# Calculate next_start_time
|
||||
track = self._get_row_object(row)
|
||||
next_start_time = self._calculate_track_end_time(
|
||||
track, self.current_track_start_time)
|
||||
|
||||
# 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)
|
||||
|
||||
# Render next track
|
||||
elif row == next_row:
|
||||
# if there's a track playing, set start time from that
|
||||
if current_row:
|
||||
start_time = self.current_track_start_time
|
||||
else:
|
||||
# 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)
|
||||
|
||||
# Set end time
|
||||
track = self._get_row_object(row)
|
||||
next_start_time = self._calculate_track_end_time(
|
||||
track, start_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)
|
||||
|
||||
self._set_row_not_bold(row)
|
||||
else:
|
||||
# This is a track row other than next or current
|
||||
track = self._get_row_object(row)
|
||||
if row in played:
|
||||
# Played today, so update last played column
|
||||
last_playedtime = Playdates.last_played(
|
||||
session, track.id)
|
||||
last_played_str = get_relative_date(last_playedtime)
|
||||
self.item(row, self.COL_LAST_PLAYED).setText(
|
||||
last_played_str)
|
||||
self._set_row_not_bold(row)
|
||||
# Set start/end times as we haven't played it yet
|
||||
if next_start_time and row >= start_times_row:
|
||||
self._set_row_start_time(row, next_start_time)
|
||||
next_start_time = self._calculate_track_end_time(
|
||||
track, next_start_time)
|
||||
# Set end time
|
||||
self._set_row_end_time(row, next_start_time)
|
||||
else:
|
||||
# Set start/end times as we haven't played it yet
|
||||
if next_start_time and row >= start_times_row:
|
||||
self._set_row_start_time(row, next_start_time)
|
||||
next_start_time = self._calculate_track_end_time(
|
||||
track, 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)
|
||||
# Stripe rows
|
||||
if row % 2:
|
||||
self._set_row_colour(
|
||||
row, QColor(Config.COLOUR_ODD_PLAYLIST))
|
||||
else:
|
||||
self._set_row_colour(
|
||||
row, QColor(Config.COLOUR_EVEN_PLAYLIST))
|
||||
# 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)
|
||||
# Stripe rows
|
||||
if row % 2:
|
||||
self._set_row_colour(
|
||||
row, QColor(Config.COLOUR_ODD_PLAYLIST))
|
||||
else:
|
||||
self._set_row_colour(
|
||||
row, QColor(Config.COLOUR_EVEN_PLAYLIST))
|
||||
|
||||
# ########## Internally called functions ##########
|
||||
|
||||
@ -779,8 +780,9 @@ class PlaylistTab(QTableWidget):
|
||||
if row in self._meta_get_notes():
|
||||
return None
|
||||
|
||||
track: Tracks = self._get_row_object(row)
|
||||
open_in_audacity(track.path)
|
||||
with Session() as session:
|
||||
track: Tracks = self._get_row_object(row, session)
|
||||
open_in_audacity(track.path)
|
||||
|
||||
@staticmethod
|
||||
def _calculate_track_end_time(track: Tracks,
|
||||
@ -811,11 +813,12 @@ class PlaylistTab(QTableWidget):
|
||||
if row in self._meta_get_notes():
|
||||
return None
|
||||
|
||||
track: Optional[Tracks] = self._get_row_object(row)
|
||||
if track:
|
||||
cb: QApplication.clipboard = QApplication.clipboard()
|
||||
cb.clear(mode=cb.Clipboard)
|
||||
cb.setText(track.path, mode=cb.Clipboard)
|
||||
with Session() as session:
|
||||
track: Optional[Tracks] = self._get_row_object(row, session)
|
||||
if track:
|
||||
cb: QApplication.clipboard = QApplication.clipboard()
|
||||
cb.clear(mode=cb.Clipboard)
|
||||
cb.setText(track.path, mode=cb.Clipboard)
|
||||
|
||||
def _cell_changed(self, row: int, column: int) -> None:
|
||||
"""Called when cell content has changed"""
|
||||
@ -828,8 +831,9 @@ class PlaylistTab(QTableWidget):
|
||||
new_text: str = self.item(row, column).text()
|
||||
DEBUG(f"_cell_changed({row=}, {column=}, {new_text=}")
|
||||
|
||||
row_object: Union[Tracks, Notes] = self._get_row_object(row)
|
||||
with Session() as session:
|
||||
row_object: Union[Tracks, Notes] = self._get_row_object(
|
||||
row, session)
|
||||
if row in self._meta_get_notes():
|
||||
# Save change to database
|
||||
DEBUG(f"Notes.update_note: saving new note text '{new_text=}'")
|
||||
@ -866,7 +870,8 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# update_display to update start times, such as when a note has
|
||||
# been edited
|
||||
self.update_display()
|
||||
with Session() as session:
|
||||
self.update_display(session)
|
||||
|
||||
self.parent.enable_play_next_controls()
|
||||
|
||||
@ -908,18 +913,18 @@ class PlaylistTab(QTableWidget):
|
||||
if msg.exec() == QMessageBox.Yes:
|
||||
rows_to_delete.append(row)
|
||||
|
||||
# delete in reverse row order so row numbers don't
|
||||
# change
|
||||
for row in sorted(rows_to_delete, reverse=True):
|
||||
row_object = self._get_row_object(row)
|
||||
if row in note_rows:
|
||||
row_object.delete_note(session)
|
||||
else:
|
||||
self.remove_track(session, row)
|
||||
self.removeRow(row)
|
||||
# delete in reverse row order so row numbers don't
|
||||
# change
|
||||
for row in sorted(rows_to_delete, reverse=True):
|
||||
row_object = self._get_row_object(row, session)
|
||||
if row in note_rows:
|
||||
row_object.delete_note(session)
|
||||
else:
|
||||
self.remove_track(session, row)
|
||||
self.removeRow(row)
|
||||
|
||||
self.save_playlist(session)
|
||||
self.update_display()
|
||||
self.save_playlist(session)
|
||||
self.update_display(session)
|
||||
|
||||
def _drop_on(self, event): # review
|
||||
index = self.indexAt(event.pos())
|
||||
@ -958,10 +963,14 @@ class PlaylistTab(QTableWidget):
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def _get_row_object(self, row: int) -> Union[Tracks, Notes]:
|
||||
def _get_row_object(self, row: int, session: Session) \
|
||||
-> Union[Tracks, Notes]:
|
||||
"""Return content associated with this row"""
|
||||
|
||||
return self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
|
||||
obj = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
|
||||
if obj not in session:
|
||||
session.add(obj)
|
||||
return obj
|
||||
|
||||
def _get_row_start_time(self, row: int) -> Optional[datetime]:
|
||||
try:
|
||||
@ -980,27 +989,29 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
txt: str
|
||||
|
||||
row_object: Union[Tracks, Notes] = self._get_row_object(row)
|
||||
if row in self._meta_get_notes():
|
||||
txt = row_object.note
|
||||
else:
|
||||
track = row_object
|
||||
txt = (
|
||||
f"Title: {track.title}\n"
|
||||
f"Artist: {track.artist}\n"
|
||||
f"Track ID: {track.id}\n"
|
||||
f"Track duration: {helpers.ms_to_mmss(track.duration)}\n"
|
||||
f"Track fade at: {helpers.ms_to_mmss(track.fade_at)}\n"
|
||||
f"Track silence at: {helpers.ms_to_mmss(track.silence_at)}"
|
||||
"\n\n"
|
||||
f"Path: {track.path}\n"
|
||||
)
|
||||
info: QMessageBox = QMessageBox(self)
|
||||
info.setIcon(QMessageBox.Information)
|
||||
info.setText(txt)
|
||||
info.setStandardButtons(QMessageBox.Ok)
|
||||
info.setDefaultButton(QMessageBox.Cancel)
|
||||
info.exec()
|
||||
with Session() as session:
|
||||
row_object: Union[Tracks, Notes] = self._get_row_object(
|
||||
row, session)
|
||||
if row in self._meta_get_notes():
|
||||
txt = row_object.note
|
||||
else:
|
||||
track = row_object
|
||||
txt = (
|
||||
f"Title: {track.title}\n"
|
||||
f"Artist: {track.artist}\n"
|
||||
f"Track ID: {track.id}\n"
|
||||
f"Track duration: {helpers.ms_to_mmss(track.duration)}\n"
|
||||
f"Track fade at: {helpers.ms_to_mmss(track.fade_at)}\n"
|
||||
f"Track silence at: {helpers.ms_to_mmss(track.silence_at)}"
|
||||
"\n\n"
|
||||
f"Path: {track.path}\n"
|
||||
)
|
||||
info: QMessageBox = QMessageBox(self)
|
||||
info.setIcon(QMessageBox.Information)
|
||||
info.setText(txt)
|
||||
info.setStandardButtons(QMessageBox.Ok)
|
||||
info.setDefaultButton(QMessageBox.Cancel)
|
||||
info.exec()
|
||||
|
||||
def _insert_note(self, session: Session, note: Notes,
|
||||
row: Optional[int] = None, repaint: bool = True) -> None:
|
||||
@ -1040,7 +1051,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
if repaint:
|
||||
self.save_playlist(session)
|
||||
self.update_display(clear_selection=False)
|
||||
self.update_display(session, clear_selection=False)
|
||||
|
||||
def _is_below(self, pos, index): # review
|
||||
rect = self.visualRect(index)
|
||||
@ -1110,6 +1121,13 @@ class PlaylistTab(QTableWidget):
|
||||
current_row: Optional[int] = self._meta_get_current()
|
||||
if current_row is not None:
|
||||
self._meta_clear_attribute(current_row, RowMeta.CURRENT)
|
||||
# Reset row colour
|
||||
if current_row % 2:
|
||||
self._set_row_colour(
|
||||
current_row, QColor(Config.COLOUR_ODD_PLAYLIST))
|
||||
else:
|
||||
self._set_row_colour(
|
||||
current_row, QColor(Config.COLOUR_EVEN_PLAYLIST))
|
||||
|
||||
def _meta_clear_next(self) -> None:
|
||||
"""
|
||||
@ -1274,7 +1292,7 @@ class PlaylistTab(QTableWidget):
|
||||
# We possibly don't need to save the playlist here, but row
|
||||
# numbers may have changed during population, and it's cheap to do
|
||||
self.save_playlist(session)
|
||||
self.update_display()
|
||||
self.update_display(session)
|
||||
|
||||
def _rescan(self, row: int) -> None:
|
||||
"""
|
||||
@ -1287,11 +1305,11 @@ class PlaylistTab(QTableWidget):
|
||||
if row in self._meta_get_notes():
|
||||
return None
|
||||
|
||||
track: Tracks = self._get_row_object(row)
|
||||
if track:
|
||||
with Session() as session:
|
||||
with Session() as session:
|
||||
track: Tracks = self._get_row_object(row, session)
|
||||
if track:
|
||||
track.rescan(session)
|
||||
self._update_row(row, track)
|
||||
self._update_row(session, row, track)
|
||||
|
||||
def _select_event(self) -> None:
|
||||
"""
|
||||
@ -1304,15 +1322,19 @@ class PlaylistTab(QTableWidget):
|
||||
track_rows = list(row_set - note_row_set)
|
||||
tracks: List[Tracks]
|
||||
|
||||
tracks = [self._get_row_object(row) for row in track_rows]
|
||||
ms: int = sum([track.duration for track in tracks])
|
||||
with Session() as session: # checked
|
||||
tracks = [self._get_row_object(row, session) for row in track_rows]
|
||||
for track in tracks:
|
||||
if track not in session:
|
||||
session.add(track)
|
||||
ms: int = sum([track.duration for track in tracks])
|
||||
|
||||
# Only paint message if there are selected track rows
|
||||
if ms > 0:
|
||||
self.parent.lblSumPlaytime.setText(
|
||||
f"Selected duration: {helpers.ms_to_mmss(ms)}")
|
||||
else:
|
||||
self.parent.lblSumPlaytime.setText("")
|
||||
# Only paint message if there are selected track rows
|
||||
if ms > 0:
|
||||
self.parent.lblSumPlaytime.setText(
|
||||
f"Selected duration: {helpers.ms_to_mmss(ms)}")
|
||||
else:
|
||||
self.parent.lblSumPlaytime.setText("")
|
||||
|
||||
def _set_column_widths(self) -> None:
|
||||
"""Column widths from settings"""
|
||||
@ -1340,26 +1362,27 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
DEBUG(f"_set_next({row=})")
|
||||
|
||||
# Check row is a track row
|
||||
if row in self._meta_get_notes():
|
||||
return None
|
||||
track: Tracks = self._get_row_object(row)
|
||||
if not track:
|
||||
return None
|
||||
with Session() as session:
|
||||
# Check row is a track row
|
||||
if row in self._meta_get_notes():
|
||||
return None
|
||||
track: Tracks = self._get_row_object(row, session)
|
||||
if not track:
|
||||
return None
|
||||
|
||||
# Check track is readable
|
||||
if not self._track_is_readable(track):
|
||||
self._meta_set_unreadable(row)
|
||||
return None
|
||||
# Check track is readable
|
||||
if not self._file_is_readable(track.path):
|
||||
self._meta_set_unreadable(row)
|
||||
return None
|
||||
|
||||
# Mark as next track
|
||||
self._meta_set_next(row)
|
||||
# Mark as next track
|
||||
self._meta_set_next(row)
|
||||
|
||||
# Notify musicmuster
|
||||
self.parent.this_is_the_next_track(self, track)
|
||||
# Notify musicmuster
|
||||
self.parent.this_is_the_next_track(self, track)
|
||||
|
||||
# Update display
|
||||
self.update_display()
|
||||
# Update display
|
||||
self.update_display(session)
|
||||
|
||||
def _set_row_bold(self, row: int, bold: bool = True) -> None:
|
||||
"""Make row bold (bold=True) or not bold"""
|
||||
@ -1415,23 +1438,42 @@ class PlaylistTab(QTableWidget):
|
||||
item: QTableWidgetItem = QTableWidgetItem(time_str)
|
||||
self.setItem(row, self.COL_START_TIME, item)
|
||||
|
||||
def _track_path_is_readable(self, track_id):
|
||||
def _select_tracks(self, played: bool) -> None:
|
||||
"""
|
||||
Select all played (played=True) or unplayed (played=False)
|
||||
tracks in playlist
|
||||
"""
|
||||
|
||||
# Need to allow multiple rows to be selected
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
||||
notes_rows: List[int] = self._meta_get_notes()
|
||||
self.clearSelection()
|
||||
|
||||
played_rows: List[int] = self._meta_get_played()
|
||||
for row in range(self.rowCount()):
|
||||
if row in notes_rows:
|
||||
continue
|
||||
if row in played_rows == played:
|
||||
self.selectRow(row)
|
||||
|
||||
# Reset extended selection
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
|
||||
@staticmethod
|
||||
def _file_is_readable(path: str) -> bool:
|
||||
"""
|
||||
Returns True if track path is readable, else False
|
||||
|
||||
vlc cannot read files with a colon in the path
|
||||
"""
|
||||
|
||||
with Session() as session:
|
||||
path = Tracks.get_path(session, track_id)
|
||||
|
||||
if os.access(path, os.R_OK):
|
||||
if ':' not in path:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _update_row(self, row: int, track: Tracks) -> None:
|
||||
def _update_row(self, session, row: int, track: Tracks) -> None:
|
||||
"""
|
||||
Update the passed row with info from the passed track.
|
||||
"""
|
||||
@ -1454,4 +1496,4 @@ class PlaylistTab(QTableWidget):
|
||||
item_duration: QTableWidgetItem = self.item(row, self.COL_DURATION)
|
||||
item_duration.setText(helpers.ms_to_mmss(track.duration))
|
||||
|
||||
self.update_display()
|
||||
self.update_display(session)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user