Compare commits
3 Commits
3557d22c54
...
9554336860
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9554336860 | ||
|
|
813b325029 | ||
|
|
734d5cb545 |
20
app/datastructures.py
Normal file
20
app/datastructures.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from PyQt6.QtCore import pyqtSignal, QObject
|
||||||
|
|
||||||
|
from helpers import singleton
|
||||||
|
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
class MusicMusterSignals(QObject):
|
||||||
|
"""
|
||||||
|
Class for all MusicMuster signals. See:
|
||||||
|
- https://zetcode.com/gui/pyqt5/eventssignals/
|
||||||
|
- https://stackoverflow.com/questions/62654525/
|
||||||
|
emit-a-signal-from-another-class-to-main-class
|
||||||
|
and Singleton class at
|
||||||
|
https://refactoring.guru/design-patterns/singleton/python/example#example-0
|
||||||
|
"""
|
||||||
|
|
||||||
|
enable_escape_signal = pyqtSignal(bool)
|
||||||
|
set_next_track_signal = pyqtSignal(int, int)
|
||||||
|
span_cells_signal = pyqtSignal(int, int, int, int)
|
||||||
|
add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import functools
|
||||||
import os
|
import os
|
||||||
import psutil
|
import psutil
|
||||||
import shutil
|
import shutil
|
||||||
@ -154,23 +155,21 @@ def get_file_metadata(filepath: str) -> dict:
|
|||||||
# Get title, artist, bitrate, duration, path
|
# Get title, artist, bitrate, duration, path
|
||||||
metadata: Dict[str, str | int | float] = get_tags(filepath)
|
metadata: Dict[str, str | int | float] = get_tags(filepath)
|
||||||
|
|
||||||
metadata['mtime'] = os.path.getmtime(filepath)
|
metadata["mtime"] = os.path.getmtime(filepath)
|
||||||
|
|
||||||
# Set start_gap, fade_at and silence_at
|
# Set start_gap, fade_at and silence_at
|
||||||
audio = get_audio_segment(filepath)
|
audio = get_audio_segment(filepath)
|
||||||
if not audio:
|
if not audio:
|
||||||
audio_values = dict(
|
audio_values = dict(start_gap=0, fade_at=0, silence_at=0)
|
||||||
start_gap=0,
|
|
||||||
fade_at=0,
|
|
||||||
silence_at=0
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
audio_values = dict(
|
audio_values = dict(
|
||||||
start_gap=leading_silence(audio),
|
start_gap=leading_silence(audio),
|
||||||
fade_at=int(round(fade_point(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000),
|
fade_at=int(
|
||||||
|
round(fade_point(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000
|
||||||
|
),
|
||||||
silence_at=int(
|
silence_at=int(
|
||||||
round(trailing_silence(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000
|
round(trailing_silence(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
metadata |= audio_values
|
metadata |= audio_values
|
||||||
|
|
||||||
@ -381,6 +380,22 @@ def show_warning(parent: QMainWindow, title: str, msg: str) -> None:
|
|||||||
QMessageBox.warning(parent, title, msg, buttons=QMessageBox.StandardButton.Cancel)
|
QMessageBox.warning(parent, title, msg, buttons=QMessageBox.StandardButton.Cancel)
|
||||||
|
|
||||||
|
|
||||||
|
def singleton(cls):
|
||||||
|
"""
|
||||||
|
Make a class a Singleton class (see
|
||||||
|
https://realpython.com/primer-on-python-decorators/#creating-singletons)
|
||||||
|
"""
|
||||||
|
|
||||||
|
@functools.wraps(cls)
|
||||||
|
def wrapper_singleton(*args, **kwargs):
|
||||||
|
if not wrapper_singleton.instance:
|
||||||
|
wrapper_singleton.instance = cls(*args, **kwargs)
|
||||||
|
return wrapper_singleton.instance
|
||||||
|
|
||||||
|
wrapper_singleton.instance = None
|
||||||
|
return wrapper_singleton
|
||||||
|
|
||||||
|
|
||||||
def trailing_silence(
|
def trailing_silence(
|
||||||
audio_segment: AudioSegment,
|
audio_segment: AudioSegment,
|
||||||
silence_threshold: int = -50,
|
silence_threshold: int = -50,
|
||||||
|
|||||||
113
app/models.py
113
app/models.py
@ -11,6 +11,7 @@ from typing import List, Optional, Sequence
|
|||||||
from sqlalchemy.ext.associationproxy import association_proxy
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
|
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
|
bindparam,
|
||||||
Boolean,
|
Boolean,
|
||||||
DateTime,
|
DateTime,
|
||||||
delete,
|
delete,
|
||||||
@ -49,9 +50,9 @@ class Carts(Base):
|
|||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||||
cart_number: Mapped[int] = mapped_column(unique=True)
|
cart_number: Mapped[int] = mapped_column(unique=True)
|
||||||
name: Mapped[str] = mapped_column(String(256), index=True)
|
name: Mapped[str] = mapped_column(String(256), index=True)
|
||||||
duration: Mapped[int] = mapped_column(index=True)
|
duration: Mapped[Optional[int]] = mapped_column(index=True)
|
||||||
path: Mapped[str] = mapped_column(String(2048), index=False)
|
path: Mapped[Optional[str]] = mapped_column(String(2048), index=False)
|
||||||
enabled: Mapped[bool] = mapped_column(default=False)
|
enabled: Mapped[Optional[bool]] = mapped_column(default=False)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
@ -63,7 +64,7 @@ class Carts(Base):
|
|||||||
self,
|
self,
|
||||||
session: scoped_session,
|
session: scoped_session,
|
||||||
cart_number: int,
|
cart_number: int,
|
||||||
name: Optional[str] = None,
|
name: str,
|
||||||
duration: Optional[int] = None,
|
duration: Optional[int] = None,
|
||||||
path: Optional[str] = None,
|
path: Optional[str] = None,
|
||||||
enabled: bool = True,
|
enabled: bool = True,
|
||||||
@ -134,14 +135,11 @@ class NoteColours(Base):
|
|||||||
if not text:
|
if not text:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
for rec in (
|
for rec in session.scalars(
|
||||||
session.scalars(
|
|
||||||
select(NoteColours)
|
select(NoteColours)
|
||||||
.filter(NoteColours.enabled.is_(True))
|
.filter(NoteColours.enabled.is_(True))
|
||||||
.order_by(NoteColours.order)
|
.order_by(NoteColours.order)
|
||||||
)
|
).all():
|
||||||
.all()
|
|
||||||
):
|
|
||||||
if rec.is_regex:
|
if rec.is_regex:
|
||||||
flags = re.UNICODE
|
flags = re.UNICODE
|
||||||
if not rec.is_casesensitive:
|
if not rec.is_casesensitive:
|
||||||
@ -202,14 +200,11 @@ class Playdates(Base):
|
|||||||
def played_after(session: scoped_session, since: datetime) -> Sequence["Playdates"]:
|
def played_after(session: scoped_session, since: datetime) -> Sequence["Playdates"]:
|
||||||
"""Return a list of Playdates objects since passed time"""
|
"""Return a list of Playdates objects since passed time"""
|
||||||
|
|
||||||
return (
|
return session.scalars(
|
||||||
session.scalars(
|
|
||||||
select(Playdates)
|
select(Playdates)
|
||||||
.where(Playdates.lastplayed >= since)
|
.where(Playdates.lastplayed >= since)
|
||||||
.order_by(Playdates.lastplayed)
|
.order_by(Playdates.lastplayed)
|
||||||
)
|
).all()
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Playlists(Base):
|
class Playlists(Base):
|
||||||
@ -285,32 +280,25 @@ class Playlists(Base):
|
|||||||
def get_all(cls, session: scoped_session) -> Sequence["Playlists"]:
|
def get_all(cls, session: scoped_session) -> Sequence["Playlists"]:
|
||||||
"""Returns a list of all playlists ordered by last use"""
|
"""Returns a list of all playlists ordered by last use"""
|
||||||
|
|
||||||
return (
|
return session.scalars(
|
||||||
session.scalars(
|
|
||||||
select(cls)
|
select(cls)
|
||||||
.filter(cls.is_template.is_(False))
|
.filter(cls.is_template.is_(False))
|
||||||
.order_by(cls.tab.desc(), cls.last_used.desc())
|
.order_by(cls.tab.desc(), cls.last_used.desc())
|
||||||
)
|
).all()
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all_templates(cls, session: scoped_session) -> Sequence["Playlists"]:
|
def get_all_templates(cls, session: scoped_session) -> Sequence["Playlists"]:
|
||||||
"""Returns a list of all templates ordered by name"""
|
"""Returns a list of all templates ordered by name"""
|
||||||
|
|
||||||
return (
|
return session.scalars(
|
||||||
session.scalars(
|
|
||||||
select(cls).filter(cls.is_template.is_(True)).order_by(cls.name)
|
select(cls).filter(cls.is_template.is_(True)).order_by(cls.name)
|
||||||
)
|
).all()
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_closed(cls, session: scoped_session) -> Sequence["Playlists"]:
|
def get_closed(cls, session: scoped_session) -> Sequence["Playlists"]:
|
||||||
"""Returns a list of all closed playlists ordered by last use"""
|
"""Returns a list of all closed playlists ordered by last use"""
|
||||||
|
|
||||||
return (
|
return session.scalars(
|
||||||
session.scalars(
|
|
||||||
select(cls)
|
select(cls)
|
||||||
.filter(
|
.filter(
|
||||||
cls.tab.is_(None),
|
cls.tab.is_(None),
|
||||||
@ -318,9 +306,7 @@ class Playlists(Base):
|
|||||||
cls.deleted.is_(False),
|
cls.deleted.is_(False),
|
||||||
)
|
)
|
||||||
.order_by(cls.last_used.desc())
|
.order_by(cls.last_used.desc())
|
||||||
)
|
).all()
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_open(cls, session: scoped_session) -> Sequence[Optional["Playlists"]]:
|
def get_open(cls, session: scoped_session) -> Sequence[Optional["Playlists"]]:
|
||||||
@ -328,10 +314,9 @@ class Playlists(Base):
|
|||||||
Return a list of loaded playlists ordered by tab order.
|
Return a list of loaded playlists ordered by tab order.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (
|
return session.scalars(
|
||||||
session.scalars(select(cls).where(cls.tab.is_not(None)).order_by(cls.tab))
|
select(cls).where(cls.tab.is_not(None)).order_by(cls.tab)
|
||||||
.all()
|
).all()
|
||||||
)
|
|
||||||
|
|
||||||
def mark_open(self, session: scoped_session, tab_index: int) -> None:
|
def mark_open(self, session: scoped_session, tab_index: int) -> None:
|
||||||
"""Mark playlist as loaded and used now"""
|
"""Mark playlist as loaded and used now"""
|
||||||
@ -433,12 +418,9 @@ class PlaylistRows(Base):
|
|||||||
def copy_playlist(session: scoped_session, src_id: int, dst_id: int) -> None:
|
def copy_playlist(session: scoped_session, src_id: int, dst_id: int) -> None:
|
||||||
"""Copy playlist entries"""
|
"""Copy playlist entries"""
|
||||||
|
|
||||||
src_rows = (
|
src_rows = session.scalars(
|
||||||
session.scalars(
|
|
||||||
select(PlaylistRows).filter(PlaylistRows.playlist_id == src_id)
|
select(PlaylistRows).filter(PlaylistRows.playlist_id == src_id)
|
||||||
)
|
).all()
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
for plr in src_rows:
|
for plr in src_rows:
|
||||||
PlaylistRows(
|
PlaylistRows(
|
||||||
@ -512,14 +494,11 @@ class PlaylistRows(Base):
|
|||||||
Ensure the row numbers for passed playlist have no gaps
|
Ensure the row numbers for passed playlist have no gaps
|
||||||
"""
|
"""
|
||||||
|
|
||||||
plrs = (
|
plrs = session.scalars(
|
||||||
session.scalars(
|
|
||||||
select(PlaylistRows)
|
select(PlaylistRows)
|
||||||
.where(PlaylistRows.playlist_id == playlist_id)
|
.where(PlaylistRows.playlist_id == playlist_id)
|
||||||
.order_by(PlaylistRows.plr_rownum)
|
.order_by(PlaylistRows.plr_rownum)
|
||||||
)
|
).all()
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, plr in enumerate(plrs):
|
for i, plr in enumerate(plrs):
|
||||||
plr.plr_rownum = i
|
plr.plr_rownum = i
|
||||||
@ -536,14 +515,11 @@ class PlaylistRows(Base):
|
|||||||
PlaylistRows objects
|
PlaylistRows objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
plrs = (
|
plrs = session.scalars(
|
||||||
session.scalars(
|
|
||||||
select(cls)
|
select(cls)
|
||||||
.where(cls.playlist_id == playlist_id, cls.id.in_(plr_ids))
|
.where(cls.playlist_id == playlist_id, cls.id.in_(plr_ids))
|
||||||
.order_by(cls.plr_rownum)
|
.order_by(cls.plr_rownum)
|
||||||
)
|
).all()
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
return plrs
|
return plrs
|
||||||
|
|
||||||
@ -581,14 +557,11 @@ class PlaylistRows(Base):
|
|||||||
have been played.
|
have been played.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
plrs = (
|
plrs = session.scalars(
|
||||||
session.scalars(
|
|
||||||
select(cls)
|
select(cls)
|
||||||
.where(cls.playlist_id == playlist_id, cls.played.is_(True))
|
.where(cls.playlist_id == playlist_id, cls.played.is_(True))
|
||||||
.order_by(cls.plr_rownum)
|
.order_by(cls.plr_rownum)
|
||||||
)
|
).all()
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
return plrs
|
return plrs
|
||||||
|
|
||||||
@ -626,8 +599,7 @@ class PlaylistRows(Base):
|
|||||||
have not been played.
|
have not been played.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
plrs = (
|
plrs = session.scalars(
|
||||||
session.scalars(
|
|
||||||
select(cls)
|
select(cls)
|
||||||
.where(
|
.where(
|
||||||
cls.playlist_id == playlist_id,
|
cls.playlist_id == playlist_id,
|
||||||
@ -635,12 +607,17 @@ class PlaylistRows(Base):
|
|||||||
cls.played.is_(False),
|
cls.played.is_(False),
|
||||||
)
|
)
|
||||||
.order_by(cls.plr_rownum)
|
.order_by(cls.plr_rownum)
|
||||||
)
|
).all()
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
return plrs
|
return plrs
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def insert_row(
|
||||||
|
cls, session: scoped_session, playlist_id: int, new_row_number: int
|
||||||
|
) -> "PlaylistRows":
|
||||||
|
cls.move_rows_down(session, playlist_id, new_row_number, 1)
|
||||||
|
return cls(session, playlist_id, new_row_number)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def move_rows_down(
|
def move_rows_down(
|
||||||
session: scoped_session, playlist_id: int, starting_row: int, move_by: int
|
session: scoped_session, playlist_id: int, starting_row: int, move_by: int
|
||||||
@ -659,6 +636,26 @@ class PlaylistRows(Base):
|
|||||||
.values(plr_rownum=PlaylistRows.plr_rownum + move_by)
|
.values(plr_rownum=PlaylistRows.plr_rownum + move_by)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_plr_rownumbers(
|
||||||
|
session: scoped_session, playlist_id: int, sqla_map: List[dict[str, int]]
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Take a {plrid: plr_rownum} dictionary and update the row numbers accordingly
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Update database. Ref:
|
||||||
|
# https://docs.sqlalchemy.org/en/20/tutorial/data_update.html#the-update-sql-expression-construct
|
||||||
|
stmt = (
|
||||||
|
update(PlaylistRows)
|
||||||
|
.where(
|
||||||
|
PlaylistRows.playlist_id == playlist_id,
|
||||||
|
PlaylistRows.id == bindparam("plrid"),
|
||||||
|
)
|
||||||
|
.values(plr_rownum=bindparam("plr_rownum"))
|
||||||
|
)
|
||||||
|
session.connection().execute(stmt, sqla_map)
|
||||||
|
|
||||||
|
|
||||||
class Settings(Base):
|
class Settings(Base):
|
||||||
"""Manage settings"""
|
"""Manage settings"""
|
||||||
|
|||||||
@ -15,7 +15,6 @@ from datetime import datetime, timedelta
|
|||||||
from pygame import mixer
|
from pygame import mixer
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import (
|
from typing import (
|
||||||
Callable,
|
|
||||||
cast,
|
cast,
|
||||||
List,
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
@ -69,6 +68,7 @@ import icons_rc # noqa F401
|
|||||||
import music
|
import music
|
||||||
from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
|
from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
|
||||||
from config import Config
|
from config import Config
|
||||||
|
from datastructures import MusicMusterSignals
|
||||||
from playlists import PlaylistTab
|
from playlists import PlaylistTab
|
||||||
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
|
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
|
||||||
from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
|
from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
|
||||||
@ -238,20 +238,6 @@ class ImportTrack(QObject):
|
|||||||
self.finished.emit(self.playlist)
|
self.finished.emit(self.playlist)
|
||||||
|
|
||||||
|
|
||||||
class MusicMusterSignals(QObject):
|
|
||||||
"""
|
|
||||||
Class for all MusicMuster signals. See:
|
|
||||||
- https://zetcode.com/gui/pyqt5/eventssignals/
|
|
||||||
- https://stackoverflow.com/questions/62654525/
|
|
||||||
emit-a-signal-from-another-class-to-main-class
|
|
||||||
"""
|
|
||||||
|
|
||||||
enable_escape_signal = pyqtSignal(bool)
|
|
||||||
set_next_track_signal = pyqtSignal(int, int)
|
|
||||||
span_cells_signal = pyqtSignal(int, int, int, int)
|
|
||||||
add_track_to_playlist_signal = pyqtSignal(int, int, str)
|
|
||||||
|
|
||||||
|
|
||||||
class PlaylistTrack:
|
class PlaylistTrack:
|
||||||
"""
|
"""
|
||||||
Used to provide a single reference point for specific playlist tracks,
|
Used to provide a single reference point for specific playlist tracks,
|
||||||
@ -349,8 +335,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.previous_track_position: Optional[float] = None
|
self.previous_track_position: Optional[float] = None
|
||||||
self.selected_plrs: Optional[List[PlaylistRows]] = None
|
self.selected_plrs: Optional[List[PlaylistRows]] = None
|
||||||
|
|
||||||
self.signals = MusicMusterSignals()
|
|
||||||
|
|
||||||
self.set_main_window_size()
|
self.set_main_window_size()
|
||||||
self.lblSumPlaytime = QLabel("")
|
self.lblSumPlaytime = QLabel("")
|
||||||
self.statusbar.addPermanentWidget(self.lblSumPlaytime)
|
self.statusbar.addPermanentWidget(self.lblSumPlaytime)
|
||||||
@ -379,6 +363,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.timer10.start(10)
|
self.timer10.start(10)
|
||||||
self.timer500.start(500)
|
self.timer500.start(500)
|
||||||
self.timer1000.start(1000)
|
self.timer1000.start(1000)
|
||||||
|
self.signals = MusicMusterSignals()
|
||||||
self.connect_signals_slots()
|
self.connect_signals_slots()
|
||||||
|
|
||||||
def about(self) -> None:
|
def about(self) -> None:
|
||||||
@ -731,7 +716,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
playlist_tab = PlaylistTab(
|
playlist_tab = PlaylistTab(
|
||||||
musicmuster=self,
|
musicmuster=self,
|
||||||
playlist_id=playlist.id,
|
playlist_id=playlist.id,
|
||||||
signals=self.signals,
|
|
||||||
)
|
)
|
||||||
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
||||||
self.tabPlaylist.setCurrentIndex(idx)
|
self.tabPlaylist.setCurrentIndex(idx)
|
||||||
@ -1065,7 +1049,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
with Session() as session:
|
with Session() as session:
|
||||||
dlg = TrackSelectDialog(
|
dlg = TrackSelectDialog(
|
||||||
session=session,
|
session=session,
|
||||||
signals=self.signals,
|
new_row_number=self.active_tab().get_selected_row_number(),
|
||||||
playlist_id=self.active_tab().playlist_id,
|
playlist_id=self.active_tab().playlist_id,
|
||||||
)
|
)
|
||||||
dlg.exec()
|
dlg.exec()
|
||||||
@ -1923,7 +1907,7 @@ class TrackSelectDialog(QDialog):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
session: scoped_session,
|
session: scoped_session,
|
||||||
signals: MusicMusterSignals,
|
new_row_number: int,
|
||||||
playlist_id: int,
|
playlist_id: int,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@ -1934,7 +1918,7 @@ class TrackSelectDialog(QDialog):
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.session = session
|
self.session = session
|
||||||
self.signals = signals
|
self.new_row_number = new_row_number
|
||||||
self.playlist_id = playlist_id
|
self.playlist_id = playlist_id
|
||||||
self.ui = Ui_Dialog()
|
self.ui = Ui_Dialog()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
@ -1946,6 +1930,7 @@ class TrackSelectDialog(QDialog):
|
|||||||
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.track: Optional[Tracks] = None
|
||||||
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
record = Settings.get_int_settings(self.session, "dbdialog_width")
|
record = Settings.get_int_settings(self.session, "dbdialog_width")
|
||||||
width = record.f_int or 800
|
width = record.f_int or 800
|
||||||
@ -1974,7 +1959,9 @@ class TrackSelectDialog(QDialog):
|
|||||||
track_id = None
|
track_id = None
|
||||||
if track:
|
if track:
|
||||||
track_id = track.id
|
track_id = track.id
|
||||||
self.signals.add_track_to_playlist_signal.emit(self.playlist_id, track_id, note)
|
self.signals.add_track_to_playlist_signal.emit(
|
||||||
|
self.playlist_id, self.new_row_number, track_id, note
|
||||||
|
)
|
||||||
|
|
||||||
def add_selected_and_close(self) -> None:
|
def add_selected_and_close(self) -> None:
|
||||||
"""Handle Add and Close button"""
|
"""Handle Add and Close button"""
|
||||||
|
|||||||
@ -3,15 +3,12 @@ from enum import auto, Enum
|
|||||||
from sqlalchemy import bindparam, update
|
from sqlalchemy import bindparam, update
|
||||||
from typing import List, Optional, TYPE_CHECKING
|
from typing import List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from dbconfig import scoped_session, Session
|
|
||||||
|
|
||||||
from PyQt6.QtCore import (
|
from PyQt6.QtCore import (
|
||||||
QAbstractTableModel,
|
QAbstractTableModel,
|
||||||
QModelIndex,
|
QModelIndex,
|
||||||
Qt,
|
Qt,
|
||||||
QVariant,
|
QVariant,
|
||||||
)
|
)
|
||||||
|
|
||||||
from PyQt6.QtGui import (
|
from PyQt6.QtGui import (
|
||||||
QBrush,
|
QBrush,
|
||||||
QColor,
|
QColor,
|
||||||
@ -19,14 +16,15 @@ from PyQt6.QtGui import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
from playlists import PlaylistTab
|
from datastructures import MusicMusterSignals
|
||||||
|
from dbconfig import scoped_session, Session
|
||||||
from helpers import (
|
from helpers import (
|
||||||
file_is_unreadable,
|
file_is_unreadable,
|
||||||
)
|
)
|
||||||
from models import PlaylistRows, Tracks
|
from models import PlaylistRows, Tracks
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from musicmuster import MusicMusterSignals
|
HEADER_NOTES_COLUMN = 1
|
||||||
|
|
||||||
|
|
||||||
class Col(Enum):
|
class Col(Enum):
|
||||||
@ -41,9 +39,6 @@ class Col(Enum):
|
|||||||
NOTE = auto()
|
NOTE = auto()
|
||||||
|
|
||||||
|
|
||||||
HEADER_NOTES_COLUMN = 1
|
|
||||||
|
|
||||||
|
|
||||||
class PlaylistRowData:
|
class PlaylistRowData:
|
||||||
def __init__(self, plr: PlaylistRows) -> None:
|
def __init__(self, plr: PlaylistRows) -> None:
|
||||||
"""
|
"""
|
||||||
@ -98,18 +93,15 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
playlist: PlaylistTab,
|
|
||||||
playlist_id: int,
|
playlist_id: int,
|
||||||
signals: "MusicMusterSignals",
|
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
self.playlist = playlist
|
|
||||||
self.playlist_id = playlist_id
|
self.playlist_id = playlist_id
|
||||||
self.signals = signals
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.playlist_rows: dict[int, PlaylistRowData] = {}
|
self.playlist_rows: dict[int, PlaylistRowData] = {}
|
||||||
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
self.signals.add_track_to_playlist_signal.connect(self.add_track)
|
self.signals.add_track_to_playlist_signal.connect(self.add_track)
|
||||||
|
|
||||||
@ -122,7 +114,11 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def add_track(
|
def add_track(
|
||||||
self, playlist_id: int, track: Optional[Tracks], note: Optional[str]
|
self,
|
||||||
|
playlist_id: int,
|
||||||
|
new_row_number: int,
|
||||||
|
track_id: Optional[int],
|
||||||
|
note: Optional[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Add track if it's for our playlist
|
Add track if it's for our playlist
|
||||||
@ -132,14 +128,12 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
if playlist_id != self.playlist_id:
|
if playlist_id != self.playlist_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
row_number = self.playlist.get_selected_row_number()
|
|
||||||
|
|
||||||
# Insert track if we have one
|
# Insert track if we have one
|
||||||
if track:
|
if track_id:
|
||||||
self.insert_track_row(row_number, track, note)
|
self.insert_track_row(new_row_number, track_id, note)
|
||||||
# If we only have a note, add as a header row
|
# If we only have a note, add as a header row
|
||||||
elif note:
|
elif note:
|
||||||
self.insert_header_row(row_number, note)
|
self.insert_header_row(new_row_number, note)
|
||||||
else:
|
else:
|
||||||
# No track, no note, no point
|
# No track, no note, no point
|
||||||
return
|
return
|
||||||
@ -366,16 +360,8 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
else:
|
else:
|
||||||
new_row_number = row_number
|
new_row_number = row_number
|
||||||
|
|
||||||
# Move rows below new row down
|
|
||||||
stmt = (
|
|
||||||
update(PlaylistRows)
|
|
||||||
.where(PlaylistRows.plr_rownum >= new_row_number)
|
|
||||||
.values({PlaylistRows.plr_rownum: PlaylistRows.plr_rownum + 1})
|
|
||||||
)
|
|
||||||
session.execute(stmt)
|
|
||||||
|
|
||||||
# Insert the new row and return it
|
# Insert the new row and return it
|
||||||
return PlaylistRows(session, self.playlist_id, new_row_number)
|
return PlaylistRows.insert_row(session, self.playlist_id, new_row_number)
|
||||||
|
|
||||||
def insert_track_row(
|
def insert_track_row(
|
||||||
self, row_number: Optional[int], track_id: int, text: Optional[str]
|
self, row_number: Optional[int], track_id: int, text: Optional[str]
|
||||||
@ -452,20 +438,8 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
plrid = self.playlist_rows[oldrow].plrid
|
plrid = self.playlist_rows[oldrow].plrid
|
||||||
sqla_map.append({"plrid": plrid, "plr_rownum": newrow})
|
sqla_map.append({"plrid": plrid, "plr_rownum": newrow})
|
||||||
|
|
||||||
# Update database. Ref:
|
|
||||||
# https://docs.sqlalchemy.org/en/20/tutorial/data_update.html#the-update-sql-expression-construct
|
|
||||||
stmt = (
|
|
||||||
update(PlaylistRows)
|
|
||||||
.where(
|
|
||||||
PlaylistRows.playlist_id == self.playlist_id,
|
|
||||||
PlaylistRows.id == bindparam("plrid"),
|
|
||||||
)
|
|
||||||
.values(plr_rownum=bindparam("plr_rownum"))
|
|
||||||
)
|
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
session.connection().execute(stmt, sqla_map)
|
PlaylistRows.update_plr_rownumbers(session, self.playlist_id, sqla_map)
|
||||||
|
|
||||||
# Update playlist_rows
|
# Update playlist_rows
|
||||||
self.refresh_data(session)
|
self.refresh_data(session)
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import threading
|
|||||||
|
|
||||||
import obsws_python as obs # type: ignore
|
import obsws_python as obs # type: ignore
|
||||||
|
|
||||||
# from collections import namedtuple
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, cast, List, Optional, Tuple, TYPE_CHECKING
|
from typing import Any, cast, List, Optional, Tuple, TYPE_CHECKING
|
||||||
|
|
||||||
@ -36,8 +35,9 @@ from PyQt6.QtWidgets import (
|
|||||||
QStyleOption,
|
QStyleOption,
|
||||||
)
|
)
|
||||||
|
|
||||||
from config import Config
|
from datastructures import MusicMusterSignals
|
||||||
from dbconfig import Session, scoped_session
|
from dbconfig import Session, scoped_session
|
||||||
|
from config import Config
|
||||||
from helpers import (
|
from helpers import (
|
||||||
ask_yes_no,
|
ask_yes_no,
|
||||||
file_is_unreadable,
|
file_is_unreadable,
|
||||||
@ -48,10 +48,8 @@ from helpers import (
|
|||||||
set_track_metadata,
|
set_track_metadata,
|
||||||
)
|
)
|
||||||
from log import log
|
from log import log
|
||||||
|
|
||||||
from models import Playlists, PlaylistRows, Settings, Tracks, NoteColours
|
from models import Playlists, PlaylistRows, Settings, Tracks, NoteColours
|
||||||
|
from playlistmodel import PlaylistModel
|
||||||
import playlistmodel
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from musicmuster import Window, MusicMusterSignals
|
from musicmuster import Window, MusicMusterSignals
|
||||||
@ -66,9 +64,9 @@ class EscapeDelegate(QStyledItemDelegate):
|
|||||||
- checks with user before abandoning edit on Escape
|
- checks with user before abandoning edit on Escape
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent, signals: "MusicMusterSignals") -> None:
|
def __init__(self, parent) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.signals = signals
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
def createEditor(
|
def createEditor(
|
||||||
self,
|
self,
|
||||||
@ -80,7 +78,7 @@ class EscapeDelegate(QStyledItemDelegate):
|
|||||||
Intercept createEditor call and make row just a little bit taller
|
Intercept createEditor call and make row just a little bit taller
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.signals.enable_escape_signal.emit(False)
|
signals.enable_escape_signal.emit(False)
|
||||||
if isinstance(self.parent(), PlaylistTab):
|
if isinstance(self.parent(), PlaylistTab):
|
||||||
p = cast(PlaylistTab, self.parent())
|
p = cast(PlaylistTab, self.parent())
|
||||||
if isinstance(index.data(), str):
|
if isinstance(index.data(), str):
|
||||||
@ -156,17 +154,15 @@ class PlaylistTab(QTableView):
|
|||||||
self,
|
self,
|
||||||
musicmuster: "Window",
|
musicmuster: "Window",
|
||||||
playlist_id: int,
|
playlist_id: int,
|
||||||
signals: "MusicMusterSignals",
|
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
# Save passed settings
|
# Save passed settings
|
||||||
self.musicmuster = musicmuster
|
self.musicmuster = musicmuster
|
||||||
self.playlist_id = playlist_id
|
self.playlist_id = playlist_id
|
||||||
self.signals = signals
|
|
||||||
|
|
||||||
# Set up widget
|
# Set up widget
|
||||||
self.setItemDelegate(EscapeDelegate(self, self.signals))
|
self.setItemDelegate(EscapeDelegate(self))
|
||||||
self.setAlternatingRowColors(True)
|
self.setAlternatingRowColors(True)
|
||||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||||
@ -194,6 +190,7 @@ class PlaylistTab(QTableView):
|
|||||||
h_header.setStretchLastSection(True)
|
h_header.setStretchLastSection(True)
|
||||||
# self.itemSelectionChanged.connect(self._select_event)
|
# self.itemSelectionChanged.connect(self._select_event)
|
||||||
# self.signals.set_next_track_signal.connect(self._reset_next)
|
# self.signals.set_next_track_signal.connect(self._reset_next)
|
||||||
|
self.signals = MusicMusterSignals()
|
||||||
self.signals.span_cells_signal.connect(self._span_cells)
|
self.signals.span_cells_signal.connect(self._span_cells)
|
||||||
|
|
||||||
# Call self.eventFilter() for events
|
# Call self.eventFilter() for events
|
||||||
@ -205,7 +202,7 @@ class PlaylistTab(QTableView):
|
|||||||
# self.edit_cell_type: Optional[int]
|
# self.edit_cell_type: Optional[int]
|
||||||
|
|
||||||
# Load playlist rows
|
# Load playlist rows
|
||||||
self.setModel(playlistmodel.PlaylistModel(self, playlist_id, signals))
|
self.setModel(PlaylistModel(playlist_id))
|
||||||
self._set_column_widths()
|
self._set_column_widths()
|
||||||
|
|
||||||
# kae def __repr__(self) -> str:
|
# kae def __repr__(self) -> str:
|
||||||
|
|||||||
@ -4,13 +4,12 @@ from app.models import (
|
|||||||
from app import playlistmodel
|
from app import playlistmodel
|
||||||
from dbconfig import scoped_session
|
from dbconfig import scoped_session
|
||||||
|
|
||||||
|
|
||||||
def create_model_with_playlist_rows(
|
def create_model_with_playlist_rows(
|
||||||
session: scoped_session, rows: int
|
session: scoped_session, rows: int
|
||||||
) -> "playlistmodel.PlaylistModel":
|
) -> "playlistmodel.PlaylistModel":
|
||||||
playlist = Playlists(session, "test playlist")
|
playlist = Playlists(session, "test playlist")
|
||||||
# Create a model
|
# Create a model
|
||||||
model = playlistmodel.PlaylistModel(playlist.id, None)
|
model = playlistmodel.PlaylistModel(playlist.id)
|
||||||
for row in range(rows):
|
for row in range(rows):
|
||||||
plr = model._insert_row(session, row)
|
plr = model._insert_row(session, row)
|
||||||
newrow = plr.plr_rownum
|
newrow = plr.plr_rownum
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user