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