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