No mypy errors; four FIXMEs
This commit is contained in:
parent
e4ef0b34c8
commit
4f3fb6c1ae
@ -7,7 +7,7 @@ import stackprinter # type: ignore
|
||||
from dbconfig import Session, scoped_session
|
||||
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from typing import Iterable, List, Optional
|
||||
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
|
||||
@ -95,13 +95,13 @@ class NoteColours(Base):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_colour(session: scoped_session, text: str) -> Optional[str]:
|
||||
def get_colour(session: scoped_session, text: str) -> str:
|
||||
"""
|
||||
Parse text and return colour string if matched, else None
|
||||
Parse text and return colour string if matched, else empty string
|
||||
"""
|
||||
|
||||
if not text:
|
||||
return None
|
||||
return ""
|
||||
|
||||
for rec in session.execute(
|
||||
select(NoteColours)
|
||||
@ -123,7 +123,7 @@ class NoteColours(Base):
|
||||
if rec.substring.lower() in text.lower():
|
||||
return rec.colour
|
||||
|
||||
return None
|
||||
return ""
|
||||
|
||||
|
||||
class Playdates(Base):
|
||||
@ -196,7 +196,7 @@ class Playlists(Base):
|
||||
is_template = Column(Boolean, default=False, nullable=False)
|
||||
query = Column(String(256), default=None, nullable=True, unique=False)
|
||||
deleted = Column(Boolean, default=False, nullable=False)
|
||||
rows: "PlaylistRows" = relationship(
|
||||
rows: List["PlaylistRows"] = relationship(
|
||||
"PlaylistRows",
|
||||
back_populates="playlist",
|
||||
cascade="all, delete-orphan",
|
||||
@ -370,7 +370,7 @@ class PlaylistRows(Base):
|
||||
def __init__(self,
|
||||
session: scoped_session,
|
||||
playlist_id: int,
|
||||
track_id: int,
|
||||
track_id: Optional[int],
|
||||
row_number: int,
|
||||
note: Optional[str] = None
|
||||
) -> None:
|
||||
@ -409,7 +409,7 @@ class PlaylistRows(Base):
|
||||
|
||||
@staticmethod
|
||||
def delete_plrids_not_in_list(session: scoped_session, playlist_id: int,
|
||||
plrids: List["PlaylistRows"]) -> None:
|
||||
plrids: List[int]) -> None:
|
||||
"""
|
||||
Delete rows in given playlist that have a higher row number
|
||||
than 'maxrow'
|
||||
@ -469,7 +469,7 @@ class PlaylistRows(Base):
|
||||
|
||||
@classmethod
|
||||
def get_played_rows(cls, session: scoped_session,
|
||||
playlist_id: int) -> List[int]:
|
||||
playlist_id: int) -> List["PlaylistRows"]:
|
||||
"""
|
||||
For passed playlist, return a list of rows that
|
||||
have been played.
|
||||
@ -488,7 +488,7 @@ class PlaylistRows(Base):
|
||||
|
||||
@classmethod
|
||||
def get_rows_with_tracks(cls, session: scoped_session,
|
||||
playlist_id: int) -> List[int]:
|
||||
playlist_id: int) -> List["PlaylistRows"]:
|
||||
"""
|
||||
For passed playlist, return a list of rows that
|
||||
contain tracks
|
||||
@ -526,24 +526,8 @@ class PlaylistRows(Base):
|
||||
return plrs
|
||||
|
||||
@staticmethod
|
||||
def move_rows_down(session: scoped_session, playlist_id: int,
|
||||
starting_row: int, move_by: int) -> None:
|
||||
"""
|
||||
Create space to insert move_by additional rows by incremented row
|
||||
number from starting_row to end of playlist
|
||||
"""
|
||||
|
||||
session.execute(
|
||||
update(PlaylistRows)
|
||||
.where(
|
||||
(PlaylistRows.playlist_id == playlist_id),
|
||||
(PlaylistRows.row_number >= starting_row)
|
||||
)
|
||||
.values(row_number=PlaylistRows.row_number + move_by)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def indexed_by_id(session: scoped_session, plr_ids: List[int]) -> dict:
|
||||
def indexed_by_id(session: scoped_session,
|
||||
plr_ids: Iterable[int]) -> dict:
|
||||
"""
|
||||
Return a dictionary of playlist_rows indexed by their plr id from
|
||||
the passed plr_id list.
|
||||
@ -562,6 +546,23 @@ class PlaylistRows(Base):
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def move_rows_down(session: scoped_session, playlist_id: int,
|
||||
starting_row: int, move_by: int) -> None:
|
||||
"""
|
||||
Create space to insert move_by additional rows by incremented row
|
||||
number from starting_row to end of playlist
|
||||
"""
|
||||
|
||||
session.execute(
|
||||
update(PlaylistRows)
|
||||
.where(
|
||||
(PlaylistRows.playlist_id == playlist_id),
|
||||
(PlaylistRows.row_number >= starting_row)
|
||||
)
|
||||
.values(row_number=PlaylistRows.row_number + move_by)
|
||||
)
|
||||
|
||||
|
||||
class Settings(Base):
|
||||
"""Manage settings"""
|
||||
|
||||
@ -9,10 +9,29 @@ import threading
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from time import sleep
|
||||
from typing import Callable, List, Optional
|
||||
from typing import (
|
||||
Callable,
|
||||
cast,
|
||||
List,
|
||||
Optional,
|
||||
)
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QDate, QEvent, Qt, QSize, QTime, QTimer
|
||||
from PyQt5.QtGui import QColor, QFont, QPalette, QResizeEvent
|
||||
from PyQt5.QtCore import (
|
||||
pyqtSignal,
|
||||
QDate,
|
||||
QEvent,
|
||||
Qt,
|
||||
QSize,
|
||||
QTime,
|
||||
QTimer,
|
||||
)
|
||||
from PyQt5.QtGui import (
|
||||
QColor,
|
||||
QFont,
|
||||
QMouseEvent,
|
||||
QPalette,
|
||||
QResizeEvent,
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication,
|
||||
QDialog,
|
||||
@ -27,10 +46,13 @@ from PyQt5.QtWidgets import (
|
||||
QProgressBar,
|
||||
)
|
||||
|
||||
from dbconfig import engine, Session, scoped_session
|
||||
from dbconfig import (
|
||||
engine,
|
||||
Session,
|
||||
scoped_session,
|
||||
)
|
||||
import helpers
|
||||
import music
|
||||
|
||||
from models import (
|
||||
Base,
|
||||
Carts,
|
||||
@ -55,12 +77,11 @@ class CartButton(QPushButton):
|
||||
|
||||
progress = pyqtSignal(int)
|
||||
|
||||
def __init__(self, parent: QMainWindow, cart: Carts):
|
||||
def __init__(self, musicmuster: "Window", cart: Carts, *args, **kwargs):
|
||||
"""Create a cart pushbutton and set it disabled"""
|
||||
|
||||
super().__init__(parent)
|
||||
# Next line is redundant (check)
|
||||
# self.parent = parent
|
||||
super().__init__(*args, **kwargs)
|
||||
self.musicmuster = musicmuster
|
||||
self.cart_id = cart.id
|
||||
if cart.path and cart.enabled and not cart.duration:
|
||||
tags = helpers.get_tags(cart.path)
|
||||
@ -101,8 +122,9 @@ class CartButton(QPushButton):
|
||||
"""Allow right click even when button is disabled"""
|
||||
|
||||
if event.type() == QEvent.MouseButtonRelease:
|
||||
if event.button() == Qt.RightButton:
|
||||
self.parent.cart_edit(self, event)
|
||||
mouse_event = cast(QMouseEvent, event)
|
||||
if mouse_event.button() == Qt.RightButton:
|
||||
self.musicmuster.cart_edit(self, event) # type: ignore # FIXME
|
||||
return True
|
||||
|
||||
return super().event(event)
|
||||
@ -137,7 +159,7 @@ class PlaylistTrack:
|
||||
self.playlist_id: Optional[int] = None
|
||||
self.playlist_tab: Optional[PlaylistTab] = None
|
||||
self.plr_id: Optional[int] = None
|
||||
self.silence_at: Optional[datetime] = None
|
||||
self.silence_at: Optional[int] = None
|
||||
self.start_gap: Optional[int] = None
|
||||
self.start_time: Optional[datetime] = None
|
||||
self.title: Optional[str] = None
|
||||
@ -164,7 +186,6 @@ class PlaylistTrack:
|
||||
self.duration = track.duration
|
||||
self.end_time = None
|
||||
self.fade_at = track.fade_at
|
||||
self.fade_length = track.silence_at - track.fade_at
|
||||
self.path = track.path
|
||||
self.playlist_id = plr.playlist_id
|
||||
self.plr_id = plr.id
|
||||
@ -174,18 +195,23 @@ class PlaylistTrack:
|
||||
self.title = track.title
|
||||
self.track_id = track.id
|
||||
|
||||
if track.silence_at and track.fade_at:
|
||||
self.fade_length = track.silence_at - track.fade_at
|
||||
|
||||
def start(self) -> None:
|
||||
"""
|
||||
Called when track starts playing
|
||||
"""
|
||||
|
||||
self.start_time = datetime.now()
|
||||
self.end_time = self.start_time + timedelta(milliseconds=self.duration)
|
||||
if self.duration:
|
||||
self.end_time = (
|
||||
self.start_time + timedelta(milliseconds=self.duration))
|
||||
|
||||
|
||||
class Window(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
def __init__(self, parent=None, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.setupUi(self)
|
||||
|
||||
self.timer: QTimer = QTimer()
|
||||
@ -198,8 +224,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.next_track = PlaylistTrack()
|
||||
self.previous_track = PlaylistTrack()
|
||||
|
||||
self.previous_track_position: Optional[int] = None
|
||||
self.selected_plrs = None
|
||||
self.previous_track_position: Optional[float] = None
|
||||
self.selected_plrs: Optional[List[PlaylistRows]] = None
|
||||
|
||||
# Set colours that will be used by playlist row stripes
|
||||
palette = QPalette()
|
||||
@ -239,7 +265,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
colour = Config.COLOUR_CART_READY
|
||||
btn.path = cart.path
|
||||
btn.player = self.music.VLC.media_player_new(cart.path)
|
||||
btn.player.audio_set_volume(Config.VOLUME_VLC_DEFAULT)
|
||||
if btn.player:
|
||||
btn.player.audio_set_volume(Config.VOLUME_VLC_DEFAULT)
|
||||
if cart.enabled:
|
||||
btn.setEnabled(True)
|
||||
btn.pgb.setVisible(True)
|
||||
@ -249,16 +276,24 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
colour = Config.COLOUR_CART_UNCONFIGURED
|
||||
|
||||
btn.setStyleSheet("background-color: " + colour + ";\n")
|
||||
btn.setText(cart.name)
|
||||
if cart.name is not None:
|
||||
btn.setText(cart.name)
|
||||
|
||||
def cart_click(self) -> None:
|
||||
"""Handle cart click"""
|
||||
|
||||
btn = self.sender()
|
||||
if not isinstance(btn, CartButton):
|
||||
return
|
||||
|
||||
if helpers.file_is_readable(btn.path):
|
||||
# Don't allow clicks while we're playing
|
||||
btn.setEnabled(False)
|
||||
if not btn.player:
|
||||
log.debug(
|
||||
f"musicmuster.cart_click(): no player assigned ({btn=})")
|
||||
return
|
||||
|
||||
btn.player.play()
|
||||
btn.is_playing = True
|
||||
colour = Config.COLOUR_CART_PLAYING
|
||||
@ -268,7 +303,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
else:
|
||||
colour = Config.COLOUR_CART_ERROR
|
||||
btn.setStyleSheet("background-color: " + colour + ";\n")
|
||||
btn.pgb.minimum = 0
|
||||
btn.pgb.setMinimum(0)
|
||||
|
||||
def cart_edit(self, btn: CartButton, event: QEvent):
|
||||
"""Handle context menu for cart button"""
|
||||
@ -276,10 +311,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
with Session() as session:
|
||||
cart = session.query(Carts).get(btn.cart_id)
|
||||
if cart is None:
|
||||
log.ERROR("cart_edit: cart not found")
|
||||
log.error("cart_edit: cart not found")
|
||||
return
|
||||
|
||||
dlg = CartDialog(parent=self, session=session, cart=cart)
|
||||
dlg = CartDialog(musicmuster=self, session=session, cart=cart)
|
||||
if dlg.exec():
|
||||
name = dlg.ui.lineEditName.text()
|
||||
if not name:
|
||||
@ -324,6 +359,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
def cart_progressbar(self, btn: CartButton) -> None:
|
||||
"""Manage progress bar"""
|
||||
|
||||
if not btn.duration:
|
||||
return
|
||||
|
||||
ms = 0
|
||||
btn.pgb.setMaximum(btn.duration)
|
||||
while ms <= btn.duration:
|
||||
@ -437,7 +475,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
with Session() as session:
|
||||
playlist_id = self.tabPlaylist.widget(tab_index).playlist_id
|
||||
playlist = session.get(Playlists, playlist_id)
|
||||
playlist.close(session)
|
||||
if playlist:
|
||||
playlist.close(session)
|
||||
|
||||
# Close playlist and remove tab
|
||||
self.tabPlaylist.widget(tab_index).close()
|
||||
@ -498,10 +537,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
playlist_name: Optional[str] = None) -> Playlists:
|
||||
"""Create new playlist"""
|
||||
|
||||
if not playlist_name:
|
||||
while not playlist_name:
|
||||
playlist_name = self.solicit_playlist_name()
|
||||
if not playlist_name:
|
||||
return
|
||||
|
||||
playlist = Playlists(session, playlist_name)
|
||||
return playlist
|
||||
@ -521,6 +558,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
add tab to display. Return index number of tab.
|
||||
"""
|
||||
|
||||
assert playlist.id
|
||||
|
||||
playlist_tab = PlaylistTab(
|
||||
musicmuster=self, session=session, playlist_id=playlist.id)
|
||||
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
||||
@ -622,7 +661,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# Repaint playlist to remove currently playing track colour
|
||||
# What was current track is now previous track
|
||||
with Session() as session:
|
||||
self.previous_track.playlist_tab.update_display(session)
|
||||
if self.previous_track.playlist_tab:
|
||||
self.previous_track.playlist_tab.update_display(session)
|
||||
|
||||
# Reset clocks
|
||||
self.frame_fade.setStyleSheet("")
|
||||
@ -638,8 +678,11 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.label_track_length.setText(
|
||||
helpers.ms_to_mmss(self.next_track.duration)
|
||||
)
|
||||
self.label_fade_length.setText(helpers.ms_to_mmss(
|
||||
self.next_track.silence_at - self.next_track.fade_at))
|
||||
if self.next_track.silence_at and self.next_track.fade_at:
|
||||
self.label_fade_length.setText(helpers.ms_to_mmss(
|
||||
self.next_track.silence_at - self.next_track.fade_at))
|
||||
else:
|
||||
self.label_fade_length.setText("0:00")
|
||||
else:
|
||||
self.label_track_length.setText("0:00")
|
||||
self.label_fade_length.setText("0:00")
|
||||
@ -662,6 +705,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
# Get output filename
|
||||
playlist = session.get(Playlists, playlist_id)
|
||||
if not playlist:
|
||||
return
|
||||
|
||||
pathspec = QFileDialog.getSaveFileName(
|
||||
self, 'Save Playlist',
|
||||
directory=f"{playlist.name}.m3u",
|
||||
@ -679,6 +725,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# Required directive on first line
|
||||
f.write("#EXTM3U\n")
|
||||
for track in [a.track for a in plrs]:
|
||||
if track.duration is None:
|
||||
track.duration = 0
|
||||
f.write(
|
||||
"#EXTINF:"
|
||||
f"{int(track.duration / 1000)},"
|
||||
@ -707,7 +755,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
git_tag = str(exc_info.output)
|
||||
|
||||
with Session() as session:
|
||||
dbname = session.bind.engine.url.database
|
||||
if session.bind:
|
||||
dbname = session.bind.engine.url.database
|
||||
|
||||
QMessageBox.information(
|
||||
self,
|
||||
@ -721,7 +770,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
dlg = DbDialog(self, session, get_one_track=True)
|
||||
if dlg.exec():
|
||||
return dlg.ui.track
|
||||
return dlg.track
|
||||
else:
|
||||
return None
|
||||
|
||||
def hide_played(self):
|
||||
"""Toggle hide played tracks"""
|
||||
@ -832,7 +883,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
with Session() as session:
|
||||
for playlist in Playlists.get_open(session):
|
||||
_ = self.create_playlist_tab(session, playlist)
|
||||
if playlist:
|
||||
_ = self.create_playlist_tab(session, playlist)
|
||||
# Set active tab
|
||||
record = Settings.get_int_settings(session, "active_tab")
|
||||
if record and record.f_int >= 0:
|
||||
@ -854,11 +906,14 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# Remove current/next rows from list
|
||||
plrs_to_move = [plr for plr in playlistrows if
|
||||
plr.id not in
|
||||
[self.current_track.plr_id,
|
||||
self.next_track.plr_id]
|
||||
[self.current_track.plr_id,
|
||||
self.next_track.plr_id]
|
||||
]
|
||||
|
||||
rows_to_delete = [plr.row_number for plr in plrs_to_move]
|
||||
rows_to_delete = [plr.row_number for plr in plrs_to_move
|
||||
if plr.row_number is not None]
|
||||
if not rows_to_delete:
|
||||
return
|
||||
|
||||
# Identify destination playlist
|
||||
playlists = []
|
||||
@ -912,11 +967,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
Move selected rows to another playlist
|
||||
"""
|
||||
|
||||
selected_plrs = self.visible_playlist_tab().get_selected_playlistrows(
|
||||
session)
|
||||
if not selected_plrs:
|
||||
return
|
||||
|
||||
with Session() as session:
|
||||
self.move_playlist_rows(
|
||||
session,
|
||||
self.visible_playlist_tab().get_selected_playlistrows(session)
|
||||
)
|
||||
self.move_playlist_rows(session, selected_plrs)
|
||||
|
||||
def move_tab(self, frm: int, to: int) -> None:
|
||||
"""Handle tabs being moved"""
|
||||
@ -954,6 +1011,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
return
|
||||
playlist = Playlists.create_playlist_from_template(
|
||||
session, template, playlist_name)
|
||||
if not playlist:
|
||||
return
|
||||
tab_index = self.create_playlist_tab(session, playlist)
|
||||
playlist.mark_open(session, tab_index)
|
||||
|
||||
@ -1007,7 +1066,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
plr.row_number = row
|
||||
row += 1
|
||||
|
||||
session.commit()
|
||||
if not src_playlist_id:
|
||||
return
|
||||
|
||||
session.flush()
|
||||
|
||||
# Update display
|
||||
self.visible_playlist_tab().populate_display(
|
||||
@ -1063,8 +1125,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# Ensure playlist tabs are the correct colour
|
||||
# If next track is on a different playlist_tab to the
|
||||
# current track, reset the current track playlist_tab colour
|
||||
if self.current_track.playlist_tab != self.next_track.playlist_tab:
|
||||
self.set_tab_colour(self.current_track.playlist_tab,
|
||||
current_tab = self.current_track.playlist_tab
|
||||
if current_tab and current_tab != self.next_track.playlist_tab:
|
||||
self.set_tab_colour(current_tab,
|
||||
QColor(Config.COLOUR_NORMAL_TAB))
|
||||
|
||||
# Move next track to current track.
|
||||
@ -1073,9 +1136,17 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.current_track = self.next_track
|
||||
self.next_track = PlaylistTrack()
|
||||
|
||||
if not self.current_track.track_id:
|
||||
log.debug("musicmuster.play_next(): no id for next track")
|
||||
return
|
||||
if not self.current_track.path:
|
||||
log.debug("musicmuster.play_next(): no path for next track")
|
||||
return
|
||||
|
||||
# Set current track playlist_tab colour
|
||||
self.set_tab_colour(self.current_track.playlist_tab,
|
||||
QColor(Config.COLOUR_CURRENT_TAB))
|
||||
if current_tab:
|
||||
self.set_tab_colour(
|
||||
current_tab, QColor(Config.COLOUR_CURRENT_TAB))
|
||||
|
||||
# Restore volume if -3dB active
|
||||
if self.btnDrop3db.isChecked():
|
||||
@ -1089,7 +1160,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
Playdates(session, self.current_track.track_id)
|
||||
|
||||
# Tell playlist track is now playing
|
||||
self.current_track.playlist_tab.play_started(session)
|
||||
if self.current_track.playlist_tab:
|
||||
self.current_track.playlist_tab.play_started(session)
|
||||
|
||||
# Note that track is now playing
|
||||
self.playing = True
|
||||
@ -1116,11 +1188,14 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
)
|
||||
self.label_fade_length.setText(
|
||||
helpers.ms_to_mmss(self.current_track.fade_length))
|
||||
self.label_start_time.setText(
|
||||
self.current_track.start_time.strftime(
|
||||
Config.TRACK_TIME_FORMAT))
|
||||
self.label_end_time.setText(
|
||||
self.current_track.end_time.strftime(Config.TRACK_TIME_FORMAT))
|
||||
if self.current_track.start_time:
|
||||
self.label_start_time.setText(
|
||||
self.current_track.start_time.strftime(
|
||||
Config.TRACK_TIME_FORMAT))
|
||||
if self.current_track.end_time:
|
||||
self.label_end_time.setText(
|
||||
self.current_track.end_time.strftime(
|
||||
Config.TRACK_TIME_FORMAT))
|
||||
|
||||
def resume(self) -> None:
|
||||
"""
|
||||
@ -1157,6 +1232,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# Reset next track if there was one
|
||||
if original_next_plr_id:
|
||||
next_plr = session.get(PlaylistRows, original_next_plr_id)
|
||||
if not next_plr or not original_next_plr_playlist_tab:
|
||||
return
|
||||
self.this_is_the_next_playlist_row(
|
||||
session, next_plr, original_next_plr_playlist_tab)
|
||||
|
||||
@ -1308,12 +1385,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.music.stop()
|
||||
|
||||
# Reset playlist_tab colour
|
||||
if self.current_track.playlist_tab == self.next_track.playlist_tab:
|
||||
self.set_tab_colour(self.current_track.playlist_tab,
|
||||
QColor(Config.COLOUR_NEXT_TAB))
|
||||
else:
|
||||
self.set_tab_colour(self.current_track.playlist_tab,
|
||||
QColor(Config.COLOUR_NORMAL_TAB))
|
||||
if self.current_track.playlist_tab:
|
||||
if self.current_track.playlist_tab == self.next_track.playlist_tab:
|
||||
self.set_tab_colour(self.current_track.playlist_tab,
|
||||
QColor(Config.COLOUR_NEXT_TAB))
|
||||
else:
|
||||
self.set_tab_colour(self.current_track.playlist_tab,
|
||||
QColor(Config.COLOUR_NORMAL_TAB))
|
||||
|
||||
# Run end-of-track actions
|
||||
self.end_of_track_actions()
|
||||
@ -1366,10 +1444,11 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.next_track = PlaylistTrack()
|
||||
|
||||
self.next_track.set_plr(session, plr, playlist_tab)
|
||||
self.next_track.playlist_tab.update_display(session)
|
||||
if self.current_track.playlist_tab != self.next_track.playlist_tab:
|
||||
self.set_tab_colour(self.next_track.playlist_tab,
|
||||
QColor(Config.COLOUR_NEXT_TAB))
|
||||
if self.next_track.playlist_tab:
|
||||
self.next_track.playlist_tab.update_display(session)
|
||||
if self.current_track.playlist_tab != self.next_track.playlist_tab:
|
||||
self.set_tab_colour(self.next_track.playlist_tab,
|
||||
QColor(Config.COLOUR_NEXT_TAB))
|
||||
|
||||
# If we've changed playlist tabs for next track, refresh old one
|
||||
# to remove highligting of next track
|
||||
@ -1505,14 +1584,14 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
class CartDialog(QDialog):
|
||||
"""Edit cart details"""
|
||||
|
||||
def __init__(self, parent: QMainWindow, session: scoped_session,
|
||||
cart: Carts) -> None:
|
||||
def __init__(self, musicmuster: Window, session: scoped_session,
|
||||
cart: Carts, *args, **kwargs) -> None:
|
||||
"""
|
||||
Manage carts
|
||||
"""
|
||||
|
||||
super().__init__(parent)
|
||||
self.parent = parent
|
||||
super().__init__(*args, **kwargs)
|
||||
self.musicmuster = musicmuster
|
||||
self.session = session
|
||||
|
||||
self.ui = Ui_DialogCartEdit()
|
||||
@ -1522,7 +1601,7 @@ class CartDialog(QDialog):
|
||||
self.ui.lineEditName.setText(cart.name)
|
||||
self.ui.chkEnabled.setChecked(cart.enabled)
|
||||
|
||||
self.ui.windowTitle = "Edit Cart " + str(cart.id)
|
||||
self.setWindowTitle("Edit Cart " + str(cart.id))
|
||||
|
||||
self.ui.btnFile.clicked.connect(self.choose_file)
|
||||
|
||||
@ -1542,8 +1621,8 @@ class CartDialog(QDialog):
|
||||
class DbDialog(QDialog):
|
||||
"""Select track from database"""
|
||||
|
||||
def __init__(self, parent: Window, session: scoped_session,
|
||||
get_one_track: bool = False) -> None:
|
||||
def __init__(self, musicmuster: Window, session: scoped_session,
|
||||
get_one_track: bool = False, *args, **kwargs) -> None:
|
||||
"""
|
||||
Subclassed QDialog to manage track selection
|
||||
|
||||
@ -1552,7 +1631,8 @@ class DbDialog(QDialog):
|
||||
to be added to the playlist.
|
||||
"""
|
||||
|
||||
super().__init__(parent)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.musicmuster = musicmuster
|
||||
self.session = session
|
||||
self.get_one_track = get_one_track
|
||||
self.ui = Ui_Dialog()
|
||||
@ -1564,6 +1644,7 @@ class DbDialog(QDialog):
|
||||
self.ui.matchList.itemSelectionChanged.connect(self.selection_changed)
|
||||
self.ui.radioTitle.toggled.connect(self.title_artist_toggle)
|
||||
self.ui.searchString.textEdited.connect(self.chars_typed)
|
||||
self.track: Optional[Tracks] = None
|
||||
|
||||
if get_one_track:
|
||||
self.ui.txtNote.hide()
|
||||
@ -1609,19 +1690,19 @@ class DbDialog(QDialog):
|
||||
"""Add passed track to playlist on screen"""
|
||||
|
||||
if self.get_one_track:
|
||||
self.ui.track = track
|
||||
self.track = track
|
||||
self.accept()
|
||||
return
|
||||
|
||||
if track:
|
||||
self.parent().visible_playlist_tab().insert_track(
|
||||
self.musicmuster.visible_playlist_tab().insert_track(
|
||||
self.session, track, note=self.ui.txtNote.text())
|
||||
else:
|
||||
self.parent().visible_playlist_tab().insert_header(
|
||||
self.musicmuster.visible_playlist_tab().insert_header(
|
||||
self.session, note=self.ui.txtNote.text())
|
||||
|
||||
# Save to database (which will also commit changes)
|
||||
self.parent().visible_playlist_tab().save_playlist(self.session)
|
||||
self.musicmuster.visible_playlist_tab().save_playlist(self.session)
|
||||
# Clear note field and select search text to make it easier for
|
||||
# next search
|
||||
self.ui.txtNote.clear()
|
||||
@ -1684,7 +1765,7 @@ class DbDialog(QDialog):
|
||||
|
||||
class DownloadCSV(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.ui = Ui_DateSelect()
|
||||
self.ui.setupUi(self)
|
||||
@ -1696,7 +1777,7 @@ class DownloadCSV(QDialog):
|
||||
|
||||
class SelectPlaylistDialog(QDialog):
|
||||
def __init__(self, parent=None, playlists=None, session=None):
|
||||
super().__init__(parent)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if playlists is None:
|
||||
return
|
||||
|
||||
339
app/playlists.py
339
app/playlists.py
@ -5,7 +5,7 @@ import threading
|
||||
|
||||
from collections import namedtuple
|
||||
from datetime import datetime, timedelta
|
||||
from typing import cast, List, Optional
|
||||
from typing import cast, List, Optional, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
pyqtSignal,
|
||||
@ -59,6 +59,9 @@ from models import (
|
||||
NoteColours
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from musicmuster import Window
|
||||
|
||||
start_time_re = re.compile(r"@\d\d:\d\d:\d\d")
|
||||
HEADER_NOTES_COLUMN = 2
|
||||
MINIMUM_ROW_HEIGHT = 30
|
||||
@ -129,10 +132,11 @@ class PlaylistTab(QTableWidget):
|
||||
ROW_DURATION = Qt.UserRole + 2
|
||||
PLAYLISTROW_ID = Qt.UserRole + 3
|
||||
|
||||
def __init__(self, musicmuster: QMainWindow, session: scoped_session,
|
||||
def __init__(self, musicmuster: Window,
|
||||
session: scoped_session,
|
||||
playlist_id: int, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.musicmuster = musicmuster
|
||||
self.musicmuster: Window = musicmuster
|
||||
self.playlist_id = playlist_id
|
||||
|
||||
self.menu: Optional[QMenu] = None
|
||||
@ -154,7 +158,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# Header row
|
||||
for idx in [a for a in range(len(columns))]:
|
||||
item: QTableWidgetItem = QTableWidgetItem()
|
||||
item = QTableWidgetItem()
|
||||
self.setHorizontalHeaderItem(idx, item)
|
||||
self.horizontalHeader().setMinimumSectionSize(0)
|
||||
# Set column headings sorted by idx
|
||||
@ -183,7 +187,7 @@ class PlaylistTab(QTableWidget):
|
||||
self.itemSelectionChanged.connect(self._select_event)
|
||||
|
||||
self.search_text: str = ""
|
||||
self.edit_cell_type = None
|
||||
self.edit_cell_type: Optional[int]
|
||||
self.selecting_in_progress = False
|
||||
# Connect signals
|
||||
self.horizontalHeader().sectionResized.connect(self._column_resize)
|
||||
@ -212,7 +216,9 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
rows: List = sorted(set(item.row() for item in self.selectedItems()))
|
||||
rows_to_move = [
|
||||
[QTableWidgetItem(self.item(row_index, column_index)) for
|
||||
[QTableWidgetItem(
|
||||
self.item(row_index, column_index) # type: ignore
|
||||
) for
|
||||
column_index in range(self.columnCount())]
|
||||
for row_index in rows
|
||||
]
|
||||
@ -407,10 +413,14 @@ class PlaylistTab(QTableWidget):
|
||||
# change cell again (metadata)
|
||||
self.cellChanged.disconnect(self._cell_changed)
|
||||
|
||||
new_text = self.item(row, column).text().strip()
|
||||
cell = self.item(row, column)
|
||||
if not cell:
|
||||
return
|
||||
|
||||
new_text = cell.text().strip()
|
||||
|
||||
# Update cell with strip()'d text
|
||||
self.item(row, column).setText(new_text)
|
||||
cell.setText(new_text)
|
||||
|
||||
track_id = self._get_row_track_id(row)
|
||||
|
||||
@ -419,6 +429,8 @@ class PlaylistTab(QTableWidget):
|
||||
# Get playlistrow object
|
||||
plr_id = self._get_playlistrow_id(row)
|
||||
plr_item = session.get(PlaylistRows, plr_id)
|
||||
if not plr_item:
|
||||
return
|
||||
|
||||
# Note any updates needed to PlaylistTrack objects
|
||||
update_current = self.musicmuster.current_track.plr_id == plr_id
|
||||
@ -473,7 +485,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
super(PlaylistTab, self).closeEditor(editor, hint)
|
||||
|
||||
def edit(self, index: QModelIndex,
|
||||
def edit(self, index: QModelIndex, # type: ignore # FIXME
|
||||
trigger: QAbstractItemView.EditTrigger,
|
||||
event: QEvent) -> bool:
|
||||
"""
|
||||
@ -492,7 +504,6 @@ class PlaylistTab(QTableWidget):
|
||||
if track_row:
|
||||
# If a track row, we only allow editing of title, artist and
|
||||
# note. Check that this column is one of those.
|
||||
self.edit_cell_type = None
|
||||
if column in [TITLE, ARTIST, ROW_NOTES]:
|
||||
self.edit_cell_type = column
|
||||
else:
|
||||
@ -523,6 +534,8 @@ class PlaylistTab(QTableWidget):
|
||||
plr_id = self._get_playlistrow_id(row)
|
||||
plr_item = session.get(PlaylistRows, plr_id)
|
||||
item = self.item(row, note_column)
|
||||
if not plr_item or not plr_item.note or not item:
|
||||
return False
|
||||
item.setText(plr_item.note)
|
||||
|
||||
# Connect signal so we know when cell has changed.
|
||||
@ -563,6 +576,8 @@ class PlaylistTab(QTableWidget):
|
||||
"""
|
||||
|
||||
plr_ids = self.get_selected_playlistrow_ids()
|
||||
if not plr_ids:
|
||||
return None
|
||||
return [session.get(PlaylistRows, a) for a in plr_ids]
|
||||
|
||||
def insert_header(self, session: scoped_session, note: str,
|
||||
@ -588,6 +603,9 @@ class PlaylistTab(QTableWidget):
|
||||
Insert passed playlist row (plr) into playlist tab.
|
||||
"""
|
||||
|
||||
if plr.row_number is None:
|
||||
return
|
||||
|
||||
row = plr.row_number
|
||||
self.insertRow(row)
|
||||
|
||||
@ -604,44 +622,46 @@ class PlaylistTab(QTableWidget):
|
||||
start_gap = plr.track.start_gap
|
||||
except AttributeError:
|
||||
return
|
||||
start_gap_item = QTableWidgetItem(str(start_gap))
|
||||
start_gap_item = self._set_item_text(
|
||||
row, START_GAP, str(start_gap))
|
||||
if start_gap and start_gap >= 500:
|
||||
start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START))
|
||||
self.setItem(row, START_GAP, start_gap_item)
|
||||
|
||||
title_item = QTableWidgetItem(plr.track.title)
|
||||
self.setItem(row, TITLE, title_item)
|
||||
track_title = plr.track.title
|
||||
if not track_title:
|
||||
track_title = ""
|
||||
_ = self._set_item_text(row, TITLE, track_title)
|
||||
|
||||
artist_item = QTableWidgetItem(plr.track.artist)
|
||||
self.setItem(row, ARTIST, artist_item)
|
||||
track_artist = plr.track.artist
|
||||
if not track_artist:
|
||||
track_artist = ""
|
||||
_ = self._set_item_text(row, ARTIST, track_artist)
|
||||
|
||||
duration_item = QTableWidgetItem(
|
||||
ms_to_mmss(plr.track.duration))
|
||||
self.setItem(row, DURATION, duration_item)
|
||||
self._set_row_duration(row, plr.track.duration)
|
||||
_ = self._set_item_text(row, DURATION,
|
||||
ms_to_mmss(plr.track.duration))
|
||||
if plr.track.duration:
|
||||
self._set_row_duration(row, plr.track.duration)
|
||||
|
||||
start_item = QTableWidgetItem()
|
||||
self.setItem(row, START_TIME, start_item)
|
||||
_ = self._set_item_text(row, START_TIME, "")
|
||||
|
||||
end_item = QTableWidgetItem()
|
||||
self.setItem(row, END_TIME, end_item)
|
||||
_ = self._set_item_text(row, END_TIME, "")
|
||||
|
||||
if plr.track.bitrate:
|
||||
bitrate = str(plr.track.bitrate)
|
||||
else:
|
||||
bitrate = ""
|
||||
bitrate_item = QTableWidgetItem(bitrate)
|
||||
self.setItem(row, BITRATE, bitrate_item)
|
||||
_ = self._set_item_text(row, BITRATE, bitrate)
|
||||
|
||||
# As we have track info, any notes should be contained in
|
||||
# the notes column
|
||||
notes_item = QTableWidgetItem(plr.note)
|
||||
self.setItem(row, ROW_NOTES, notes_item)
|
||||
plr_note = plr.note
|
||||
if not plr_note:
|
||||
plr_note = ""
|
||||
_ = self._set_item_text(row, ROW_NOTES, plr_note)
|
||||
|
||||
last_playtime = Playdates.last_played(session, plr.track.id)
|
||||
last_played_str = get_relative_date(last_playtime)
|
||||
last_played_item = QTableWidgetItem(last_played_str)
|
||||
self.setItem(row, LASTPLAYED, last_played_item)
|
||||
_ = self._set_item_text(row, LASTPLAYED, last_played_str)
|
||||
|
||||
else:
|
||||
# This is a section header so it must have note text
|
||||
@ -661,8 +681,7 @@ class PlaylistTab(QTableWidget):
|
||||
continue
|
||||
self.setItem(row, i, QTableWidgetItem())
|
||||
self.setSpan(row, HEADER_NOTES_COLUMN, 1, len(columns) - 1)
|
||||
notes_item = QTableWidgetItem(plr.note)
|
||||
self.setItem(row, HEADER_NOTES_COLUMN, notes_item)
|
||||
_ = self._set_item_text(row, HEADER_NOTES_COLUMN, plr.note)
|
||||
|
||||
# Save (no) track_id
|
||||
userdata_item.setData(self.ROW_TRACK_ID, 0)
|
||||
@ -773,8 +792,9 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# Scroll to top
|
||||
if scroll_to_top:
|
||||
scroll_to: QTableWidgetItem = self.item(0, 0)
|
||||
self.scrollToItem(scroll_to, QAbstractItemView.PositionAtTop)
|
||||
row0_item = self.item(0, 0)
|
||||
if row0_item:
|
||||
self.scrollToItem(row0_item, QAbstractItemView.PositionAtTop)
|
||||
|
||||
# Set widths
|
||||
self._set_column_widths(session)
|
||||
@ -818,8 +838,8 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# Now build a dictionary of
|
||||
# {display_row_number: display_row_plr}
|
||||
plr_dict_by_id = PlaylistRows.indexed_by_id(session,
|
||||
display_plr_ids.values())
|
||||
plr_dict_by_id = PlaylistRows.indexed_by_id(
|
||||
session, iter(display_plr_ids.values())) # type: ignore # FIXME
|
||||
|
||||
# Finally a dictionary of
|
||||
# {display_row_number: plr}
|
||||
@ -835,21 +855,24 @@ class PlaylistTab(QTableWidget):
|
||||
# that's not in the displayed playlist need to be deleted.
|
||||
|
||||
# Ensure changes flushed
|
||||
session.commit()
|
||||
PlaylistRows.delete_plrids_not_in_list(session, self.playlist_id,
|
||||
display_plr_ids.values())
|
||||
session.flush()
|
||||
PlaylistRows.delete_plrids_not_in_list(
|
||||
session, self.playlist_id,
|
||||
iter(display_plr_ids.values())) # type: ignore # FIXME
|
||||
|
||||
def scroll_current_to_top(self) -> None:
|
||||
"""Scroll currently-playing row to top"""
|
||||
|
||||
current_row = self._get_current_track_row_number()
|
||||
self._scroll_to_top(current_row)
|
||||
if current_row is not None:
|
||||
self._scroll_to_top(current_row)
|
||||
|
||||
def scroll_next_to_top(self) -> None:
|
||||
"""Scroll nextly-playing row to top"""
|
||||
|
||||
next_row = self._get_next_track_row_number()
|
||||
self._scroll_to_top(next_row)
|
||||
if next_row is not None:
|
||||
self._scroll_to_top(next_row)
|
||||
|
||||
def set_search(self, text: str) -> None:
|
||||
"""Set search text and find first match"""
|
||||
@ -1003,9 +1026,15 @@ class PlaylistTab(QTableWidget):
|
||||
# Extract note text from database to ignore section timings
|
||||
playlist_row = session.get(PlaylistRows,
|
||||
self._get_playlistrow_id(row))
|
||||
if not playlist_row:
|
||||
continue
|
||||
note_text = playlist_row.note
|
||||
if not note_text:
|
||||
note_text = ""
|
||||
# Get note colour
|
||||
note_colour = NoteColours.get_colour(session, note_text)
|
||||
note_colour = None
|
||||
if note_text:
|
||||
note_colour = NoteColours.get_colour(session, note_text)
|
||||
|
||||
# Get track if there is one
|
||||
track_id = self._get_row_track_id(row)
|
||||
@ -1022,9 +1051,9 @@ class PlaylistTab(QTableWidget):
|
||||
else:
|
||||
note_text = f"track_id {missing_track} not found"
|
||||
playlist_row.note = note_text
|
||||
session.commit()
|
||||
note_item = QTableWidgetItem(note_text)
|
||||
self.setItem(row, HEADER_NOTES_COLUMN, note_item)
|
||||
session.flush()
|
||||
_ = self._set_item_text(row, HEADER_NOTES_COLUMN,
|
||||
note_text)
|
||||
|
||||
if track:
|
||||
# Reset colour in case it was current/next/unplayable
|
||||
@ -1042,33 +1071,36 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# Colour any note
|
||||
if note_colour:
|
||||
(self.item(row, ROW_NOTES)
|
||||
.setBackground(QColor(note_colour)))
|
||||
notes_item = self.item(row, ROW_NOTES)
|
||||
if notes_item:
|
||||
notes_item.setBackground(QColor(note_colour))
|
||||
|
||||
# Highlight low bitrates
|
||||
if track.bitrate:
|
||||
bitrate_str = str(track.bitrate)
|
||||
bitrate_item = self.item(row, BITRATE)
|
||||
if bitrate_item.text() != bitrate_str:
|
||||
bitrate_item.setText(bitrate_str)
|
||||
if track.bitrate < Config.BITRATE_LOW_THRESHOLD:
|
||||
cell_colour = Config.COLOUR_BITRATE_LOW
|
||||
elif track.bitrate < Config.BITRATE_OK_THRESHOLD:
|
||||
cell_colour = Config.COLOUR_BITRATE_MEDIUM
|
||||
else:
|
||||
cell_colour = Config.COLOUR_BITRATE_OK
|
||||
brush = QBrush(QColor(cell_colour))
|
||||
self.item(row, BITRATE).setBackground(brush)
|
||||
bitrate_item = self._set_item_text(
|
||||
row, BITRATE, str(track.bitrate))
|
||||
if bitrate_item:
|
||||
if track.bitrate < Config.BITRATE_LOW_THRESHOLD:
|
||||
cell_colour = Config.COLOUR_BITRATE_LOW
|
||||
elif track.bitrate < Config.BITRATE_OK_THRESHOLD:
|
||||
cell_colour = Config.COLOUR_BITRATE_MEDIUM
|
||||
else:
|
||||
cell_colour = Config.COLOUR_BITRATE_OK
|
||||
brush = QBrush(QColor(cell_colour))
|
||||
bitrate_item.setBackground(brush)
|
||||
|
||||
# Render playing track
|
||||
if row == current_row:
|
||||
# Set last played time to "Today"
|
||||
self.item(row, LASTPLAYED).setText("Today")
|
||||
self._set_item_text(
|
||||
row, LASTPLAYED, Config.LAST_PLAYED_TODAY_STRING)
|
||||
# Calculate next_start_time
|
||||
next_start_time = self._calculate_end_time(
|
||||
self.musicmuster.current_track.start_time,
|
||||
track.duration
|
||||
)
|
||||
if track.duration:
|
||||
next_start_time = self._calculate_end_time(
|
||||
self.musicmuster.current_track.start_time,
|
||||
track.duration
|
||||
)
|
||||
# Set end time
|
||||
self._set_row_end_time(row, next_start_time)
|
||||
# Set colour
|
||||
@ -1093,8 +1125,9 @@ class PlaylistTab(QTableWidget):
|
||||
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)
|
||||
if track.duration:
|
||||
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
|
||||
@ -1106,8 +1139,8 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
if row in played:
|
||||
# Played today, so update last played column
|
||||
self.item(row, LASTPLAYED).setText(
|
||||
Config.LAST_PLAYED_TODAY_STRING)
|
||||
self._set_item_text(
|
||||
row, LASTPLAYED, Config.LAST_PLAYED_TODAY_STRING)
|
||||
if self.musicmuster.hide_played_tracks:
|
||||
self.hideRow(row)
|
||||
else:
|
||||
@ -1117,8 +1150,9 @@ class PlaylistTab(QTableWidget):
|
||||
# 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)
|
||||
if track.duration:
|
||||
next_start_time = self._calculate_end_time(
|
||||
start_time, track.duration)
|
||||
# Set end time
|
||||
self._set_row_end_time(row, next_start_time)
|
||||
else:
|
||||
@ -1176,28 +1210,34 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# Add track to playlist row
|
||||
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
|
||||
if not plr:
|
||||
return
|
||||
|
||||
plr.track_id = track.id
|
||||
session.commit()
|
||||
session.flush()
|
||||
|
||||
# Reset row span
|
||||
for column in range(len(columns)):
|
||||
self.setSpan(row, column, 1, 1)
|
||||
|
||||
# Update attributes of row
|
||||
self.item(row, USERDATA).setData(self.ROW_TRACK_ID, track.id)
|
||||
start_gap_item = self.item(row, START_GAP)
|
||||
start_gap_item.setText(str(track.start_gap))
|
||||
if track.start_gap and track.start_gap >= 500:
|
||||
start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START))
|
||||
self.item(row, TITLE).setText(str(track.title))
|
||||
self.item(row, ARTIST).setText(str(track.artist))
|
||||
self.item(row, DURATION).setText(ms_to_mmss(track.duration))
|
||||
userdata_item = self.item(row, USERDATA)
|
||||
if not userdata_item:
|
||||
userdata_item = QTableWidgetItem()
|
||||
userdata_item.setData(self.ROW_TRACK_ID, track.id)
|
||||
|
||||
last_playtime = Playdates.last_played(session, track.id)
|
||||
last_played_str = get_relative_date(last_playtime)
|
||||
self.item(row, LASTPLAYED).setText(last_played_str)
|
||||
self.item(row, ROW_NOTES).setText(plr.note)
|
||||
_ = self._set_item_text(row, LASTPLAYED, last_played_str)
|
||||
|
||||
self.update_display(session)
|
||||
_ = self._set_item_text(row, ROW_NOTES, plr.note)
|
||||
|
||||
start_gap_item = self._set_item_text(row, START_GAP,
|
||||
track.start_gap)
|
||||
if track.start_gap and track.start_gap >= 500:
|
||||
start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START))
|
||||
|
||||
self._update_row(session, row, track)
|
||||
|
||||
def _calculate_end_time(self, start: Optional[datetime],
|
||||
duration: int) -> Optional[datetime]:
|
||||
@ -1246,7 +1286,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
with Session() as session:
|
||||
track = session.get(Tracks, track_id)
|
||||
if track:
|
||||
if track and track.path:
|
||||
# Escape single quotes and spaces in name
|
||||
path = track.path
|
||||
pathq = path.replace("'", "\\'")
|
||||
@ -1266,6 +1306,8 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# Delete rows from database
|
||||
plr_ids = self.get_selected_playlistrow_ids()
|
||||
if not plr_ids:
|
||||
return
|
||||
|
||||
# Get confirmation
|
||||
row_count = len(plr_ids)
|
||||
@ -1292,7 +1334,8 @@ class PlaylistTab(QTableWidget):
|
||||
else index.row())
|
||||
|
||||
def _find_next_track_row(self, session: scoped_session,
|
||||
starting_row: int = None) -> Optional[int]:
|
||||
starting_row: Optional[int] = None) \
|
||||
-> Optional[int]:
|
||||
"""
|
||||
Find next track to play. If a starting row is given, start there;
|
||||
otherwise, start from top. Skip rows already played.
|
||||
@ -1315,6 +1358,8 @@ class PlaylistTab(QTableWidget):
|
||||
]
|
||||
for row in range(starting_row, self.rowCount()):
|
||||
plr = self._get_playlistrow_object(session, row)
|
||||
if not plr:
|
||||
continue
|
||||
if (
|
||||
row not in track_rows or
|
||||
row in played_rows or
|
||||
@ -1330,12 +1375,18 @@ class PlaylistTab(QTableWidget):
|
||||
"""Return current track row or None"""
|
||||
|
||||
current_track = self.musicmuster.current_track
|
||||
if not current_track or not current_track.plr_id:
|
||||
return None
|
||||
|
||||
return self._plrid_to_row_number(current_track.plr_id)
|
||||
|
||||
def _get_next_track_row_number(self) -> Optional[int]:
|
||||
"""Return next track row or None"""
|
||||
|
||||
next_track = self.musicmuster.next_track
|
||||
if not next_track or not next_track.plr_id:
|
||||
return None
|
||||
|
||||
return self._plrid_to_row_number(next_track.plr_id)
|
||||
|
||||
@staticmethod
|
||||
@ -1355,18 +1406,27 @@ class PlaylistTab(QTableWidget):
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def _get_playlistrow_id(self, row: int) -> int:
|
||||
def _get_playlistrow_id(self, row: int) -> Optional[int]:
|
||||
"""Return the playlistrow_id associated with this row"""
|
||||
|
||||
playlistrow_id = (self.item(row, USERDATA).data(self.PLAYLISTROW_ID))
|
||||
userdata_item = self.item(row, USERDATA)
|
||||
if not userdata_item:
|
||||
return None
|
||||
|
||||
return playlistrow_id
|
||||
return userdata_item.data(self.PLAYLISTROW_ID)
|
||||
|
||||
def _get_playlistrow_object(self, session: scoped_session,
|
||||
row: int) -> PlaylistRows:
|
||||
row: int) -> Optional[PlaylistRows]:
|
||||
"""Return the playlistrow object associated with this row"""
|
||||
|
||||
playlistrow_id = (self.item(row, USERDATA).data(self.PLAYLISTROW_ID))
|
||||
userdata_item = self.item(row, USERDATA)
|
||||
if not userdata_item:
|
||||
return None
|
||||
|
||||
playlistrow_id = userdata_item.data(self.PLAYLISTROW_ID)
|
||||
if not playlistrow_id:
|
||||
return None
|
||||
|
||||
return session.get(PlaylistRows, playlistrow_id)
|
||||
|
||||
def _get_row_artist(self, row: int) -> Optional[str]:
|
||||
@ -1377,12 +1437,19 @@ class PlaylistTab(QTableWidget):
|
||||
return None
|
||||
|
||||
item_artist = self.item(row, ARTIST)
|
||||
if not item_artist:
|
||||
return None
|
||||
|
||||
return item_artist.text()
|
||||
|
||||
def _get_row_duration(self, row: int) -> int:
|
||||
"""Return duration associated with this row"""
|
||||
|
||||
duration = (self.item(row, USERDATA).data(self.ROW_DURATION))
|
||||
userdata_item = self.item(row, USERDATA)
|
||||
if not userdata_item:
|
||||
return 0
|
||||
|
||||
duration = userdata_item.data(self.ROW_DURATION)
|
||||
if duration:
|
||||
return duration
|
||||
else:
|
||||
@ -1396,17 +1463,21 @@ class PlaylistTab(QTableWidget):
|
||||
item_note = self.item(row, ROW_NOTES)
|
||||
else:
|
||||
item_note = self.item(row, HEADER_NOTES_COLUMN)
|
||||
if not item_note:
|
||||
return None
|
||||
|
||||
return item_note.text()
|
||||
|
||||
def _get_row_start_time(self, row: int) -> Optional[datetime]:
|
||||
"""Return row start time as string or None"""
|
||||
|
||||
start_time_item = self.item(row, START_TIME)
|
||||
if not start_time_item:
|
||||
return None
|
||||
|
||||
try:
|
||||
if self.item(row, START_TIME):
|
||||
return datetime.strptime(self.item(
|
||||
row, START_TIME).text(),
|
||||
Config.NOTE_TIME_FORMAT
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return datetime.strptime(start_time_item.text(),
|
||||
Config.NOTE_TIME_FORMAT)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
@ -1418,16 +1489,22 @@ class PlaylistTab(QTableWidget):
|
||||
return None
|
||||
|
||||
item_title = self.item(row, TITLE)
|
||||
if not item_title:
|
||||
return None
|
||||
|
||||
return item_title.text()
|
||||
|
||||
def _get_row_track_id(self, row: int) -> int:
|
||||
"""Return the track_id associated with this row or None"""
|
||||
|
||||
userdata_item = self.item(row, USERDATA)
|
||||
if not userdata_item:
|
||||
return 0
|
||||
|
||||
try:
|
||||
track_id = (self.item(row, USERDATA)
|
||||
.data(self.ROW_TRACK_ID))
|
||||
track_id = userdata_item.data(self.ROW_TRACK_ID)
|
||||
except AttributeError:
|
||||
return None
|
||||
return 0
|
||||
|
||||
return track_id
|
||||
|
||||
@ -1496,6 +1573,9 @@ class PlaylistTab(QTableWidget):
|
||||
new_row_number: int) -> None:
|
||||
"""Move playlist row to new_row_number using parent copy/paste"""
|
||||
|
||||
if plr.row_number is None:
|
||||
return
|
||||
|
||||
# Remove source row
|
||||
self.removeRow(plr.row_number)
|
||||
# Fixup plr row number
|
||||
@ -1535,7 +1615,13 @@ class PlaylistTab(QTableWidget):
|
||||
)
|
||||
return
|
||||
|
||||
open_in_audacity(track.path)
|
||||
if track.path is None:
|
||||
log.error(
|
||||
f"playlists._open_in_audacity({track_id=}): "
|
||||
"Track has no path"
|
||||
)
|
||||
else:
|
||||
open_in_audacity(track.path)
|
||||
|
||||
def _plrid_to_row_number(self, plrid: int) -> Optional[int]:
|
||||
"""
|
||||
@ -1559,6 +1645,9 @@ class PlaylistTab(QTableWidget):
|
||||
# Update playlist_rows record
|
||||
with Session() as session:
|
||||
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
|
||||
if not plr:
|
||||
return
|
||||
|
||||
plr.track_id = None
|
||||
# We can't have null text
|
||||
if not plr.note:
|
||||
@ -1567,15 +1656,17 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# Clear track text items
|
||||
for i in range(2, len(columns)):
|
||||
self.item(row, i).setText("")
|
||||
_ = self._set_item_text(row, i, "")
|
||||
# Remove row duration
|
||||
self._set_row_duration(row, 0)
|
||||
# Remote track_id from row
|
||||
self.item(row, USERDATA).setData(self.ROW_TRACK_ID, 0)
|
||||
userdata_item = self.item(row, USERDATA)
|
||||
if userdata_item:
|
||||
userdata_item.setData(self.ROW_TRACK_ID, 0)
|
||||
# Span the rows
|
||||
self.setSpan(row, HEADER_NOTES_COLUMN, 1, len(columns) - 1)
|
||||
# Set note text in correct column for section head
|
||||
self.item(row, HEADER_NOTES_COLUMN).setText(plr.note)
|
||||
_ = self._set_item_text(row, HEADER_NOTES_COLUMN, plr.note)
|
||||
# And refresh display
|
||||
self.update_display(session)
|
||||
|
||||
@ -1730,6 +1821,19 @@ class PlaylistTab(QTableWidget):
|
||||
else:
|
||||
self.setColumnWidth(idx, Config.DEFAULT_COLUMN_WIDTH)
|
||||
|
||||
def _set_item_text(self, row, column, text) -> QTableWidgetItem:
|
||||
"""
|
||||
Set text for item if it exists, else create it, and return item
|
||||
"""
|
||||
|
||||
item = self.item(row, column)
|
||||
if not item:
|
||||
item = QTableWidgetItem(text)
|
||||
self.setItem(row, column, item)
|
||||
else:
|
||||
item.setText(text)
|
||||
return item
|
||||
|
||||
def _set_next(self, session: scoped_session, row_number: int) -> None:
|
||||
"""
|
||||
Set passed row as next playlist row to play.
|
||||
@ -1759,7 +1863,10 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# Notify musicmuster
|
||||
plr = session.get(PlaylistRows, self._get_playlistrow_id(row_number))
|
||||
self.musicmuster.this_is_the_next_playlist_row(session, plr, self)
|
||||
if not plr:
|
||||
log.debug(f"playists._set_next({row_number=}) can't retrieve plr")
|
||||
else:
|
||||
self.musicmuster.this_is_the_next_playlist_row(session, plr, self)
|
||||
|
||||
# Update display
|
||||
self.clear_selection()
|
||||
@ -1787,8 +1894,9 @@ class PlaylistTab(QTableWidget):
|
||||
for column in range(self.columnCount()):
|
||||
if column == ROW_NOTES:
|
||||
continue
|
||||
if self.item(row, column):
|
||||
self.item(row, column).setFont(boldfont)
|
||||
item = self.item(row, column)
|
||||
if item:
|
||||
item.setFont(boldfont)
|
||||
|
||||
def _set_row_colour(self, row: int,
|
||||
colour: Optional[QColor] = None) -> None:
|
||||
@ -1807,21 +1915,25 @@ class PlaylistTab(QTableWidget):
|
||||
# Don't change colour on start gap columns
|
||||
if column == START_GAP:
|
||||
continue
|
||||
if self.item(row, column):
|
||||
self.item(row, column).setBackground(brush)
|
||||
item = self.item(row, column)
|
||||
if item:
|
||||
item.setBackground(brush)
|
||||
|
||||
def _set_row_duration(self, row: int, ms: int) -> None:
|
||||
"""Set duration of this row in row metadata"""
|
||||
|
||||
self.item(row, USERDATA).setData(self.ROW_DURATION, ms)
|
||||
item = self.item(row, USERDATA)
|
||||
if item:
|
||||
item.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 = time.strftime(Config.TRACK_TIME_FORMAT)
|
||||
time_str = time.strftime(Config.TRACK_TIME_FORMAT) # type: ignore
|
||||
except AttributeError:
|
||||
time_str = ""
|
||||
|
||||
item = QTableWidgetItem(time_str)
|
||||
self.setItem(row, END_TIME, item)
|
||||
|
||||
@ -1834,14 +1946,13 @@ class PlaylistTab(QTableWidget):
|
||||
"""Set passed row start time to passed time"""
|
||||
|
||||
try:
|
||||
time_str = time.strftime(Config.TRACK_TIME_FORMAT)
|
||||
time_str = time.strftime(Config.TRACK_TIME_FORMAT) # type: ignore
|
||||
except AttributeError:
|
||||
time_str = ""
|
||||
item = QTableWidgetItem(time_str)
|
||||
self.setItem(row, START_TIME, item)
|
||||
_ = self._set_item_text(row, START_TIME, time_str)
|
||||
|
||||
def _get_section_timing_string(self, ms: int,
|
||||
no_end: bool = False) -> None:
|
||||
no_end: bool = False) -> str:
|
||||
"""Return string describing section duration"""
|
||||
|
||||
duration = ms_to_mmss(ms)
|
||||
|
||||
@ -36,7 +36,6 @@ parent_dir = os.path.dirname(source_dir)
|
||||
name_and_tags: List[str] = []
|
||||
tags_not_name: List[str] = []
|
||||
# multiple_similar: List[str] = []
|
||||
no_match: List[str] = []
|
||||
# possibles: List[str] = []
|
||||
no_match: int = 0
|
||||
|
||||
|
||||
@ -41,7 +41,8 @@ requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
mypy_path = "/home/kae/.cache/pypoetry/virtualenvs/musicmuster-oWgGw1IG-py3.9:/home/kae/git/musicmuster/app"
|
||||
# mypy_path = "/home/kae/.cache/pypoetry/virtualenvs/musicmuster-oWgGw1IG-py3.9:/home/kae/git/musicmuster/app"
|
||||
mypy_path = "/home/kae/git/musicmuster/app"
|
||||
plugins = "sqlalchemy.ext.mypy.plugin"
|
||||
|
||||
[tool.vulture]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user