Compare commits
No commits in common. "0c38fc2ef45cb53e4fcd518422d9ba76add13c89" and "f182f49f15df2a8dbdb85f192e8846e2b44db1c3" have entirely different histories.
0c38fc2ef4
...
f182f49f15
0
app/__init__.py
Normal file
0
app/__init__.py
Normal 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"]:
|
||||||
|
|||||||
@ -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,8 +238,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)
|
||||||
btn.pgb.setVisible(True)
|
btn.pgb.setVisible(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,8 +436,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
|
||||||
self.tabPlaylist.widget(tab_index).close()
|
self.tabPlaylist.widget(tab_index).close()
|
||||||
@ -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,8 +621,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
|
||||||
self.frame_fade.setStyleSheet("")
|
self.frame_fade.setStyleSheet("")
|
||||||
@ -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,8 +706,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(
|
||||||
self,
|
self,
|
||||||
@ -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,8 +831,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")
|
||||||
if record and record.f_int >= 0:
|
if record and record.f_int >= 0:
|
||||||
@ -955,14 +853,11 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# Remove current/next rows from list
|
# Remove current/next rows from list
|
||||||
plrs_to_move = [plr for plr in playlistrows if
|
plrs_to_move = [plr for plr in playlistrows if
|
||||||
plr.id not in
|
plr.id not in
|
||||||
[self.current_track.plr_id,
|
[self.current_track.plr_id,
|
||||||
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,8 +1088,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
|
||||||
self.playing = True
|
self.playing = True
|
||||||
@ -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))
|
self.label_end_time.setText(
|
||||||
if self.current_track.end_time:
|
self.current_track.end_time.strftime(Config.TRACK_TIME_FORMAT))
|
||||||
self.label_end_time.setText(
|
|
||||||
self.current_track.end_time.strftime(
|
|
||||||
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,13 +1307,12 @@ 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))
|
else:
|
||||||
else:
|
self.set_tab_colour(self.current_track.playlist_tab,
|
||||||
self.set_tab_colour(self.current_track.playlist_tab,
|
QColor(Config.COLOUR_NORMAL_TAB))
|
||||||
QColor(Config.COLOUR_NORMAL_TAB))
|
|
||||||
|
|
||||||
# Run end-of-track actions
|
# Run end-of-track actions
|
||||||
self.end_of_track_actions()
|
self.end_of_track_actions()
|
||||||
@ -1493,11 +1365,10 @@ 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,
|
QColor(Config.COLOUR_NEXT_TAB))
|
||||||
QColor(Config.COLOUR_NEXT_TAB))
|
|
||||||
|
|
||||||
# If we've changed playlist tabs for next track, refresh old one
|
# If we've changed playlist tabs for next track, refresh old one
|
||||||
# to remove highligting of next track
|
# to remove highligting of next track
|
||||||
@ -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()
|
||||||
@ -1732,7 +1597,7 @@ class DbDialog(QDialog):
|
|||||||
item = self.ui.matchList.currentItem()
|
item = self.ui.matchList.currentItem()
|
||||||
if item:
|
if item:
|
||||||
track = item.data(Qt.UserRole)
|
track = item.data(Qt.UserRole)
|
||||||
self.add_track(track)
|
self.add_track(track)
|
||||||
|
|
||||||
def add_selected_and_close(self) -> None:
|
def add_selected_and_close(self) -> None:
|
||||||
"""Handle Add and Close button"""
|
"""Handle Add and Close button"""
|
||||||
@ -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
|
||||||
|
|||||||
391
app/playlists.py
391
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, 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,12 +111,10 @@ 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:
|
self.commitData.emit(editor)
|
||||||
if key_event.modifiers() == Qt.ControlModifier:
|
self.closeEditor.emit(editor)
|
||||||
self.commitData.emit(editor)
|
|
||||||
self.closeEditor.emit(editor)
|
|
||||||
|
|
||||||
return super().eventFilter(editor, event)
|
return super().eventFilter(editor, event)
|
||||||
|
|
||||||
@ -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,24 +832,21 @@ 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:
|
||||||
"""Set search text and find first match"""
|
"""Set search text and find first match"""
|
||||||
@ -1026,15 +1000,9 @@ 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
|
note_colour = NoteColours.get_colour(session, note_text)
|
||||||
if note_text:
|
|
||||||
note_colour = NoteColours.get_colour(session, note_text)
|
|
||||||
|
|
||||||
# Get track if there is one
|
# Get track if there is one
|
||||||
track_id = self._get_row_track_id(row)
|
track_id = self._get_row_track_id(row)
|
||||||
@ -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,36 +1039,33 @@ 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:
|
||||||
cell_colour = Config.COLOUR_BITRATE_MEDIUM
|
cell_colour = Config.COLOUR_BITRATE_MEDIUM
|
||||||
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
|
)
|
||||||
)
|
|
||||||
# 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
|
||||||
@ -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(
|
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:
|
||||||
@ -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,13 +1532,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if track.path is None:
|
open_in_audacity(track.path)
|
||||||
log.error(
|
|
||||||
f"playlists._open_in_audacity({track_id=}): "
|
|
||||||
"Track has no path"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
open_in_audacity(track.path)
|
|
||||||
|
|
||||||
def _plrid_to_row_number(self, plrid: int) -> Optional[int]:
|
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,10 +1756,7 @@ 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:
|
self.musicmuster.this_is_the_next_playlist_row(session, plr, self)
|
||||||
log.debug(f"playists._set_next({row_number=}) can't retrieve plr")
|
|
||||||
else:
|
|
||||||
self.musicmuster.this_is_the_next_playlist_row(session, plr, self)
|
|
||||||
|
|
||||||
# Update display
|
# Update display
|
||||||
self.clear_selection()
|
self.clear_selection()
|
||||||
@ -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
|
# FIXME temporary workaround to issue #147
|
||||||
else:
|
try:
|
||||||
new_text = additional_text
|
self.item(playlist_row.row_number, column).setText(new_text)
|
||||||
|
except AttributeError as exc:
|
||||||
_ = self._set_item_text(playlist_row.row_number, column, new_text)
|
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)
|
||||||
|
|
||||||
|
|||||||
@ -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
114
poetry.lock
generated
@ -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"},
|
||||||
|
]
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user