Relayout files
Created classes.py and moved common classes to classes.py. Ordered imports.
This commit is contained in:
parent
15ecae54cf
commit
d9ad001c75
176
app/classes.py
Normal file
176
app/classes.py
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from PyQt6.QtCore import pyqtSignal, QObject
|
||||||
|
import numpy as np
|
||||||
|
import pyqtgraph as pg # type: ignore
|
||||||
|
|
||||||
|
from config import Config
|
||||||
|
from dbconfig import scoped_session
|
||||||
|
from models import PlaylistRows
|
||||||
|
import helpers
|
||||||
|
|
||||||
|
|
||||||
|
class FadeCurve:
|
||||||
|
GraphWidget = None
|
||||||
|
|
||||||
|
def __init__(self, track):
|
||||||
|
"""
|
||||||
|
Set up fade graph array
|
||||||
|
"""
|
||||||
|
|
||||||
|
audio = helpers.get_audio_segment(track.path)
|
||||||
|
if not audio:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Start point of curve is Config.FADE_CURVE_MS_BEFORE_FADE
|
||||||
|
# milliseconds before fade starts to silence
|
||||||
|
self.start_ms = max(0, track.fade_at - Config.FADE_CURVE_MS_BEFORE_FADE - 1)
|
||||||
|
self.end_ms = track.silence_at
|
||||||
|
self.audio_segment = audio[self.start_ms : self.end_ms]
|
||||||
|
self.graph_array = np.array(self.audio_segment.get_array_of_samples())
|
||||||
|
|
||||||
|
# Calculate the factor to map milliseconds of track to array
|
||||||
|
self.ms_to_array_factor = len(self.graph_array) / (self.end_ms - self.start_ms)
|
||||||
|
|
||||||
|
self.region = None
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Clear the current graph"""
|
||||||
|
|
||||||
|
if self.GraphWidget:
|
||||||
|
self.GraphWidget.clear()
|
||||||
|
|
||||||
|
def plot(self):
|
||||||
|
self.curve = self.GraphWidget.plot(self.graph_array)
|
||||||
|
self.curve.setPen(Config.FADE_CURVE_FOREGROUND)
|
||||||
|
|
||||||
|
def tick(self, play_time) -> None:
|
||||||
|
"""Update volume fade curve"""
|
||||||
|
|
||||||
|
if not self.GraphWidget:
|
||||||
|
return
|
||||||
|
|
||||||
|
ms_of_graph = play_time - self.start_ms
|
||||||
|
if ms_of_graph < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.region is None:
|
||||||
|
# Create the region now that we're into fade
|
||||||
|
self.region = pg.LinearRegionItem([0, 0], bounds=[0, len(self.graph_array)])
|
||||||
|
self.GraphWidget.addItem(self.region)
|
||||||
|
|
||||||
|
# Update region position
|
||||||
|
self.region.setRegion([0, ms_of_graph * self.ms_to_array_factor])
|
||||||
|
|
||||||
|
|
||||||
|
@helpers.singleton
|
||||||
|
@dataclass
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
add_track_to_header_signal = pyqtSignal(int, int, int)
|
||||||
|
add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
|
||||||
|
enable_escape_signal = pyqtSignal(bool)
|
||||||
|
set_next_track_signal = pyqtSignal(int, int)
|
||||||
|
span_cells_signal = pyqtSignal(int, int, int, int)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistTrack:
|
||||||
|
"""
|
||||||
|
Used to provide a single reference point for specific playlist tracks,
|
||||||
|
typically the previous, current and next track.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""
|
||||||
|
Only initialises data structure. Call set_plr to populate.
|
||||||
|
|
||||||
|
Do NOT store row_number here - that changes if tracks are reordered
|
||||||
|
in playlist (add, remove, drag/drop) and we shouldn't care about row
|
||||||
|
number: that's the playlist's problem.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.artist: Optional[str] = None
|
||||||
|
self.duration: Optional[int] = None
|
||||||
|
self.end_time: Optional[datetime] = None
|
||||||
|
self.fade_at: Optional[int] = None
|
||||||
|
self.fade_curve: Optional[FadeCurve] = None
|
||||||
|
self.fade_length: Optional[int] = None
|
||||||
|
self.path: Optional[str] = None
|
||||||
|
self.playlist_id: Optional[int] = None
|
||||||
|
self.plr_id: Optional[int] = None
|
||||||
|
self.silence_at: Optional[int] = None
|
||||||
|
self.start_gap: Optional[int] = None
|
||||||
|
self.start_time: Optional[datetime] = None
|
||||||
|
self.title: Optional[str] = None
|
||||||
|
self.track_id: Optional[int] = None
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"<PlaylistTrack(title={self.title}, artist={self.artist}, "
|
||||||
|
f"playlist_id={self.playlist_id}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_plr(self, session: scoped_session, plr: PlaylistRows) -> None:
|
||||||
|
"""
|
||||||
|
Update with new plr information
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not plr.track:
|
||||||
|
return
|
||||||
|
|
||||||
|
session.add(plr)
|
||||||
|
track = plr.track
|
||||||
|
|
||||||
|
self.artist = track.artist
|
||||||
|
self.duration = track.duration
|
||||||
|
self.end_time = None
|
||||||
|
self.fade_at = track.fade_at
|
||||||
|
self.fade_graph = FadeCurve(track)
|
||||||
|
self.path = track.path
|
||||||
|
self.playlist_id = plr.playlist_id
|
||||||
|
self.plr_id = plr.id
|
||||||
|
self.silence_at = track.silence_at
|
||||||
|
self.start_gap = track.start_gap
|
||||||
|
self.start_time = None
|
||||||
|
self.title = track.title
|
||||||
|
self.track_id = track.id
|
||||||
|
|
||||||
|
if track.silence_at and track.fade_at:
|
||||||
|
self.fade_length = track.silence_at - track.fade_at
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
"""
|
||||||
|
Called when track starts playing
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.start_time = datetime.now()
|
||||||
|
if self.duration:
|
||||||
|
self.end_time = self.start_time + timedelta(milliseconds=self.duration)
|
||||||
|
|
||||||
|
|
||||||
|
@helpers.singleton
|
||||||
|
class CurrentTrack(PlaylistTrack):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@helpers.singleton
|
||||||
|
class NextTrack(PlaylistTrack):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@helpers.singleton
|
||||||
|
class PreviousTrack(PlaylistTrack):
|
||||||
|
pass
|
||||||
@ -1,14 +1,14 @@
|
|||||||
from PyQt6.QtCore import QEvent, Qt
|
|
||||||
from PyQt6.QtWidgets import QDialog, QListWidgetItem
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from PyQt6.QtCore import QEvent, Qt
|
||||||
|
from PyQt6.QtWidgets import QDialog, QListWidgetItem
|
||||||
|
|
||||||
|
from classes import MusicMusterSignals
|
||||||
|
from dbconfig import scoped_session
|
||||||
from helpers import (
|
from helpers import (
|
||||||
get_relative_date,
|
get_relative_date,
|
||||||
ms_to_mmss,
|
ms_to_mmss,
|
||||||
MusicMusterSignals,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from dbconfig import scoped_session
|
|
||||||
from models import Settings, Tracks
|
from models import Settings, Tracks
|
||||||
from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
|
from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from email.message import EmailMessage
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import psutil
|
import psutil
|
||||||
@ -6,52 +9,19 @@ import smtplib
|
|||||||
import ssl
|
import ssl
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from config import Config
|
|
||||||
from datetime import datetime
|
|
||||||
from email.message import EmailMessage
|
|
||||||
from log import log
|
|
||||||
from mutagen.flac import FLAC # type: ignore
|
from mutagen.flac import FLAC # type: ignore
|
||||||
from mutagen.mp3 import MP3 # type: ignore
|
from mutagen.mp3 import MP3 # type: ignore
|
||||||
from pydub import AudioSegment, effects
|
from pydub import AudioSegment, effects
|
||||||
from pydub.utils import mediainfo
|
from pydub.utils import mediainfo
|
||||||
from PyQt6.QtCore import pyqtSignal, QObject
|
|
||||||
from PyQt6.QtWidgets import QMainWindow, QMessageBox # type: ignore
|
from PyQt6.QtWidgets import QMainWindow, QMessageBox # type: ignore
|
||||||
from tinytag import TinyTag # type: ignore
|
from tinytag import TinyTag # type: ignore
|
||||||
from typing import Any, Dict, Optional
|
|
||||||
|
|
||||||
|
from config import Config
|
||||||
|
from log import log
|
||||||
|
|
||||||
def singleton(cls):
|
# Classes are defined after global functions so that classes can use
|
||||||
"""
|
# those functions.
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@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
|
|
||||||
"""
|
|
||||||
|
|
||||||
add_track_to_header_signal = pyqtSignal(int, int, int)
|
|
||||||
add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
|
|
||||||
enable_escape_signal = pyqtSignal(bool)
|
|
||||||
set_next_track_signal = pyqtSignal(int, int)
|
|
||||||
span_cells_signal = pyqtSignal(int, int, int, int)
|
|
||||||
|
|
||||||
def ask_yes_no(title: str, question: str, default_yes: bool = False) -> bool:
|
def ask_yes_no(title: str, question: str, default_yes: bool = False) -> bool:
|
||||||
"""Ask question; return True for yes, False for no"""
|
"""Ask question; return True for yes, False for no"""
|
||||||
@ -414,6 +384,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,
|
||||||
|
|||||||
@ -1,18 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from log import log
|
|
||||||
from os.path import basename
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import numpy as np
|
|
||||||
import pyqtgraph as pg # type: ignore
|
|
||||||
import stackprinter # type: ignore
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pygame import mixer
|
from os.path import basename
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import (
|
from typing import (
|
||||||
cast,
|
cast,
|
||||||
@ -20,18 +9,20 @@ from typing import (
|
|||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
)
|
)
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
|
||||||
from playlistmodel import PlaylistModel
|
from pygame import mixer
|
||||||
|
|
||||||
from sqlalchemy import text
|
|
||||||
|
|
||||||
from PyQt6.QtCore import (
|
from PyQt6.QtCore import (
|
||||||
pyqtSignal,
|
pyqtSignal,
|
||||||
QDate,
|
QDate,
|
||||||
QEvent,
|
QEvent,
|
||||||
QObject,
|
QObject,
|
||||||
Qt,
|
|
||||||
QSize,
|
QSize,
|
||||||
|
Qt,
|
||||||
QThread,
|
QThread,
|
||||||
QTime,
|
QTime,
|
||||||
QTimer,
|
QTimer,
|
||||||
@ -54,27 +45,39 @@ from PyQt6.QtWidgets import (
|
|||||||
QListWidgetItem,
|
QListWidgetItem,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QPushButton,
|
|
||||||
QProgressBar,
|
QProgressBar,
|
||||||
|
QPushButton,
|
||||||
)
|
)
|
||||||
|
from sqlalchemy import text
|
||||||
|
import stackprinter # type: ignore
|
||||||
|
|
||||||
|
from classes import (
|
||||||
|
CurrentTrack,
|
||||||
|
FadeCurve,
|
||||||
|
MusicMusterSignals,
|
||||||
|
NextTrack,
|
||||||
|
PlaylistTrack,
|
||||||
|
PreviousTrack,
|
||||||
|
)
|
||||||
|
from config import Config
|
||||||
from dbconfig import (
|
from dbconfig import (
|
||||||
engine,
|
engine,
|
||||||
Session,
|
|
||||||
scoped_session,
|
scoped_session,
|
||||||
|
Session,
|
||||||
)
|
)
|
||||||
import helpers
|
|
||||||
import icons_rc # noqa F401
|
|
||||||
import music
|
|
||||||
from dialogs import TrackSelectDialog
|
from dialogs import TrackSelectDialog
|
||||||
|
from log import log
|
||||||
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 playlistmodel import PlaylistModel
|
||||||
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_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
|
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
|
||||||
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
|
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
|
||||||
from ui.main_window_ui import Ui_MainWindow # type: ignore
|
from ui.main_window_ui import Ui_MainWindow # type: ignore
|
||||||
from utilities import check_db, update_bitrates
|
from utilities import check_db, update_bitrates
|
||||||
|
import helpers
|
||||||
|
import icons_rc # noqa F401
|
||||||
|
import music
|
||||||
|
|
||||||
|
|
||||||
class CartButton(QPushButton):
|
class CartButton(QPushButton):
|
||||||
@ -144,59 +147,6 @@ class CartButton(QPushButton):
|
|||||||
self.pgb.setGeometry(0, 0, self.width(), 10)
|
self.pgb.setGeometry(0, 0, self.width(), 10)
|
||||||
|
|
||||||
|
|
||||||
class FadeCurve:
|
|
||||||
GraphWidget = None
|
|
||||||
|
|
||||||
def __init__(self, track):
|
|
||||||
"""
|
|
||||||
Set up fade graph array
|
|
||||||
"""
|
|
||||||
|
|
||||||
audio = helpers.get_audio_segment(track.path)
|
|
||||||
if not audio:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Start point of curve is Config.FADE_CURVE_MS_BEFORE_FADE
|
|
||||||
# milliseconds before fade starts to silence
|
|
||||||
self.start_ms = max(0, track.fade_at - Config.FADE_CURVE_MS_BEFORE_FADE - 1)
|
|
||||||
self.end_ms = track.silence_at
|
|
||||||
self.audio_segment = audio[self.start_ms : self.end_ms]
|
|
||||||
self.graph_array = np.array(self.audio_segment.get_array_of_samples())
|
|
||||||
|
|
||||||
# Calculate the factor to map milliseconds of track to array
|
|
||||||
self.ms_to_array_factor = len(self.graph_array) / (self.end_ms - self.start_ms)
|
|
||||||
|
|
||||||
self.region = None
|
|
||||||
|
|
||||||
def clear(self) -> None:
|
|
||||||
"""Clear the current graph"""
|
|
||||||
|
|
||||||
if self.GraphWidget:
|
|
||||||
self.GraphWidget.clear()
|
|
||||||
|
|
||||||
def plot(self):
|
|
||||||
self.curve = self.GraphWidget.plot(self.graph_array)
|
|
||||||
self.curve.setPen(Config.FADE_CURVE_FOREGROUND)
|
|
||||||
|
|
||||||
def tick(self, play_time) -> None:
|
|
||||||
"""Update volume fade curve"""
|
|
||||||
|
|
||||||
if not self.GraphWidget:
|
|
||||||
return
|
|
||||||
|
|
||||||
ms_of_graph = play_time - self.start_ms
|
|
||||||
if ms_of_graph < 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.region is None:
|
|
||||||
# Create the region now that we're into fade
|
|
||||||
self.region = pg.LinearRegionItem([0, 0], bounds=[0, len(self.graph_array)])
|
|
||||||
self.GraphWidget.addItem(self.region)
|
|
||||||
|
|
||||||
# Update region position
|
|
||||||
self.region.setRegion([0, ms_of_graph * self.ms_to_array_factor])
|
|
||||||
|
|
||||||
|
|
||||||
class ImportTrack(QObject):
|
class ImportTrack(QObject):
|
||||||
import_error = pyqtSignal(str)
|
import_error = pyqtSignal(str)
|
||||||
importing = pyqtSignal(str)
|
importing = pyqtSignal(str)
|
||||||
@ -237,84 +187,6 @@ class ImportTrack(QObject):
|
|||||||
self.finished.emit(self.playlist)
|
self.finished.emit(self.playlist)
|
||||||
|
|
||||||
|
|
||||||
class PlaylistTrack:
|
|
||||||
"""
|
|
||||||
Used to provide a single reference point for specific playlist tracks,
|
|
||||||
typically the previous, current and next track.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
"""
|
|
||||||
Only initialises data structure. Call set_plr to populate.
|
|
||||||
|
|
||||||
Do NOT store row_number here - that changes if tracks are reordered
|
|
||||||
in playlist (add, remove, drag/drop) and we shouldn't care about row
|
|
||||||
number: that's the playlist's problem.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.artist: Optional[str] = None
|
|
||||||
self.duration: Optional[int] = None
|
|
||||||
self.end_time: Optional[datetime] = None
|
|
||||||
self.fade_at: Optional[int] = None
|
|
||||||
self.fade_curve: Optional[FadeCurve] = None
|
|
||||||
self.fade_length: Optional[int] = None
|
|
||||||
self.path: Optional[str] = None
|
|
||||||
self.playlist_id: Optional[int] = None
|
|
||||||
self.playlist_tab: Optional[PlaylistTab] = None
|
|
||||||
self.plr_id: Optional[int] = None
|
|
||||||
self.silence_at: Optional[int] = None
|
|
||||||
self.start_gap: Optional[int] = None
|
|
||||||
self.start_time: Optional[datetime] = None
|
|
||||||
self.title: Optional[str] = None
|
|
||||||
self.track_id: Optional[int] = None
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return (
|
|
||||||
f"<PlaylistTrack(title={self.title}, artist={self.artist}, "
|
|
||||||
f"playlist_id={self.playlist_id}>"
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_plr(
|
|
||||||
self, session: scoped_session, plr: PlaylistRows, tab: PlaylistTab
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Update with new plr information
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not plr.track:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.playlist_tab = tab
|
|
||||||
session.add(plr)
|
|
||||||
track = plr.track
|
|
||||||
|
|
||||||
self.artist = track.artist
|
|
||||||
self.duration = track.duration
|
|
||||||
self.end_time = None
|
|
||||||
self.fade_at = track.fade_at
|
|
||||||
self.fade_graph = FadeCurve(track)
|
|
||||||
self.path = track.path
|
|
||||||
self.playlist_id = plr.playlist_id
|
|
||||||
self.plr_id = plr.id
|
|
||||||
self.silence_at = track.silence_at
|
|
||||||
self.start_gap = track.start_gap
|
|
||||||
self.start_time = None
|
|
||||||
self.title = track.title
|
|
||||||
self.track_id = track.id
|
|
||||||
|
|
||||||
if track.silence_at and track.fade_at:
|
|
||||||
self.fade_length = track.silence_at - track.fade_at
|
|
||||||
|
|
||||||
def start(self) -> None:
|
|
||||||
"""
|
|
||||||
Called when track starts playing
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.start_time = datetime.now()
|
|
||||||
if self.duration:
|
|
||||||
self.end_time = self.start_time + timedelta(milliseconds=self.duration)
|
|
||||||
|
|
||||||
|
|
||||||
class Window(QMainWindow, Ui_MainWindow):
|
class Window(QMainWindow, Ui_MainWindow):
|
||||||
def __init__(self, parent=None, *args, **kwargs) -> None:
|
def __init__(self, parent=None, *args, **kwargs) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -327,9 +199,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.music: music.Music = music.Music()
|
self.music: music.Music = music.Music()
|
||||||
self.playing: bool = False
|
self.playing: bool = False
|
||||||
|
|
||||||
self.current_track = PlaylistTrack()
|
self.current_track = CurrentTrack()
|
||||||
self.next_track = PlaylistTrack()
|
self.next_track = NextTrack()
|
||||||
self.previous_track = PlaylistTrack()
|
self.previous_track = PreviousTrack()
|
||||||
|
|
||||||
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
|
||||||
@ -362,7 +234,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 = helpers.MusicMusterSignals()
|
self.signals = MusicMusterSignals()
|
||||||
self.connect_signals_slots()
|
self.connect_signals_slots()
|
||||||
|
|
||||||
def about(self) -> None:
|
def about(self) -> None:
|
||||||
@ -521,7 +393,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
Clear next track
|
Clear next track
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.next_track = PlaylistTrack()
|
self.next_track = NextTrack()
|
||||||
self.update_headers()
|
self.update_headers()
|
||||||
|
|
||||||
def clear_selection(self) -> None:
|
def clear_selection(self) -> None:
|
||||||
@ -598,27 +470,29 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
Return True if tab closed else False.
|
Return True if tab closed else False.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Don't close current track playlist
|
|
||||||
if self.tabPlaylist.widget(tab_index) == (self.current_track.playlist_tab):
|
|
||||||
self.statusbar.showMessage("Can't close current track playlist", 5000)
|
|
||||||
return False
|
return False
|
||||||
|
# TODO Reimplement without ussing self.current_track.playlist_tab
|
||||||
|
# # Don't close current track playlist
|
||||||
|
# if self.tabPlaylist.widget(tab_index) == (self.current_track.playlist_tab):
|
||||||
|
# self.statusbar.showMessage("Can't close current track playlist", 5000)
|
||||||
|
# return False
|
||||||
|
|
||||||
# Attempt to close next track playlist
|
# # Attempt to close next track playlist
|
||||||
if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab:
|
# if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab:
|
||||||
self.next_track.playlist_tab.clear_next()
|
# self.next_track.playlist_tab.clear_next()
|
||||||
|
|
||||||
# Record playlist as closed and update remaining playlist tabs
|
# # Record playlist as closed and update remaining playlist tabs
|
||||||
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:
|
# 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()
|
||||||
self.tabPlaylist.removeTab(tab_index)
|
# self.tabPlaylist.removeTab(tab_index)
|
||||||
|
|
||||||
return True
|
# return True
|
||||||
|
|
||||||
def connect_signals_slots(self) -> None:
|
def connect_signals_slots(self) -> None:
|
||||||
self.action_About.triggered.connect(self.about)
|
self.action_About.triggered.connect(self.about)
|
||||||
@ -834,16 +708,17 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.playing = False
|
self.playing = False
|
||||||
|
|
||||||
# Tell playlist_tab track has finished
|
# Tell playlist_tab track has finished
|
||||||
if self.current_track.playlist_tab:
|
# TODO Reimplement as a signal
|
||||||
self.current_track.playlist_tab.play_ended()
|
# if self.current_track.playlist_tab:
|
||||||
|
# self.current_track.playlist_tab.play_ended()
|
||||||
|
|
||||||
# Reset fade graph
|
# Reset fade graph
|
||||||
self.current_track.fade_graph.clear()
|
self.current_track.fade_graph.clear()
|
||||||
|
|
||||||
# Reset PlaylistTrack objects
|
# Reset PlaylistTrack objects
|
||||||
if self.current_track.track_id:
|
if self.current_track.track_id:
|
||||||
self.previous_track = self.current_track
|
self.previous_track = cast(PreviousTrack, self.current_track)
|
||||||
self.current_track = PlaylistTrack()
|
self.current_track = CurrentTrack()
|
||||||
|
|
||||||
# Reset clocks
|
# Reset clocks
|
||||||
self.frame_fade.setStyleSheet("")
|
self.frame_fade.setStyleSheet("")
|
||||||
@ -907,18 +782,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
self.stop_playing(fade=True)
|
self.stop_playing(fade=True)
|
||||||
|
|
||||||
def get_one_track(self, session: scoped_session) -> Optional[Tracks]:
|
|
||||||
"""Show dialog box to select one track and return it to caller"""
|
|
||||||
|
|
||||||
dlg = TrackSelectDialog(self, session)
|
|
||||||
dlg.ui.txtNote.hide()
|
|
||||||
dlg.ui.lblNote.hide()
|
|
||||||
|
|
||||||
if dlg.exec():
|
|
||||||
return dlg.track
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_playtime(self) -> int:
|
def get_playtime(self) -> int:
|
||||||
"""
|
"""
|
||||||
Return number of milliseconds current track has been playing or
|
Return number of milliseconds current track has been playing or
|
||||||
@ -1013,7 +876,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.worker.finished.connect(self.import_complete)
|
self.worker.finished.connect(self.import_complete)
|
||||||
self.import_thread.start()
|
self.import_thread.start()
|
||||||
|
|
||||||
def import_complete(self, playlist_tab: PlaylistTab):
|
def import_complete(self):
|
||||||
"""
|
"""
|
||||||
Called by thread when track import complete
|
Called by thread when track import complete
|
||||||
"""
|
"""
|
||||||
@ -1302,7 +1165,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# Move next track to current track.
|
# Move next track to current track.
|
||||||
# stop_playing() above has called end_of_track_actions()
|
# stop_playing() above has called end_of_track_actions()
|
||||||
# which will have populated self.previous_track
|
# which will have populated self.previous_track
|
||||||
self.current_track = self.next_track
|
self.current_track = cast(CurrentTrack, self.next_track)
|
||||||
self.clear_next()
|
self.clear_next()
|
||||||
|
|
||||||
if not self.current_track.track_id:
|
if not self.current_track.track_id:
|
||||||
@ -1313,9 +1176,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Set current track playlist_tab colour
|
# Set current track playlist_tab colour
|
||||||
current_tab = self.current_track.playlist_tab
|
# TODO Reimplement without reference to self.current_track.playlist_tab
|
||||||
if current_tab:
|
# current_tab = self.current_track.playlist_tab
|
||||||
self.set_tab_colour(current_tab, QColor(Config.COLOUR_CURRENT_TAB))
|
# if current_tab:
|
||||||
|
# self.set_tab_colour(current_tab, QColor(Config.COLOUR_CURRENT_TAB))
|
||||||
|
|
||||||
# Restore volume if -3dB active
|
# Restore volume if -3dB active
|
||||||
if self.btnDrop3db.isChecked():
|
if self.btnDrop3db.isChecked():
|
||||||
@ -1344,8 +1208,9 @@ 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:
|
# TODO Reimplement without reference to self.current_track.playlist_tab:
|
||||||
self.current_track.playlist_tab.play_started(session)
|
# if self.current_track.playlist_tab:
|
||||||
|
# self.current_track.playlist_tab.play_started(session)
|
||||||
|
|
||||||
# Note that track is now playing
|
# Note that track is now playing
|
||||||
self.playing = True
|
self.playing = True
|
||||||
@ -1404,41 +1269,45 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
- If a track is playing, make that the next track
|
- If a track is playing, make that the next track
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Return if no saved position
|
|
||||||
if not self.previous_track_position:
|
|
||||||
return
|
return
|
||||||
|
# TODO Reimplement without reference to playlist_tab
|
||||||
|
# # Return if no saved position
|
||||||
|
# if not self.previous_track_position:
|
||||||
|
# return
|
||||||
|
|
||||||
# Note any playing track as this will become the next track
|
# # Note any playing track as this will become the next track
|
||||||
playing_track = None
|
# playing_track = None
|
||||||
if self.current_track.track_id:
|
# if self.current_track.track_id:
|
||||||
playing_track = self.current_track
|
# playing_track = self.current_track
|
||||||
|
|
||||||
# Set next plr to be track to resume
|
# # Set next plr to be track to resume
|
||||||
if not self.previous_track.plr_id:
|
# if not self.previous_track.plr_id:
|
||||||
return
|
# return
|
||||||
if not self.previous_track.playlist_tab:
|
# # TODO Reimplement following two lines
|
||||||
return
|
# # if not self.previous_track.playlist_tab:
|
||||||
|
# # return
|
||||||
|
|
||||||
# Resume last track
|
# # Resume last track
|
||||||
self.set_next_plr_id(
|
# # TODO Reimplement next four lines
|
||||||
self.previous_track.plr_id, self.previous_track.playlist_tab
|
# # self.set_next_plr_id(
|
||||||
)
|
# # self.previous_track.plr_id, self.previous_track.playlist_tab
|
||||||
self.play_next(self.previous_track_position)
|
# # )
|
||||||
|
# # self.play_next(self.previous_track_position)
|
||||||
|
|
||||||
# Adjust track info so that clocks and graph are correct.
|
# # Adjust track info so that clocks and graph are correct.
|
||||||
# Easiest way is to fake the start time.
|
# # Easiest way is to fake the start time.
|
||||||
if self.current_track.start_time and self.current_track.duration:
|
# if self.current_track.start_time and self.current_track.duration:
|
||||||
elapsed_ms = self.current_track.duration * self.previous_track_position
|
# elapsed_ms = self.current_track.duration * self.previous_track_position
|
||||||
self.current_track.start_time -= timedelta(milliseconds=elapsed_ms)
|
# self.current_track.start_time -= timedelta(milliseconds=elapsed_ms)
|
||||||
|
|
||||||
# If a track was playing when we were called, get details to
|
# # If a track was playing when we were called, get details to
|
||||||
# set it as the next track
|
# # set it as the next track
|
||||||
if playing_track:
|
# if playing_track:
|
||||||
if not playing_track.plr_id:
|
# if not playing_track.plr_id:
|
||||||
return
|
# return
|
||||||
if not playing_track.playlist_tab:
|
# if not playing_track.playlist_tab:
|
||||||
return
|
# return
|
||||||
self.set_next_plr_id(playing_track.plr_id, playing_track.playlist_tab)
|
# self.set_next_plr_id(playing_track.plr_id, playing_track.playlist_tab)
|
||||||
|
|
||||||
def save_as_template(self) -> None:
|
def save_as_template(self) -> None:
|
||||||
"""Save current playlist as template"""
|
"""Save current playlist as template"""
|
||||||
@ -1564,16 +1433,20 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
def show_current(self) -> None:
|
def show_current(self) -> None:
|
||||||
"""Scroll to show current track"""
|
"""Scroll to show current track"""
|
||||||
|
|
||||||
if self.current_track.playlist_tab != self.active_tab():
|
return
|
||||||
self.tabPlaylist.setCurrentWidget(self.current_track.playlist_tab)
|
# TODO Reimplement
|
||||||
self.tabPlaylist.currentWidget().scroll_current_to_top()
|
# if self.current_track.playlist_tab != self.active_tab():
|
||||||
|
# self.tabPlaylist.setCurrentWidget(self.current_track.playlist_tab)
|
||||||
|
# self.tabPlaylist.currentWidget().scroll_current_to_top()
|
||||||
|
|
||||||
def show_next(self) -> None:
|
def show_next(self) -> None:
|
||||||
"""Scroll to show next track"""
|
"""Scroll to show next track"""
|
||||||
|
|
||||||
if self.next_track.playlist_tab != self.active_tab():
|
return
|
||||||
self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab)
|
# TODO Reimplement
|
||||||
self.tabPlaylist.currentWidget().scroll_next_to_top()
|
# if self.next_track.playlist_tab != self.active_tab():
|
||||||
|
# self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab)
|
||||||
|
# self.tabPlaylist.currentWidget().scroll_next_to_top()
|
||||||
|
|
||||||
def solicit_playlist_name(self, default: Optional[str] = "") -> Optional[str]:
|
def solicit_playlist_name(self, default: Optional[str] = "") -> Optional[str]:
|
||||||
"""Get name of playlist from user"""
|
"""Get name of playlist from user"""
|
||||||
@ -1618,15 +1491,16 @@ 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:
|
# TODO Reimplement
|
||||||
if self.current_track.playlist_tab == self.next_track.playlist_tab:
|
# if self.current_track.playlist_tab:
|
||||||
self.set_tab_colour(
|
# if self.current_track.playlist_tab == self.next_track.playlist_tab:
|
||||||
self.current_track.playlist_tab, QColor(Config.COLOUR_NEXT_TAB)
|
# self.set_tab_colour(
|
||||||
)
|
# self.current_track.playlist_tab, QColor(Config.COLOUR_NEXT_TAB)
|
||||||
else:
|
# )
|
||||||
self.set_tab_colour(
|
# else:
|
||||||
self.current_track.playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)
|
# self.set_tab_colour(
|
||||||
)
|
# self.current_track.playlist_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()
|
||||||
@ -1670,11 +1544,11 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
with Session() as session:
|
with Session() as session:
|
||||||
# Update self.next_track PlaylistTrack structure
|
# Update self.next_track PlaylistTrack structure
|
||||||
old_next_track = self.next_track
|
old_next_track = self.next_track
|
||||||
self.next_track = PlaylistTrack()
|
self.next_track = NextTrack()
|
||||||
if next_plr_id:
|
if next_plr_id:
|
||||||
next_plr = session.get(PlaylistRows, next_plr_id)
|
next_plr = session.get(PlaylistRows, next_plr_id)
|
||||||
if next_plr:
|
if next_plr:
|
||||||
self.next_track.set_plr(session, next_plr, playlist_tab)
|
self.next_track.set_plr(session, next_plr)
|
||||||
|
|
||||||
# Tell playlist tabs to update their 'next track' highlighting
|
# Tell playlist tabs to update their 'next track' highlighting
|
||||||
# Args must both be ints, so use zero for no next track
|
# Args must both be ints, so use zero for no next track
|
||||||
@ -1709,30 +1583,32 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
# If the original next playlist tab isn't the same as the
|
# If the original next playlist tab isn't the same as the
|
||||||
# new one or the current track, it needs its colour reset.
|
# new one or the current track, it needs its colour reset.
|
||||||
if (
|
return
|
||||||
old_next_track
|
# TODO Reimplement
|
||||||
and old_next_track.playlist_tab
|
# if (
|
||||||
and old_next_track.playlist_tab
|
# old_next_track
|
||||||
not in [self.next_track.playlist_tab, self.current_track.playlist_tab]
|
# and old_next_track.playlist_tab
|
||||||
):
|
# and old_next_track.playlist_tab
|
||||||
self.set_tab_colour(
|
# not in [self.next_track.playlist_tab, self.current_track.playlist_tab]
|
||||||
old_next_track.playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)
|
# ):
|
||||||
)
|
# self.set_tab_colour(
|
||||||
# If the new next playlist tab isn't the same as the
|
# old_next_track.playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)
|
||||||
# old one or the current track, it needs its colour set.
|
# )
|
||||||
if old_next_track:
|
# # If the new next playlist tab isn't the same as the
|
||||||
old_tab = old_next_track.playlist_tab
|
# # old one or the current track, it needs its colour set.
|
||||||
else:
|
# if old_next_track:
|
||||||
old_tab = None
|
# old_tab = old_next_track.playlist_tab
|
||||||
if (
|
# else:
|
||||||
self.next_track
|
# old_tab = None
|
||||||
and self.next_track.playlist_tab
|
# if (
|
||||||
and self.next_track.playlist_tab
|
# self.next_track
|
||||||
not in [old_tab, self.current_track.playlist_tab]
|
# and self.next_track.playlist_tab
|
||||||
):
|
# and self.next_track.playlist_tab
|
||||||
self.set_tab_colour(
|
# not in [old_tab, self.current_track.playlist_tab]
|
||||||
self.next_track.playlist_tab, QColor(Config.COLOUR_NEXT_TAB)
|
# ):
|
||||||
)
|
# self.set_tab_colour(
|
||||||
|
# self.next_track.playlist_tab, QColor(Config.COLOUR_NEXT_TAB)
|
||||||
|
# )
|
||||||
|
|
||||||
def tick_10ms(self) -> None:
|
def tick_10ms(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import auto, Enum
|
from enum import auto, Enum
|
||||||
from sqlalchemy import bindparam, update
|
from typing import List, Optional
|
||||||
from typing import List, Optional, TYPE_CHECKING
|
|
||||||
|
|
||||||
from PyQt6.QtCore import (
|
from PyQt6.QtCore import (
|
||||||
QAbstractTableModel,
|
QAbstractTableModel,
|
||||||
@ -15,12 +14,10 @@ from PyQt6.QtGui import (
|
|||||||
QFont,
|
QFont,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from classes import CurrentTrack, MusicMusterSignals, NextTrack
|
||||||
from config import Config
|
from config import Config
|
||||||
from dbconfig import scoped_session, Session
|
from dbconfig import scoped_session, Session
|
||||||
from helpers import (
|
from helpers import file_is_unreadable
|
||||||
file_is_unreadable,
|
|
||||||
MusicMusterSignals,
|
|
||||||
)
|
|
||||||
from log import log
|
from log import log
|
||||||
from models import PlaylistRows, Tracks
|
from models import PlaylistRows, Tracks
|
||||||
|
|
||||||
|
|||||||
@ -37,13 +37,13 @@ from PyQt6.QtWidgets import (
|
|||||||
|
|
||||||
from dbconfig import Session, scoped_session
|
from dbconfig import Session, scoped_session
|
||||||
from dialogs import TrackSelectDialog
|
from dialogs import TrackSelectDialog
|
||||||
|
from classes import MusicMusterSignals
|
||||||
from config import Config
|
from config import Config
|
||||||
from helpers import (
|
from helpers import (
|
||||||
ask_yes_no,
|
ask_yes_no,
|
||||||
file_is_unreadable,
|
file_is_unreadable,
|
||||||
get_relative_date,
|
get_relative_date,
|
||||||
ms_to_mmss,
|
ms_to_mmss,
|
||||||
MusicMusterSignals,
|
|
||||||
open_in_audacity,
|
open_in_audacity,
|
||||||
send_mail,
|
send_mail,
|
||||||
set_track_metadata,
|
set_track_metadata,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user