Compare commits

..

No commits in common. "de710b1dc70bcc380ca5b96e04e179d75cc2c90a" and "d5871fe77f9b7395bbae955b09fa7872c87101b8" have entirely different histories.

3 changed files with 53 additions and 86 deletions

View File

@ -1,7 +1,5 @@
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import auto, Enum
from operator import attrgetter
from typing import List, Optional
from PyQt6.QtCore import (
@ -22,7 +20,6 @@ from dbconfig import scoped_session, Session
from helpers import (
file_is_unreadable,
get_embedded_time,
get_relative_date,
open_in_audacity,
ms_to_mmss,
set_track_metadata,
@ -55,10 +52,12 @@ class PlaylistRowData:
self.artist: str = ""
self.bitrate = 0
self.duration: int = 0
self.end_time: Optional[datetime] = None
self.lastplayed: datetime = Config.EPOCH
self.path = ""
self.played = False
self.start_gap: Optional[int] = None
self.start_time: Optional[datetime] = None
self.title: str = ""
self.plrid: int = plr.id
@ -85,12 +84,6 @@ class PlaylistRowData:
)
@dataclass
class StartEndTimes:
start_time: Optional[datetime] = None
end_time: Optional[datetime] = None
class PlaylistModel(QAbstractTableModel):
"""
The Playlist Model
@ -117,7 +110,6 @@ class PlaylistModel(QAbstractTableModel):
super().__init__(*args, **kwargs)
self.playlist_rows: dict[int, PlaylistRowData] = {}
self.start_end_times: dict[int, StartEndTimes] = {}
self.signals = MusicMusterSignals()
self.signals.add_track_to_header_signal.connect(self.add_track_to_header)
@ -274,9 +266,10 @@ class PlaylistModel(QAbstractTableModel):
self.refresh_row(session, plr.plr_rownum)
# Update track times
self.start_end_times[row_number].start_time = track_sequence.now.start_time
self.start_end_times[row_number].end_time = track_sequence.now.end_time
self.playlist_rows[row_number].start_time = datetime.now()
self.playlist_rows[row_number].end_time = datetime.now() + timedelta(
milliseconds=self.playlist_rows[row_number].duration
)
# Update colour and times for current row
self.invalidate_row(row_number)
# Update all other track times
@ -374,19 +367,17 @@ class PlaylistModel(QAbstractTableModel):
if column == Col.DURATION.value:
return QVariant(ms_to_mmss(prd.duration))
if column == Col.START_TIME.value:
if row in self.start_end_times:
start_time = self.start_end_times[row].start_time
if start_time:
return QVariant(start_time.strftime(Config.TRACK_TIME_FORMAT))
if prd.start_time:
return QVariant(prd.start_time.strftime(Config.TRACK_TIME_FORMAT))
else:
return QVariant()
if column == Col.END_TIME.value:
if row in self.start_end_times:
end_time = self.start_end_times[row].end_time
if end_time:
return QVariant(end_time.strftime(Config.TRACK_TIME_FORMAT))
if prd.end_time:
return QVariant(prd.end_time.strftime(Config.TRACK_TIME_FORMAT))
else:
return QVariant()
if column == Col.LAST_PLAYED.value:
return QVariant(get_relative_date(prd.lastplayed))
return QVariant(prd.lastplayed)
if column == Col.BITRATE.value:
return QVariant(prd.bitrate)
if column == Col.NOTE.value:
@ -656,20 +647,13 @@ class PlaylistModel(QAbstractTableModel):
for modified_row in modified_rows:
self.invalidate_row(modified_row)
def mark_unplayed(self, row_numbers: List[int]) -> None:
def mark_unplayed(self, row_number: int) -> None:
"""
Mark row as unplayed
"""
with Session() as session:
for row_number in row_numbers:
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
if not plr:
return
plr.played = False
self.refresh_row(session, row_number)
self.invalidate_rows(row_numbers)
self.playlist_rows[row_number].played = False
self.invalidate_row(row_number)
def move_rows(self, from_rows: List[int], to_row_number: int) -> None:
"""
@ -692,7 +676,6 @@ class PlaylistModel(QAbstractTableModel):
from_rows, range(next_to_row, next_to_row + len(from_rows))
):
row_map[from_row] = to_row
# Move the remaining rows to the row_map. We want to fill it
# before (if there are gaps) and after (likewise) the rows that
# are moving.
@ -710,14 +693,6 @@ class PlaylistModel(QAbstractTableModel):
old_row, HEADER_NOTES_COLUMN, 1, 1
)
# Check to see whether any rows in track_sequence have moved
if track_sequence.previous.plr_rownum in row_map:
track_sequence.previous.plr_rownum = row_map[track_sequence.previous.plr_rownum]
if track_sequence.now.plr_rownum in row_map:
track_sequence.now.plr_rownum = row_map[track_sequence.now.plr_rownum]
if track_sequence.next.plr_rownum in row_map:
track_sequence.next.plr_rownum = row_map[track_sequence.next.plr_rownum]
# For SQLAlchemy, build a list of dictionaries that map plrid to
# new row number:
sqla_map: List[dict[str, int]] = []
@ -731,7 +706,6 @@ class PlaylistModel(QAbstractTableModel):
self.refresh_data(session)
# Update display
self.update_track_times()
self.invalidate_rows(list(row_map.keys()))
def open_in_audacity(self, row_number: int) -> None:
@ -779,7 +753,7 @@ class PlaylistModel(QAbstractTableModel):
self.playlist_rows[p.plr_rownum] = PlaylistRowData(p)
def refresh_row(self, session, row_number):
"""Populate dict for one row from database"""
"""Populate dict for one row for data calls"""
p = PlaylistRows.deep_row(session, self.playlist_id, row_number)
self.playlist_rows[row_number] = PlaylistRowData(p)
@ -923,43 +897,34 @@ class PlaylistModel(QAbstractTableModel):
Sort selected rows by artist
"""
self.sort_by_attribute(row_numbers, 'artist')
def sort_by_attribute(self, row_numbers: List[int], attr_name: str) -> None:
"""
Sort selected rows by passed attribute name where 'attribute' is a
key in PlaylistRowData
"""
# Create a subset of playlist_rows with the rows we are
# interested in
shortlist_rows = {k: self.playlist_rows[k] for k in row_numbers}
sorted_list = [
plr.plr_rownum for plr in
sorted(shortlist_rows.values(), key=attrgetter(attr_name))
]
self.move_rows(sorted_list, min(sorted_list))
pass
def sort_by_duration(self, row_numbers: List[int]) -> None:
"""
Sort selected rows by duration
"""
self.sort_by_attribute(row_numbers, 'duration')
pass
def sort_by_lastplayed(self, row_numbers: List[int]) -> None:
"""
Sort selected rows by lastplayed
"""
self.sort_by_attribute(row_numbers, 'lastplayed')
pass
def sort_by_title(self, row_numbers: List[int]) -> None:
"""
Sort selected rows by title
"""
self.sort_by_attribute(row_numbers, 'title')
# Create a subset of playlist_rows with the rows we are
# interested in
shortlist_rows = {k: self.playlist_rows[k] for k in row_numbers}
sorted_list = [
k for k, v in sorted(shortlist_rows.items(), key=lambda item: item[1].title)
]
self.move_rows(sorted_list, min(sorted_list))
def supportedDropActions(self) -> Qt.DropAction:
return Qt.DropAction.MoveAction | Qt.DropAction.CopyAction
@ -973,22 +938,21 @@ class PlaylistModel(QAbstractTableModel):
update_rows: List[int] = []
for row_number in range(len(self.playlist_rows)):
stend = self.start_end_times[row_number] = StartEndTimes()
prd = self.playlist_rows[row_number]
# Reset start_time if this is the current row
if row_number == track_sequence.now.plr_rownum:
stend.start_time = track_sequence.now.start_time
stend.end_time = track_sequence.now.end_time
# Start/end times for current track are set in current_track_started
if not next_start_time:
next_start_time = stend.end_time
next_start_time = prd.end_time
continue
# Set start time for next row if we have a current track
if row_number == track_sequence.next.plr_rownum and track_sequence.now.end_time:
stend.start_time = track_sequence.now.end_time
stend.end_time = stend.start_time + timedelta(milliseconds=prd.duration)
next_start_time = stend.end_time
current_end_time = track_sequence.now.end_time
if row_number == track_sequence.next.plr_rownum and current_end_time:
prd.start_time = current_end_time
prd.end_time = current_end_time + timedelta(milliseconds=prd.duration)
next_start_time = prd.end_time
update_rows.append(row_number)
continue
@ -1009,11 +973,14 @@ class PlaylistModel(QAbstractTableModel):
< row_number
< track_sequence.next.plr_rownum
):
prd.start_time = None
prd.end_time = None
update_rows.append(row_number)
continue
# Reset start time if timing in header or at current track
if self.is_header_row(row_number):
if not prd.path:
# This is a header row
header_time = get_embedded_time(prd.note)
if header_time:
next_start_time = header_time
@ -1022,14 +989,14 @@ class PlaylistModel(QAbstractTableModel):
# start time
if next_start_time is None:
continue
if stend.start_time != next_start_time:
stend.start_time = next_start_time
if prd.start_time != next_start_time:
prd.start_time = next_start_time
update_rows.append(row_number)
next_start_time += timedelta(
milliseconds=self.playlist_rows[row_number].duration
)
if stend.end_time != next_start_time:
stend.end_time = next_start_time
if prd.end_time != next_start_time:
prd.end_time = next_start_time
update_rows.append(row_number)
# Update start/stop times of rows that have changed

View File

@ -1103,15 +1103,11 @@ class PlaylistTab(QTableView):
# Mark unplayed
if track_row and model.is_unplayed_row(row_number):
self._add_context_menu(
"Mark unplayed", lambda: model.mark_unplayed(self._get_selected_rows())
)
self._add_context_menu("Mark unplayed", lambda: model.mark_unplayed(row_number))
# Unmark as next
if next_row:
self._add_context_menu(
"Unmark as next track", lambda: model.set_next_row(None)
)
self._add_context_menu("Unmark as next track", lambda: model.set_next_row(None))
# ----------------------
self.menu.addSeparator()
@ -1138,6 +1134,9 @@ class PlaylistTab(QTableView):
lambda: model.sort_by_lastplayed(self._get_selected_rows()),
parent_menu=sort_menu,
)
if sort_menu:
sort_menu.setEnabled(model.selection_is_sortable(self._get_selected_rows()))
self._add_context_menu("Undo sort", self._sort_undo, not bool(self.sort_undo))
# Info TODO
if track_row:
@ -1145,7 +1144,8 @@ class PlaylistTab(QTableView):
# Track path TODO
if track_row:
self._add_context_menu("Copy track path", lambda: print("Track path"))
self._add_context_menu(
"Copy track path", lambda: print("Track path"))
def _calculate_end_time(
self, start: Optional[datetime], duration: int

View File

@ -202,7 +202,7 @@ def test_insert_header_row_end(monkeypatch, session):
# Test against edit_role because display_role for headers is
# handled differently (sets up row span)
assert (
model.edit_role(model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd)
model.edit_role(model.rowCount(), playlistmodel.Col.NOTE.value, prd)
== note_text
)
@ -222,7 +222,7 @@ def test_insert_header_row_middle(monkeypatch, session):
# Test against edit_role because display_role for headers is
# handled differently (sets up row span)
assert (
model.edit_role(model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd)
model.edit_role(model.rowCount(), playlistmodel.Col.NOTE.value, prd)
== note_text
)