Compare commits

..

No commits in common. "0c38fc2ef45cb53e4fcd518422d9ba76add13c89" and "f182f49f15df2a8dbdb85f192e8846e2b44db1c3" have entirely different histories.

7 changed files with 327 additions and 588 deletions

0
app/__init__.py Normal file
View File

View File

@ -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 Iterable, List, Optional, Union, ValuesView from typing import List, Optional
from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.associationproxy import association_proxy
@ -30,10 +30,7 @@ from sqlalchemy.orm import (
relationship, relationship,
) )
from sqlalchemy.orm.exc import ( from sqlalchemy.orm.exc import (
NoResultFound, NoResultFound
)
from sqlalchemy.exc import (
IntegrityError,
) )
from config import Config from config import Config
from helpers import ( from helpers import (
@ -98,13 +95,13 @@ class NoteColours(Base):
) )
@staticmethod @staticmethod
def get_colour(session: scoped_session, text: str) -> str: def get_colour(session: scoped_session, text: str) -> Optional[str]:
""" """
Parse text and return colour string if matched, else empty string Parse text and return colour string if matched, else None
""" """
if not text: if not text:
return "" return None
for rec in session.execute( for rec in session.execute(
select(NoteColours) select(NoteColours)
@ -126,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 "" return None
class Playdates(Base): class Playdates(Base):
@ -199,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: List["PlaylistRows"] = relationship( rows: "PlaylistRows" = relationship(
"PlaylistRows", "PlaylistRows",
back_populates="playlist", back_populates="playlist",
cascade="all, delete-orphan", cascade="all, delete-orphan",
@ -373,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: Optional[int], track_id: int,
row_number: int, row_number: int,
note: Optional[str] = None note: Optional[str] = None
) -> None: ) -> None:
@ -411,9 +408,8 @@ class PlaylistRows(Base):
plr.note) plr.note)
@staticmethod @staticmethod
def delete_plrids_not_in_list( def delete_plrids_not_in_list(session: scoped_session, playlist_id: int,
session: scoped_session, playlist_id: int, plrids: List["PlaylistRows"]) -> None:
plr_ids: Union[Iterable[int], ValuesView]) -> 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'
@ -423,7 +419,7 @@ class PlaylistRows(Base):
delete(PlaylistRows) delete(PlaylistRows)
.where( .where(
PlaylistRows.playlist_id == playlist_id, PlaylistRows.playlist_id == playlist_id,
PlaylistRows.id.not_in(plr_ids) PlaylistRows.id.not_in(plrids)
) )
) )
# Delete won't take effect until commit() # Delete won't take effect until commit()
@ -473,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["PlaylistRows"]: playlist_id: int) -> List[int]:
""" """
For passed playlist, return a list of rows that For passed playlist, return a list of rows that
have been played. have been played.
@ -492,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["PlaylistRows"]: playlist_id: int) -> List[int]:
""" """
For passed playlist, return a list of rows that For passed playlist, return a list of rows that
contain tracks contain tracks
@ -530,8 +526,24 @@ class PlaylistRows(Base):
return plrs return plrs
@staticmethod @staticmethod
def indexed_by_id(session: scoped_session, def move_rows_down(session: scoped_session, playlist_id: int,
plr_ids: Union[Iterable[int], ValuesView]) -> dict: 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:
""" """
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.
@ -550,23 +562,6 @@ 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"""
@ -657,16 +652,8 @@ class Tracks(Base):
self.mtime = mtime self.mtime = mtime
self.lastplayed = lastplayed self.lastplayed = lastplayed
try:
session.add(self) session.add(self)
session.commit() session.commit()
except IntegrityError as error:
session.rollback()
log.error(
f"Error importing track ({title=}, "
f"{title=}, {artist=}, {path=}, {error=})"
)
raise ValueError
@classmethod @classmethod
def get_all(cls, session) -> List["Tracks"]: def get_all(cls, session) -> List["Tracks"]:

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
from log import log from log import log
from os.path import basename
import argparse import argparse
import stackprinter # type: ignore import stackprinter # type: ignore
import subprocess import subprocess
@ -10,31 +9,10 @@ import threading
from datetime import datetime, timedelta from datetime import datetime, timedelta
from time import sleep from time import sleep
from typing import ( from typing import Callable, List, Optional
Callable,
cast,
List,
Optional,
)
from PyQt5.QtCore import ( from PyQt5.QtCore import pyqtSignal, QDate, QEvent, Qt, QSize, QTime, QTimer
pyqtSignal, from PyQt5.QtGui import QColor, QFont, QPalette, QResizeEvent
QDate,
QEvent,
QObject,
Qt,
QSize,
QThread,
QTime,
QTimer,
)
from PyQt5.QtGui import (
QColor,
QFont,
QMouseEvent,
QPalette,
QResizeEvent,
)
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QApplication, QApplication,
QDialog, QDialog,
@ -49,13 +27,10 @@ from PyQt5.QtWidgets import (
QProgressBar, QProgressBar,
) )
from dbconfig import ( from dbconfig import engine, Session, scoped_session
engine,
Session,
scoped_session,
)
import helpers import helpers
import music import music
from models import ( from models import (
Base, Base,
Carts, Carts,
@ -80,11 +55,12 @@ class CartButton(QPushButton):
progress = pyqtSignal(int) progress = pyqtSignal(int)
def __init__(self, musicmuster: "Window", cart: Carts, *args, **kwargs): def __init__(self, parent: QMainWindow, cart: Carts):
"""Create a cart pushbutton and set it disabled""" """Create a cart pushbutton and set it disabled"""
super().__init__(*args, **kwargs) super().__init__(parent)
self.musicmuster = musicmuster # Next line is redundant (check)
# 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 +77,7 @@ class CartButton(QPushButton):
self.setFont(font) self.setFont(font)
self.setObjectName("cart_" + str(cart.cart_number)) self.setObjectName("cart_" + str(cart.cart_number))
self.pgb = QProgressBar(self) self.pgb = QProgressBar(self, textVisible=False)
self.pgb.setTextVisible(False)
self.pgb.setVisible(False) self.pgb.setVisible(False)
palette = self.pgb.palette() palette = self.pgb.palette()
palette.setColor(QPalette.Highlight, palette.setColor(QPalette.Highlight,
@ -125,9 +100,8 @@ 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:
mouse_event = cast(QMouseEvent, event) if event.button() == Qt.RightButton:
if mouse_event.button() == Qt.RightButton: self.parent.cart_edit(self, event)
self.musicmuster.cart_edit(self, event)
return True return True
return super().event(event) return super().event(event)
@ -162,7 +136,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[int] = None self.silence_at: Optional[datetime] = 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
@ -189,6 +163,7 @@ 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
@ -198,59 +173,18 @@ 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()
if self.duration: self.end_time = self.start_time + timedelta(milliseconds=self.duration)
self.end_time = (
self.start_time + timedelta(milliseconds=self.duration))
class ImportTrack(QObject):
import_error = pyqtSignal(str)
importing = pyqtSignal(str)
finished = pyqtSignal(PlaylistTab)
def __init__(self, playlist: PlaylistTab, filenames: list) -> None:
super().__init__()
self.filenames = filenames
self.playlist = playlist
def run(self):
"""
Create track objects from passed files and add to visible playlist
"""
with Session() as session:
for fname in self.filenames:
self.importing.emit(f"Importing {basename(fname)}")
try:
track = Tracks(session, fname)
except ValueError:
self.import_error.emit(basename(fname))
continue
helpers.set_track_metadata(session, track)
helpers.normalise_track(track.path)
self.playlist.insert_track(session, track)
# We're importing potentially multiple tracks in a loop.
# If there's an error adding the track to the Tracks
# table, the session will rollback, thus losing any
# previous additions in this loop. So, commit now to
# lock in what we've just done.
session.commit()
self.playlist.save_playlist(session)
self.finished.emit(self.playlist)
class Window(QMainWindow, Ui_MainWindow): class Window(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None, *args, **kwargs) -> None: def __init__(self, parent=None) -> None:
super().__init__(*args, **kwargs) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
self.timer: QTimer = QTimer() self.timer: QTimer = QTimer()
@ -263,8 +197,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[float] = None self.previous_track_position: Optional[int] = None
self.selected_plrs: Optional[List[PlaylistRows]] = None self.selected_plrs = 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()
@ -304,7 +238,6 @@ 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)
@ -315,24 +248,16 @@ 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
@ -342,7 +267,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.setMinimum(0) btn.pgb.minimum = 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"""
@ -350,10 +275,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(musicmuster=self, session=session, cart=cart) dlg = CartDialog(parent=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:
@ -398,9 +323,6 @@ 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:
@ -514,7 +436,6 @@ 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
@ -576,8 +497,10 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_name: Optional[str] = None) -> Playlists: playlist_name: Optional[str] = None) -> Playlists:
"""Create new playlist""" """Create new playlist"""
while not playlist_name: if 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
@ -597,8 +520,6 @@ 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)
@ -700,7 +621,6 @@ 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
@ -717,11 +637,8 @@ 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")
@ -744,9 +661,6 @@ 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",
@ -764,8 +678,6 @@ 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)},"
@ -794,7 +706,6 @@ 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(
@ -809,9 +720,7 @@ 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.track return dlg.ui.track
else:
return None
def hide_played(self): def hide_played(self):
"""Toggle hide played tracks""" """Toggle hide played tracks"""
@ -845,7 +754,7 @@ class Window(QMainWindow, Ui_MainWindow):
for fname in dlg.selectedFiles(): for fname in dlg.selectedFiles():
txt = "" txt = ""
tags = helpers.get_tags(fname) tags = helpers.get_tags(fname)
new_tracks.append(fname) new_tracks.append((fname, tags))
title = tags['title'] title = tags['title']
artist = tags['artist'] artist = tags['artist']
count = 0 count = 0
@ -873,32 +782,22 @@ class Window(QMainWindow, Ui_MainWindow):
return return
# Import in separate thread # Import in separate thread
self.import_thread = QThread() thread = threading.Thread(target=self._import_tracks,
self.worker = ImportTrack(self.visible_playlist_tab(), new_tracks) args=(new_tracks,))
self.worker.moveToThread(self.import_thread) thread.start()
self.import_thread.started.connect(self.worker.run)
self.worker.finished.connect(self.import_thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.import_thread.finished.connect(self.import_thread.deleteLater)
self.worker.import_error.connect(
lambda msg: helpers.show_warning(
"Import error", "Error importing " + msg
)
)
self.worker.importing.connect(
lambda msg: self.statusbar.showMessage("Importing " + msg, 5000)
)
self.worker.finished.connect(self.import_complete)
self.import_thread.start()
def import_complete(self, playlist_tab: PlaylistTab): def _import_tracks(self, tracks: list):
""" """
Called by thread when track import complete Create track objects from passed files and add to visible playlist
""" """
self.statusbar.showMessage("Imports complete")
with Session() as session: with Session() as session:
playlist_tab.update_display(session) for (fname, tags) in tracks:
track = Tracks(session, fname)
helpers.set_track_metadata(session, track)
helpers.normalise_track(track.path)
self.visible_playlist_tab().insert_track(session, track)
self.visible_playlist_tab().save_playlist(session)
def insert_header(self) -> None: def insert_header(self) -> None:
"""Show dialog box to enter header text and add to playlist""" """Show dialog box to enter header text and add to playlist"""
@ -932,7 +831,6 @@ 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")
@ -959,10 +857,7 @@ 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 = []
@ -1016,13 +911,11 @@ 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(session, selected_plrs) self.move_playlist_rows(
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"""
@ -1060,8 +953,6 @@ 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)
@ -1115,10 +1006,7 @@ class Window(QMainWindow, Ui_MainWindow):
plr.row_number = row plr.row_number = row
row += 1 row += 1
if not src_playlist_id: session.commit()
return
session.flush()
# Update display # Update display
self.visible_playlist_tab().populate_display( self.visible_playlist_tab().populate_display(
@ -1174,9 +1062,8 @@ 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
current_tab = self.current_track.playlist_tab if self.current_track.playlist_tab != self.next_track.playlist_tab:
if current_tab and current_tab != self.next_track.playlist_tab: self.set_tab_colour(self.current_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.
@ -1185,17 +1072,9 @@ 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
if current_tab: self.set_tab_colour(self.current_track.playlist_tab,
self.set_tab_colour( QColor(Config.COLOUR_CURRENT_TAB))
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():
@ -1209,7 +1088,6 @@ 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
@ -1237,14 +1115,11 @@ 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( self.current_track.end_time.strftime(Config.TRACK_TIME_FORMAT))
Config.TRACK_TIME_FORMAT))
def resume(self) -> None: def resume(self) -> None:
""" """
@ -1281,8 +1156,6 @@ 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)
@ -1434,7 +1307,6 @@ 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))
@ -1493,7 +1365,6 @@ 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,
@ -1610,26 +1481,21 @@ class Window(QMainWindow, Ui_MainWindow):
Update last / current / next track headers Update last / current / next track headers
""" """
if self.previous_track.title and self.previous_track.artist: if self.previous_track.title:
self.hdrPreviousTrack.setText( self.hdrPreviousTrack.setText(
f"{self.previous_track.title.replace('&', '&&')} - " f"{self.previous_track.title} - {self.previous_track.artist}")
f"{self.previous_track.artist.replace('&', '&&')}"
)
else: else:
self.hdrPreviousTrack.setText("") self.hdrPreviousTrack.setText("")
if self.current_track.title and self.current_track.artist: if self.current_track.title:
self.hdrCurrentTrack.setText( self.hdrCurrentTrack.setText(
f"{self.current_track.title.replace('&', '&&')} - " f"{self.current_track.title} - {self.current_track.artist}")
f"{self.current_track.artist.replace('&', '&&')}"
)
else: else:
self.hdrCurrentTrack.setText("") self.hdrCurrentTrack.setText("")
if self.next_track.title and self.next_track.artist: if self.next_track.title:
self.hdrNextTrack.setText( self.hdrNextTrack.setText(
f"{self.next_track.title.replace('&', '&&')} - " f"{self.next_track.title} - {self.next_track.artist}"
f"{self.next_track.artist.replace('&', '&&')}"
) )
else: else:
self.hdrNextTrack.setText("") self.hdrNextTrack.setText("")
@ -1638,14 +1504,14 @@ class Window(QMainWindow, Ui_MainWindow):
class CartDialog(QDialog): class CartDialog(QDialog):
"""Edit cart details""" """Edit cart details"""
def __init__(self, musicmuster: Window, session: scoped_session, def __init__(self, parent: QMainWindow, session: scoped_session,
cart: Carts, *args, **kwargs) -> None: cart: Carts) -> None:
""" """
Manage carts Manage carts
""" """
super().__init__(*args, **kwargs) super().__init__(parent)
self.musicmuster = musicmuster self.parent = parent
self.session = session self.session = session
self.ui = Ui_DialogCartEdit() self.ui = Ui_DialogCartEdit()
@ -1655,7 +1521,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.setWindowTitle("Edit Cart " + str(cart.id)) self.ui.windowTitle = "Edit Cart " + str(cart.id)
self.ui.btnFile.clicked.connect(self.choose_file) self.ui.btnFile.clicked.connect(self.choose_file)
@ -1675,8 +1541,8 @@ class CartDialog(QDialog):
class DbDialog(QDialog): class DbDialog(QDialog):
"""Select track from database""" """Select track from database"""
def __init__(self, musicmuster: Window, session: scoped_session, def __init__(self, parent: QMainWindow, session: scoped_session,
get_one_track: bool = False, *args, **kwargs) -> None: get_one_track: bool = False) -> None:
""" """
Subclassed QDialog to manage track selection Subclassed QDialog to manage track selection
@ -1685,8 +1551,7 @@ class DbDialog(QDialog):
to be added to the playlist. to be added to the playlist.
""" """
super().__init__(*args, **kwargs) super().__init__(parent)
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()
@ -1698,7 +1563,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 self.ui.track = None
if get_one_track: if get_one_track:
self.ui.txtNote.hide() self.ui.txtNote.hide()
@ -1744,19 +1609,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.track = track self.ui.track = track
self.accept() self.accept()
return return
if track: if track:
self.musicmuster.visible_playlist_tab().insert_track( self.parent().visible_playlist_tab().insert_track(
self.session, track, note=self.ui.txtNote.text()) self.session, track, note=self.ui.txtNote.text())
else: else:
self.musicmuster.visible_playlist_tab().insert_header( self.parent().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.musicmuster.visible_playlist_tab().save_playlist(self.session) self.parent().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()
@ -1819,7 +1684,7 @@ class DbDialog(QDialog):
class DownloadCSV(QDialog): class DownloadCSV(QDialog):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(*args, **kwargs) super().__init__(parent)
self.ui = Ui_DateSelect() self.ui = Ui_DateSelect()
self.ui.setupUi(self) self.ui.setupUi(self)
@ -1831,7 +1696,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__(*args, **kwargs) super().__init__(parent)
if playlists is None: if playlists is None:
return return

View File

@ -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, TYPE_CHECKING from typing import List, Optional
from PyQt5.QtCore import ( from PyQt5.QtCore import (
pyqtSignal, pyqtSignal,
@ -21,7 +21,6 @@ from PyQt5.QtGui import (
QColor, QColor,
QFont, QFont,
QDropEvent, QDropEvent,
QKeyEvent
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemDelegate, QAbstractItemDelegate,
@ -59,9 +58,6 @@ 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
@ -115,10 +111,8 @@ class NoSelectDelegate(QStyledItemDelegate):
def eventFilter(self, editor: QObject, event: QEvent): def eventFilter(self, editor: QObject, event: QEvent):
"""By default, QPlainTextEdit doesn't handle enter or return""" """By default, QPlainTextEdit doesn't handle enter or return"""
if event.type() == QEvent.KeyPress: if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Return:
key_event = cast(QKeyEvent, event) if event.modifiers() == Qt.ControlModifier:
if key_event.key() == Qt.Key_Return:
if key_event.modifiers() == Qt.ControlModifier:
self.commitData.emit(editor) self.commitData.emit(editor)
self.closeEditor.emit(editor) self.closeEditor.emit(editor)
@ -132,11 +126,10 @@ 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: "Window", def __init__(self, musicmuster: QMainWindow, session: scoped_session,
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: Window = musicmuster self.musicmuster = musicmuster
self.playlist_id = playlist_id self.playlist_id = playlist_id
self.menu: Optional[QMenu] = None self.menu: Optional[QMenu] = None
@ -158,7 +151,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() item: QTableWidgetItem = 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
@ -187,7 +180,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: Optional[int] self.edit_cell_type = None
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)
@ -216,9 +209,7 @@ 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( [QTableWidgetItem(self.item(row_index, column_index)) for
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
] ]
@ -413,14 +404,10 @@ class PlaylistTab(QTableWidget):
# change cell again (metadata) # change cell again (metadata)
self.cellChanged.disconnect(self._cell_changed) self.cellChanged.disconnect(self._cell_changed)
cell = self.item(row, column) new_text = self.item(row, column).text().strip()
if not cell:
return
new_text = cell.text().strip()
# Update cell with strip()'d text # Update cell with strip()'d text
cell.setText(new_text) self.item(row, column).setText(new_text)
track_id = self._get_row_track_id(row) track_id = self._get_row_track_id(row)
@ -429,8 +416,6 @@ 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
@ -485,7 +470,7 @@ class PlaylistTab(QTableWidget):
super(PlaylistTab, self).closeEditor(editor, hint) super(PlaylistTab, self).closeEditor(editor, hint)
def edit(self, index: QModelIndex, # type: ignore # FIXME def edit(self, index: QModelIndex,
trigger: QAbstractItemView.EditTrigger, trigger: QAbstractItemView.EditTrigger,
event: QEvent) -> bool: event: QEvent) -> bool:
""" """
@ -504,6 +489,7 @@ 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:
@ -534,8 +520,6 @@ 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.
@ -576,8 +560,6 @@ 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,
@ -603,9 +585,6 @@ 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)
@ -622,46 +601,44 @@ class PlaylistTab(QTableWidget):
start_gap = plr.track.start_gap start_gap = plr.track.start_gap
except AttributeError: except AttributeError:
return return
start_gap_item = self._set_item_text( start_gap_item = QTableWidgetItem(str(start_gap))
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)
track_title = plr.track.title title_item = QTableWidgetItem(plr.track.title)
if not track_title: self.setItem(row, TITLE, title_item)
track_title = ""
_ = self._set_item_text(row, TITLE, track_title)
track_artist = plr.track.artist artist_item = QTableWidgetItem(plr.track.artist)
if not track_artist: self.setItem(row, ARTIST, artist_item)
track_artist = ""
_ = self._set_item_text(row, ARTIST, track_artist)
_ = self._set_item_text(row, DURATION, duration_item = QTableWidgetItem(
ms_to_mmss(plr.track.duration)) ms_to_mmss(plr.track.duration))
if plr.track.duration: self.setItem(row, DURATION, duration_item)
self._set_row_duration(row, plr.track.duration) self._set_row_duration(row, plr.track.duration)
_ = self._set_item_text(row, START_TIME, "") start_item = QTableWidgetItem()
self.setItem(row, START_TIME, start_item)
_ = self._set_item_text(row, END_TIME, "") end_item = QTableWidgetItem()
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 = ""
_ = self._set_item_text(row, BITRATE, bitrate) bitrate_item = QTableWidgetItem(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
plr_note = plr.note notes_item = QTableWidgetItem(plr.note)
if not plr_note: self.setItem(row, ROW_NOTES, notes_item)
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)
_ = self._set_item_text(row, LASTPLAYED, last_played_str) last_played_item = QTableWidgetItem(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
@ -681,7 +658,8 @@ 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)
_ = self._set_item_text(row, HEADER_NOTES_COLUMN, plr.note) notes_item = QTableWidgetItem(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)
@ -792,9 +770,8 @@ class PlaylistTab(QTableWidget):
# Scroll to top # Scroll to top
if scroll_to_top: if scroll_to_top:
row0_item = self.item(0, 0) scroll_to: QTableWidgetItem = self.item(0, 0)
if row0_item: self.scrollToItem(scroll_to, QAbstractItemView.PositionAtTop)
self.scrollToItem(row0_item, QAbstractItemView.PositionAtTop)
# Set widths # Set widths
self._set_column_widths(session) self._set_column_widths(session)
@ -838,8 +815,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( plr_dict_by_id = PlaylistRows.indexed_by_id(session,
session, display_plr_ids.values()) display_plr_ids.values())
# Finally a dictionary of # Finally a dictionary of
# {display_row_number: plr} # {display_row_number: plr}
@ -855,23 +832,20 @@ 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.flush() session.commit()
PlaylistRows.delete_plrids_not_in_list( PlaylistRows.delete_plrids_not_in_list(session, self.playlist_id,
session, self.playlist_id,
display_plr_ids.values()) display_plr_ids.values())
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:
@ -1026,14 +1000,8 @@ 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
@ -1051,9 +1019,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.flush() session.commit()
_ = self._set_item_text(row, HEADER_NOTES_COLUMN, note_item = QTableWidgetItem(note_text)
note_text) self.setItem(row, HEADER_NOTES_COLUMN, note_item)
if track: if track:
# Reset colour in case it was current/next/unplayable # Reset colour in case it was current/next/unplayable
@ -1071,16 +1039,15 @@ class PlaylistTab(QTableWidget):
# Colour any note # Colour any note
if note_colour: if note_colour:
notes_item = self.item(row, ROW_NOTES) (self.item(row, ROW_NOTES)
if notes_item: .setBackground(QColor(note_colour)))
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._set_item_text( bitrate_item = self.item(row, BITRATE)
row, BITRATE, str(track.bitrate)) if bitrate_item.text() != bitrate_str:
if bitrate_item: bitrate_item.setText(bitrate_str)
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:
@ -1088,15 +1055,13 @@ 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))
bitrate_item.setBackground(brush) self.item(row, BITRATE).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._set_item_text( self.item(row, LASTPLAYED).setText("Today")
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
@ -1125,9 +1090,8 @@ 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
if track.duration: next_start_time = self._calculate_end_time(start_time,
next_start_time = self._calculate_end_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)
# Set colour # Set colour
@ -1139,8 +1103,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._set_item_text( self.item(row, LASTPLAYED).setText(
row, LASTPLAYED, Config.LAST_PLAYED_TODAY_STRING) 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:
@ -1150,9 +1114,8 @@ 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(
start_time, track.duration) next_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:
@ -1210,34 +1173,28 @@ 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.flush() session.commit()
# 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
userdata_item = self.item(row, USERDATA) self.item(row, USERDATA).setData(self.ROW_TRACK_ID, track.id)
if not userdata_item: start_gap_item = self.item(row, START_GAP)
userdata_item = QTableWidgetItem() start_gap_item.setText(str(track.start_gap))
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._set_item_text(row, LASTPLAYED, last_played_str)
_ = 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: if track.start_gap and track.start_gap >= 500:
start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START)) 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_played_str = get_relative_date(last_playtime)
self.item(row, LASTPLAYED).setText(last_played_str)
self.item(row, ROW_NOTES).setText(plr.note)
self._update_row(session, row, track) self.update_display(session)
def _calculate_end_time(self, start: Optional[datetime], def _calculate_end_time(self, start: Optional[datetime],
duration: int) -> Optional[datetime]: duration: int) -> Optional[datetime]:
@ -1286,7 +1243,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 and track.path: if track:
# 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("'", "\\'")
@ -1306,8 +1263,6 @@ 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)
@ -1334,8 +1289,7 @@ 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: Optional[int] = None) \ starting_row: int = None) -> Optional[int]:
-> 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.
@ -1358,8 +1312,6 @@ 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
@ -1375,18 +1327,12 @@ 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
@ -1406,27 +1352,18 @@ class PlaylistTab(QTableWidget):
except ValueError: except ValueError:
return None return None
def _get_playlistrow_id(self, row: int) -> Optional[int]: def _get_playlistrow_id(self, row: int) -> int:
"""Return the playlistrow_id associated with this row""" """Return the playlistrow_id associated with this row"""
userdata_item = self.item(row, USERDATA) playlistrow_id = (self.item(row, USERDATA).data(self.PLAYLISTROW_ID))
if not userdata_item:
return None
return userdata_item.data(self.PLAYLISTROW_ID) return playlistrow_id
def _get_playlistrow_object(self, session: scoped_session, def _get_playlistrow_object(self, session: scoped_session,
row: int) -> Optional[PlaylistRows]: row: int) -> PlaylistRows:
"""Return the playlistrow object associated with this row""" """Return the playlistrow object associated with this row"""
userdata_item = self.item(row, USERDATA) playlistrow_id = (self.item(row, USERDATA).data(self.PLAYLISTROW_ID))
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]:
@ -1437,19 +1374,12 @@ 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"""
userdata_item = self.item(row, USERDATA) duration = (self.item(row, USERDATA).data(self.ROW_DURATION))
if not userdata_item:
return 0
duration = userdata_item.data(self.ROW_DURATION)
if duration: if duration:
return duration return duration
else: else:
@ -1463,21 +1393,17 @@ 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]:
"""Return row start time as string or None"""
start_time_item = self.item(row, START_TIME)
if not start_time_item:
return None
try: try:
return datetime.strptime(start_time_item.text(), if self.item(row, START_TIME):
Config.NOTE_TIME_FORMAT) return datetime.strptime(self.item(
row, START_TIME).text(),
Config.NOTE_TIME_FORMAT
)
else:
return None
except ValueError: except ValueError:
return None return None
@ -1489,22 +1415,16 @@ 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 = userdata_item.data(self.ROW_TRACK_ID) track_id = (self.item(row, USERDATA)
.data(self.ROW_TRACK_ID))
except AttributeError: except AttributeError:
return 0 return None
return track_id return track_id
@ -1573,9 +1493,6 @@ 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
@ -1615,12 +1532,6 @@ 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]:
@ -1645,9 +1556,6 @@ 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:
@ -1656,17 +1564,15 @@ 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._set_item_text(row, i, "") self.item(row, i).setText("")
# 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
userdata_item = self.item(row, USERDATA) self.item(row, USERDATA).setData(self.ROW_TRACK_ID, 0)
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._set_item_text(row, HEADER_NOTES_COLUMN, plr.note) self.item(row, HEADER_NOTES_COLUMN).setText(plr.note)
# And refresh display # And refresh display
self.update_display(session) self.update_display(session)
@ -1821,19 +1727,6 @@ 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.
@ -1863,9 +1756,6 @@ 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
@ -1894,9 +1784,8 @@ 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
item = self.item(row, column) if self.item(row, column):
if item: self.item(row, column).setFont(boldfont)
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:
@ -1915,25 +1804,21 @@ 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
item = self.item(row, column) if self.item(row, column):
if item: self.item(row, column).setBackground(brush)
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"""
item = self.item(row, USERDATA) self.item(row, USERDATA).setData(self.ROW_DURATION, ms)
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) # type: ignore time_str = time.strftime(Config.TRACK_TIME_FORMAT)
except AttributeError: except AttributeError:
time_str = "" time_str = ""
item = QTableWidgetItem(time_str) item = QTableWidgetItem(time_str)
self.setItem(row, END_TIME, item) self.setItem(row, END_TIME, item)
@ -1946,13 +1831,14 @@ 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) # type: ignore time_str = time.strftime(Config.TRACK_TIME_FORMAT)
except AttributeError: except AttributeError:
time_str = "" time_str = ""
_ = self._set_item_text(row, START_TIME, time_str) item = QTableWidgetItem(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) -> str: no_end: bool = False) -> None:
"""Return string describing section duration""" """Return string describing section duration"""
duration = ms_to_mmss(ms) duration = ms_to_mmss(ms)
@ -1980,29 +1866,40 @@ class PlaylistTab(QTableWidget):
column = HEADER_NOTES_COLUMN column = HEADER_NOTES_COLUMN
# Update text # Update text
if playlist_row.note:
new_text = playlist_row.note + additional_text new_text = playlist_row.note + additional_text
else: # FIXME temporary workaround to issue #147
new_text = additional_text try:
self.item(playlist_row.row_number, column).setText(new_text)
_ = self._set_item_text(playlist_row.row_number, column, new_text) except AttributeError as exc:
msg = f"Issue 147 occurred. {playlist_row=}, {additional_text=}"
msg += "\n\n"
msg += stackprinter.format(exc)
helpers.send_mail(Config.ERRORS_TO, Confit.ERRORS_FROM,
"Issue #147 from musicmuster", msg)
def _update_row(self, session, row: int, track: Tracks) -> None: def _update_row(self, session, row: int, track: Tracks) -> None:
""" """
Update the passed row with info from the passed track. Update the passed row with info from the passed track.
""" """
start_gap_item = self._set_item_text( item_startgap = self.item(row, START_GAP)
row, START_GAP, str(track.start_gap)) item_startgap.setText(str(track.start_gap))
if track.start_gap and track.start_gap >= 500: if track.start_gap >= 500:
start_gap_item.setBackground(QColor(Config.COLOUR_LONG_START)) item_startgap.setBackground(QColor(Config.COLOUR_LONG_START))
else: else:
start_gap_item.setBackground(QColor("white")) item_startgap.setBackground(QColor("white"))
_ = self._set_item_text(row, TITLE, track.title) item_title = self.item(row, TITLE)
_ = self._set_item_text(row, ARTIST, track.artist) item_title.setText(track.title)
_ = self._set_item_text(row, DURATION, track.duration)
_ = self._set_item_text(row, BITRATE, track.bitrate) item_artist = self.item(row, ARTIST)
item_artist.setText(track.artist)
item_duration = self.item(row, DURATION)
item_duration.setText(ms_to_mmss(track.duration))
item_bitrate = self.item(row, BITRATE)
item_bitrate.setText(str(track.bitrate))
self.update_display(session) self.update_display(session)

View File

@ -36,6 +36,7 @@ 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

114
poetry.lock generated
View File

@ -1,6 +1,6 @@
[[package]] [[package]]
name = "alembic" name = "alembic"
version = "1.9.3" version = "1.9.1"
description = "A database migration tool for SQLAlchemy." description = "A database migration tool for SQLAlchemy."
category = "main" category = "main"
optional = false optional = false
@ -67,6 +67,17 @@ category = "dev"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
[[package]]
name = "commonmark"
version = "0.9.1"
description = "Python parser for the CommonMark Markdown spec"
category = "main"
optional = false
python-versions = "*"
[package.extras]
test = ["hypothesis (==3.55.3)", "flake8 (==3.7.8)"]
[[package]] [[package]]
name = "decorator" name = "decorator"
version = "5.1.1" version = "5.1.1"
@ -140,7 +151,7 @@ dev = ["dlint", "flake8-2020", "flake8-aaa", "flake8-absolute-import", "flake8-a
[[package]] [[package]]
name = "greenlet" name = "greenlet"
version = "2.0.2" version = "2.0.1"
description = "Lightweight in-process concurrent programming" description = "Lightweight in-process concurrent programming"
category = "main" category = "main"
optional = false optional = false
@ -148,15 +159,15 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
[package.extras] [package.extras]
docs = ["sphinx", "docutils (<0.18)"] docs = ["sphinx", "docutils (<0.18)"]
test = ["objgraph", "psutil"] test = ["objgraph", "psutil", "faulthandler"]
[[package]] [[package]]
name = "iniconfig" name = "iniconfig"
version = "2.0.0" version = "1.1.1"
description = "brain-dead simple config-ini parsing" description = "iniconfig: brain-dead simple config-ini parsing"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = "*"
[[package]] [[package]]
name = "ipdb" name = "ipdb"
@ -173,7 +184,7 @@ tomli = {version = "*", markers = "python_version > \"3.6\" and python_version <
[[package]] [[package]]
name = "ipython" name = "ipython"
version = "8.9.0" version = "8.7.0"
description = "IPython: Productive Interactive Computing" description = "IPython: Productive Interactive Computing"
category = "dev" category = "dev"
optional = false optional = false
@ -188,7 +199,7 @@ jedi = ">=0.16"
matplotlib-inline = "*" matplotlib-inline = "*"
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
pickleshare = "*" pickleshare = "*"
prompt-toolkit = ">=3.0.30,<3.1.0" prompt-toolkit = ">=3.0.11,<3.1.0"
pygments = ">=2.4.0" pygments = ">=2.4.0"
stack-data = "*" stack-data = "*"
traitlets = ">=5" traitlets = ">=5"
@ -256,30 +267,9 @@ babel = ["babel"]
lingua = ["lingua"] lingua = ["lingua"]
testing = ["pytest"] testing = ["pytest"]
[[package]]
name = "markdown-it-py"
version = "2.1.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
mdurl = ">=0.1,<1.0"
[package.extras]
benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"]
code_style = ["pre-commit (==2.6)"]
compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"]
linkify = ["linkify-it-py (>=1.0,<2.0)"]
plugins = ["mdit-py-plugins"]
profiling = ["gprof2dot"]
rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx-book-theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]] [[package]]
name = "markupsafe" name = "markupsafe"
version = "2.1.2" version = "2.1.1"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
category = "main" category = "main"
optional = false optional = false
@ -304,14 +294,6 @@ category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
[[package]]
name = "mdurl"
version = "0.1.2"
description = "Markdown URL utilities"
category = "main"
optional = false
python-versions = ">=3.7"
[[package]] [[package]]
name = "mutagen" name = "mutagen"
version = "1.46.0" version = "1.46.0"
@ -341,11 +323,11 @@ reports = ["lxml"]
[[package]] [[package]]
name = "mypy-extensions" name = "mypy-extensions"
version = "1.0.0" version = "0.4.3"
description = "Type system extensions for programs checked with the mypy type checker." description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = "*"
[[package]] [[package]]
name = "mysqlclient" name = "mysqlclient"
@ -357,7 +339,7 @@ python-versions = ">=3.5"
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "23.0" version = "22.0"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
category = "dev" category = "dev"
optional = false optional = false
@ -500,7 +482,7 @@ python-versions = "*"
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.14.0" version = "2.13.0"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
category = "main" category = "main"
optional = false optional = false
@ -511,14 +493,14 @@ plugins = ["importlib-metadata"]
[[package]] [[package]]
name = "pyqt5" name = "pyqt5"
version = "5.15.9" version = "5.15.7"
description = "Python bindings for the Qt cross platform application toolkit" description = "Python bindings for the Qt cross platform application toolkit"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
[package.dependencies] [package.dependencies]
PyQt5-Qt5 = ">=5.15.2" PyQt5-Qt5 = ">=5.15.0"
PyQt5-sip = ">=12.11,<13" PyQt5-sip = ">=12.11,<13"
[[package]] [[package]]
@ -531,7 +513,7 @@ python-versions = "*"
[[package]] [[package]]
name = "pyqt5-sip" name = "pyqt5-sip"
version = "12.11.1" version = "12.11.0"
description = "The sip module support for PyQt5" description = "The sip module support for PyQt5"
category = "main" category = "main"
optional = false optional = false
@ -571,7 +553,7 @@ python-versions = "*"
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "7.2.1" version = "7.2.0"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
category = "dev" category = "dev"
optional = false optional = false
@ -636,18 +618,18 @@ python-versions = "*"
[[package]] [[package]]
name = "rich" name = "rich"
version = "13.3.1" version = "12.6.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7.0" python-versions = ">=3.6.3,<4.0.0"
[package.dependencies] [package.dependencies]
markdown-it-py = ">=2.1.0,<3.0.0" commonmark = ">=0.9.0,<0.10.0"
pygments = ">=2.14.0,<3.0.0" pygments = ">=2.6.0,<3.0.0"
[package.extras] [package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"] jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
[[package]] [[package]]
name = "six" name = "six"
@ -659,7 +641,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]] [[package]]
name = "sqlalchemy" name = "sqlalchemy"
version = "1.4.46" version = "1.4.45"
description = "Database Abstraction Library" description = "Database Abstraction Library"
category = "main" category = "main"
optional = false optional = false
@ -772,7 +754,7 @@ python-versions = ">=3.7"
[[package]] [[package]]
name = "traitlets" name = "traitlets"
version = "5.9.0" version = "5.8.0"
description = "Traitlets Python configuration system" description = "Traitlets Python configuration system"
category = "dev" category = "dev"
optional = false optional = false
@ -784,7 +766,7 @@ test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
[[package]] [[package]]
name = "types-psutil" name = "types-psutil"
version = "5.9.5.6" version = "5.9.5.5"
description = "Typing stubs for psutil" description = "Typing stubs for psutil"
category = "main" category = "main"
optional = false optional = false
@ -800,7 +782,7 @@ python-versions = ">=3.7"
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "1.26.14" version = "1.26.13"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "dev" category = "dev"
optional = false optional = false
@ -813,7 +795,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]] [[package]]
name = "wcwidth" name = "wcwidth"
version = "0.2.6" version = "0.2.5"
description = "Measures the displayed width of unicode strings in a terminal" description = "Measures the displayed width of unicode strings in a terminal"
category = "dev" category = "dev"
optional = false optional = false
@ -834,6 +816,7 @@ backcall = [
{file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
] ]
colorama = [] colorama = []
commonmark = []
decorator = [ decorator = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
@ -844,20 +827,24 @@ executing = []
flake8 = [] flake8 = []
flakehell = [] flakehell = []
greenlet = [] greenlet = []
iniconfig = [] iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
ipdb = [] ipdb = []
ipython = [] ipython = []
jedi = [] jedi = []
line-profiler = [] line-profiler = []
mako = [] mako = []
markdown-it-py = []
markupsafe = [] markupsafe = []
matplotlib-inline = [] matplotlib-inline = []
mccabe = [] mccabe = []
mdurl = []
mutagen = [] mutagen = []
mypy = [] mypy = []
mypy-extensions = [] mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
mysqlclient = [] mysqlclient = []
packaging = [] packaging = []
parso = [ parso = [
@ -943,4 +930,7 @@ traitlets = []
types-psutil = [] types-psutil = []
typing-extensions = [] typing-extensions = []
urllib3 = [] urllib3 = []
wcwidth = [] wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
]

View File

@ -41,8 +41,7 @@ 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]