SQLA2: WIP, playlists load

This commit is contained in:
Keith Edmunds 2022-08-03 20:14:26 +01:00
parent caed7fd079
commit 4f03306aff
3 changed files with 452 additions and 427 deletions

View File

@ -1,8 +1,8 @@
# import os import os
# import psutil # import psutil
# #
# from config import Config # from config import Config
# from datetime import datetime from datetime import datetime
# from pydub import AudioSegment # from pydub import AudioSegment
# from PyQt5.QtWidgets import QMessageBox # from PyQt5.QtWidgets import QMessageBox
# from tinytag import TinyTag # from tinytag import TinyTag
@ -44,7 +44,22 @@
# # if there is no trailing silence, return lenght of track (it's less # # if there is no trailing silence, return lenght of track (it's less
# # the chunk_size, but for chunk_size = 10ms, this may be ignored) # # the chunk_size, but for chunk_size = 10ms, this may be ignored)
# return int(trim_ms) # return int(trim_ms)
#
def file_is_readable(path: str, check_colon: bool = True) -> bool:
"""
Returns True if passed path is readable, else False
vlc cannot read files with a colon in the path
"""
if os.access(path, os.R_OK):
if check_colon:
return ':' not in path
else:
return True
return False
# #
# def get_audio_segment(path: str) -> Optional[AudioSegment]: # def get_audio_segment(path: str) -> Optional[AudioSegment]:
# try: # try:
@ -70,47 +85,47 @@
# path=path # path=path
# ) # )
# return d # return d
#
#
# def get_relative_date(past_date: datetime, reference_date: datetime = None) \ def get_relative_date(past_date: datetime, reference_date: datetime = None) \
# -> str: -> str:
# """ """
# Return how long before reference_date past_date is as string. Return how long before reference_date past_date is as string.
#
# Params: Params:
# @past_date: datetime @past_date: datetime
# @reference_date: datetime, defaults to current date and time @reference_date: datetime, defaults to current date and time
#
# @return: string @return: string
# """ """
#
# if not past_date: if not past_date:
# return "Never" return "Never"
# if not reference_date: if not reference_date:
# reference_date = datetime.now() reference_date = datetime.now()
#
# # Check parameters # Check parameters
# if past_date > reference_date: if past_date > reference_date:
# return "get_relative_date() past_date is after relative_date" return "get_relative_date() past_date is after relative_date"
#
# days: int days: int
# days_str: str days_str: str
# weeks: int weeks: int
# weeks_str: str weeks_str: str
#
# weeks, days = divmod((reference_date.date() - past_date.date()).days, 7) weeks, days = divmod((reference_date.date() - past_date.date()).days, 7)
# if weeks == days == 0: if weeks == days == 0:
# # Played today, so return time instead # Same day so return time instead
# return past_date.strftime("%H:%M") return past_date.strftime("%H:%M")
# if weeks == 1: if weeks == 1:
# weeks_str = "week" weeks_str = "week"
# else: else:
# weeks_str = "weeks" weeks_str = "weeks"
# if days == 1: if days == 1:
# days_str = "day" days_str = "day"
# else: else:
# days_str = "days" days_str = "days"
# return f"{weeks} {weeks_str}, {days} {days_str} ago" return f"{weeks} {weeks_str}, {days} {days_str} ago"
# #
# #
# def leading_silence( # def leading_silence(

View File

@ -232,19 +232,22 @@ class Playdates(Base):
# self.track_id = track_id # self.track_id = track_id
# session.add(self) # session.add(self)
# session.flush() # session.flush()
#
# @staticmethod @staticmethod
# def last_played(session: Session, track_id: int) -> Optional[datetime]: def last_played(session: Session, track_id: int) -> Optional[datetime]:
# """Return datetime track last played or None""" """Return datetime track last played or None"""
#
# last_played: Optional[Playdates] = session.query( last_played = session.execute(
# Playdates.lastplayed).filter( select(Playdates.lastplayed)
# (Playdates.track_id == track_id) .where(Playdates.track_id == track_id)
# ).order_by(Playdates.lastplayed.desc()).first() .order_by(Playdates.lastplayed.desc())
# if last_played: .limit(1)
# return last_played[0] ).first()
# else:
# return None if last_played:
return last_played[0]
else:
return None
# #
# @staticmethod # @staticmethod
# def played_after(session: Session, since: datetime) -> List["Playdates"]: # def played_after(session: Session, since: datetime) -> List["Playdates"]:

