SQLA2: WIP, playlists load
This commit is contained in:
parent
caed7fd079
commit
4f03306aff
103
app/helpers.py
103
app/helpers.py
@ -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(
|
||||
|
||||
@ -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"]:
|
||||
|
||||
747
app/playlists.py
747
app/playlists.py
@ -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"""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user