V2 using ids rather than objects. Looking good.
This commit is contained in:
parent
26edd5a2d0
commit
e8211414f9
@ -118,9 +118,9 @@ class NoteColours(Base):
|
|||||||
|
|
||||||
for rec in (
|
for rec in (
|
||||||
session.query(NoteColours)
|
session.query(NoteColours)
|
||||||
.filter(NoteColours.enabled.is_(True))
|
.filter(NoteColours.enabled.is_(True))
|
||||||
.order_by(NoteColours.order)
|
.order_by(NoteColours.order)
|
||||||
.all()
|
.all()
|
||||||
):
|
):
|
||||||
if rec.is_regex:
|
if rec.is_regex:
|
||||||
flags = re.UNICODE
|
flags = re.UNICODE
|
||||||
@ -175,6 +175,18 @@ class Notes(Base):
|
|||||||
session.query(Notes).filter_by(id=self.id).delete()
|
session.query(Notes).filter_by(id=self.id).delete()
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_id(cls, session: Session, note_id: int) -> Optional["Notes"]:
|
||||||
|
"""Return note or None"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
DEBUG(f"Notes.get_track(track_id={note_id})")
|
||||||
|
note = session.query(cls).filter(cls.id == note_id).one()
|
||||||
|
return note
|
||||||
|
except NoResultFound:
|
||||||
|
ERROR(f"get_track({note_id}): not found")
|
||||||
|
return None
|
||||||
|
|
||||||
def update_note(
|
def update_note(
|
||||||
self, session: Session, row: int,
|
self, session: Session, row: int,
|
||||||
text: Optional[str] = None) -> None:
|
text: Optional[str] = None) -> None:
|
||||||
@ -215,8 +227,9 @@ 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).filter((Playdates.track_id == track_id)
|
Playdates.lastplayed).filter(
|
||||||
).order_by(Playdates.lastplayed.desc()).first()
|
(Playdates.track_id == track_id)
|
||||||
|
).order_by(Playdates.lastplayed.desc()).first()
|
||||||
if last_played:
|
if last_played:
|
||||||
return last_played[0]
|
return last_played[0]
|
||||||
else:
|
else:
|
||||||
@ -246,7 +259,9 @@ class Playlists(Base):
|
|||||||
last_used: datetime = Column(DateTime, default=None, nullable=True)
|
last_used: datetime = Column(DateTime, default=None, nullable=True)
|
||||||
loaded: bool = Column(Boolean, default=True, nullable=False)
|
loaded: bool = Column(Boolean, default=True, nullable=False)
|
||||||
notes = relationship(
|
notes = relationship(
|
||||||
"Notes", order_by="Notes.row", back_populates="playlist", lazy="joined")
|
"Notes", order_by="Notes.row",
|
||||||
|
back_populates="playlist", lazy="joined"
|
||||||
|
)
|
||||||
|
|
||||||
tracks = association_proxy('playlist_tracks', 'tracks')
|
tracks = association_proxy('playlist_tracks', 'tracks')
|
||||||
row = association_proxy('playlist_tracks', 'row')
|
row = association_proxy('playlist_tracks', 'row')
|
||||||
@ -265,7 +280,7 @@ class Playlists(Base):
|
|||||||
return Notes(session, self.id, row, text)
|
return Notes(session, self.id, row, text)
|
||||||
|
|
||||||
def add_track(
|
def add_track(
|
||||||
self, session: Session, track: "Tracks",
|
self, session: Session, track_id: int,
|
||||||
row: Optional[int] = None) -> None:
|
row: Optional[int] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Add track to playlist at given row.
|
Add track to playlist at given row.
|
||||||
@ -275,7 +290,7 @@ class Playlists(Base):
|
|||||||
if not row:
|
if not row:
|
||||||
row = PlaylistTracks.next_free_row(session, self)
|
row = PlaylistTracks.next_free_row(session, self)
|
||||||
|
|
||||||
PlaylistTracks(session, self.id, track.id, row)
|
PlaylistTracks(session, self.id, track_id, row)
|
||||||
|
|
||||||
def close(self, session: Session) -> None:
|
def close(self, session: Session) -> None:
|
||||||
"""Record playlist as no longer loaded"""
|
"""Record playlist as no longer loaded"""
|
||||||
@ -289,8 +304,7 @@ class Playlists(Base):
|
|||||||
"""Returns a list of all playlists ordered by last use"""
|
"""Returns a list of all playlists ordered by last use"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
session.query(cls)
|
session.query(cls).order_by(cls.last_used.desc())
|
||||||
.order_by(cls.last_used.desc())
|
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -303,8 +317,8 @@ class Playlists(Base):
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
session.query(cls)
|
session.query(cls)
|
||||||
.filter(cls.loaded.is_(False))
|
.filter(cls.loaded.is_(False))
|
||||||
.order_by(cls.last_used.desc())
|
.order_by(cls.last_used.desc())
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -315,8 +329,8 @@ class Playlists(Base):
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
session.query(cls)
|
session.query(cls)
|
||||||
.filter(cls.loaded.is_(True))
|
.filter(cls.loaded.is_(True))
|
||||||
.order_by(cls.last_used.desc())
|
.order_by(cls.last_used.desc())
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
def mark_open(self, session: Session) -> None:
|
def mark_open(self, session: Session) -> None:
|
||||||
@ -353,7 +367,7 @@ class PlaylistTracks(Base):
|
|||||||
|
|
||||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
playlist_id: int = Column(Integer, ForeignKey('playlists.id'),
|
playlist_id: int = Column(Integer, ForeignKey('playlists.id'),
|
||||||
primary_key=True)
|
primary_key=True)
|
||||||
track_id: int = Column(Integer, ForeignKey('tracks.id'), primary_key=True)
|
track_id: int = Column(Integer, ForeignKey('tracks.id'), primary_key=True)
|
||||||
row: int = Column(Integer, nullable=False)
|
row: int = Column(Integer, nullable=False)
|
||||||
tracks: RelationshipProperty = relationship("Tracks")
|
tracks: RelationshipProperty = relationship("Tracks")
|
||||||
@ -421,7 +435,7 @@ class PlaylistTracks(Base):
|
|||||||
|
|
||||||
row: int
|
row: int
|
||||||
|
|
||||||
last_row: int = session.query(
|
last_row = session.query(
|
||||||
func.max(PlaylistTracks.row)
|
func.max(PlaylistTracks.row)
|
||||||
).filter_by(playlist_id=playlist.id).first()
|
).filter_by(playlist_id=playlist.id).first()
|
||||||
# if there are no rows, the above returns (None, ) which is True
|
# if there are no rows, the above returns (None, ) which is True
|
||||||
@ -480,11 +494,11 @@ class Tracks(Base):
|
|||||||
mtime: float = Column(Float, index=True)
|
mtime: float = Column(Float, index=True)
|
||||||
lastplayed: datetime = Column(DateTime, index=True, default=None)
|
lastplayed: datetime = Column(DateTime, index=True, default=None)
|
||||||
playlists: RelationshipProperty = relationship("PlaylistTracks",
|
playlists: RelationshipProperty = relationship("PlaylistTracks",
|
||||||
back_populates="tracks",
|
back_populates="tracks",
|
||||||
lazy="joined")
|
lazy="joined")
|
||||||
playdates: RelationshipProperty = relationship("Playdates",
|
playdates: RelationshipProperty = relationship("Playdates",
|
||||||
back_populates="tracks",
|
back_populates="tracks",
|
||||||
lazy="joined")
|
lazy="joined")
|
||||||
|
|
||||||
def __init__(self, session: Session, path: str) -> None:
|
def __init__(self, session: Session, path: str) -> None:
|
||||||
self.path = path
|
self.path = path
|
||||||
@ -572,10 +586,10 @@ class Tracks(Base):
|
|||||||
audio: AudioSegment = get_audio_segment(self.path)
|
audio: AudioSegment = get_audio_segment(self.path)
|
||||||
self.duration = len(audio)
|
self.duration = len(audio)
|
||||||
self.fade_at = round(fade_point(audio) / 1000,
|
self.fade_at = round(fade_point(audio) / 1000,
|
||||||
Config.MILLISECOND_SIGFIGS) * 1000
|
Config.MILLISECOND_SIGFIGS) * 1000
|
||||||
self.mtime = os.path.getmtime(self.path)
|
self.mtime = os.path.getmtime(self.path)
|
||||||
self.silence_at = round(trailing_silence(audio) / 1000,
|
self.silence_at = round(trailing_silence(audio) / 1000,
|
||||||
Config.MILLISECOND_SIGFIGS) * 1000
|
Config.MILLISECOND_SIGFIGS) * 1000
|
||||||
self.start_gap = leading_silence(audio)
|
self.start_gap = leading_silence(audio)
|
||||||
session.add(self)
|
session.add(self)
|
||||||
session.commit()
|
session.commit()
|
||||||
@ -597,16 +611,16 @@ class Tracks(Base):
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
session.query(cls)
|
session.query(cls)
|
||||||
.filter(cls.artist.ilike(f"%{text}%"))
|
.filter(cls.artist.ilike(f"%{text}%"))
|
||||||
.order_by(cls.title)
|
.order_by(cls.title)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
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)
|
||||||
.filter(cls.title.ilike(f"%{text}%"))
|
.filter(cls.title.ilike(f"%{text}%"))
|
||||||
.order_by(cls.title)
|
.order_by(cls.title)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
def update_lastplayed(self, session: Session) -> None:
|
def update_lastplayed(self, session: Session) -> None:
|
||||||
|
|||||||
@ -86,8 +86,6 @@ 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
|
||||||
|
|
||||||
|
|||||||
@ -397,6 +397,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
if not dlg.plid:
|
if not dlg.plid:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# TODO: just update dest playlist and call populate if
|
||||||
|
# visible
|
||||||
|
|
||||||
# If destination playlist is visible, we need to add the moved
|
# If destination playlist is visible, we need to add the moved
|
||||||
# tracks to it. If not, they will be automatically loaded when
|
# tracks to it. If not, they will be automatically loaded when
|
||||||
# the playlistis opened.
|
# the playlistis opened.
|
||||||
@ -753,7 +756,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
session.add(self.current_track)
|
session.add(self.current_track)
|
||||||
playtime: int = self.music.get_playtime()
|
playtime: int = self.music.get_playtime()
|
||||||
time_to_fade: int = (self.current_track.fade_at - playtime)
|
time_to_fade: int = (self.current_track.fade_at - playtime)
|
||||||
time_to_silence: int = (self.current_track.silence_at - playtime)
|
time_to_silence: int = (
|
||||||
|
self.current_track.silence_at - playtime)
|
||||||
time_to_end: int = (self.current_track.duration - playtime)
|
time_to_end: int = (self.current_track.duration - playtime)
|
||||||
|
|
||||||
# Elapsed time
|
# Elapsed time
|
||||||
@ -762,7 +766,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
helpers.ms_to_mmss(self.current_track.duration)
|
helpers.ms_to_mmss(self.current_track.duration)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.label_elapsed_timer.setText(helpers.ms_to_mmss(playtime))
|
self.label_elapsed_timer.setText(
|
||||||
|
helpers.ms_to_mmss(playtime))
|
||||||
|
|
||||||
# Time to fade
|
# Time to fade
|
||||||
self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade))
|
self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade))
|
||||||
@ -935,9 +940,11 @@ class SelectPlaylistDialog(QDialog):
|
|||||||
self.plid = None
|
self.plid = None
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
record = Settings.get_int_settings(session, "select_playlist_dialog_width")
|
record = Settings.get_int_settings(
|
||||||
|
session, "select_playlist_dialog_width")
|
||||||
width = record.f_int or 800
|
width = record.f_int or 800
|
||||||
record = Settings.get_int_settings(session, "select_playlist_dialog_height")
|
record = Settings.get_int_settings(
|
||||||
|
session, "select_playlist_dialog_height")
|
||||||
height = record.f_int or 600
|
height = record.f_int or 600
|
||||||
self.resize(width, height)
|
self.resize(width, height)
|
||||||
|
|
||||||
@ -949,11 +956,13 @@ class SelectPlaylistDialog(QDialog):
|
|||||||
|
|
||||||
def __del__(self): # review
|
def __del__(self): # review
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
record = Settings.get_int_settings(session, "select_playlist_dialog_height")
|
record = Settings.get_int_settings(
|
||||||
|
session, "select_playlist_dialog_height")
|
||||||
if record.f_int != self.height():
|
if record.f_int != self.height():
|
||||||
record.update(session, {'f_int': self.height()})
|
record.update(session, {'f_int': self.height()})
|
||||||
|
|
||||||
record = Settings.get_int_settings(session, "select_playlist_dialog_width")
|
record = Settings.get_int_settings(
|
||||||
|
session, "select_playlist_dialog_width")
|
||||||
if record.f_int != self.width():
|
if record.f_int != self.width():
|
||||||
record.update(session, {'f_int': self.width()})
|
record.update(session, {'f_int': self.width()})
|
||||||
|
|
||||||
|
|||||||
279
app/playlists.py
279
app/playlists.py
@ -15,8 +15,6 @@ from PyQt5.QtWidgets import (
|
|||||||
QTableWidget,
|
QTableWidget,
|
||||||
QTableWidgetItem,
|
QTableWidgetItem,
|
||||||
)
|
)
|
||||||
from sqlalchemy import inspect
|
|
||||||
from sqlalchemy.orm.exc import DetachedInstanceError
|
|
||||||
|
|
||||||
import helpers
|
import helpers
|
||||||
import os
|
import os
|
||||||
@ -40,9 +38,9 @@ class RowMeta:
|
|||||||
CLEAR = 0
|
CLEAR = 0
|
||||||
NOTE = 1
|
NOTE = 1
|
||||||
UNREADABLE = 2
|
UNREADABLE = 2
|
||||||
NEXT = 4
|
NEXT = 3
|
||||||
CURRENT = 8
|
CURRENT = 4
|
||||||
PLAYED = 16
|
PLAYED = 5
|
||||||
|
|
||||||
|
|
||||||
class PlaylistTab(QTableWidget):
|
class PlaylistTab(QTableWidget):
|
||||||
@ -142,7 +140,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
self.cellEditingEnded.connect(self._cell_edit_ended)
|
self.cellEditingEnded.connect(self._cell_edit_ended)
|
||||||
|
|
||||||
# Now load our tracks and notes
|
# Now load our tracks and notes
|
||||||
self._populate(session)
|
self._populate(session, playlist)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@ -180,7 +178,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
# rows. Check and fix:
|
# rows. Check and fix:
|
||||||
row = 0 # So row is defined even if there are no rows in range
|
row = 0 # So row is defined even if there are no rows in range
|
||||||
for row in range(drop_row, drop_row + len(rows_to_move)):
|
for row in range(drop_row, drop_row + len(rows_to_move)):
|
||||||
if row in self._meta_get_notes():
|
if row in self._get_notes_rows():
|
||||||
self.setSpan(
|
self.setSpan(
|
||||||
row, self.COL_NOTE, self.NOTE_ROW_SPAN, self.NOTE_COL_SPAN)
|
row, self.COL_NOTE, self.NOTE_ROW_SPAN, self.NOTE_COL_SPAN)
|
||||||
|
|
||||||
@ -217,13 +215,13 @@ class PlaylistTab(QTableWidget):
|
|||||||
if item is not None:
|
if item is not None:
|
||||||
row = item.row()
|
row = item.row()
|
||||||
DEBUG(f"playlist.eventFilter(): Right-click on row {row}")
|
DEBUG(f"playlist.eventFilter(): Right-click on row {row}")
|
||||||
current = row == self._meta_get_current()
|
current = row == self._get_current_track_row()
|
||||||
next_row = row == self._meta_get_next()
|
next_row = row == self._get_next_track_row()
|
||||||
self.menu = QMenu(self)
|
self.menu = QMenu(self)
|
||||||
act_info = self.menu.addAction('Info')
|
act_info = self.menu.addAction('Info')
|
||||||
act_info.triggered.connect(lambda: self._info_row(row))
|
act_info.triggered.connect(lambda: self._info_row(row))
|
||||||
self.menu.addSeparator()
|
self.menu.addSeparator()
|
||||||
if row not in self._meta_get_notes():
|
if row not in self._get_notes_rows():
|
||||||
if not current and not next_row:
|
if not current and not next_row:
|
||||||
act_setnext = self.menu.addAction("Set next")
|
act_setnext = self.menu.addAction("Set next")
|
||||||
act_setnext.triggered.connect(
|
act_setnext.triggered.connect(
|
||||||
@ -375,12 +373,12 @@ class PlaylistTab(QTableWidget):
|
|||||||
stop_item: QTableWidgetItem = QTableWidgetItem()
|
stop_item: QTableWidgetItem = QTableWidgetItem()
|
||||||
self.setItem(row, self.COL_END_TIME, stop_item)
|
self.setItem(row, self.COL_END_TIME, stop_item)
|
||||||
|
|
||||||
# Attach track object to row
|
# Attach track.id object to row
|
||||||
self._set_row_content(row, track)
|
self._set_row_content(row, track.id)
|
||||||
|
|
||||||
# Mark track if file is unreadable
|
# Mark track if file is unreadable
|
||||||
if not self._file_is_readable(track.path):
|
if not self._file_is_readable(track.path):
|
||||||
self._meta_set_unreadable(row)
|
self._set_unreadable_row(row)
|
||||||
# Scroll to new row
|
# Scroll to new row
|
||||||
self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter)
|
self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter)
|
||||||
|
|
||||||
@ -418,11 +416,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
self.current_track_start_time = datetime.now()
|
self.current_track_start_time = datetime.now()
|
||||||
|
|
||||||
# Mark next-track row as current
|
# Mark next-track row as current
|
||||||
current_row = self._meta_get_next()
|
current_row = self._get_next_track_row()
|
||||||
self._meta_set_current(current_row)
|
self._set_current_track_row(current_row)
|
||||||
|
|
||||||
# Mark current row as played
|
# Mark current row as played
|
||||||
self._meta_set_played(current_row)
|
self._set_played_row(current_row)
|
||||||
|
|
||||||
# Scroll to put current track in middle
|
# Scroll to put current track in middle
|
||||||
scroll_to = self.item(current_row, self.COL_MSS)
|
scroll_to = self.item(current_row, self.COL_MSS)
|
||||||
@ -447,7 +445,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
- Update display
|
- Update display
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._meta_clear_current()
|
self._clear_current_track_row()
|
||||||
self.current_track_start_time = None
|
self.current_track_start_time = None
|
||||||
|
|
||||||
def save_playlist(self, session) -> None:
|
def save_playlist(self, session) -> None:
|
||||||
@ -470,11 +468,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
# Create dictionaries indexed by note_id
|
# Create dictionaries indexed by note_id
|
||||||
playlist_notes: Dict[int, Notes] = {}
|
playlist_notes: Dict[int, Notes] = {}
|
||||||
database_notes: Dict[int, Notes] = {}
|
database_notes: Dict[int, Notes] = {}
|
||||||
notes_rows: List[int] = self._meta_get_notes()
|
notes_rows: List[int] = self._get_notes_rows()
|
||||||
|
|
||||||
# PlaylistTab
|
# PlaylistTab
|
||||||
for row in notes_rows:
|
for row in notes_rows:
|
||||||
note: Notes = self._get_row_object(row, session)
|
note: Notes = self._get_row_notes_object(row, session)
|
||||||
session.add(note)
|
session.add(note)
|
||||||
playlist_notes[note.id] = note
|
playlist_notes[note.id] = note
|
||||||
|
|
||||||
@ -512,9 +510,9 @@ class PlaylistTab(QTableWidget):
|
|||||||
for row in range(self.rowCount()):
|
for row in range(self.rowCount()):
|
||||||
if row in notes_rows:
|
if row in notes_rows:
|
||||||
continue
|
continue
|
||||||
track: Tracks = self.item(
|
track_id: int = self.item(
|
||||||
row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
|
row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
|
||||||
self.playlist.add_track(session, track, row)
|
self.playlist.add_track(session, track_id, row)
|
||||||
|
|
||||||
def select_next_row(self) -> None:
|
def select_next_row(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -539,7 +537,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
# Don't select notes
|
# Don't select notes
|
||||||
wrapped: bool = False
|
wrapped: bool = False
|
||||||
while row in self._meta_get_notes():
|
while row in self._get_notes_rows():
|
||||||
row += 1
|
row += 1
|
||||||
if row >= self.rowCount():
|
if row >= self.rowCount():
|
||||||
if wrapped:
|
if wrapped:
|
||||||
@ -580,7 +578,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
# Don't select notes
|
# Don't select notes
|
||||||
wrapped: bool = False
|
wrapped: bool = False
|
||||||
while row in self._meta_get_notes():
|
while row in self._get_notes_rows():
|
||||||
row -= 1
|
row -= 1
|
||||||
if row < 0:
|
if row < 0:
|
||||||
if wrapped:
|
if wrapped:
|
||||||
@ -620,17 +618,17 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
if self.playlist not in session:
|
if self.playlist not in session:
|
||||||
session.add(self.playlist)
|
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
|
||||||
if clear_selection:
|
if clear_selection:
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
|
|
||||||
current_row: Optional[int] = self._meta_get_current()
|
current_row: Optional[int] = self._get_current_track_row()
|
||||||
next_row: Optional[int] = self._meta_get_next()
|
next_row: Optional[int] = self._get_next_track_row()
|
||||||
notes: Optional[List[int]] = self._meta_get_notes()
|
notes: Optional[List[int]] = self._get_notes_rows()
|
||||||
played: Optional[List[int]] = self._meta_get_played()
|
played: Optional[List[int]] = self._get_played_track_rows()
|
||||||
unreadable: Optional[List[int]] = self._meta_get_unreadable()
|
unreadable: Optional[List[int]] = self._get_unreadable_track_rows()
|
||||||
|
|
||||||
last_played_str: Optional[str]
|
last_played_str: Optional[str]
|
||||||
last_playedtime: Optional[datetime]
|
last_playedtime: Optional[datetime]
|
||||||
@ -697,7 +695,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
last_played_str)
|
last_played_str)
|
||||||
|
|
||||||
# Calculate next_start_time
|
# Calculate next_start_time
|
||||||
track = self._get_row_object(row, session)
|
track = self._get_row_track_object(row, session)
|
||||||
next_start_time = self._calculate_track_end_time(
|
next_start_time = self._calculate_track_end_time(
|
||||||
track, self.current_track_start_time)
|
track, self.current_track_start_time)
|
||||||
|
|
||||||
@ -725,7 +723,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
self._set_row_start_time(row, start_time)
|
self._set_row_start_time(row, start_time)
|
||||||
|
|
||||||
# Set end time
|
# Set end time
|
||||||
track = self._get_row_object(row, session)
|
track = self._get_row_track_object(row, session)
|
||||||
next_start_time = self._calculate_track_end_time(
|
next_start_time = self._calculate_track_end_time(
|
||||||
track, start_time)
|
track, start_time)
|
||||||
self._set_row_end_time(row, next_start_time)
|
self._set_row_end_time(row, next_start_time)
|
||||||
@ -739,7 +737,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
# This is a track row other than next or current
|
# This is a track row other than next or current
|
||||||
track = self._get_row_object(row, session)
|
track = self._get_row_track_object(row, session)
|
||||||
if row in played:
|
if row in played:
|
||||||
# Played today, so update last played column
|
# Played today, so update last played column
|
||||||
last_playedtime = Playdates.last_played(
|
last_playedtime = Playdates.last_played(
|
||||||
@ -777,11 +775,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
DEBUG(f"_audacity({row})")
|
DEBUG(f"_audacity({row})")
|
||||||
|
|
||||||
if row in self._meta_get_notes():
|
if row in self._get_notes_rows():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
track: Tracks = self._get_row_object(row, session)
|
track: Tracks = self._get_row_track_object(row, session)
|
||||||
open_in_audacity(track.path)
|
open_in_audacity(track.path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -810,11 +808,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
DEBUG(f"_copy_path({row})")
|
DEBUG(f"_copy_path({row})")
|
||||||
|
|
||||||
if row in self._meta_get_notes():
|
if row in self._get_notes_rows():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
track: Optional[Tracks] = self._get_row_object(row, session)
|
track: Optional[Tracks] = self._get_row_track_object(row, session)
|
||||||
if track:
|
if track:
|
||||||
cb: QApplication.clipboard = QApplication.clipboard()
|
cb: QApplication.clipboard = QApplication.clipboard()
|
||||||
cb.clear(mode=cb.Clipboard)
|
cb.clear(mode=cb.Clipboard)
|
||||||
@ -832,12 +830,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
DEBUG(f"_cell_changed({row=}, {column=}, {new_text=}")
|
DEBUG(f"_cell_changed({row=}, {column=}, {new_text=}")
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
row_object: Union[Tracks, Notes] = self._get_row_object(
|
if row in self._get_notes_rows():
|
||||||
row, session)
|
|
||||||
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=}'")
|
||||||
row_object.update_note(session, row, new_text)
|
note: Notes = self._get_notes_row_object(row, session)
|
||||||
|
note.update_note(session, row, new_text)
|
||||||
# Set/clear row start time accordingly
|
# Set/clear row start time accordingly
|
||||||
start_time = self._get_note_text_time(new_text)
|
start_time = self._get_note_text_time(new_text)
|
||||||
if start_time:
|
if start_time:
|
||||||
@ -854,10 +851,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
"start time"
|
"start time"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
track: Tracks = self._get_track_row_object(row, session)
|
||||||
if column == self.COL_ARTIST:
|
if column == self.COL_ARTIST:
|
||||||
row_object.update_artist(session, artist=new_text)
|
track.update_artist(session, artist=new_text)
|
||||||
elif column == self.COL_TITLE:
|
elif column == self.COL_TITLE:
|
||||||
row_object.update_title(session, title=new_text)
|
track.update_title(session, title=new_text)
|
||||||
else:
|
else:
|
||||||
ERROR("_cell_changed(): unrecognised column")
|
ERROR("_cell_changed(): unrecognised column")
|
||||||
|
|
||||||
@ -896,7 +894,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
set(item.row() for item in self.selectedItems())
|
set(item.row() for item in self.selectedItems())
|
||||||
)
|
)
|
||||||
rows_to_delete: List[int] = []
|
rows_to_delete: List[int] = []
|
||||||
note_rows: Optional[List[int]] = self._meta_get_notes()
|
note_rows: Optional[List[int]] = self._get_notes_rows()
|
||||||
row: int
|
row: int
|
||||||
row_object: Union[Tracks, Notes]
|
row_object: Union[Tracks, Notes]
|
||||||
|
|
||||||
@ -913,15 +911,15 @@ 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, session)
|
if row in note_rows:
|
||||||
if row in note_rows:
|
note: Notes = self._get_row_notes_object(row, session)
|
||||||
row_object.delete_note(session)
|
note.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(session)
|
self.update_display(session)
|
||||||
@ -963,14 +961,21 @@ class PlaylistTab(QTableWidget):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_row_object(self, row: int, session: Session) \
|
def _get_row_track_object(self, row: int, session: Session) \
|
||||||
-> Union[Tracks, Notes]:
|
-> Optional[Tracks]:
|
||||||
"""Return content associated with this row"""
|
"""Return track associated with this row"""
|
||||||
|
|
||||||
obj = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
|
track_id = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
|
||||||
if obj not in session:
|
track = Tracks.get_by_id(session, track_id)
|
||||||
session.add(obj)
|
return track
|
||||||
return obj
|
|
||||||
|
def _get_row_notes_object(self, row: int, session: Session) \
|
||||||
|
-> Optional[Notes]:
|
||||||
|
"""Return note associated with this row"""
|
||||||
|
|
||||||
|
note_id = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
|
||||||
|
note = Notes.get_by_id(session, note_id)
|
||||||
|
return note
|
||||||
|
|
||||||
def _get_row_start_time(self, row: int) -> Optional[datetime]:
|
def _get_row_start_time(self, row: int) -> Optional[datetime]:
|
||||||
try:
|
try:
|
||||||
@ -990,12 +995,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
txt: str
|
txt: str
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
row_object: Union[Tracks, Notes] = self._get_row_object(
|
if row in self._get_notes_rows():
|
||||||
row, session)
|
note: Notes = self._get_row_notes_object(row, session)
|
||||||
if row in self._meta_get_notes():
|
txt = note.note
|
||||||
txt = row_object.note
|
|
||||||
else:
|
else:
|
||||||
track = row_object
|
track: Tracks = self._get_row_track_object(row, session)
|
||||||
txt = (
|
txt = (
|
||||||
f"Title: {track.title}\n"
|
f"Title: {track.title}\n"
|
||||||
f"Artist: {track.artist}\n"
|
f"Artist: {track.artist}\n"
|
||||||
@ -1038,13 +1042,14 @@ class PlaylistTab(QTableWidget):
|
|||||||
# Add text of note from title column onwards
|
# Add text of note from title column onwards
|
||||||
titleitem: QTableWidgetItem = QTableWidgetItem(note.note)
|
titleitem: QTableWidgetItem = QTableWidgetItem(note.note)
|
||||||
self.setItem(row, self.COL_NOTE, titleitem)
|
self.setItem(row, self.COL_NOTE, titleitem)
|
||||||
self.setSpan(row, self.COL_NOTE, self.NOTE_ROW_SPAN, self.NOTE_COL_SPAN)
|
self.setSpan(row, self.COL_NOTE, self.NOTE_ROW_SPAN,
|
||||||
|
self.NOTE_COL_SPAN)
|
||||||
|
|
||||||
# Attach note object to row
|
# Attach note id to row
|
||||||
self._set_row_content(row, note)
|
self._set_row_content(row, note.id)
|
||||||
|
|
||||||
# Mark row as a Note row
|
# Mark row as a Note row
|
||||||
self._meta_set_note(row)
|
self._set_note_row(row)
|
||||||
|
|
||||||
# Scroll to new row
|
# Scroll to new row
|
||||||
self.scrollToItem(titleitem, QAbstractItemView.PositionAtCenter)
|
self.scrollToItem(titleitem, QAbstractItemView.PositionAtCenter)
|
||||||
@ -1088,13 +1093,13 @@ class PlaylistTab(QTableWidget):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if starting_row is None:
|
if starting_row is None:
|
||||||
current_row = self._meta_get_current()
|
current_row = self._get_current_track_row()
|
||||||
if current_row is not None:
|
if current_row is not None:
|
||||||
starting_row = current_row + 1
|
starting_row = current_row + 1
|
||||||
else:
|
else:
|
||||||
starting_row = 0
|
starting_row = 0
|
||||||
notes_rows = self._meta_get_notes()
|
notes_rows = self._get_notes_rows()
|
||||||
played_rows = self._meta_get_played()
|
played_rows = self._get_played_track_rows()
|
||||||
for row in range(starting_row, self.rowCount()):
|
for row in range(starting_row, self.rowCount()):
|
||||||
if row in notes_rows or row in played_rows:
|
if row in notes_rows or row in played_rows:
|
||||||
continue
|
continue
|
||||||
@ -1109,16 +1114,16 @@ class PlaylistTab(QTableWidget):
|
|||||||
if row is None:
|
if row is None:
|
||||||
raise ValueError(f"_meta_clear_attribute({row=}, {attribute=})")
|
raise ValueError(f"_meta_clear_attribute({row=}, {attribute=})")
|
||||||
|
|
||||||
new_metadata: int = self._meta_get(row) ^ attribute
|
new_metadata: int = self._meta_get(row) & ~(1 << attribute)
|
||||||
self.item(row, self.COL_USERDATA).setData(
|
self.item(row, self.COL_USERDATA).setData(
|
||||||
self.ROW_METADATA, new_metadata)
|
self.ROW_METADATA, new_metadata)
|
||||||
|
|
||||||
def _meta_clear_current(self) -> None:
|
def _clear_current_track_row(self) -> None:
|
||||||
"""
|
"""
|
||||||
Clear current row if there is one.
|
Clear current row if there is one.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
current_row: Optional[int] = self._meta_get_current()
|
current_row: Optional[int] = self._get_current_track_row()
|
||||||
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
|
# Reset row colour
|
||||||
@ -1134,11 +1139,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
Clear next row if there is one.
|
Clear next row if there is one.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
next_row: Optional[int] = self._meta_get_next()
|
next_row: Optional[int] = self._get_next_track_row()
|
||||||
if next_row is not None:
|
if next_row is not None:
|
||||||
self._meta_clear_attribute(next_row, RowMeta.NEXT)
|
self._meta_clear_attribute(next_row, RowMeta.NEXT)
|
||||||
|
|
||||||
def _meta_clear_played(self, row: int) -> None:
|
def _clear_played_row_status(self, row: int) -> None:
|
||||||
"""Clear played status on row"""
|
"""Clear played status on row"""
|
||||||
|
|
||||||
self._meta_clear_attribute(row, RowMeta.PLAYED)
|
self._meta_clear_attribute(row, RowMeta.PLAYED)
|
||||||
@ -1148,31 +1153,51 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
return self.item(row, self.COL_USERDATA).data(self.ROW_METADATA)
|
return self.item(row, self.COL_USERDATA).data(self.ROW_METADATA)
|
||||||
|
|
||||||
def _meta_get_current(self) -> Optional[int]:
|
def _get_current_track_row(self) -> Optional[int]:
|
||||||
"""Return row marked as current, or None"""
|
"""Return row marked as current, or None"""
|
||||||
|
|
||||||
return self._meta_search(RowMeta.CURRENT)
|
return self._meta_search(RowMeta.CURRENT)
|
||||||
|
|
||||||
def _meta_get_next(self) -> Optional[int]:
|
def _get_next_track_row(self) -> Optional[int]:
|
||||||
"""Return row marked as next, or None"""
|
"""Return row marked as next, or None"""
|
||||||
|
|
||||||
return self._meta_search(RowMeta.NEXT)
|
return self._meta_search(RowMeta.NEXT)
|
||||||
|
|
||||||
def _meta_get_notes(self) -> Optional[List[int]]:
|
def _get_notes_rows(self) -> Optional[List[int]]:
|
||||||
"""Return rows marked as notes, or None"""
|
"""Return rows marked as notes, or None"""
|
||||||
|
|
||||||
return self._meta_search(RowMeta.NOTE, one=False)
|
return self._meta_search(RowMeta.NOTE, one=False)
|
||||||
|
|
||||||
def _meta_get_played(self) -> Optional[List[int]]:
|
def _get_track_rows(self) -> Optional[List[int]]:
|
||||||
|
"""Return rows marked as tracks, or None"""
|
||||||
|
|
||||||
|
return self._meta_notset(RowMeta.NOTE)
|
||||||
|
|
||||||
|
def _get_played_track_rows(self) -> Optional[List[int]]:
|
||||||
"""Return rows marked as played, or None"""
|
"""Return rows marked as played, or None"""
|
||||||
|
|
||||||
return self._meta_search(RowMeta.PLAYED, one=False)
|
return self._meta_search(RowMeta.PLAYED, one=False)
|
||||||
|
|
||||||
def _meta_get_unreadable(self) -> Optional[List[int]]:
|
def _get_unreadable_track_rows(self) -> Optional[List[int]]:
|
||||||
"""Return rows marked as unreadable, or None"""
|
"""Return rows marked as unreadable, or None"""
|
||||||
|
|
||||||
return self._meta_search(RowMeta.UNREADABLE, one=False)
|
return self._meta_search(RowMeta.UNREADABLE, one=False)
|
||||||
|
|
||||||
|
def _meta_notset(self, metadata: int) -> Union[List[int]]:
|
||||||
|
"""
|
||||||
|
Search rows for metadata not set.
|
||||||
|
|
||||||
|
Return a list of matching row numbers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
matches = []
|
||||||
|
for row in range(self.rowCount()):
|
||||||
|
if self._meta_get(row):
|
||||||
|
if not self._meta_get(row) & (1 << metadata):
|
||||||
|
matches.append(row)
|
||||||
|
|
||||||
|
return matches
|
||||||
|
|
||||||
def _meta_search(self, metadata: int, one: bool = True) -> Union[
|
def _meta_search(self, metadata: int, one: bool = True) -> Union[
|
||||||
List[int], int, None]:
|
List[int], int, None]:
|
||||||
"""
|
"""
|
||||||
@ -1187,7 +1212,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
matches = []
|
matches = []
|
||||||
for row in range(self.rowCount()):
|
for row in range(self.rowCount()):
|
||||||
if self._meta_get(row):
|
if self._meta_get(row):
|
||||||
if self._meta_get(row) & metadata:
|
if self._meta_get(row) & (1 << metadata):
|
||||||
matches.append(row)
|
matches.append(row)
|
||||||
|
|
||||||
if not one:
|
if not one:
|
||||||
@ -1212,40 +1237,40 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
current_metadata: int = self._meta_get(row)
|
current_metadata: int = self._meta_get(row)
|
||||||
if not current_metadata:
|
if not current_metadata:
|
||||||
new_metadata = attribute
|
new_metadata = (1 << attribute)
|
||||||
else:
|
else:
|
||||||
new_metadata: int = self._meta_get(row) | attribute
|
new_metadata: int = self._meta_get(row) | (1 << attribute)
|
||||||
self.item(row, self.COL_USERDATA).setData(
|
self.item(row, self.COL_USERDATA).setData(
|
||||||
self.ROW_METADATA, new_metadata)
|
self.ROW_METADATA, new_metadata)
|
||||||
|
|
||||||
def _meta_set_current(self, row: int) -> None:
|
def _set_current_track_row(self, row: int) -> None:
|
||||||
"""Mark this row as current track"""
|
"""Mark this row as current track"""
|
||||||
|
|
||||||
self._meta_clear_current()
|
self._clear_current_track_row()
|
||||||
self._meta_set_attribute(row, RowMeta.CURRENT)
|
self._meta_set_attribute(row, RowMeta.CURRENT)
|
||||||
|
|
||||||
def _meta_set_next(self, row: int) -> None:
|
def _set_next_track_row(self, row: int) -> None:
|
||||||
"""Mark this row as next track"""
|
"""Mark this row as next track"""
|
||||||
|
|
||||||
self._meta_clear_next()
|
self._meta_clear_next()
|
||||||
self._meta_set_attribute(row, RowMeta.NEXT)
|
self._meta_set_attribute(row, RowMeta.NEXT)
|
||||||
|
|
||||||
def _meta_set_note(self, row: int) -> None:
|
def _set_note_row(self, row: int) -> None:
|
||||||
"""Mark this row as a note"""
|
"""Mark this row as a note"""
|
||||||
|
|
||||||
self._meta_set_attribute(row, RowMeta.NOTE)
|
self._meta_set_attribute(row, RowMeta.NOTE)
|
||||||
|
|
||||||
def _meta_set_played(self, row: int) -> None:
|
def _set_played_row(self, row: int) -> None:
|
||||||
"""Mark this row as played"""
|
"""Mark this row as played"""
|
||||||
|
|
||||||
self._meta_set_attribute(row, RowMeta.PLAYED)
|
self._meta_set_attribute(row, RowMeta.PLAYED)
|
||||||
|
|
||||||
def _meta_set_unreadable(self, row: int) -> None:
|
def _set_unreadable_row(self, row: int) -> None:
|
||||||
"""Mark this row as unreadable"""
|
"""Mark this row as unreadable"""
|
||||||
|
|
||||||
self._meta_set_attribute(row, RowMeta.UNREADABLE)
|
self._meta_set_attribute(row, RowMeta.UNREADABLE)
|
||||||
|
|
||||||
def _populate(self, session: Session) -> None:
|
def _populate(self, session: Session, playlist: Playlists) -> None:
|
||||||
"""
|
"""
|
||||||
Populate from the associated playlist object
|
Populate from the associated playlist object
|
||||||
|
|
||||||
@ -1261,16 +1286,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
row: int
|
row: int
|
||||||
track: Tracks
|
track: Tracks
|
||||||
|
|
||||||
# Make sure the database object is usable
|
if playlist not in session:
|
||||||
insp = inspect(self.playlist)
|
session.add(playlist)
|
||||||
if insp.detached:
|
|
||||||
session.add(self.playlist)
|
|
||||||
assert insp.persistent
|
|
||||||
|
|
||||||
for row, track in self.playlist.tracks.items():
|
for row, track in self.playlist.tracks.items():
|
||||||
data.append(([row], track))
|
data.append(([row], track))
|
||||||
# Add track to session to expose attributes
|
|
||||||
session.add(track)
|
|
||||||
for note in self.playlist.notes:
|
for note in self.playlist.notes:
|
||||||
data.append(([note.row], note))
|
data.append(([note.row], note))
|
||||||
|
|
||||||
@ -1302,14 +1322,12 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
DEBUG(f"_rescan({row=})")
|
DEBUG(f"_rescan({row=})")
|
||||||
|
|
||||||
if row in self._meta_get_notes():
|
|
||||||
return None
|
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
track: Tracks = self._get_row_object(row, session)
|
for row in self._get_track_rows():
|
||||||
if track:
|
track: Tracks = self._get_row_track_object(row, session)
|
||||||
track.rescan(session)
|
if track:
|
||||||
self._update_row(session, row, track)
|
track.rescan(session)
|
||||||
|
self._update_row(session, row, track)
|
||||||
|
|
||||||
def _select_event(self) -> None:
|
def _select_event(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -1317,24 +1335,21 @@ class PlaylistTab(QTableWidget):
|
|||||||
If multiple rows are selected, display sum of durations in status bar.
|
If multiple rows are selected, display sum of durations in status bar.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
row_set: Set[int] = set([item.row() for item in self.selectedItems()])
|
# Get the row number of all selected items and put into a set
|
||||||
note_row_set: Set[int] = set(self._meta_get_notes())
|
# to deduplicate
|
||||||
track_rows = list(row_set - note_row_set)
|
sel_rows: Set[int] = set([item.row() for item in self.selectedItems()])
|
||||||
tracks: List[Tracks]
|
notes_rows: Set[int] = set(self._get_notes_rows())
|
||||||
|
ms: int = 0
|
||||||
|
with Session() as session:
|
||||||
|
for row in (sel_rows - notes_rows):
|
||||||
|
ms += self._get_row_track_object(row, session).duration
|
||||||
|
|
||||||
with Session() as session: # checked
|
# Only paint message if there are selected track rows
|
||||||
tracks = [self._get_row_object(row, session) for row in track_rows]
|
if ms > 0:
|
||||||
for track in tracks:
|
self.parent.lblSumPlaytime.setText(
|
||||||
if track not in session:
|
f"Selected duration: {helpers.ms_to_mmss(ms)}")
|
||||||
session.add(track)
|
else:
|
||||||
ms: int = sum([track.duration for track in tracks])
|
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:
|
def _set_column_widths(self) -> None:
|
||||||
"""Column widths from settings"""
|
"""Column widths from settings"""
|
||||||
@ -1342,7 +1357,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
with Session() as session:
|
with Session() as session:
|
||||||
for column in range(self.columnCount()):
|
for column in range(self.columnCount()):
|
||||||
name: str = f"playlist_col_{str(column)}_width"
|
name: str = f"playlist_col_{str(column)}_width"
|
||||||
record: int = Settings.get_int_settings(session, name)
|
record: Settings = Settings.get_int_settings(session, name)
|
||||||
if record and record.f_int is not None:
|
if record and record.f_int is not None:
|
||||||
self.setColumnWidth(column, record.f_int)
|
self.setColumnWidth(column, record.f_int)
|
||||||
else:
|
else:
|
||||||
@ -1364,19 +1379,19 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
# Check row is a track row
|
# Check row is a track row
|
||||||
if row in self._meta_get_notes():
|
if row in self._get_notes_rows():
|
||||||
return None
|
return None
|
||||||
track: Tracks = self._get_row_object(row, session)
|
track: Tracks = self._get_row_track_object(row, session)
|
||||||
if not track:
|
if not track:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Check track is readable
|
# Check track is readable
|
||||||
if not self._file_is_readable(track.path):
|
if not self._file_is_readable(track.path):
|
||||||
self._meta_set_unreadable(row)
|
self._set_unreadable_row(row)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Mark as next track
|
# Mark as next track
|
||||||
self._meta_set_next(row)
|
self._set_next_track_row(row)
|
||||||
|
|
||||||
# Notify musicmuster
|
# Notify musicmuster
|
||||||
self.parent.this_is_the_next_track(self, track)
|
self.parent.this_is_the_next_track(self, track)
|
||||||
@ -1405,13 +1420,13 @@ class PlaylistTab(QTableWidget):
|
|||||||
if self.item(row, j):
|
if self.item(row, j):
|
||||||
self.item(row, j).setBackground(colour)
|
self.item(row, j).setBackground(colour)
|
||||||
|
|
||||||
def _set_row_content(self, row: int, content: Union[Tracks, Notes]) \
|
def _set_row_content(self, row: int, object_id: int) -> None:
|
||||||
-> None:
|
|
||||||
"""Set content associated with this row"""
|
"""Set content associated with this row"""
|
||||||
|
|
||||||
assert self.item(row, self.COL_USERDATA)
|
assert self.item(row, self.COL_USERDATA)
|
||||||
|
|
||||||
self.item(row, self.COL_USERDATA).setData(self.CONTENT_OBJECT, content)
|
self.item(row, self.COL_USERDATA).setData(
|
||||||
|
self.CONTENT_OBJECT, object_id)
|
||||||
|
|
||||||
def _set_row_end_time(self, row: int, time: Optional[datetime]) -> None:
|
def _set_row_end_time(self, row: int, time: Optional[datetime]) -> None:
|
||||||
"""Set passed row end time to passed time"""
|
"""Set passed row end time to passed time"""
|
||||||
@ -1446,10 +1461,10 @@ class PlaylistTab(QTableWidget):
|
|||||||
|
|
||||||
# Need to allow multiple rows to be selected
|
# Need to allow multiple rows to be selected
|
||||||
self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
||||||
notes_rows: List[int] = self._meta_get_notes()
|
notes_rows: List[int] = self._get_notes_rows()
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
|
|
||||||
played_rows: List[int] = self._meta_get_played()
|
played_rows: List[int] = self._get_played_track_rows()
|
||||||
for row in range(self.rowCount()):
|
for row in range(self.rowCount()):
|
||||||
if row in notes_rows:
|
if row in notes_rows:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QFontMetrics, QPainter
|
from PyQt5.QtGui import QFontMetrics, QPainter
|
||||||
|
from PyQt5.QtWidgets import QLabel
|
||||||
|
|
||||||
|
|
||||||
class ElideLabel(QLabel):
|
class ElideLabel(QLabel):
|
||||||
|
|||||||
@ -6,12 +6,11 @@ import shutil
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
from helpers import show_warning
|
|
||||||
from log import DEBUG, INFO
|
from log import DEBUG, INFO
|
||||||
from models import Notes, Playdates, Session, Tracks
|
from models import Notes, Playdates, Session, Tracks
|
||||||
from mutagen.flac import FLAC
|
from mutagen.flac import FLAC
|
||||||
from mutagen.mp3 import MP3
|
from mutagen.mp3 import MP3
|
||||||
from pydub import AudioSegment, effects
|
from pydub import effects
|
||||||
|
|
||||||
# Globals (I know)
|
# Globals (I know)
|
||||||
messages = []
|
messages = []
|
||||||
|
|||||||
@ -5,8 +5,8 @@ from models import Tracks
|
|||||||
|
|
||||||
|
|
||||||
def test_fade_point():
|
def test_fade_point():
|
||||||
test_track_path = "../testdata/isa.mp3"
|
test_track_path = "testdata/isa.mp3"
|
||||||
test_track_data = "../testdata/isa.py"
|
test_track_data = "testdata/isa.py"
|
||||||
|
|
||||||
audio_segment = get_audio_segment(test_track_path)
|
audio_segment = get_audio_segment(test_track_path)
|
||||||
assert audio_segment
|
assert audio_segment
|
||||||
@ -23,8 +23,8 @@ def test_fade_point():
|
|||||||
|
|
||||||
|
|
||||||
def test_get_tags():
|
def test_get_tags():
|
||||||
test_track_path = "../testdata/mom.mp3"
|
test_track_path = "testdata/mom.mp3"
|
||||||
test_track_data = "../testdata/mom.py"
|
test_track_data = "testdata/mom.py"
|
||||||
|
|
||||||
tags = get_tags(test_track_path)
|
tags = get_tags(test_track_path)
|
||||||
|
|
||||||
@ -49,8 +49,8 @@ def test_get_relative_date():
|
|||||||
|
|
||||||
|
|
||||||
def test_leading_silence():
|
def test_leading_silence():
|
||||||
test_track_path = "../testdata/isa.mp3"
|
test_track_path = "testdata/isa.mp3"
|
||||||
test_track_data = "../testdata/isa.py"
|
test_track_data = "testdata/isa.py"
|
||||||
|
|
||||||
audio_segment = get_audio_segment(test_track_path)
|
audio_segment = get_audio_segment(test_track_path)
|
||||||
assert audio_segment
|
assert audio_segment
|
||||||
@ -69,4 +69,4 @@ def test_leading_silence():
|
|||||||
def test_ms_to_mmss():
|
def test_ms_to_mmss():
|
||||||
assert ms_to_mmss(None) == "-"
|
assert ms_to_mmss(None) == "-"
|
||||||
assert ms_to_mmss(59600) == "0:59"
|
assert ms_to_mmss(59600) == "0:59"
|
||||||
assert ms_to_mmss((5 * 60 * 1000) + 23000) == "5:23"
|
assert ms_to_mmss((5 * 60 * 1000) + 23000) == "5:23"
|
||||||
|
|||||||
@ -172,7 +172,7 @@ def test_playlist_add_track(session):
|
|||||||
|
|
||||||
row = 17
|
row = 17
|
||||||
|
|
||||||
playlist.add_track(session, track, row)
|
playlist.add_track(session, track.id, row)
|
||||||
|
|
||||||
assert len(playlist.tracks) == 1
|
assert len(playlist.tracks) == 1
|
||||||
playlist_track = playlist.tracks[row]
|
playlist_track = playlist.tracks[row]
|
||||||
@ -192,8 +192,8 @@ def test_playlist_tracks(session):
|
|||||||
track2_row = 29
|
track2_row = 29
|
||||||
track2 = Tracks(session, track2_path)
|
track2 = Tracks(session, track2_path)
|
||||||
|
|
||||||
playlist.add_track(session, track1, track1_row)
|
playlist.add_track(session, track1.id, track1_row)
|
||||||
playlist.add_track(session, track2, track2_row)
|
playlist.add_track(session, track2.id, track2_row)
|
||||||
|
|
||||||
tracks = playlist.tracks
|
tracks = playlist.tracks
|
||||||
assert tracks[track1_row] == track1
|
assert tracks[track1_row] == track1
|
||||||
@ -269,7 +269,7 @@ def test_playlist_remove_tracks(session):
|
|||||||
# Add all tracks to both playlists
|
# Add all tracks to both playlists
|
||||||
for p in [playlist1, playlist2]:
|
for p in [playlist1, playlist2]:
|
||||||
for t in [track1, track2, track3]:
|
for t in [track1, track2, track3]:
|
||||||
p.add_track(session, t)
|
p.add_track(session, t.id)
|
||||||
|
|
||||||
assert len(playlist1.tracks) == 3
|
assert len(playlist1.tracks) == 3
|
||||||
assert len(playlist2.tracks) == 3
|
assert len(playlist2.tracks) == 3
|
||||||
@ -295,9 +295,9 @@ def test_playlist_get_track_playlists(session):
|
|||||||
track2 = Tracks(session, track2_path)
|
track2 = Tracks(session, track2_path)
|
||||||
|
|
||||||
# Put track1 in both playlists, track2 only in playlist1
|
# Put track1 in both playlists, track2 only in playlist1
|
||||||
playlist1.add_track(session, track1)
|
playlist1.add_track(session, track1.id)
|
||||||
playlist2.add_track(session, track1)
|
playlist2.add_track(session, track1.id)
|
||||||
playlist1.add_track(session, track2)
|
playlist1.add_track(session, track2.id)
|
||||||
|
|
||||||
playlists_track1 = track1.playlists
|
playlists_track1 = track1.playlists
|
||||||
playlists_track2 = track2.playlists
|
playlists_track2 = track2.playlists
|
||||||
@ -324,8 +324,8 @@ def test_playlisttracks_move_track(session):
|
|||||||
track1 = Tracks(session, track1_path)
|
track1 = Tracks(session, track1_path)
|
||||||
|
|
||||||
# Add both to playlist1 and check
|
# Add both to playlist1 and check
|
||||||
playlist1.add_track(session, track1, track1_row)
|
playlist1.add_track(session, track1.id, track1_row)
|
||||||
playlist1.add_track(session, track2, track2_row)
|
playlist1.add_track(session, track2.id, track2_row)
|
||||||
|
|
||||||
tracks = playlist1.tracks
|
tracks = playlist1.tracks
|
||||||
assert tracks[track1_row] == track1
|
assert tracks[track1_row] == track1
|
||||||
|
|||||||
@ -2,7 +2,6 @@ from PyQt5.QtCore import Qt
|
|||||||
|
|
||||||
from app.playlists import Notes, PlaylistTab, Tracks
|
from app.playlists import Notes, PlaylistTab, Tracks
|
||||||
from app.models import Playlists
|
from app.models import Playlists
|
||||||
# from musicmuster import Window
|
|
||||||
from musicmuster import Window
|
from musicmuster import Window
|
||||||
|
|
||||||
|
|
||||||
@ -60,11 +59,11 @@ def test_meta_all_clear(qtbot, session):
|
|||||||
track3 = Tracks(session, track3_path)
|
track3 = Tracks(session, track3_path)
|
||||||
playlist_tab.insert_track(session, track3)
|
playlist_tab.insert_track(session, track3)
|
||||||
|
|
||||||
assert playlist_tab._meta_get_current() is None
|
assert playlist_tab._get_current_track_row() is None
|
||||||
assert playlist_tab._meta_get_next() is None
|
assert playlist_tab._get_next_track_row() is None
|
||||||
assert playlist_tab._meta_get_notes() == []
|
assert playlist_tab._get_notes_rows() == []
|
||||||
assert playlist_tab._meta_get_played() == []
|
assert playlist_tab._get_played_track_rows() == []
|
||||||
assert len(playlist_tab._meta_get_unreadable()) == 3
|
assert len(playlist_tab._get_unreadable_track_rows()) == 3
|
||||||
|
|
||||||
|
|
||||||
def test_meta(qtbot, session):
|
def test_meta(qtbot, session):
|
||||||
@ -84,18 +83,18 @@ def test_meta(qtbot, session):
|
|||||||
track3 = Tracks(session, track3_path)
|
track3 = Tracks(session, track3_path)
|
||||||
playlist_tab.insert_track(session, track3)
|
playlist_tab.insert_track(session, track3)
|
||||||
|
|
||||||
assert len(playlist_tab._meta_get_unreadable()) == 3
|
assert len(playlist_tab._get_unreadable_track_rows()) == 3
|
||||||
|
|
||||||
assert playlist_tab._meta_get_played() == []
|
assert playlist_tab._get_played_track_rows() == []
|
||||||
assert playlist_tab._meta_get_current() is None
|
assert playlist_tab._get_current_track_row() is None
|
||||||
assert playlist_tab._meta_get_next() is None
|
assert playlist_tab._get_next_track_row() is None
|
||||||
assert playlist_tab._meta_get_notes() == []
|
assert playlist_tab._get_notes_rows() == []
|
||||||
|
|
||||||
playlist_tab._meta_set_played(0)
|
playlist_tab._set_played_row(0)
|
||||||
assert playlist_tab._meta_get_played() == [0]
|
assert playlist_tab._get_played_track_rows() == [0]
|
||||||
assert playlist_tab._meta_get_current() is None
|
assert playlist_tab._get_current_track_row() is None
|
||||||
assert playlist_tab._meta_get_next() is None
|
assert playlist_tab._get_next_track_row() is None
|
||||||
assert playlist_tab._meta_get_notes() == []
|
assert playlist_tab._get_notes_rows() == []
|
||||||
|
|
||||||
# Add a note
|
# Add a note
|
||||||
note_text = "my note"
|
note_text = "my note"
|
||||||
@ -103,40 +102,47 @@ def test_meta(qtbot, session):
|
|||||||
note = Notes(session, playlist.id, note_row, note_text)
|
note = Notes(session, playlist.id, note_row, note_text)
|
||||||
playlist_tab._insert_note(session, note)
|
playlist_tab._insert_note(session, note)
|
||||||
|
|
||||||
assert playlist_tab._meta_get_played() == [0]
|
assert playlist_tab._get_played_track_rows() == [0]
|
||||||
assert playlist_tab._meta_get_current() is None
|
assert playlist_tab._get_current_track_row() is None
|
||||||
assert playlist_tab._meta_get_next() is None
|
assert playlist_tab._get_next_track_row() is None
|
||||||
assert playlist_tab._meta_get_notes() == [3]
|
assert playlist_tab._get_notes_rows() == [3]
|
||||||
|
|
||||||
playlist_tab._meta_set_next(1)
|
playlist_tab._set_next_track_row(1)
|
||||||
assert playlist_tab._meta_get_played() == [0]
|
assert playlist_tab._get_played_track_rows() == [0]
|
||||||
assert playlist_tab._meta_get_current() is None
|
assert playlist_tab._get_current_track_row() is None
|
||||||
assert playlist_tab._meta_get_next() == 1
|
assert playlist_tab._get_next_track_row() == 1
|
||||||
assert playlist_tab._meta_get_notes() == [3]
|
assert playlist_tab._get_notes_rows() == [3]
|
||||||
|
|
||||||
playlist_tab._meta_set_current(2)
|
playlist_tab._set_current_track_row(2)
|
||||||
assert playlist_tab._meta_get_played() == [0]
|
assert playlist_tab._get_played_track_rows() == [0]
|
||||||
assert playlist_tab._meta_get_current() == 2
|
assert playlist_tab._get_current_track_row() == 2
|
||||||
assert playlist_tab._meta_get_next() == 1
|
assert playlist_tab._get_next_track_row() == 1
|
||||||
assert playlist_tab._meta_get_notes() == [3]
|
assert playlist_tab._get_notes_rows() == [3]
|
||||||
|
|
||||||
playlist_tab._meta_clear_played(0)
|
playlist_tab._clear_played_row_status(0)
|
||||||
assert playlist_tab._meta_get_played() == []
|
assert playlist_tab._get_played_track_rows() == []
|
||||||
assert playlist_tab._meta_get_current() == 2
|
assert playlist_tab._get_current_track_row() == 2
|
||||||
assert playlist_tab._meta_get_next() == 1
|
assert playlist_tab._get_next_track_row() == 1
|
||||||
assert playlist_tab._meta_get_notes() == [3]
|
assert playlist_tab._get_notes_rows() == [3]
|
||||||
|
|
||||||
playlist_tab._meta_clear_next()
|
playlist_tab._meta_clear_next()
|
||||||
assert playlist_tab._meta_get_played() == []
|
assert playlist_tab._get_played_track_rows() == []
|
||||||
assert playlist_tab._meta_get_current() == 2
|
assert playlist_tab._get_current_track_row() == 2
|
||||||
assert playlist_tab._meta_get_next() is None
|
assert playlist_tab._get_next_track_row() is None
|
||||||
assert playlist_tab._meta_get_notes() == [3]
|
assert playlist_tab._get_notes_rows() == [3]
|
||||||
|
|
||||||
playlist_tab._meta_clear_current()
|
playlist_tab._clear_current_track_row()
|
||||||
assert playlist_tab._meta_get_played() == []
|
assert playlist_tab._get_played_track_rows() == []
|
||||||
assert playlist_tab._meta_get_current() is None
|
assert playlist_tab._get_current_track_row() is None
|
||||||
assert playlist_tab._meta_get_next() is None
|
assert playlist_tab._get_next_track_row() is None
|
||||||
assert playlist_tab._meta_get_notes() == [3]
|
assert playlist_tab._get_notes_rows() == [3]
|
||||||
|
|
||||||
|
# Test clearing again has no effect
|
||||||
|
playlist_tab._clear_current_track_row()
|
||||||
|
assert playlist_tab._get_played_track_rows() == []
|
||||||
|
assert playlist_tab._get_current_track_row() is None
|
||||||
|
assert playlist_tab._get_next_track_row() is None
|
||||||
|
assert playlist_tab._get_notes_rows() == [3]
|
||||||
|
|
||||||
|
|
||||||
def test_clear_next(qtbot, session):
|
def test_clear_next(qtbot, session):
|
||||||
@ -152,11 +158,11 @@ def test_clear_next(qtbot, session):
|
|||||||
track2 = Tracks(session, track2_path)
|
track2 = Tracks(session, track2_path)
|
||||||
playlist_tab.insert_track(session, track2)
|
playlist_tab.insert_track(session, track2)
|
||||||
|
|
||||||
playlist_tab._meta_set_next(1)
|
playlist_tab._set_next_track_row(1)
|
||||||
assert playlist_tab._meta_get_next() == 1
|
assert playlist_tab._get_next_track_row() == 1
|
||||||
|
|
||||||
playlist_tab.clear_next()
|
playlist_tab.clear_next(session)
|
||||||
assert playlist_tab._meta_get_next() is None
|
assert playlist_tab._get_next_track_row() is None
|
||||||
|
|
||||||
|
|
||||||
def test_get_selected_row(qtbot, session):
|
def test_get_selected_row(qtbot, session):
|
||||||
@ -174,10 +180,9 @@ def test_get_selected_row(qtbot, session):
|
|||||||
playlist_tab.insert_track(session, track2)
|
playlist_tab.insert_track(session, track2)
|
||||||
|
|
||||||
window = Window()
|
window = Window()
|
||||||
window.show()
|
|
||||||
qtbot.addWidget(playlist_tab)
|
qtbot.addWidget(playlist_tab)
|
||||||
qtbot.wait_for_window_shown(playlist_tab)
|
with qtbot.waitExposed(window):
|
||||||
|
window.show()
|
||||||
row0_item0 = playlist_tab.item(0, 0)
|
row0_item0 = playlist_tab.item(0, 0)
|
||||||
assert row0_item0 is not None
|
assert row0_item0 is not None
|
||||||
rect = playlist_tab.visualItemRect(row0_item0)
|
rect = playlist_tab.visualItemRect(row0_item0)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user