View File

@ -1,15 +1,19 @@
from collections import namedtuple from collections import namedtuple
# from enum import Enum, auto # from enum import Enum, auto
from typing import List, Optional
# from typing import Dict, List, Optional, Set, Tuple, Union # from typing import Dict, List, Optional, Set, Tuple, Union
# #
# from PyQt5 import QtCore # from PyQt5 import QtCore
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
# from PyQt5.Qt import QFont from PyQt5.Qt import QFont
# from PyQt5.QtGui import QColor, QDropEvent from PyQt5.QtGui import (
QColor,
# QDropEvent
)
# from PyQt5 import QtWidgets # from PyQt5 import QtWidgets
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
# QAbstractItemView, QAbstractItemView,
# QApplication, # QApplication,
# QInputDialog, # QInputDialog,
# QLineEdit, # QLineEdit,
@ -28,29 +32,33 @@ import helpers
# import threading # import threading
# #
from config import Config from config import Config
# from datetime import datetime, timedelta from datetime import datetime #, timedelta
# from helpers import get_relative_date, open_in_audacity from helpers import (
get_relative_date,
# open_in_audacity
)
# from log import log.debug, log.error # from log import log.debug, log.error
from models import ( from models import (
# Notes, # Notes,
# Playdates, Playdates,
Playlists, Playlists,
PlaylistRows, PlaylistRows,
Settings, Settings,
# Tracks, Tracks,
# NoteColours # NoteColours
) )
from dbconfig import Session from dbconfig import Session
# start_time_re = re.compile(r"@\d\d:\d\d:\d\d") # start_time_re = re.compile(r"@\d\d:\d\d:\d\d")
#
#
# class RowMeta: class RowMeta:
# CLEAR = 0 CLEAR = 0
# NOTE = 1 NOTE = 1
# UNREADABLE = 2 UNREADABLE = 2
# NEXT = 3 NEXT = 3
# CURRENT = 4 CURRENT = 4
# PLAYED = 5 PLAYED = 5
# Columns # Columns
Column = namedtuple("Column", ['idx', 'heading']) Column = namedtuple("Column", ['idx', 'heading'])
@ -89,8 +97,8 @@ class PlaylistTab(QTableWidget):
# Qt.UserRoles # Qt.UserRoles
ROW_METADATA = Qt.UserRole ROW_METADATA = Qt.UserRole
# CONTENT_OBJECT = Qt.UserRole + 1 ROW_TRACK_ID = Qt.UserRole + 1
ROW_DURATION = Qt.UserRole + 2 PLAYLISTROW_ID = Qt.UserRole + 2
def __init__(self, musicmuster: QMainWindow, session: Session, def __init__(self, musicmuster: QMainWindow, session: Session,
playlist_id: int, *args, **kwargs): playlist_id: int, *args, **kwargs):
@ -144,7 +152,7 @@ class PlaylistTab(QTableWidget):
# #
# self.itemSelectionChanged.connect(self._select_event) # self.itemSelectionChanged.connect(self._select_event)
# #
# self.row_filter: Optional[str] = None self.row_filter: Optional[str] = None
# self.editing_cell: bool = False # self.editing_cell: bool = False
# self.selecting_in_progress = False # self.selecting_in_progress = False
# Connect signals # Connect signals
@ -394,15 +402,14 @@ class PlaylistTab(QTableWidget):
duration_item = QTableWidgetItem( duration_item = QTableWidgetItem(
helpers.ms_to_mmss(row_data.track.duration)) helpers.ms_to_mmss(row_data.track.duration))
self._set_row_duration(row, row_data.track.duration)
last_playtime = Playdates.last_played(session, row_data.track.id) last_playtime = Playdates.last_played(session, row_data.track.id)
last_played_str = get_relative_date(last_playtime) last_played_str = get_relative_date(last_playtime)
last_played_item = QTableWidgetItem(last_played_str) last_played_item = QTableWidgetItem(last_played_str)
self.setItem(row, columns['lastplayed'], last_played_item) self.setItem(row, columns['lastplayed'].idx, last_played_item)
# Mark track if file is unreadable # Mark track if file is unreadable
if not self._file_is_readable(row_data.track.path): if not helpers.file_is_readable(row_data.track.path):
self._set_unreadable_row(row) self._set_unreadable_row(row)
else: else:
@ -423,9 +430,18 @@ class PlaylistTab(QTableWidget):
self.setItem(row, columns['end_time'].idx, end_item) self.setItem(row, columns['end_time'].idx, end_item)
self.setItem(row, columns['row_notes'].idx, notes_item) self.setItem(row, columns['row_notes'].idx, notes_item)
# Save track_id and playlistrow_id
userdata_item = QTableWidgetItem()
if row_data.track_id:
userdata_item.setData(self.ROW_TRACK_ID, row_data.track_id)
else:
userdata_item.setData(self.ROW_TRACK_ID, 0)
userdata_item.setData(self.PLAYLISTROW_ID, row_data.id)
self.setItem(row, columns['userdata'].idx, userdata_item)
# Span note across table
if not row_data.track_id: if not row_data.track_id:
# Span note across table self.setSpan(row, columns['row_notes'].idx, len(columns), 1)
self.setSpan(row, 0, len(columns), 1)
# Scroll to new row # Scroll to new row
self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter) self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter)
@ -815,211 +831,200 @@ class PlaylistTab(QTableWidget):
# #
# with Session() as session: # with Session() as session:
# self._set_next(row, session) # self._set_next(row, session)
#
# def update_display(self, session, 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
#
# Actions required: Actions required:
# - Clear selection if required - Clear selection if required
# - Render notes in correct colour - Render notes in correct colour
# - Render current, next and unplayable tracks in correct colour - Render current, next and unplayable tracks in correct colour
# - Set start and end times - Set start and end times
# - Show unplayed tracks in bold - Show unplayed tracks in bold
# """ """
#
# # Clear selection if required # Clear selection if required
# if clear_selection: if clear_selection:
# self.clearSelection() self.clearSelection()
#
# current_row: Optional[int] = self._get_current_track_row() current_row: Optional[int] = self._get_current_track_row()
# next_row: Optional[int] = self._get_next_track_row() next_row: Optional[int] = self._get_next_track_row()
# notes: List[int] = self._get_notes_rows() played: Optional[List[int]] = self._get_played_track_rows()
# played: Optional[List[int]] = self._get_played_track_rows() unreadable: List[int] = self._get_unreadable_track_rows()
# unreadable: List[int] = self._get_unreadable_track_rows()
# if self.row_filter:
# if self.row_filter: filter_text = self.row_filter.lower()
# filter_text = self.row_filter.lower() else:
# else: filter_text = None
# filter_text = None next_start_time = None
# hide_played = self.musicmuster.hide_played_tracks section_start_row = None
# last_played_str: str section_time = 0
# last_playedtime: Optional[datetime]
# next_start_time: Optional[datetime] = None # Start time calculations
# note_colour: str # Don't change start times for tracks that have been played.
# note_start_time: Optional[str] # For unplayed tracks, if there's a 'current' or 'next'
# note_text: str # track marked, populate start times from then onwards. A note
# row: int # with a start time will reset the next track start time.
# row_time: Optional[datetime]
# section_start_row: Optional[int] = None # Cycle through all rows
# section_time: int = 0 for row in range(self.rowCount()):
# start_time: Optional[datetime]
# start_times_row: Optional[int] # Get track if there is one
# track: Optional[Tracks] track_id = self._get_row_track_id(row)
# track = None
# # Start time calculations if track_id:
# # Don't change start times for tracks that have been played. track = session.get(Tracks, track_id)
# # For unplayed tracks, if there's a 'current' or 'next'
# # track marked, populate start times from then onwards. A note if track:
# # with a start time will reset the next track start time. # Render unplayable tracks in correct colour
# if not helpers.file_is_readable(track.path):
# # Cycle through all rows self._set_row_colour(row, QColor(Config.COLOUR_UNREADABLE))
# for row in range(self.rowCount()): self._set_row_bold(row)
# continue
# # Render notes in correct colour
# if row in notes: # Add track time to section time if in timed section
# # Extract note text from database to ignore section timings if section_start_row is not None:
# note_text = self._get_row_notes_object(row, session).note section_time += track.duration
# if filter_text:
# if filter_text not in note_text.lower(): # If filtering, only show matching tracks
# self.hideRow(row) if filter_text:
# continue try:
# else: if (track.title
# self.showRow(row) and filter_text not in track.title.lower()
# else: and track.artist
# self.showRow(row) and filter_text not in track.artist.lower()):
# # Does the note have a start time? self.hideRow(row)
# row_time = self._get_note_text_time(note_text) continue
# if row_time: else:
# next_start_time = row_time self.showRow(row)
# # Does it delimit a section? except TypeError:
# if section_start_row is not None: print(f"TypeError: {track=}")
# if note_text.endswith("-"): else:
# self._set_timed_section(session, section_start_row, self.showRow(row)
# section_time)
# section_start_row = None # Render playing track
# section_time = 0 if row == current_row:
# elif note_text.endswith("+"): # Set start time
# section_start_row = row self._set_row_start_time(
# section_time = 0 row, self.current_track_start_time)
# # Set colour # Set last played time to "Today"
# note_colour = NoteColours.get_colour(session, note_text) self.item(row, self.COL_LAST_PLAYED).setText("Today")
# if not note_colour: # Calculate next_start_time
# note_colour = Config.COLOUR_NOTES_PLAYLIST next_start_time = self._calculate_end_time(
# self._set_row_colour( self.current_track_start_time, track.duration)
# row, QColor(note_colour) # Set end time
# ) self._set_row_end_time(row, next_start_time)
# # Notes are always bold # Set colour
# self._set_row_bold(row) self._set_row_colour(row, QColor(
# continue Config.COLOUR_CURRENT_PLAYLIST))
# # Make bold
# # Render unplayable tracks in correct colour self._set_row_bold(row)
# if row in unreadable: continue
# self._set_row_colour(
# row, QColor(Config.COLOUR_UNREADABLE) # Render next track
# ) if row == next_row:
# self._set_row_bold(row) # Set start time
# continue # if there's a track playing, set start time from that
# if current_row is not None:
# # Current row is a track row start_time = self._calculate_end_time(
# track = self._get_row_track_object(row, session) self.current_track_start_time, track.duration)
# # Add track time to section time if in timed section else:
# if section_start_row is not None: # No current track to base from, but don't change
# section_time += track.duration # time if it's already set
# # Render current track start_time = self._get_row_start_time(row)
# if filter_text: if not start_time:
# try: start_time = next_start_time
# if (track.title self._set_row_start_time(row, start_time)
# and filter_text not in track.title.lower() # Calculate next_start_time
# and track.artist next_start_time = self._calculate_end_time(start_time,
# and filter_text not in track.artist.lower()): track.duration)
# self.hideRow(row) # Set end time
# continue self._set_row_end_time(row, next_start_time)
# else: # Set colour
# self.showRow(row) self._set_row_colour(
# except TypeError: row, QColor(Config.COLOUR_NEXT_PLAYLIST))
# print(f"TypeError: {track=}") # Make bold
# else: self._set_row_bold(row)
# self.showRow(row) continue
# if row == current_row:
# # Set start time # This is a track row other than next or current
# self._set_row_start_time( if row in played:
# row, self.current_track_start_time) # Played today, so update last played column
# last_playedtime = track.lastplayed
# # Set last played time last_played_str = get_relative_date(last_playedtime)
# last_played_str = get_relative_date( self.item(row, self.COL_LAST_PLAYED).setText(
# self.current_track_start_time) last_played_str)
# self.item(row, self.COL_LAST_PLAYED).setText( if self.musicmuster.hide_played_tracks:
# last_played_str) self.hideRow(row)
# else:
# # Calculate next_start_time self._set_row_not_bold(row)
# next_start_time = self._calculate_row_end_time( else:
# row, self.current_track_start_time) # Set start/end times as we haven't played it yet
# if next_start_time:
# # Set end time self._set_row_start_time(row, next_start_time)
# self._set_row_end_time(row, next_start_time) next_start_time = self._calculate_end_time(
# next_start_time, track.duration)
# # Set colour # Set end time
# self._set_row_colour(row, QColor( self._set_row_end_time(row, next_start_time)
# Config.COLOUR_CURRENT_PLAYLIST)) else:
# # Clear start and end time
# # Make bold self._set_row_start_time(row, None)
# self._set_row_bold(row) self._set_row_end_time(row, None)
# continue # Don't dim unplayed tracks
# self._set_row_bold(row)
# # Render next track # Stripe rows
# if row == next_row: if row % 2:
# # if there's a track playing, set start time from that self._set_row_colour(
# if current_row is not None: row, QColor(Config.COLOUR_ODD_PLAYLIST))
# start_time = self._calculate_row_end_time( else:
# current_row, self.current_track_start_time) self._set_row_colour(
# else: row, QColor(Config.COLOUR_EVEN_PLAYLIST))
# # No current track to base from, but don't change
# # time if it's already set continue
# start_time = self._get_row_start_time(row)
# if not start_time: # No track associated, so this row is a section header
# start_time = next_start_time # Extract note text from database to ignore section timings
# self._set_row_start_time(row, start_time) playlist_row = session.get(PlaylistTracks,
# self._get_playlistrow_id(row))
# # Set end time note_text = playlist_row.note
# next_start_time = self._calculate_row_end_time(row, start_time) if filter_text:
# self._set_row_end_time(row, next_start_time) if filter_text not in note_text.lower():
# self.hideRow(row)
# # Set colour continue
# self._set_row_colour( else:
# row, QColor(Config.COLOUR_NEXT_PLAYLIST)) self.showRow(row)
# else:
# # Make bold self.showRow(row)
# self._set_row_bold(row) # Does the note have a start time?
# row_time = self._get_note_text_time(note_text)
# else: if row_time:
# # This is a track row other than next or current next_start_time = row_time
# if row in played: # Does it delimit a section?
# # Played today, so update last played column if section_start_row is not None:
# last_playedtime = track.lastplayed if note_text.endswith("-"):
# last_played_str = get_relative_date(last_playedtime) self._set_timed_section(session, section_start_row,
# self.item(row, self.COL_LAST_PLAYED).setText( section_time)
# last_played_str) section_start_row = None
# if hide_played: section_time = 0
# self.hideRow(row) elif note_text.endswith("+"):
# else: section_start_row = row
# self._set_row_not_bold(row) section_time = 0
# else: # Set colour
# # Set start/end times as we haven't played it yet note_colour = NoteColours.get_colour(session, note_text)
# if next_start_time: if not note_colour:
# self._set_row_start_time(row, next_start_time) note_colour = Config.COLOUR_NOTES_PLAYLIST
# next_start_time = self._calculate_row_end_time( self._set_row_colour(
# row, next_start_time) row, QColor(note_colour)
# # Set end time )
# self._set_row_end_time(row, next_start_time) # Notes are always bold
# else: self._set_row_bold(row)
# # Clear start and end time continue
# self._set_row_start_time(row, None)
# self._set_row_end_time(row, None) # Have we had a section start but not end?
# # Don't dim unplayed tracks if section_start_row is not None:
# self._set_row_bold(row) self._set_timed_section(
# # Stripe rows session, section_start_row, section_time, no_end=True)
# if row % 2:
# self._set_row_colour(
# row, QColor(Config.COLOUR_ODD_PLAYLIST))
# else:
# self._set_row_colour(
# row, QColor(Config.COLOUR_EVEN_PLAYLIST))
#
# # Have we had a section start but not end?
# if section_start_row is not None:
# self._set_timed_section(
# session, section_start_row, section_time, no_end=True)
# #
# # ########## Internally called functions ########## # # ########## Internally called functions ##########
# #
@ -1034,16 +1039,15 @@ class PlaylistTab(QTableWidget):
# with Session() as session: # with Session() as session:
# track: Tracks = self._get_row_track_object(row, session) # track: Tracks = self._get_row_track_object(row, session)
# open_in_audacity(track.path) # open_in_audacity(track.path)
#
# def _calculate_row_end_time(self, row, start: Optional[datetime]) \ def _calculate_end_time(self, start: Optional[datetime],
# -> Optional[datetime]: duration: int) -> Optional[datetime]:
# """Return this row's end time given its start time""" """Return datetime 'duration' ms after 'start'"""
#
# if start is None: if start is None:
# return None return None
#
# duration = self._get_row_duration(row) return start + timedelta(milliseconds=duration)
# return start + timedelta(milliseconds=duration)
# #
# def _context_menu(self, pos): # review # def _context_menu(self, pos): # review
# #
@ -1239,25 +1243,28 @@ class PlaylistTab(QTableWidget):
# if column in [self.COL_TITLE, self.COL_ARTIST]: # if column in [self.COL_TITLE, self.COL_ARTIST]:
# self.editItem(item) # self.editItem(item)
# #
# @staticmethod
# def _file_is_readable(path: str) -> bool:
# """
# Returns True if track path is readable, else False
#
# vlc cannot read files with a colon in the path
# """
#
# if os.access(path, os.R_OK):
# if ':' not in path:
# return True
#
# return False
#
# def _get_notes_rows(self) -> List[int]: # def _get_notes_rows(self) -> 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 _get_playlistrow_id(self, row):
"""Return the playlistrow_id associated with this row"""
playlistrow_id = (self.item(row, columns['userdata'].idx)
.data(self.PLAYLISTROW_ID))
return playlistrow_id
def _get_row_track_id(self, row):
"""Return the track_id associated with this row or None"""
track_id = (self.item(row, columns['userdata'].idx)
.data(self.ROW_TRACK_ID))
return track_id
# def _find_next_track_row(self, starting_row: int = None) -> Optional[int]: # def _find_next_track_row(self, starting_row: int = None) -> Optional[int]:
# """ # """
# Find next track to play. If a starting row is given, start there; # Find next track to play. If a starting row is given, start there;
@ -1284,24 +1291,24 @@ class PlaylistTab(QTableWidget):
# return row # return row
# #
# return None # return None
#
# def _get_current_track_row(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"""
#
# row = self._meta_search(RowMeta.CURRENT) row = self._meta_search(RowMeta.CURRENT)
# if len(row) > 0: if len(row) > 0:
# return row[0] return row[0]
# else: else:
# return None return None
#
# def _get_next_track_row(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"""
#
# row = self._meta_search(RowMeta.NEXT) row = self._meta_search(RowMeta.NEXT)
# if len(row) > 0: if len(row) > 0:
# return row[0] return row[0]
# else: else:
# return None return None
# #
# @staticmethod # @staticmethod
# def _get_note_text_time(text: str) -> Optional[datetime]: # def _get_note_text_time(text: str) -> Optional[datetime]:
@ -1316,6 +1323,11 @@ class PlaylistTab(QTableWidget):
# Config.NOTE_TIME_FORMAT) # Config.NOTE_TIME_FORMAT)
# except ValueError: # except ValueError:
# return None # return None
def _get_played_track_rows(self) -> List[int]:
"""Return rows marked as played, or None"""
return self._meta_search(RowMeta.PLAYED, one=False)
# #
# def _get_row_duration(self, row: int) -> int: # def _get_row_duration(self, row: int) -> int:
# """Return duration associated with this row""" # """Return duration associated with this row"""
@ -1349,11 +1361,6 @@ class PlaylistTab(QTableWidget):
# note = Notes.get_by_id(session, note_id) # note = Notes.get_by_id(session, note_id)
# return note # return note
# #
# def _get_played_track_rows(self) -> List[int]:
# """Return rows marked as played, or None"""
#
# return self._meta_search(RowMeta.PLAYED, one=False)
#
# def _get_unplayed_track_rows(self) -> Optional[List[int]]: # def _get_unplayed_track_rows(self) -> Optional[List[int]]:
# """Return rows marked as unplayed, or None""" # """Return rows marked as unplayed, or None"""
# #
@ -1386,11 +1393,11 @@ class PlaylistTab(QTableWidget):
# """Return rows marked as tracks, or None""" # """Return rows marked as tracks, or None"""
# #
# return self._meta_notset(RowMeta.NOTE) # return self._meta_notset(RowMeta.NOTE)
#
# def _get_unreadable_track_rows(self) -> List[int]: def _get_unreadable_track_rows(self) -> 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 _info_row(self, row: int) -> None: # def _info_row(self, row: int) -> None:
# """Display popup with info re row""" # """Display popup with info re row"""
@ -1502,11 +1509,12 @@ class PlaylistTab(QTableWidget):
# next_row: Optional[int] = self._get_next_track_row() # 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_get(self, row: int) -> int: def _meta_get(self, row: int) -> int:
# """Return row metadata""" """Return row metadata"""
#
# return self.item(row, self.COL_USERDATA).data(self.ROW_METADATA) return (self.item(row, columns['userdata'].idx)
.data(self.ROW_METADATA))
# #
# def _meta_notset(self, metadata: int) -> List[int]: # def _meta_notset(self, metadata: int) -> List[int]:
# """ # """
@ -1523,34 +1531,34 @@ class PlaylistTab(QTableWidget):
# matches.append(row) # matches.append(row)
# #
# return matches # return matches
#
# def _meta_search(self, metadata: int, one: bool = True) -> List[int]: def _meta_search(self, metadata: int, one: bool = True) -> List[int]:
# """ """
# Search rows for metadata. Search rows for metadata.
#
# If one is True, check that only one row matches and return If one is True, check that only one row matches and return
# the row number. the row number.
#
# If one is False, return a list of matching row numbers. If one is False, return a list of matching row numbers.
# """ """
#
# 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) & (1 << metadata): if self._meta_get(row) & (1 << metadata):
# matches.append(row) matches.append(row)
#
# if not one: if not one:
# return matches return matches
#
# if len(matches) <= 1: if len(matches) <= 1:
# return matches return matches
# else: else:
# log.error( log.error(
# f"Multiple matches for metadata '{metadata}' found " f"Multiple matches for metadata '{metadata}' found "
# f"in rows: {', '.join([str(x) for x in matches])}" f"in rows: {', '.join([str(x) for x in matches])}"
# ) )
# raise AttributeError(f"Multiple '{metadata}' metadata {matches}") raise AttributeError(f"Multiple '{metadata}' metadata {matches}")
# #
# def _meta_set_attribute(self, row: int, attribute: int) -> None: # def _meta_set_attribute(self, row: int, attribute: int) -> None:
# """Set row metadata""" # """Set row metadata"""
@ -1622,12 +1630,12 @@ class PlaylistTab(QTableWidget):
# """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 _set_unreadable_row(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 _select_event(self) -> None: # def _select_event(self) -> None:
# """ # """
# Called when item selection changes. # Called when item selection changes.
@ -1726,27 +1734,26 @@ class PlaylistTab(QTableWidget):
# #
# # Notify musicmuster # # Notify musicmuster
# self.musicmuster.this_is_the_next_track(self, track, session) # self.musicmuster.this_is_the_next_track(self, track, 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"""
#
# i: int j: int
# j: int
# boldfont: QFont = QFont()
# boldfont: QFont = QFont() boldfont.setBold(bold)
# boldfont.setBold(bold) for j in range(self.columnCount()):
# for j in range(self.columnCount()): if self.item(row, j):
# if self.item(row, j): self.item(row, j).setFont(boldfont)
# self.item(row, j).setFont(boldfont)
# def _set_row_colour(self, row: int, colour: QColor) -> None:
# def _set_row_colour(self, row: int, colour: QColor) -> None: """Set row background colour"""
# """Set row background colour"""
# j: int
# j: int
# for j in range(2, self.columnCount()):
# for j in range(2, self.columnCount()): 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, object_id: int) -> None: # def _set_row_content(self, row: int, object_id: int) -> None:
# """Set content associated with this row""" # """Set content associated with this row"""
@ -1756,37 +1763,37 @@ class PlaylistTab(QTableWidget):
# self.item(row, self.COL_USERDATA).setData( # self.item(row, self.COL_USERDATA).setData(
# self.CONTENT_OBJECT, object_id) # self.CONTENT_OBJECT, object_id)
# #
# def _set_row_duration(self, row: int, ms: int) -> None: # def _set_row_duration(self, row: int, ms: int) -> None:
# """Set duration of this row in milliseconds""" # """Set duration of this row in row metadata"""
# #
# assert self.item(row, self.COL_USERDATA) # assert self.item(row, columns['userdata'].idx)
# #
# self.item(row, self.COL_USERDATA).setData(self.ROW_DURATION, ms) # self.item(row, columns['userdata'].idx).setData(self.ROW_DURATION, ms)
#
# 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"""
#
# try: try:
# time_str: str = time.strftime(Config.TRACK_TIME_FORMAT) time_str: str = time.strftime(Config.TRACK_TIME_FORMAT)
# except AttributeError: except AttributeError:
# time_str = "" time_str = ""
# item = QTableWidgetItem(time_str) item = QTableWidgetItem(time_str)
# self.setItem(row, self.COL_END_TIME, item) self.setItem(row, columns['end_time'].idx, item)
#
# def _set_row_not_bold(self, row: int) -> None: def _set_row_not_bold(self, row: int) -> None:
# """Set row to not be bold""" """Set row to not be bold"""
#
# self._set_row_bold(row, False) self._set_row_bold(row, False)
#
# def _set_row_start_time(self, row: int, time: Optional[datetime]) -> None: def _set_row_start_time(self, row: int, time: Optional[datetime]) -> None:
# """Set passed row start time to passed time""" """Set passed row start time to passed time"""
#
# try: try:
# time_str: str = time.strftime(Config.TRACK_TIME_FORMAT) time_str: str = time.strftime(Config.TRACK_TIME_FORMAT)
# except AttributeError: except AttributeError:
# time_str = "" time_str = ""
# item: QTableWidgetItem = QTableWidgetItem(time_str) item: QTableWidgetItem = QTableWidgetItem(time_str)
# self.setItem(row, self.COL_START_TIME, item) self.setItem(row, columns['start_time'].idx, item)
# #
# def _set_timed_section(self, session, start_row, ms, no_end=False): # def _set_timed_section(self, session, start_row, ms, no_end=False):
# """Add duration to a marked section""" # """Add duration to a marked section"""