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
#
# from config import Config
# from datetime import datetime
from datetime import datetime
# from pydub import AudioSegment
# from PyQt5.QtWidgets import QMessageBox
# from tinytag import TinyTag
@ -44,7 +44,22 @@
# # 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)
# 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]:
# try:
@ -70,47 +85,47 @@
# path=path
# )
# return d
#
#
# def get_relative_date(past_date: datetime, reference_date: datetime = None) \
# -> str:
# """
# Return how long before reference_date past_date is as string.
#
# Params:
# @past_date: datetime
# @reference_date: datetime, defaults to current date and time
#
# @return: string
# """
#
# if not past_date:
# return "Never"
# if not reference_date:
# reference_date = datetime.now()
#
# # Check parameters
# if past_date > reference_date:
# return "get_relative_date() past_date is after relative_date"
#
# days: int
# days_str: str
# weeks: int
# weeks_str: str
#
# weeks, days = divmod((reference_date.date() - past_date.date()).days, 7)
# if weeks == days == 0:
# # Played today, so return time instead
# return past_date.strftime("%H:%M")
# if weeks == 1:
# weeks_str = "week"
# else:
# weeks_str = "weeks"
# if days == 1:
# days_str = "day"
# else:
# days_str = "days"
# return f"{weeks} {weeks_str}, {days} {days_str} ago"
def get_relative_date(past_date: datetime, reference_date: datetime = None) \
-> str:
"""
Return how long before reference_date past_date is as string.
Params:
@past_date: datetime
@reference_date: datetime, defaults to current date and time
@return: string
"""
if not past_date:
return "Never"
if not reference_date:
reference_date = datetime.now()
# Check parameters
if past_date > reference_date:
return "get_relative_date() past_date is after relative_date"
days: int
days_str: str
weeks: int
weeks_str: str
weeks, days = divmod((reference_date.date() - past_date.date()).days, 7)
if weeks == days == 0:
# Same day so return time instead
return past_date.strftime("%H:%M")
if weeks == 1:
weeks_str = "week"
else:
weeks_str = "weeks"
if days == 1:
days_str = "day"
else:
days_str = "days"
return f"{weeks} {weeks_str}, {days} {days_str} ago"
#
#
# def leading_silence(

View File

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

View File

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