From 986257bef6df92c827e6b50a49641013e64b85b3 Mon Sep 17 00:00:00 2001
From: Keith Edmunds
Date: Sun, 9 Jul 2023 16:12:21 +0100
Subject: [PATCH] Flake8 and Black run on all files
---
.flake8 | 13 +
InterceptEscapeWhenEditingTableCell.py | 11 +-
analyse_tracks.py | 2 +-
app/dbconfig.py | 14 +-
app/helpers.py | 125 ++-
app/infotabs.py | 16 +-
app/models.py | 318 ++++---
app/music.py | 13 +-
app/musicmuster.py | 510 +++++------
app/playlists.py | 572 +++++++------
app/replace_files.py | 54 +-
app/utilities.py | 12 +-
conftest.py | 11 +-
devnotes.txt | 1 +
docs/build/doctrees/environment.pickle | Bin 50839 -> 50188 bytes
docs/build/doctrees/introduction.doctree | Bin 17521 -> 17104 bytes
docs/build/html/_sources/introduction.rst.txt | 24 +-
docs/build/html/introduction.html | 24 +-
docs/build/html/searchindex.js | 2 +-
docs/source/introduction.rst | 23 +-
poetry.lock | 802 ++++++++++--------
pyproject.toml | 2 +
test.py | 7 +-
test_helpers.py | 26 +-
test_models.py | 25 +-
test_playlists.py | 18 +-
tree.py | 24 +-
web.py | 1 -
28 files changed, 1359 insertions(+), 1291 deletions(-)
create mode 100644 .flake8
create mode 100644 devnotes.txt
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..2e5812e
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,13 @@
+[flake8]
+max-line-length = 88
+select = C,E,F,W,B,B950
+extend-ignore = E203, E501
+exclude =
+ .git
+ app/ui,
+ __pycache__,
+ archive,
+ migrations,
+ prof,
+ docs,
+ app/icons_rc.py
diff --git a/InterceptEscapeWhenEditingTableCell.py b/InterceptEscapeWhenEditingTableCell.py
index d0e3eda..e9bf50e 100755
--- a/InterceptEscapeWhenEditingTableCell.py
+++ b/InterceptEscapeWhenEditingTableCell.py
@@ -2,14 +2,12 @@
from PyQt6.QtCore import Qt, QEvent, QObject
from PyQt6.QtWidgets import (
- QAbstractItemDelegate,
QAbstractItemView,
QApplication,
QMainWindow,
QMessageBox,
QPlainTextEdit,
QStyledItemDelegate,
- QStyleOptionViewItem,
QTableWidget,
QTableWidgetItem,
)
@@ -33,16 +31,15 @@ class EscapeDelegate(QStyledItemDelegate):
if event.type() == QEvent.Type.KeyPress:
key_event = cast(QKeyEvent, event)
if key_event.key() == Qt.Key.Key_Return:
- if key_event.modifiers() == (
- Qt.KeyboardModifier.ControlModifier
- ):
+ if key_event.modifiers() == (Qt.KeyboardModifier.ControlModifier):
print("save data")
self.commitData.emit(editor)
self.closeEditor.emit(editor)
return True
elif key_event.key() == Qt.Key.Key_Escape:
discard_edits = QMessageBox.question(
- self.parent(), "Abandon edit", "Discard changes?")
+ self.parent(), "Abandon edit", "Discard changes?"
+ )
if discard_edits == QMessageBox.StandardButton.Yes:
print("abandon edit")
self.closeEditor.emit(editor)
@@ -74,7 +71,7 @@ class MainWindow(QMainWindow):
self.table_widget.resizeRowsToContents()
-if __name__ == '__main__':
+if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
diff --git a/analyse_tracks.py b/analyse_tracks.py
index 4a8eb5b..a60516f 100755
--- a/analyse_tracks.py
+++ b/analyse_tracks.py
@@ -2,7 +2,7 @@
import os
-from pydub import AudioSegment, effects
+from pydub import AudioSegment
# DIR = "/home/kae/git/musicmuster/archive"
DIR = "/home/kae/git/musicmuster"
diff --git a/app/dbconfig.py b/app/dbconfig.py
index bfb5db7..8e794fe 100644
--- a/app/dbconfig.py
+++ b/app/dbconfig.py
@@ -1,19 +1,18 @@
import inspect
-import logging
import os
from config import Config
from contextlib import contextmanager
from sqlalchemy import create_engine
-from sqlalchemy.orm import (sessionmaker, scoped_session)
+from sqlalchemy.orm import sessionmaker, scoped_session
from typing import Generator
from log import log
-MYSQL_CONNECT = os.environ.get('MM_DB')
+MYSQL_CONNECT = os.environ.get("MM_DB")
if MYSQL_CONNECT is None:
raise ValueError("MYSQL_CONNECT is undefined")
else:
- dbname = MYSQL_CONNECT.split('/')[-1]
+ dbname = MYSQL_CONNECT.split("/")[-1]
log.debug(f"Database: {dbname}")
# MM_ENV = os.environ.get('MM_ENV', 'PRODUCTION')
@@ -31,10 +30,10 @@ else:
engine = create_engine(
MYSQL_CONNECT,
- encoding='utf-8',
+ encoding="utf-8",
echo=Config.DISPLAY_SQL,
pool_pre_ping=True,
- future=True
+ future=True,
)
@@ -47,8 +46,7 @@ def Session() -> Generator[scoped_session, None, None]:
Session = scoped_session(sessionmaker(bind=engine, future=True))
log.debug(f"SqlA: session acquired [{hex(id(Session))}]")
log.debug(
- f"Session acquisition: {file}:{function}:{lineno} "
- f"[{hex(id(Session))}]"
+ f"Session acquisition: {file}:{function}:{lineno} " f"[{hex(id(Session))}]"
)
yield Session
log.debug(f" SqlA: session released [{hex(id(Session))}]")
diff --git a/app/helpers.py b/app/helpers.py
index a63e49f..b01d2dc 100644
--- a/app/helpers.py
+++ b/app/helpers.py
@@ -1,4 +1,3 @@
-import numpy as np
import os
import psutil
import shutil
@@ -10,13 +9,13 @@ 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.mp3 import MP3 # type: ignore
+from mutagen.flac import FLAC # type: ignore
+from mutagen.mp3 import MP3 # type: ignore
from pydub import AudioSegment, effects
from pydub.utils import mediainfo
-from PyQt6.QtWidgets import QMainWindow, QMessageBox # type: ignore
-from tinytag import TinyTag # type: ignore
-from typing import Any, Dict, Optional, Union
+from PyQt6.QtWidgets import QMainWindow, QMessageBox # type: ignore
+from tinytag import TinyTag # type: ignore
+from typing import Any, Dict, Optional
def ask_yes_no(title: str, question: str, default_yes: bool = False) -> bool:
@@ -26,7 +25,8 @@ def ask_yes_no(title: str, question: str, default_yes: bool = False) -> bool:
dlg.setWindowTitle(title)
dlg.setText(question)
dlg.setStandardButtons(
- QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
+ )
dlg.setIcon(QMessageBox.Icon.Question)
if default_yes:
dlg.setDefaultButton(QMessageBox.StandardButton.Yes)
@@ -36,8 +36,10 @@ def ask_yes_no(title: str, question: str, default_yes: bool = False) -> bool:
def fade_point(
- audio_segment: AudioSegment, fade_threshold: float = 0.0,
- chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE) -> int:
+ audio_segment: AudioSegment,
+ fade_threshold: float = 0.0,
+ chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE,
+) -> int:
"""
Returns the millisecond/index of the point where the volume drops below
the maximum and doesn't get louder again.
@@ -55,8 +57,9 @@ def fade_point(
fade_threshold = max_vol
while (
- audio_segment[trim_ms:trim_ms + chunk_size].dBFS < fade_threshold
- and trim_ms > 0): # noqa W503
+ audio_segment[trim_ms : trim_ms + chunk_size].dBFS < fade_threshold
+ and trim_ms > 0
+ ): # noqa W503
trim_ms -= chunk_size
# if there is no trailing silence, return lenght of track (it's less
@@ -77,10 +80,10 @@ def file_is_unreadable(path: Optional[str]) -> bool:
def get_audio_segment(path: str) -> Optional[AudioSegment]:
try:
- if path.endswith('.mp3'):
+ if path.endswith(".mp3"):
return AudioSegment.from_mp3(path)
- elif path.endswith('.flac'):
- return AudioSegment.from_file(path, "flac") # type: ignore
+ elif path.endswith(".flac"):
+ return AudioSegment.from_file(path, "flac") # type: ignore
except AttributeError:
return None
@@ -99,12 +102,13 @@ def get_tags(path: str) -> Dict[str, Any]:
artist=tag.artist,
bitrate=round(tag.bitrate),
duration=int(round(tag.duration, Config.MILLISECOND_SIGFIGS) * 1000),
- path=path
+ path=path,
)
-def get_relative_date(past_date: Optional[datetime],
- reference_date: Optional[datetime] = None) -> str:
+def get_relative_date(
+ past_date: Optional[datetime], reference_date: Optional[datetime] = None
+) -> str:
"""
Return how long before reference_date past_date is as string.
@@ -145,9 +149,10 @@ def get_relative_date(past_date: Optional[datetime],
def leading_silence(
- audio_segment: AudioSegment,
- silence_threshold: int = Config.DBFS_SILENCE,
- chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE) -> int:
+ audio_segment: AudioSegment,
+ silence_threshold: int = Config.DBFS_SILENCE,
+ chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE,
+) -> int:
"""
Returns the millisecond/index that the leading silence ends.
audio_segment - the segment to find silence in
@@ -159,9 +164,11 @@ def leading_silence(
trim_ms: int = 0 # ms
assert chunk_size > 0 # to avoid infinite loop
- while (
- audio_segment[trim_ms:trim_ms + chunk_size].dBFS < # noqa W504
- silence_threshold and trim_ms < len(audio_segment)):
+ while audio_segment[
+ trim_ms : trim_ms + chunk_size
+ ].dBFS < silence_threshold and trim_ms < len( # noqa W504
+ audio_segment
+ ):
trim_ms += chunk_size
# if there is no end it should return the length of the segment
@@ -175,9 +182,9 @@ def send_mail(to_addr, from_addr, subj, body):
msg = EmailMessage()
msg.set_content(body)
- msg['Subject'] = subj
- msg['From'] = from_addr
- msg['To'] = to_addr
+ msg["Subject"] = subj
+ msg["From"] = from_addr
+ msg["To"] = to_addr
# Send the message via SMTP server.
context = ssl.create_default_context()
@@ -194,8 +201,7 @@ def send_mail(to_addr, from_addr, subj, body):
s.quit()
-def ms_to_mmss(ms: Optional[int], decimals: int = 0,
- negative: bool = False) -> str:
+def ms_to_mmss(ms: Optional[int], decimals: int = 0, negative: bool = False) -> str:
"""Convert milliseconds to mm:ss"""
minutes: int
@@ -227,13 +233,12 @@ def normalise_track(path):
# Check type
ftype = os.path.splitext(path)[1][1:]
- if ftype not in ['mp3', 'flac']:
+ if ftype not in ["mp3", "flac"]:
log.info(
- f"helpers.normalise_track({path}): "
- f"File type {ftype} not implemented"
+ f"helpers.normalise_track({path}): " f"File type {ftype} not implemented"
)
- bitrate = mediainfo(path)['bit_rate']
+ bitrate = mediainfo(path)["bit_rate"]
audio = get_audio_segment(path)
if not audio:
return
@@ -245,23 +250,20 @@ def normalise_track(path):
_, temp_path = tempfile.mkstemp()
shutil.copyfile(path, temp_path)
except Exception as err:
- log.debug(
- f"helpers.normalise_track({path}): err1: {repr(err)}"
- )
+ log.debug(f"helpers.normalise_track({path}): err1: {repr(err)}")
return
# Overwrite original file with normalised output
normalised = effects.normalize(audio)
try:
- normalised.export(path, format=os.path.splitext(path)[1][1:],
- bitrate=bitrate)
+ normalised.export(path, format=os.path.splitext(path)[1][1:], bitrate=bitrate)
# Fix up permssions and ownership
os.chown(path, stats.st_uid, stats.st_gid)
os.chmod(path, stats.st_mode)
# Copy tags
- if ftype == 'flac':
+ if ftype == "flac":
tag_handler = FLAC
- elif ftype == 'mp3':
+ elif ftype == "mp3":
tag_handler = MP3
else:
return
@@ -271,9 +273,7 @@ def normalise_track(path):
dst[tag] = src[tag]
dst.save()
except Exception as err:
- log.debug(
- f"helpers.normalise_track({path}): err2: {repr(err)}"
- )
+ log.debug(f"helpers.normalise_track({path}): err2: {repr(err)}")
# Restore original file
shutil.copyfile(path, temp_path)
finally:
@@ -296,9 +296,9 @@ def open_in_audacity(path: str) -> bool:
if not path:
return False
- to_pipe: str = '/tmp/audacity_script_pipe.to.' + str(os.getuid())
- from_pipe: str = '/tmp/audacity_script_pipe.from.' + str(os.getuid())
- eol: str = '\n'
+ to_pipe: str = "/tmp/audacity_script_pipe.to." + str(os.getuid())
+ from_pipe: str = "/tmp/audacity_script_pipe.from." + str(os.getuid())
+ eol: str = "\n"
def send_command(command: str) -> None:
"""Send a single command."""
@@ -308,13 +308,13 @@ def open_in_audacity(path: str) -> bool:
def get_response() -> str:
"""Return the command response."""
- result: str = ''
- line: str = ''
+ result: str = ""
+ line: str = ""
while True:
result += line
line = from_audacity.readline()
- if line == '\n' and len(result) > 0:
+ if line == "\n" and len(result) > 0:
break
return result
@@ -325,8 +325,7 @@ def open_in_audacity(path: str) -> bool:
response = get_response()
return response
- with open(to_pipe, 'w') as to_audacity, open(
- from_pipe, 'rt') as from_audacity:
+ with open(to_pipe, "w") as to_audacity, open(from_pipe, "rt") as from_audacity:
do_command(f'Import2: Filename="{path}"')
return True
@@ -338,18 +337,18 @@ def set_track_metadata(session, track):
t = get_tags(track.path)
audio = get_audio_segment(track.path)
- track.title = t['title']
- track.artist = t['artist']
- track.bitrate = t['bitrate']
+ track.title = t["title"]
+ track.artist = t["artist"]
+ track.bitrate = t["bitrate"]
if not audio:
return
track.duration = len(audio)
track.start_gap = leading_silence(audio)
- track.fade_at = round(fade_point(audio) / 1000,
- Config.MILLISECOND_SIGFIGS) * 1000
- track.silence_at = round(trailing_silence(audio) / 1000,
- Config.MILLISECOND_SIGFIGS) * 1000
+ track.fade_at = round(fade_point(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000
+ track.silence_at = (
+ round(trailing_silence(audio) / 1000, Config.MILLISECOND_SIGFIGS) * 1000
+ )
track.mtime = os.path.getmtime(track.path)
session.commit()
@@ -358,20 +357,20 @@ def set_track_metadata(session, track):
def show_OK(parent: QMainWindow, title: str, msg: str) -> None:
"""Display a message to user"""
- QMessageBox.information(parent, title, msg,
- buttons=QMessageBox.StandardButton.Ok)
+ QMessageBox.information(parent, title, msg, buttons=QMessageBox.StandardButton.Ok)
def show_warning(parent: QMainWindow, title: str, msg: str) -> None:
"""Display a warning to user"""
- QMessageBox.warning(parent, title, msg,
- buttons=QMessageBox.StandardButton.Cancel)
+ QMessageBox.warning(parent, title, msg, buttons=QMessageBox.StandardButton.Cancel)
def trailing_silence(
- audio_segment: AudioSegment, silence_threshold: int = -50,
- chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE) -> int:
+ audio_segment: AudioSegment,
+ silence_threshold: int = -50,
+ chunk_size: int = Config.AUDIO_SEGMENT_CHUNK_SIZE,
+) -> int:
"""Return fade point from start in milliseconds"""
return fade_point(audio_segment, silence_threshold, chunk_size)
diff --git a/app/infotabs.py b/app/infotabs.py
index 79aae45..1f14f95 100644
--- a/app/infotabs.py
+++ b/app/infotabs.py
@@ -1,9 +1,9 @@
import urllib.parse
from datetime import datetime
-from slugify import slugify # type: ignore
-from typing import Dict, Optional
-from PyQt6.QtCore import QUrl # type: ignore
+from slugify import slugify # type: ignore
+from typing import Dict
+from PyQt6.QtCore import QUrl # type: ignore
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QTabWidget
from config import Config
@@ -47,13 +47,11 @@ class InfoTabs(QTabWidget):
if url in self.tabtitles.values():
self.setCurrentIndex(
- list(self.tabtitles.keys())[
- list(self.tabtitles.values()).index(url)
- ]
+ list(self.tabtitles.keys())[list(self.tabtitles.values()).index(url)]
)
return
- short_title = title[:Config.INFO_TAB_TITLE_LENGTH]
+ short_title = title[: Config.INFO_TAB_TITLE_LENGTH]
if self.count() < Config.MAX_INFO_TABS:
# Create a new tab
@@ -63,9 +61,7 @@ class InfoTabs(QTabWidget):
else:
# Reuse oldest widget
- widget = min(
- self.last_update, key=self.last_update.get # type: ignore
- )
+ widget = min(self.last_update, key=self.last_update.get) # type: ignore
tab_index = self.indexOf(widget)
self.setTabText(tab_index, short_title)
diff --git a/app/models.py b/app/models.py
index 7d232d5..60a1878 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1,13 +1,11 @@
#!/usr/bin/python3
-import os.path
import re
-import stackprinter # type: ignore
-from dbconfig import Session, scoped_session
+from dbconfig import scoped_session
from datetime import datetime
-from typing import Iterable, List, Optional, Union, ValuesView
+from typing import List, Optional
from sqlalchemy.ext.associationproxy import association_proxy
@@ -35,21 +33,14 @@ from sqlalchemy.orm.exc import (
from sqlalchemy.exc import (
IntegrityError,
)
-from config import Config
-from helpers import (
- fade_point,
- get_audio_segment,
- get_tags,
- leading_silence,
- trailing_silence,
-)
from log import log
+
Base = declarative_base()
# Database classes
class Carts(Base):
- __tablename__ = 'carts'
+ __tablename__ = "carts"
id: int = Column(Integer, primary_key=True, autoincrement=True)
cart_number: int = Column(Integer, nullable=False, unique=True)
@@ -64,10 +55,15 @@ class Carts(Base):
f"name={self.name}, path={self.path}>"
)
- def __init__(self, session: scoped_session, cart_number: int,
- name: Optional[str] = None,
- duration: Optional[int] = None, path: Optional[str] = None,
- enabled: bool = True) -> None:
+ def __init__(
+ self,
+ session: scoped_session,
+ cart_number: int,
+ name: Optional[str] = None,
+ duration: Optional[int] = None,
+ path: Optional[str] = None,
+ enabled: bool = True,
+ ) -> None:
"""Create new cart"""
self.cart_number = cart_number
@@ -81,7 +77,7 @@ class Carts(Base):
class NoteColours(Base):
- __tablename__ = 'notecolours'
+ __tablename__ = "notecolours"
id = Column(Integer, primary_key=True, autoincrement=True)
substring = Column(String(256), index=False)
@@ -106,11 +102,15 @@ class NoteColours(Base):
if not text:
return None
- for rec in session.execute(
+ for rec in (
+ session.execute(
select(NoteColours)
.filter(NoteColours.enabled.is_(True))
.order_by(NoteColours.order)
- ).scalars().all():
+ )
+ .scalars()
+ .all()
+ ):
if rec.is_regex:
flags = re.UNICODE
if not rec.is_casesensitive:
@@ -130,11 +130,11 @@ class NoteColours(Base):
class Playdates(Base):
- __tablename__ = 'playdates'
+ __tablename__ = "playdates"
id: int = Column(Integer, primary_key=True, autoincrement=True)
lastplayed = Column(DateTime, index=True, default=None)
- track_id = Column(Integer, ForeignKey('tracks.id'))
+ track_id = Column(Integer, ForeignKey("tracks.id"))
track: "Tracks" = relationship("Tracks", back_populates="playdates")
def __repr__(self) -> str:
@@ -152,8 +152,7 @@ class Playdates(Base):
session.commit()
@staticmethod
- def last_played(session: scoped_session,
- track_id: int) -> Optional[datetime]:
+ def last_played(session: scoped_session, track_id: int) -> Optional[datetime]:
"""Return datetime track last played or None"""
last_played = session.execute(
@@ -169,8 +168,7 @@ class Playdates(Base):
return None
@staticmethod
- def played_after(session: scoped_session,
- since: datetime) -> List["Playdates"]:
+ def played_after(session: scoped_session, since: datetime) -> List["Playdates"]:
"""Return a list of Playdates objects since passed time"""
return (
@@ -203,7 +201,7 @@ class Playlists(Base):
"PlaylistRows",
back_populates="playlist",
cascade="all, delete-orphan",
- order_by="PlaylistRows.plr_rownum"
+ order_by="PlaylistRows.plr_rownum",
)
def __repr__(self) -> str:
@@ -232,11 +230,9 @@ class Playlists(Base):
)
@classmethod
- def create_playlist_from_template(cls,
- session: scoped_session,
- template: "Playlists",
- playlist_name: str) \
- -> Optional["Playlists"]:
+ def create_playlist_from_template(
+ cls, session: scoped_session, template: "Playlists", playlist_name: str
+ ) -> Optional["Playlists"]:
"""Create a new playlist from template"""
playlist = cls(session, playlist_name)
@@ -277,9 +273,7 @@ class Playlists(Base):
return (
session.execute(
- select(cls)
- .filter(cls.is_template.is_(True))
- .order_by(cls.name)
+ select(cls).filter(cls.is_template.is_(True)).order_by(cls.name)
)
.scalars()
.all()
@@ -295,7 +289,7 @@ class Playlists(Base):
.filter(
cls.tab.is_(None),
cls.is_template.is_(False),
- cls.deleted.is_(False)
+ cls.deleted.is_(False),
)
.order_by(cls.last_used.desc())
)
@@ -310,11 +304,7 @@ class Playlists(Base):
"""
return (
- session.execute(
- select(cls)
- .where(cls.tab.is_not(None))
- .order_by(cls.tab)
- )
+ session.execute(select(cls).where(cls.tab.is_not(None)).order_by(cls.tab))
.scalars()
.all()
)
@@ -329,15 +319,9 @@ class Playlists(Base):
def move_tab(session: scoped_session, frm: int, to: int) -> None:
"""Move tabs"""
- row_frm = session.execute(
- select(Playlists)
- .filter_by(tab=frm)
- ).scalar_one()
+ row_frm = session.execute(select(Playlists).filter_by(tab=frm)).scalar_one()
- row_to = session.execute(
- select(Playlists)
- .filter_by(tab=to)
- ).scalar_one()
+ row_to = session.execute(select(Playlists).filter_by(tab=to)).scalar_one()
row_frm.tab = None
row_to.tab = None
@@ -354,8 +338,9 @@ class Playlists(Base):
session.flush()
@staticmethod
- def save_as_template(session: scoped_session,
- playlist_id: int, template_name: str) -> None:
+ def save_as_template(
+ session: scoped_session, playlist_id: int, template_name: str
+ ) -> None:
"""Save passed playlist as new template"""
template = Playlists(session, template_name)
@@ -369,15 +354,14 @@ class Playlists(Base):
class PlaylistRows(Base):
- __tablename__ = 'playlist_rows'
+ __tablename__ = "playlist_rows"
id: int = Column(Integer, primary_key=True, autoincrement=True)
plr_rownum: int = Column(Integer, nullable=False)
note: str = Column(String(2048), index=False, default="", nullable=False)
- playlist_id: int = Column(Integer, ForeignKey('playlists.id'),
- nullable=False)
+ playlist_id: int = Column(Integer, ForeignKey("playlists.id"), nullable=False)
playlist: Playlists = relationship(Playlists, back_populates="rows")
- track_id = Column(Integer, ForeignKey('tracks.id'), nullable=True)
+ track_id = Column(Integer, ForeignKey("tracks.id"), nullable=True)
track: "Tracks" = relationship("Tracks", back_populates="playlistrows")
played: bool = Column(Boolean, nullable=False, index=False, default=False)
@@ -388,13 +372,14 @@ class PlaylistRows(Base):
f"note={self.note}, plr_rownum={self.plr_rownum}>"
)
- def __init__(self,
- session: scoped_session,
- playlist_id: int,
- track_id: Optional[int],
- row_number: int,
- note: str = ""
- ) -> None:
+ def __init__(
+ self,
+ session: scoped_session,
+ playlist_id: int,
+ track_id: Optional[int],
+ row_number: int,
+ note: str = "",
+ ) -> None:
"""Create PlaylistRows object"""
self.playlist_id = playlist_id
@@ -409,38 +394,38 @@ class PlaylistRows(Base):
current_note = self.note
if current_note:
- self.note = current_note + '\n' + extra_note
+ self.note = current_note + "\n" + extra_note
else:
self.note = extra_note
@staticmethod
- 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"""
- src_rows = session.execute(
- select(PlaylistRows)
- .filter(PlaylistRows.playlist_id == src_id)
- ).scalars().all()
+ src_rows = (
+ session.execute(
+ select(PlaylistRows).filter(PlaylistRows.playlist_id == src_id)
+ )
+ .scalars()
+ .all()
+ )
for plr in src_rows:
- PlaylistRows(session, dst_id, plr.track_id, plr.plr_rownum,
- plr.note)
+ PlaylistRows(session, dst_id, plr.track_id, plr.plr_rownum, plr.note)
@staticmethod
def delete_higher_rows(
- session: scoped_session, playlist_id: int, maxrow: int) -> None:
+ session: scoped_session, playlist_id: int, maxrow: int
+ ) -> None:
"""
Delete rows in given playlist that have a higher row number
than 'maxrow'
"""
session.execute(
- delete(PlaylistRows)
- .where(
+ delete(PlaylistRows).where(
PlaylistRows.playlist_id == playlist_id,
- PlaylistRows.plr_rownum > maxrow
+ PlaylistRows.plr_rownum > maxrow,
)
)
session.flush()
@@ -451,11 +436,15 @@ class PlaylistRows(Base):
Ensure the row numbers for passed playlist have no gaps
"""
- plrs = session.execute(
- select(PlaylistRows)
- .where(PlaylistRows.playlist_id == playlist_id)
- .order_by(PlaylistRows.plr_rownum)
- ).scalars().all()
+ plrs = (
+ session.execute(
+ select(PlaylistRows)
+ .where(PlaylistRows.playlist_id == playlist_id)
+ .order_by(PlaylistRows.plr_rownum)
+ )
+ .scalars()
+ .all()
+ )
for i, plr in enumerate(plrs):
plr.plr_rownum = i
@@ -464,113 +453,126 @@ class PlaylistRows(Base):
session.commit()
@classmethod
- def get_from_id_list(cls, session: scoped_session, playlist_id: int,
- plr_ids: List[int]) -> List["PlaylistRows"]:
+ def get_from_id_list(
+ cls, session: scoped_session, playlist_id: int, plr_ids: List[int]
+ ) -> List["PlaylistRows"]:
"""
Take a list of PlaylistRows ids and return a list of corresponding
PlaylistRows objects
"""
- plrs = session.execute(
- select(cls)
- .where(
- cls.playlist_id == playlist_id,
- cls.id.in_(plr_ids)
- ).order_by(cls.plr_rownum)).scalars().all()
+ plrs = (
+ session.execute(
+ select(cls)
+ .where(cls.playlist_id == playlist_id, cls.id.in_(plr_ids))
+ .order_by(cls.plr_rownum)
+ )
+ .scalars()
+ .all()
+ )
return plrs
@staticmethod
- def get_last_used_row(session: scoped_session,
- playlist_id: int) -> Optional[int]:
+ def get_last_used_row(session: scoped_session, playlist_id: int) -> Optional[int]:
"""Return the last used row for playlist, or None if no rows"""
return session.execute(
- select(func.max(PlaylistRows.plr_rownum))
- .where(PlaylistRows.playlist_id == playlist_id)
+ select(func.max(PlaylistRows.plr_rownum)).where(
+ PlaylistRows.playlist_id == playlist_id
+ )
).scalar_one()
@staticmethod
- def get_track_plr(session: scoped_session, track_id: int,
- playlist_id: int) -> Optional["PlaylistRows"]:
+ def get_track_plr(
+ session: scoped_session, track_id: int, playlist_id: int
+ ) -> Optional["PlaylistRows"]:
"""Return first matching PlaylistRows object or None"""
return session.scalars(
select(PlaylistRows)
.where(
PlaylistRows.track_id == track_id,
- PlaylistRows.playlist_id == playlist_id
+ PlaylistRows.playlist_id == playlist_id,
)
.limit(1)
).first()
@classmethod
- def get_played_rows(cls, session: scoped_session,
- playlist_id: int) -> List["PlaylistRows"]:
+ def get_played_rows(
+ cls, session: scoped_session, playlist_id: int
+ ) -> List["PlaylistRows"]:
"""
For passed playlist, return a list of rows that
have been played.
"""
- plrs = session.execute(
- select(cls)
- .where(
- cls.playlist_id == playlist_id,
- cls.played.is_(True)
+ plrs = (
+ session.execute(
+ select(cls)
+ .where(cls.playlist_id == playlist_id, cls.played.is_(True))
+ .order_by(cls.plr_rownum)
)
- .order_by(cls.plr_rownum)
- ).scalars().all()
+ .scalars()
+ .all()
+ )
return plrs
@classmethod
def get_rows_with_tracks(
- cls, session: scoped_session, playlist_id: int,
+ cls,
+ session: scoped_session,
+ playlist_id: int,
from_row: Optional[int] = None,
- to_row: Optional[int] = None) -> List["PlaylistRows"]:
+ to_row: Optional[int] = None,
+ ) -> List["PlaylistRows"]:
"""
For passed playlist, return a list of rows that
contain tracks
"""
query = select(cls).where(
- cls.playlist_id == playlist_id,
- cls.track_id.is_not(None)
+ cls.playlist_id == playlist_id, cls.track_id.is_not(None)
)
if from_row is not None:
query = query.where(cls.plr_rownum >= from_row)
if to_row is not None:
query = query.where(cls.plr_rownum <= to_row)
- plrs = (
- session.execute((query).order_by(cls.plr_rownum)).scalars().all()
- )
+ plrs = session.execute((query).order_by(cls.plr_rownum)).scalars().all()
return plrs
@classmethod
- def get_unplayed_rows(cls, session: scoped_session,
- playlist_id: int) -> List["PlaylistRows"]:
+ def get_unplayed_rows(
+ cls, session: scoped_session, playlist_id: int
+ ) -> List["PlaylistRows"]:
"""
For passed playlist, return a list of playlist rows that
have not been played.
"""
- plrs = session.execute(
- select(cls)
- .where(
- cls.playlist_id == playlist_id,
- cls.track_id.is_not(None),
- cls.played.is_(False)
+ plrs = (
+ session.execute(
+ select(cls)
+ .where(
+ cls.playlist_id == playlist_id,
+ cls.track_id.is_not(None),
+ cls.played.is_(False),
+ )
+ .order_by(cls.plr_rownum)
)
- .order_by(cls.plr_rownum)
- ).scalars().all()
+ .scalars()
+ .all()
+ )
return plrs
@staticmethod
- def move_rows_down(session: scoped_session, playlist_id: int,
- starting_row: int, move_by: int) -> None:
+ 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
@@ -580,7 +582,7 @@ class PlaylistRows(Base):
update(PlaylistRows)
.where(
(PlaylistRows.playlist_id == playlist_id),
- (PlaylistRows.plr_rownum >= starting_row)
+ (PlaylistRows.plr_rownum >= starting_row),
)
.values(plr_rownum=PlaylistRows.plr_rownum + move_by)
)
@@ -589,7 +591,7 @@ class PlaylistRows(Base):
class Settings(Base):
"""Manage settings"""
- __tablename__ = 'settings'
+ __tablename__ = "settings"
id: int = Column(Integer, primary_key=True, autoincrement=True)
name: str = Column(String(64), nullable=False, unique=True)
@@ -602,21 +604,16 @@ class Settings(Base):
return f""
def __init__(self, session: scoped_session, name: str):
-
self.name = name
session.add(self)
session.flush()
@classmethod
- def get_int_settings(cls, session: scoped_session,
- name: str) -> "Settings":
+ def get_int_settings(cls, session: scoped_session, name: str) -> "Settings":
"""Get setting for an integer or return new setting record"""
try:
- return session.execute(
- select(cls)
- .where(cls.name == name)
- ).scalar_one()
+ return session.execute(select(cls).where(cls.name == name)).scalar_one()
except NoResultFound:
return Settings(session, name)
@@ -629,7 +626,7 @@ class Settings(Base):
class Tracks(Base):
- __tablename__ = 'tracks'
+ __tablename__ = "tracks"
id: int = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String(256), index=True)
@@ -641,8 +638,7 @@ class Tracks(Base):
path: str = Column(String(2048), index=False, nullable=False, unique=True)
mtime = Column(Float, index=True)
bitrate = Column(Integer, nullable=True, default=None)
- playlistrows: PlaylistRows = relationship("PlaylistRows",
- back_populates="track")
+ playlistrows: PlaylistRows = relationship("PlaylistRows", back_populates="track")
playlists = association_proxy("playlistrows", "playlist")
playdates: Playdates = relationship("Playdates", back_populates="track")
@@ -653,18 +649,18 @@ class Tracks(Base):
)
def __init__(
- self,
- session: scoped_session,
- path: str,
- title: Optional[str] = None,
- artist: Optional[str] = None,
- duration: int = 0,
- start_gap: int = 0,
- fade_at: Optional[int] = None,
- silence_at: Optional[int] = None,
- mtime: Optional[float] = None,
- lastplayed: Optional[datetime] = None,
- ) -> None:
+ self,
+ session: scoped_session,
+ path: str,
+ title: Optional[str] = None,
+ artist: Optional[str] = None,
+ duration: int = 0,
+ start_gap: int = 0,
+ fade_at: Optional[int] = None,
+ silence_at: Optional[int] = None,
+ mtime: Optional[float] = None,
+ lastplayed: Optional[datetime] = None,
+ ) -> None:
self.path = path
self.title = title
self.artist = artist
@@ -693,46 +689,36 @@ class Tracks(Base):
return session.execute(select(cls)).scalars().all()
@classmethod
- def get_by_path(cls, session: scoped_session,
- path: str) -> Optional["Tracks"]:
+ def get_by_path(cls, session: scoped_session, path: str) -> Optional["Tracks"]:
"""
Return track with passed path, or None.
"""
try:
- return (
- session.execute(
- select(Tracks)
- .where(Tracks.path == path)
- ).scalar_one()
- )
+ return session.execute(
+ select(Tracks).where(Tracks.path == path)
+ ).scalar_one()
except NoResultFound:
return None
@classmethod
- def search_artists(cls, session: scoped_session,
- text: str) -> List["Tracks"]:
+ def search_artists(cls, session: scoped_session, text: str) -> List["Tracks"]:
"""Search case-insenstively for artists containing str"""
return (
session.execute(
- select(cls)
- .where(cls.artist.ilike(f"%{text}%"))
- .order_by(cls.title)
+ select(cls).where(cls.artist.ilike(f"%{text}%")).order_by(cls.title)
)
.scalars()
.all()
)
@classmethod
- def search_titles(cls, session: scoped_session,
- text: str) -> List["Tracks"]:
+ def search_titles(cls, session: scoped_session, text: str) -> List["Tracks"]:
"""Search case-insenstively for titles containing str"""
return (
session.execute(
- select(cls)
- .where(cls.title.like(f"{text}%"))
- .order_by(cls.title)
+ select(cls).where(cls.title.like(f"{text}%")).order_by(cls.title)
)
.scalars()
.all()
diff --git a/app/music.py b/app/music.py
index bd3df29..f413b53 100644
--- a/app/music.py
+++ b/app/music.py
@@ -1,16 +1,16 @@
# import os
import threading
-import vlc # type: ignore
+import vlc # type: ignore
+
#
from config import Config
-from datetime import datetime
from helpers import file_is_unreadable
from typing import Optional
from time import sleep
from log import log
-from PyQt6.QtCore import ( # type: ignore
+from PyQt6.QtCore import ( # type: ignore
QRunnable,
QThreadPool,
)
@@ -19,7 +19,6 @@ lock = threading.Lock()
class FadeTrack(QRunnable):
-
def __init__(self, player: vlc.MediaPlayer) -> None:
super().__init__()
self.player = player
@@ -47,8 +46,7 @@ class FadeTrack(QRunnable):
for i in range(1, steps + 1):
measures_to_reduce_by += i
- volume_factor = 1 - (
- measures_to_reduce_by / total_measures_count)
+ volume_factor = 1 - (measures_to_reduce_by / total_measures_count)
self.player.audio_set_volume(int(original_volume * volume_factor))
sleep(sleep_time)
@@ -98,8 +96,7 @@ class Music:
return None
return self.player.get_position()
- def play(self, path: str,
- position: Optional[float] = None) -> Optional[int]:
+ def play(self, path: str, position: Optional[float] = None) -> Optional[int]:
"""
Start playing the track at path.
diff --git a/app/musicmuster.py b/app/musicmuster.py
index 0c2e116..3386825 100755
--- a/app/musicmuster.py
+++ b/app/musicmuster.py
@@ -5,13 +5,12 @@ from os.path import basename
import argparse
import os
import numpy as np
-import pyqtgraph as pg # type: ignore
-import stackprinter # type: ignore
+import pyqtgraph as pg # type: ignore
+import stackprinter # type: ignore
import subprocess
import sys
import threading
-import icons_rc
from datetime import datetime, timedelta
from pygame import mixer
from time import sleep
@@ -61,22 +60,14 @@ from dbconfig import (
)
import helpers
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 playlists import PlaylistTab
-from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
-from ui.dlg_search_database_ui import Ui_Dialog # type: ignore
-from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
-from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
-from ui.main_window_ui import Ui_MainWindow # type: ignore
+from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
+from ui.dlg_search_database_ui import Ui_Dialog # type: ignore
+from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
+from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
+from ui.main_window_ui import Ui_MainWindow # type: ignore
from utilities import check_db, update_bitrates
@@ -93,7 +84,7 @@ class CartButton(QPushButton):
self.cart_id = cart.id
if cart.path and cart.enabled and not cart.duration:
tags = helpers.get_tags(cart.path)
- cart.duration = tags['duration']
+ cart.duration = tags["duration"]
self.duration = cart.duration
self.path = cart.path
self.player = None
@@ -110,8 +101,9 @@ class CartButton(QPushButton):
self.pgb.setTextVisible(False)
self.pgb.setVisible(False)
palette = self.pgb.palette()
- palette.setColor(QPalette.ColorRole.Highlight,
- QColor(Config.COLOUR_CART_PROGRESSBAR))
+ palette.setColor(
+ QPalette.ColorRole.Highlight, QColor(Config.COLOUR_CART_PROGRESSBAR)
+ )
self.pgb.setPalette(palette)
self.pgb.setGeometry(0, 0, self.width(), 10)
self.pgb.setMinimum(0)
@@ -157,16 +149,13 @@ class FadeCurve:
# 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.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.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.ms_to_array_factor = len(self.graph_array) / (self.end_ms - self.start_ms)
self.region = None
@@ -192,10 +181,7 @@ class FadeCurve:
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.region = pg.LinearRegionItem([0, 0], bounds=[0, len(self.graph_array)])
self.GraphWidget.addItem(self.region)
# Update region position
@@ -286,8 +272,9 @@ class PlaylistTrack:
f"playlist_id={self.playlist_id}>"
)
- def set_plr(self, session: scoped_session, plr: PlaylistRows,
- tab: PlaylistTab) -> None:
+ def set_plr(
+ self, session: scoped_session, plr: PlaylistRows, tab: PlaylistTab
+ ) -> None:
"""
Update with new plr information
"""
@@ -323,8 +310,7 @@ class PlaylistTrack:
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 Window(QMainWindow, Ui_MainWindow):
@@ -355,14 +341,15 @@ class Window(QMainWindow, Ui_MainWindow):
self.txtSearch.setHidden(True)
self.hide_played_tracks = False
mixer.init()
- self.widgetFadeVolume.hideAxis('bottom')
- self.widgetFadeVolume.hideAxis('left')
+ self.widgetFadeVolume.hideAxis("bottom")
+ self.widgetFadeVolume.hideAxis("left")
self.widgetFadeVolume.setDefaultPadding(0)
self.widgetFadeVolume.setBackground(Config.FADE_CURVE_BACKGROUND)
FadeCurve.GraphWidget = self.widgetFadeVolume
- self.visible_playlist_tab: Callable[[], PlaylistTab] = \
- self.tabPlaylist.currentWidget
+ self.visible_playlist_tab: Callable[
+ [], PlaylistTab
+ ] = self.tabPlaylist.currentWidget
self.load_last_playlists()
if Config.CARTS_HIDE:
@@ -380,10 +367,8 @@ class Window(QMainWindow, Ui_MainWindow):
try:
git_tag = str(
- subprocess.check_output(
- ['git', 'describe'], stderr=subprocess.STDOUT
- )
- ).strip('\'b\\n')
+ subprocess.check_output(["git", "describe"], stderr=subprocess.STDOUT)
+ ).strip("'b\\n")
except subprocess.CalledProcessError as exc_info:
git_tag = str(exc_info.output)
@@ -395,7 +380,7 @@ class Window(QMainWindow, Ui_MainWindow):
self,
"About",
f"MusicMuster {git_tag}\n\nDatabase: {dbname}",
- QMessageBox.StandardButton.Ok
+ QMessageBox.StandardButton.Ok,
)
def cart_configure(self, cart: Carts, btn: CartButton) -> None:
@@ -433,15 +418,13 @@ class Window(QMainWindow, Ui_MainWindow):
# Don't allow clicks while we're playing
btn.setEnabled(False)
if not btn.player:
- log.debug(
- f"musicmuster.cart_click(): no player assigned ({btn=})")
+ log.debug(f"musicmuster.cart_click(): no player assigned ({btn=})")
return
btn.player.play()
btn.is_playing = True
colour = Config.COLOUR_CART_PLAYING
- thread = threading.Thread(target=self.cart_progressbar,
- args=(btn,))
+ thread = threading.Thread(target=self.cart_progressbar, args=(btn,))
thread.start()
else:
colour = Config.COLOUR_CART_ERROR
@@ -469,7 +452,7 @@ class Window(QMainWindow, Ui_MainWindow):
return
if cart.path and not helpers.file_is_unreadable(cart.path):
tags = helpers.get_tags(cart.path)
- cart.duration = tags['duration']
+ cart.duration = tags["duration"]
cart.enabled = dlg.ui.chkEnabled.isChecked()
cart.name = name
@@ -488,8 +471,7 @@ class Window(QMainWindow, Ui_MainWindow):
for cart_number in range(1, Config.CARTS_COUNT + 1):
cart = session.query(Carts).get(cart_number)
if cart is None:
- cart = Carts(session, cart_number,
- name=f"Cart #{cart_number}")
+ cart = Carts(session, cart_number, name=f"Cart #{cart_number}")
btn = CartButton(self, cart)
btn.clicked.connect(self.cart_click)
@@ -540,7 +522,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.update_headers()
def clear_selection(self) -> None:
- """ Clear selected row"""
+ """Clear selected row"""
# Unselect any selected rows
if self.visible_playlist_tab():
@@ -555,27 +537,25 @@ class Window(QMainWindow, Ui_MainWindow):
if self.playing:
event.ignore()
helpers.show_warning(
- self,
- "Track playing",
- "Can't close application while track is playing")
+ self, "Track playing", "Can't close application while track is playing"
+ )
else:
with Session() as session:
- record = Settings.get_int_settings(
- session, "mainwindow_height")
+ record = Settings.get_int_settings(session, "mainwindow_height")
if record.f_int != self.height():
- record.update(session, {'f_int': self.height()})
+ record.update(session, {"f_int": self.height()})
record = Settings.get_int_settings(session, "mainwindow_width")
if record.f_int != self.width():
- record.update(session, {'f_int': self.width()})
+ record.update(session, {"f_int": self.width()})
record = Settings.get_int_settings(session, "mainwindow_x")
if record.f_int != self.x():
- record.update(session, {'f_int': self.x()})
+ record.update(session, {"f_int": self.x()})
record = Settings.get_int_settings(session, "mainwindow_y")
if record.f_int != self.y():
- record.update(session, {'f_int': self.y()})
+ record.update(session, {"f_int": self.y()})
# Save splitter settings
splitter_sizes = self.splitter.sizes()
@@ -584,16 +564,15 @@ class Window(QMainWindow, Ui_MainWindow):
record = Settings.get_int_settings(session, "splitter_top")
if record.f_int != splitter_top:
- record.update(session, {'f_int': splitter_top})
+ record.update(session, {"f_int": splitter_top})
record = Settings.get_int_settings(session, "splitter_bottom")
if record.f_int != splitter_bottom:
- record.update(session, {'f_int': splitter_bottom})
+ record.update(session, {"f_int": splitter_bottom})
# Save current tab
record = Settings.get_int_settings(session, "active_tab")
- record.update(session,
- {'f_int': self.tabPlaylist.currentIndex()})
+ record.update(session, {"f_int": self.tabPlaylist.currentIndex()})
event.accept()
@@ -613,10 +592,8 @@ class Window(QMainWindow, Ui_MainWindow):
"""
# 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)
+ 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
@@ -643,15 +620,17 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionClosePlaylist.triggered.connect(self.close_playlist_tab)
self.actionDeletePlaylist.triggered.connect(self.delete_playlist)
self.actionDownload_CSV_of_played_tracks.triggered.connect(
- self.download_played_tracks)
- self.actionEnable_controls.triggered.connect(
- self.enable_play_next_controls)
+ self.download_played_tracks
+ )
+ self.actionEnable_controls.triggered.connect(self.enable_play_next_controls)
self.actionExport_playlist.triggered.connect(self.export_playlist_tab)
self.actionFade.triggered.connect(self.fade)
self.actionFind_next.triggered.connect(
- lambda: self.tabPlaylist.currentWidget().search_next())
+ lambda: self.tabPlaylist.currentWidget().search_next()
+ )
self.actionFind_previous.triggered.connect(
- lambda: self.tabPlaylist.currentWidget().search_previous())
+ lambda: self.tabPlaylist.currentWidget().search_previous()
+ )
self.actionImport.triggered.connect(self.import_track)
self.actionInsertSectionHeader.triggered.connect(self.insert_header)
self.actionInsertTrack.triggered.connect(self.insert_track)
@@ -666,13 +645,14 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionResume.triggered.connect(self.resume)
self.actionSave_as_template.triggered.connect(self.save_as_template)
self.actionSearch_title_in_Songfacts.triggered.connect(
- lambda: self.tabPlaylist.currentWidget().lookup_row_in_songfacts())
+ lambda: self.tabPlaylist.currentWidget().lookup_row_in_songfacts()
+ )
self.actionSearch_title_in_Wikipedia.triggered.connect(
- lambda: self.tabPlaylist.currentWidget().lookup_row_in_wikipedia())
+ lambda: self.tabPlaylist.currentWidget().lookup_row_in_wikipedia()
+ )
self.actionSearch.triggered.connect(self.search_playlist)
self.actionSelect_next_track.triggered.connect(self.select_next_row)
- self.actionSelect_previous_track.triggered.connect(
- self.select_previous_row)
+ self.actionSelect_previous_track.triggered.connect(self.select_previous_row)
self.actionMoveUnplayed.triggered.connect(self.move_unplayed)
self.actionSetNext.triggered.connect(self.set_selected_track_next)
self.actionSkipToNext.triggered.connect(self.play_next)
@@ -691,10 +671,9 @@ class Window(QMainWindow, Ui_MainWindow):
self.timer.timeout.connect(self.tick)
- def create_playlist(self,
- session: scoped_session,
- playlist_name: Optional[str] = None) \
- -> Optional[Playlists]:
+ def create_playlist(
+ self, session: scoped_session, playlist_name: Optional[str] = None
+ ) -> Optional[Playlists]:
"""Create new playlist"""
playlist_name = self.solicit_playlist_name()
@@ -712,8 +691,7 @@ class Window(QMainWindow, Ui_MainWindow):
if playlist:
self.create_playlist_tab(session, playlist)
- def create_playlist_tab(self, session: scoped_session,
- playlist: Playlists) -> int:
+ def create_playlist_tab(self, session: scoped_session, playlist: Playlists) -> int:
"""
Take the passed playlist database object, create a playlist tab and
add tab to display. Return index number of tab.
@@ -722,8 +700,11 @@ class Window(QMainWindow, Ui_MainWindow):
assert playlist.id
playlist_tab = PlaylistTab(
- musicmuster=self, session=session, playlist_id=playlist.id,
- signals=self.signals)
+ musicmuster=self,
+ session=session,
+ playlist_id=playlist.id,
+ signals=self.signals,
+ )
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
self.tabPlaylist.setCurrentIndex(idx)
@@ -737,15 +718,17 @@ class Window(QMainWindow, Ui_MainWindow):
with Session() as session:
# Save the selected PlaylistRows items ready for a later
# paste
- self.selected_plrs = (
- self.visible_playlist_tab().get_selected_playlistrows(session))
+ self.selected_plrs = self.visible_playlist_tab().get_selected_playlistrows(
+ session
+ )
def debug(self):
"""Invoke debugger"""
visible_playlist_id = self.visible_playlist_tab().playlist_id
print(f"Active playlist id={visible_playlist_id}")
- import ipdb # type: ignore
+ import ipdb # type: ignore
+
ipdb.set_trace()
def delete_playlist(self) -> None:
@@ -757,10 +740,10 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_id = self.visible_playlist_tab().playlist_id
playlist = session.get(Playlists, playlist_id)
if playlist:
- if helpers.ask_yes_no("Delete playlist",
- f"Delete playlist '{playlist.name}': "
- "Are you sure?"
- ):
+ if helpers.ask_yes_no(
+ "Delete playlist",
+ f"Delete playlist '{playlist.name}': " "Are you sure?",
+ ):
if self.close_playlist_tab():
playlist.delete(session)
@@ -780,9 +763,10 @@ class Window(QMainWindow, Ui_MainWindow):
start_dt = dlg.ui.dateTimeEdit.dateTime().toPyDateTime()
# Get output filename
pathspec = QFileDialog.getSaveFileName(
- self, 'Save CSV of tracks played',
+ self,
+ "Save CSV of tracks played",
directory="/tmp/playlist.csv",
- filter="CSV files (*.csv)"
+ filter="CSV files (*.csv)",
)
if not pathspec:
return
@@ -794,9 +778,7 @@ class Window(QMainWindow, Ui_MainWindow):
with open(path, "w") as f:
with Session() as session:
for playdate in Playdates.played_after(session, start_dt):
- f.write(
- f"{playdate.track.artist},{playdate.track.title}\n"
- )
+ f.write(f"{playdate.track.artist},{playdate.track.title}\n")
def drop3db(self) -> None:
"""Drop music level by 3db if button checked"""
@@ -867,16 +849,16 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_id = self.visible_playlist_tab().playlist_id
with Session() as session:
-
# Get output filename
playlist = session.get(Playlists, playlist_id)
if not playlist:
return
pathspec = QFileDialog.getSaveFileName(
- self, 'Save Playlist',
+ self,
+ "Save Playlist",
directory=f"{playlist.name}.m3u",
- filter="M3U files (*.m3u);;All files (*.*)"
+ filter="M3U files (*.m3u);;All files (*.*)",
)
if not pathspec:
return
@@ -923,10 +905,7 @@ class Window(QMainWindow, Ui_MainWindow):
times a second; this function has much better resolution.
"""
- if (
- self.current_track.track_id is None or
- self.current_track.start_time is None
- ):
+ if self.current_track.track_id is None or self.current_track.start_time is None:
return 0
now = datetime.now()
@@ -965,16 +944,16 @@ class Window(QMainWindow, Ui_MainWindow):
txt = ""
tags = helpers.get_tags(fname)
new_tracks.append(fname)
- title = tags['title']
- artist = tags['artist']
+ title = tags["title"]
+ artist = tags["artist"]
count = 0
possible_matches = Tracks.search_titles(session, title)
if possible_matches:
- txt += 'Similar to new track '
+ txt += "Similar to new track "
txt += f'"{title}" by "{artist} ({fname})":\n\n'
for track in possible_matches:
txt += f' "{track.title}" by {track.artist}'
- txt += f' ({track.path})\n\n'
+ txt += f" ({track.path})\n\n"
count += 1
if count >= Config.MAX_IMPORT_MATCHES:
txt += "\nThere are more similar-looking tracks"
@@ -987,7 +966,7 @@ class Window(QMainWindow, Ui_MainWindow):
"Possible duplicates",
txt,
QMessageBox.StandardButton.Ok,
- QMessageBox.StandardButton.Cancel
+ QMessageBox.StandardButton.Cancel,
)
if result == QMessageBox.StandardButton.Cancel:
return
@@ -1005,9 +984,7 @@ class Window(QMainWindow, Ui_MainWindow):
self, "Import error", "Error importing " + msg
)
)
- self.worker.importing.connect(
- lambda msg: self.statusbar.showMessage(msg, 5000)
- )
+ self.worker.importing.connect(lambda msg: self.statusbar.showMessage(msg, 5000))
self.worker.finished.connect(self.import_complete)
self.import_thread.start()
@@ -1057,8 +1034,9 @@ class Window(QMainWindow, Ui_MainWindow):
if record and record.f_int >= 0:
self.tabPlaylist.setCurrentIndex(record.f_int)
- def move_playlist_rows(self, session: scoped_session,
- playlistrows: List[PlaylistRows]) -> None:
+ def move_playlist_rows(
+ self, session: scoped_session, playlistrows: List[PlaylistRows]
+ ) -> None:
"""
Move passed playlist rows to another playlist
@@ -1071,14 +1049,15 @@ class Window(QMainWindow, Ui_MainWindow):
"""
# Remove current/next rows from list
- plrs_to_move = [plr for plr in playlistrows if
- plr.id not in
- [self.current_track.plr_id,
- self.next_track.plr_id]
- ]
+ plrs_to_move = [
+ plr
+ for plr in playlistrows
+ if plr.id not in [self.current_track.plr_id, self.next_track.plr_id]
+ ]
- rows_to_delete = [plr.plr_rownum for plr in plrs_to_move
- if plr.plr_rownum is not None]
+ rows_to_delete = [
+ plr.plr_rownum for plr in plrs_to_move if plr.plr_rownum is not None
+ ]
if not rows_to_delete:
return
@@ -1100,8 +1079,7 @@ class Window(QMainWindow, Ui_MainWindow):
destination_playlist_id = dlg.playlist.id
# Update destination playlist in the database
- last_row = PlaylistRows.get_last_used_row(session,
- destination_playlist_id)
+ last_row = PlaylistRows.get_last_used_row(session, destination_playlist_id)
if last_row is not None:
next_row = last_row + 1
else:
@@ -1135,8 +1113,9 @@ class Window(QMainWindow, Ui_MainWindow):
"""
with Session() as session:
- selected_plrs = (
- self.visible_playlist_tab().get_selected_playlistrows(session))
+ selected_plrs = self.visible_playlist_tab().get_selected_playlistrows(
+ session
+ )
if not selected_plrs:
return
@@ -1155,12 +1134,10 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_id = self.visible_playlist_tab().playlist_id
with Session() as session:
- unplayed_plrs = PlaylistRows.get_unplayed_rows(
- session, playlist_id)
- if helpers.ask_yes_no("Move tracks",
- f"Move {len(unplayed_plrs)} tracks:"
- " Are you sure?"
- ):
+ unplayed_plrs = PlaylistRows.get_unplayed_rows(session, playlist_id)
+ if helpers.ask_yes_no(
+ "Move tracks", f"Move {len(unplayed_plrs)} tracks:" " Are you sure?"
+ ):
self.move_playlist_rows(session, unplayed_plrs)
def new_from_template(self) -> None:
@@ -1168,8 +1145,7 @@ class Window(QMainWindow, Ui_MainWindow):
with Session() as session:
templates = Playlists.get_all_templates(session)
- dlg = SelectPlaylistDialog(self, playlists=templates,
- session=session)
+ dlg = SelectPlaylistDialog(self, playlists=templates, session=session)
dlg.exec()
template = dlg.playlist
if template:
@@ -1177,7 +1153,8 @@ class Window(QMainWindow, Ui_MainWindow):
if not playlist_name:
return
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)
@@ -1188,8 +1165,7 @@ class Window(QMainWindow, Ui_MainWindow):
with Session() as session:
playlists = Playlists.get_closed(session)
- dlg = SelectPlaylistDialog(self, playlists=playlists,
- session=session)
+ dlg = SelectPlaylistDialog(self, playlists=playlists, session=session)
dlg.exec()
playlist = dlg.playlist
if playlist:
@@ -1217,8 +1193,9 @@ class Window(QMainWindow, Ui_MainWindow):
with Session() as session:
# Create space in destination playlist
- PlaylistRows.move_rows_down(session, dst_playlist_id,
- dst_row, len(self.selected_plrs))
+ PlaylistRows.move_rows_down(
+ session, dst_playlist_id, dst_row, len(self.selected_plrs)
+ )
session.commit()
# Update plrs
@@ -1240,7 +1217,8 @@ class Window(QMainWindow, Ui_MainWindow):
# Update display
self.visible_playlist_tab().populate_display(
- session, dst_playlist_id, scroll_to_top=False)
+ session, dst_playlist_id, scroll_to_top=False
+ )
# If source playlist is not destination playlist, fixup row
# numbers and update display
@@ -1250,13 +1228,13 @@ class Window(QMainWindow, Ui_MainWindow):
# will be re-populated when it is opened)
source_playlist_tab = None
for tab in range(self.tabPlaylist.count()):
- if self.tabPlaylist.widget(tab).playlist_id == \
- src_playlist_id:
+ if self.tabPlaylist.widget(tab).playlist_id == src_playlist_id:
source_playlist_tab = self.tabPlaylist.widget(tab)
break
if source_playlist_tab:
source_playlist_tab.populate_display(
- session, src_playlist_id, scroll_to_top=False)
+ session, src_playlist_id, scroll_to_top=False
+ )
# Reset so rows can't be repasted
self.selected_plrs = None
@@ -1305,8 +1283,7 @@ class Window(QMainWindow, Ui_MainWindow):
# Set current track playlist_tab colour
current_tab = self.current_track.playlist_tab
if current_tab:
- self.set_tab_colour(
- current_tab, QColor(Config.COLOUR_CURRENT_TAB))
+ self.set_tab_colour(current_tab, QColor(Config.COLOUR_CURRENT_TAB))
# Restore volume if -3dB active
if self.btnDrop3db.isChecked():
@@ -1356,8 +1333,7 @@ class Window(QMainWindow, Ui_MainWindow):
if self.btnPreview.isChecked():
# Get track path for first selected track if there is one
- track_path = (
- self.visible_playlist_tab().get_selected_row_track_path())
+ track_path = self.visible_playlist_tab().get_selected_row_track_path()
if not track_path:
# Otherwise get path to next track to play
track_path = self.next_track.path
@@ -1405,35 +1381,32 @@ class Window(QMainWindow, Ui_MainWindow):
if self.current_track.track_id:
playing_track = self.current_track
- with Session() as session:
- # Set next plr to be track to resume
- if not self.previous_track.plr_id:
- return
- if not self.previous_track.playlist_tab:
- return
+ # Set next plr to be track to resume
+ if not self.previous_track.plr_id:
+ return
+ if not self.previous_track.playlist_tab:
+ return
- # Resume last track
- self.set_next_plr_id(self.previous_track.plr_id,
- self.previous_track.playlist_tab)
- self.play_next(self.previous_track_position)
+ # Resume last track
+ self.set_next_plr_id(
+ self.previous_track.plr_id, self.previous_track.playlist_tab
+ )
+ self.play_next(self.previous_track_position)
- # If a track was playing when we were called, get details to
- # set it as the next track
- if playing_track:
- if not playing_track.plr_id:
- return
- if not playing_track.playlist_tab:
- return
- self.set_next_plr_id(playing_track.plr_id,
- playing_track.playlist_tab)
+ # If a track was playing when we were called, get details to
+ # set it as the next track
+ if playing_track:
+ if not playing_track.plr_id:
+ return
+ if not playing_track.playlist_tab:
+ return
+ self.set_next_plr_id(playing_track.plr_id, playing_track.playlist_tab)
def save_as_template(self) -> None:
"""Save current playlist as template"""
with Session() as session:
- template_names = [
- a.name for a in Playlists.get_all_templates(session)
- ]
+ template_names = [a.name for a in Playlists.get_all_templates(session)]
while True:
# Get name for new template
@@ -1448,13 +1421,12 @@ class Window(QMainWindow, Ui_MainWindow):
template_name = dlg.textValue()
if template_name not in template_names:
break
- helpers.show_warning(self,
- "Duplicate template",
- "Template name already in use"
- )
- Playlists.save_as_template(session,
- self.visible_playlist_tab().playlist_id,
- template_name)
+ helpers.show_warning(
+ self, "Duplicate template", "Template name already in use"
+ )
+ Playlists.save_as_template(
+ session, self.visible_playlist_tab().playlist_id, template_name
+ )
helpers.show_OK(self, "Template", "Template saved")
def search_playlist(self) -> None:
@@ -1535,8 +1507,7 @@ class Window(QMainWindow, Ui_MainWindow):
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"""
dlg = QInputDialog(self)
@@ -1581,11 +1552,13 @@ class Window(QMainWindow, Ui_MainWindow):
# Reset playlist_tab colour
if self.current_track.playlist_tab:
if self.current_track.playlist_tab == self.next_track.playlist_tab:
- self.set_tab_colour(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(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
self.end_of_track_actions()
@@ -1612,8 +1585,9 @@ class Window(QMainWindow, Ui_MainWindow):
self.set_next_plr_id(selected_plr_ids[0], playlist_tab)
- def set_next_plr_id(self, next_plr_id: Optional[int],
- playlist_tab: PlaylistTab) -> None:
+ def set_next_plr_id(
+ self, next_plr_id: Optional[int], playlist_tab: PlaylistTab
+ ) -> None:
"""
Set passed plr_id as next track to play, or clear next track if None
@@ -1636,8 +1610,9 @@ class Window(QMainWindow, Ui_MainWindow):
# Tell playlist tabs to update their 'next track' highlighting
# Args must both be ints, so use zero for no next track
- self.signals.set_next_track_signal.emit(old_next_track.plr_id,
- next_plr_id or 0)
+ self.signals.set_next_track_signal.emit(
+ old_next_track.plr_id, next_plr_id or 0
+ )
# Update headers
self.update_headers()
@@ -1650,12 +1625,15 @@ class Window(QMainWindow, Ui_MainWindow):
# because it isn't quick
if self.next_track.title:
QTimer.singleShot(
- 1, lambda: self.tabInfolist.open_in_wikipedia(
- self.next_track.title)
+ 1,
+ lambda: self.tabInfolist.open_in_wikipedia(
+ self.next_track.title
+ ),
)
def _set_next_track_playlist_tab_colours(
- self, old_next_track: Optional[PlaylistTrack]) -> None:
+ self, old_next_track: Optional[PlaylistTrack]
+ ) -> None:
"""
Set playlist tab colour for next track. self.next_track needs
to be set before calling.
@@ -1664,14 +1642,14 @@ class Window(QMainWindow, Ui_MainWindow):
# If the original next playlist tab isn't the same as the
# new one or the current track, it needs its colour reset.
if (
- old_next_track and
- old_next_track.playlist_tab and
- old_next_track.playlist_tab not in [
- self.next_track.playlist_tab,
- self.current_track.playlist_tab
- ]):
- self.set_tab_colour(old_next_track.playlist_tab,
- QColor(Config.COLOUR_NORMAL_TAB))
+ old_next_track
+ and old_next_track.playlist_tab
+ and old_next_track.playlist_tab
+ not in [self.next_track.playlist_tab, self.current_track.playlist_tab]
+ ):
+ self.set_tab_colour(
+ old_next_track.playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)
+ )
# If the new next playlist tab isn't the same as the
# old one or the current track, it needs its colour set.
if old_next_track:
@@ -1679,14 +1657,14 @@ class Window(QMainWindow, Ui_MainWindow):
else:
old_tab = None
if (
- self.next_track and
- self.next_track.playlist_tab and
- self.next_track.playlist_tab not in [
- old_tab,
- self.current_track.playlist_tab
- ]):
- self.set_tab_colour(self.next_track.playlist_tab,
- QColor(Config.COLOUR_NEXT_TAB))
+ self.next_track
+ and self.next_track.playlist_tab
+ and self.next_track.playlist_tab
+ not in [old_tab, self.current_track.playlist_tab]
+ ):
+ self.set_tab_colour(
+ self.next_track.playlist_tab, QColor(Config.COLOUR_NEXT_TAB)
+ )
def tick(self) -> None:
"""
@@ -1713,9 +1691,9 @@ class Window(QMainWindow, Ui_MainWindow):
# Update volume fade curve
if (
- self.current_track.track_id and
- self.current_track.fade_graph and
- self.current_track.start_time
+ self.current_track.track_id
+ and self.current_track.fade_graph
+ and self.current_track.start_time
):
play_time = (
datetime.now() - self.current_track.start_time
@@ -1727,8 +1705,7 @@ class Window(QMainWindow, Ui_MainWindow):
Called every 500ms
"""
- self.lblTOD.setText(datetime.now().strftime(
- Config.TOD_TIME_FORMAT))
+ self.lblTOD.setText(datetime.now().strftime(Config.TOD_TIME_FORMAT))
# Update carts
self.cart_tick()
@@ -1748,15 +1725,19 @@ class Window(QMainWindow, Ui_MainWindow):
# player.is_playing() returning True, so assume playing if less
# than Config.PLAY_SETTLE microseconds have passed since
# starting play.
- if self.music.player and self.current_track.start_time and (
- self.music.player.is_playing() or
- (datetime.now() - self.current_track.start_time)
- < timedelta(microseconds=Config.PLAY_SETTLE)):
+ if (
+ self.music.player
+ and self.current_track.start_time
+ and (
+ self.music.player.is_playing()
+ or (datetime.now() - self.current_track.start_time)
+ < timedelta(microseconds=Config.PLAY_SETTLE)
+ )
+ ):
playtime = self.get_playtime()
- time_to_fade = (self.current_track.fade_at - playtime)
- time_to_silence = (
- self.current_track.silence_at - playtime)
- time_to_end = (self.current_track.duration - playtime)
+ time_to_fade = self.current_track.fade_at - playtime
+ time_to_silence = self.current_track.silence_at - playtime
+ time_to_end = self.current_track.duration - playtime
# Elapsed time
self.label_elapsed_timer.setText(helpers.ms_to_mmss(playtime))
@@ -1787,9 +1768,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.frame_silent.setStyleSheet("")
self.frame_fade.setStyleSheet("")
- self.label_silent_timer.setText(
- helpers.ms_to_mmss(time_to_silence)
- )
+ self.label_silent_timer.setText(helpers.ms_to_mmss(time_to_silence))
# Time to end
self.label_end_timer.setText(helpers.ms_to_mmss(time_to_end))
@@ -1834,8 +1813,9 @@ class Window(QMainWindow, Ui_MainWindow):
class CartDialog(QDialog):
"""Edit cart details"""
- def __init__(self, musicmuster: Window, session: scoped_session,
- cart: Carts, *args, **kwargs) -> None:
+ def __init__(
+ self, musicmuster: Window, session: scoped_session, cart: Carts, *args, **kwargs
+ ) -> None:
"""
Manage carts
"""
@@ -1871,8 +1851,14 @@ class CartDialog(QDialog):
class DbDialog(QDialog):
"""Select track from database"""
- def __init__(self, musicmuster: Window, session: scoped_session,
- get_one_track: bool = False, *args, **kwargs) -> None:
+ def __init__(
+ self,
+ musicmuster: Window,
+ session: scoped_session,
+ get_one_track: bool = False,
+ *args,
+ **kwargs,
+ ) -> None:
"""
Subclassed QDialog to manage track selection
@@ -1911,17 +1897,16 @@ class DbDialog(QDialog):
record = Settings.get_int_settings(self.session, "dbdialog_height")
if record.f_int != self.height():
- record.update(self.session, {'f_int': self.height()})
+ record.update(self.session, {"f_int": self.height()})
record = Settings.get_int_settings(self.session, "dbdialog_width")
if record.f_int != self.width():
- record.update(self.session, {'f_int': self.width()})
+ record.update(self.session, {"f_int": self.width()})
def add_selected(self) -> None:
"""Handle Add button"""
- if (not self.ui.matchList.selectedItems() and
- not self.ui.txtNote.text()):
+ if not self.ui.matchList.selectedItems() and not self.ui.txtNote.text():
return
track = None
@@ -1946,10 +1931,12 @@ class DbDialog(QDialog):
if track:
self.musicmuster.visible_playlist_tab().insert_track(
- self.session, track, note=self.ui.txtNote.text())
+ self.session, track, note=self.ui.txtNote.text()
+ )
else:
self.musicmuster.visible_playlist_tab().insert_header(
- self.session, note=self.ui.txtNote.text())
+ self.session, note=self.ui.txtNote.text()
+ )
# TODO: this shouldn't be needed as insert_track() saves
# playlist
@@ -2041,11 +2028,11 @@ class SelectPlaylistDialog(QDialog):
self.session = session
self.playlist = None
- record = Settings.get_int_settings(
- self.session, "select_playlist_dialog_width")
+ record = Settings.get_int_settings(self.session, "select_playlist_dialog_width")
width = record.f_int or 800
record = Settings.get_int_settings(
- self.session, "select_playlist_dialog_height")
+ self.session, "select_playlist_dialog_height"
+ )
height = record.f_int or 600
self.resize(width, height)
@@ -2057,20 +2044,20 @@ class SelectPlaylistDialog(QDialog):
def __del__(self): # review
record = Settings.get_int_settings(
- self.session, "select_playlist_dialog_height")
+ self.session, "select_playlist_dialog_height"
+ )
if record.f_int != self.height():
- record.update(self.session, {'f_int': self.height()})
+ record.update(self.session, {"f_int": self.height()})
- record = Settings.get_int_settings(
- self.session, "select_playlist_dialog_width")
+ record = Settings.get_int_settings(self.session, "select_playlist_dialog_width")
if record.f_int != self.width():
- record.update(self.session, {'f_int': self.width()})
+ record.update(self.session, {"f_int": self.width()})
def list_doubleclick(self, entry): # review
self.playlist = entry.data(Qt.ItemDataRole.UserRole)
self.accept()
- def open(self): # review
+ def open(self): # review
if self.ui.lstPlaylists.selectedItems():
item = self.ui.lstPlaylists.currentItem()
self.playlist = item.data(Qt.ItemDataRole.UserRole)
@@ -2086,12 +2073,22 @@ if __name__ == "__main__":
p = argparse.ArgumentParser()
# Only allow at most one option to be specified
group = p.add_mutually_exclusive_group()
- group.add_argument('-b', '--bitrates',
- action="store_true", dest="update_bitrates",
- default=False, help="Update bitrates in database")
- group.add_argument('-c', '--check-database',
- action="store_true", dest="check_db",
- default=False, help="Check and report on database")
+ group.add_argument(
+ "-b",
+ "--bitrates",
+ action="store_true",
+ dest="update_bitrates",
+ default=False,
+ help="Update bitrates in database",
+ )
+ group.add_argument(
+ "-c",
+ "--check-database",
+ action="store_true",
+ dest="check_db",
+ default=False,
+ help="Check and report on database",
+ )
args = p.parse_args()
# Run as required
@@ -2111,13 +2108,16 @@ if __name__ == "__main__":
app = QApplication(sys.argv)
# PyQt6 defaults to a grey for labels
palette = app.palette()
- palette.setColor(QPalette.ColorRole.WindowText,
- QColor(Config.COLOUR_LABEL_TEXT))
+ palette.setColor(
+ QPalette.ColorRole.WindowText, QColor(Config.COLOUR_LABEL_TEXT)
+ )
# Set colours that will be used by playlist row stripes
- palette.setColor(QPalette.ColorRole.Base,
- QColor(Config.COLOUR_EVEN_PLAYLIST))
- palette.setColor(QPalette.ColorRole.AlternateBase,
- QColor(Config.COLOUR_ODD_PLAYLIST))
+ palette.setColor(
+ QPalette.ColorRole.Base, QColor(Config.COLOUR_EVEN_PLAYLIST)
+ )
+ palette.setColor(
+ QPalette.ColorRole.AlternateBase, QColor(Config.COLOUR_ODD_PLAYLIST)
+ )
app.setPalette(palette)
win = Window()
win.show()
@@ -2129,8 +2129,12 @@ if __name__ == "__main__":
from helpers import send_mail
msg = stackprinter.format(exc)
- send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
- "Exception from musicmuster", msg)
+ send_mail(
+ Config.ERRORS_TO,
+ Config.ERRORS_FROM,
+ "Exception from musicmuster",
+ msg,
+ )
print("\033[1;31;47mUnhandled exception starts")
stackprinter.show(style="darkbg")
diff --git a/app/playlists.py b/app/playlists.py
index 05d981c..477f598 100644
--- a/app/playlists.py
+++ b/app/playlists.py
@@ -1,10 +1,10 @@
import os
import re
-import stackprinter # type: ignore
+import stackprinter # type: ignore
import subprocess
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
@@ -14,24 +14,14 @@ from PyQt6.QtCore import (
QEvent,
QModelIndex,
QObject,
- QSize,
Qt,
QTimer,
)
-from PyQt6.QtGui import (
- QAction,
- QBrush,
- QColor,
- QFont,
- QDropEvent,
- QKeyEvent
-)
+from PyQt6.QtGui import QAction, QBrush, QColor, QFont, QDropEvent, QKeyEvent
from PyQt6.QtWidgets import (
QAbstractItemDelegate,
QAbstractItemView,
QApplication,
- QLineEdit,
- QMainWindow,
QMenu,
QMessageBox,
QPlainTextEdit,
@@ -39,7 +29,7 @@ from PyQt6.QtWidgets import (
QStyleOptionViewItem,
QTableWidget,
QTableWidgetItem,
- QWidget
+ QWidget,
)
from config import Config
@@ -54,14 +44,7 @@ from helpers import (
set_track_metadata,
)
from log import log
-from models import (
- Playdates,
- Playlists,
- PlaylistRows,
- Settings,
- Tracks,
- NoteColours
-)
+from models import Playdates, Playlists, PlaylistRows, Settings, Tracks, NoteColours
if TYPE_CHECKING:
from musicmuster import Window, MusicMusterSignals
@@ -73,11 +56,10 @@ start_time_re = re.compile(r"@\d\d:\d\d:\d\d")
HEADER_NOTES_COLUMN = 2
# Columns
-Column = namedtuple("Column", ['idx', 'heading'])
+Column = namedtuple("Column", ["idx", "heading"])
columns = {}
columns["userdata"] = Column(idx=0, heading=Config.COLUMN_NAME_AUTOPLAY)
-columns["start_gap"] = Column(idx=1,
- heading=Config.COLUMN_NAME_LEADING_SILENCE)
+columns["start_gap"] = Column(idx=1, heading=Config.COLUMN_NAME_LEADING_SILENCE)
columns["title"] = Column(idx=2, heading=Config.COLUMN_NAME_TITLE)
columns["artist"] = Column(idx=3, heading=Config.COLUMN_NAME_ARTIST)
columns["duration"] = Column(idx=4, heading=Config.COLUMN_NAME_LENGTH)
@@ -101,16 +83,17 @@ ROW_NOTES = columns["row_notes"].idx
class EscapeDelegate(QStyledItemDelegate):
"""
- - increases the height of a row when editing to make editing easier
- - closes the edit on control-return
- - checks with user before abandoning edit on Escape
+ - increases the height of a row when editing to make editing easier
+ - closes the edit on control-return
+ - checks with user before abandoning edit on Escape
"""
def __init__(self, parent) -> None:
super().__init__(parent)
- def createEditor(self, parent: QWidget, option: QStyleOptionViewItem,
- index: QModelIndex):
+ def createEditor(
+ self, parent: QWidget, option: QStyleOptionViewItem, index: QModelIndex
+ ):
"""
Intercept createEditor call and make row just a little bit taller
"""
@@ -130,15 +113,14 @@ class EscapeDelegate(QStyledItemDelegate):
if event.type() == QEvent.Type.KeyPress:
key_event = cast(QKeyEvent, event)
if key_event.key() == Qt.Key.Key_Return:
- if key_event.modifiers() == (
- Qt.KeyboardModifier.ControlModifier
- ):
+ if key_event.modifiers() == (Qt.KeyboardModifier.ControlModifier):
self.commitData.emit(editor)
self.closeEditor.emit(editor)
return True
elif key_event.key() == Qt.Key.Key_Escape:
discard_edits = QMessageBox.question(
- self.parent(), "Abandon edit", "Discard changes?")
+ self.parent(), "Abandon edit", "Discard changes?"
+ )
if discard_edits == QMessageBox.StandardButton.Yes:
self.closeEditor.emit(editor)
return True
@@ -153,9 +135,13 @@ class PlaylistTab(QTableWidget):
TRACK_PATH = Qt.ItemDataRole.UserRole + 3
PLAYED = Qt.ItemDataRole.UserRole + 4
- def __init__(self, musicmuster: "Window",
- session: scoped_session,
- playlist_id: int, signals: "MusicMusterSignals") -> None:
+ def __init__(
+ self,
+ musicmuster: "Window",
+ session: scoped_session,
+ playlist_id: int,
+ signals: "MusicMusterSignals",
+ ) -> None:
super().__init__()
self.musicmuster: Window = musicmuster
self.playlist_id = playlist_id
@@ -165,10 +151,8 @@ class PlaylistTab(QTableWidget):
self.menu = QMenu()
self.setItemDelegate(EscapeDelegate(self))
self.setAlternatingRowColors(True)
- self.setSelectionMode(
- QAbstractItemView.SelectionMode.ExtendedSelection)
- self.setSelectionBehavior(
- QAbstractItemView.SelectionBehavior.SelectRows)
+ self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
+ self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked)
self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.setRowCount(0)
@@ -184,11 +168,12 @@ class PlaylistTab(QTableWidget):
self.horizontalHeader().setMinimumSectionSize(0)
# Set column headings sorted by idx
self.setHorizontalHeaderLabels(
- [a.heading for a in list(sorted(columns.values(),
- key=lambda item: item.idx))]
+ [
+ a.heading
+ for a in list(sorted(columns.values(), key=lambda item: item.idx))
+ ]
)
- self.horizontalHeader().sectionResized.connect(
- self.resizeRowsToContents)
+ self.horizontalHeader().sectionResized.connect(self.resizeRowsToContents)
# Drag and drop setup
self.setAcceptDrops(True)
@@ -221,7 +206,7 @@ class PlaylistTab(QTableWidget):
def __repr__(self) -> str:
return f""
-# ########## Events other than cell editing ##########
+ # ########## Events other than cell editing ##########
def dropEvent(self, event: QDropEvent) -> None:
"""
@@ -257,15 +242,19 @@ class PlaylistTab(QTableWidget):
for col in range(0, colCount):
self.setItem(tgtRow, col, self.takeItem(srcRow, col))
else:
- self.setItem(tgtRow, HEADER_NOTES_COLUMN,
- self.takeItem(srcRow, HEADER_NOTES_COLUMN))
+ self.setItem(
+ tgtRow,
+ HEADER_NOTES_COLUMN,
+ self.takeItem(srcRow, HEADER_NOTES_COLUMN),
+ )
self.setSpan(tgtRow, HEADER_NOTES_COLUMN, 1, len(columns) - 1)
for row in reversed(sorted(rowMapping.keys())):
self.removeRow(row)
self.resizeRowsToContents()
# Scroll to drop zone
- self.scrollToItem(self.item(top_row, 1),
- QAbstractItemView.ScrollHint.PositionAtTop)
+ self.scrollToItem(
+ self.item(top_row, 1), QAbstractItemView.ScrollHint.PositionAtTop
+ )
event.accept()
# Reset drag mode to allow row selection by dragging
@@ -277,8 +266,9 @@ class PlaylistTab(QTableWidget):
self.hide_or_show_played_tracks()
- def _add_context_menu(self, text: str, action: Callable,
- disabled: bool = False) -> QAction:
+ def _add_context_menu(
+ self, text: str, action: Callable, disabled: bool = False
+ ) -> QAction:
"""
Add item to self.menu
"""
@@ -300,7 +290,7 @@ class PlaylistTab(QTableWidget):
self.setDragEnabled(False)
super().mouseReleaseEvent(event)
-# ########## Cell editing ##########
+ # ########## Cell editing ##########
# We only want to allow cell editing on tracks, artists and notes,
# although notes may be section headers.
@@ -368,8 +358,7 @@ class PlaylistTab(QTableWidget):
elif self.edit_cell_type == ARTIST:
track.artist = new_text
if update_current:
- self.musicmuster.current_track.artist = \
- new_text
+ self.musicmuster.current_track.artist = new_text
if update_next:
self.musicmuster.next_track.artist = new_text
@@ -383,9 +372,9 @@ class PlaylistTab(QTableWidget):
self.clear_selection()
- def closeEditor(self,
- editor: QWidget,
- hint: QAbstractItemDelegate.EndEditHint) -> None:
+ def closeEditor(
+ self, editor: QWidget, hint: QAbstractItemDelegate.EndEditHint
+ ) -> None:
"""
Override PySide2.QAbstractItemView.closeEditor to enable
play controls and update display.
@@ -413,9 +402,12 @@ class PlaylistTab(QTableWidget):
with Session() as session:
self._update_start_end_times(session)
- def edit(self, index: QModelIndex, # type: ignore # FIXME
- trigger: QAbstractItemView.EditTrigger,
- event: QEvent) -> bool:
+ def edit(
+ self,
+ index: QModelIndex, # type: ignore # FIXME
+ trigger: QAbstractItemView.EditTrigger,
+ event: QEvent,
+ ) -> bool:
"""
Override PySide2.QAbstractItemView.edit to catch when editing starts
@@ -472,7 +464,7 @@ class PlaylistTab(QTableWidget):
return result
-# # ########## Externally called functions ##########
+ # # ########## Externally called functions ##########
def clear_next(self) -> None:
"""
@@ -511,8 +503,7 @@ class PlaylistTab(QTableWidget):
return [self._get_row_plr_id(a) for a in self._get_selected_rows()]
- def get_selected_playlistrows(
- self, session: scoped_session) -> List[PlaylistRows]:
+ def get_selected_playlistrows(self, session: scoped_session) -> List[PlaylistRows]:
"""
Return a list of PlaylistRows of the selected rows
"""
@@ -547,8 +538,10 @@ class PlaylistTab(QTableWidget):
Never hide current or next track
"""
- current_next = [self._get_current_track_row_number(),
- self._get_next_track_row_number()]
+ current_next = [
+ self._get_current_track_row_number(),
+ self._get_next_track_row_number(),
+ ]
for row_number in range(self.rowCount()):
if row_number in current_next:
@@ -581,8 +574,13 @@ class PlaylistTab(QTableWidget):
self.save_playlist(session)
self._update_start_end_times(session)
- def insert_row(self, session: scoped_session, plr: PlaylistRows,
- update_track_times: bool = True, played=False) -> None:
+ def insert_row(
+ self,
+ session: scoped_session,
+ plr: PlaylistRows,
+ update_track_times: bool = True,
+ played=False,
+ ) -> None:
"""
Insert passed playlist row (plr) into playlist tab.
"""
@@ -603,9 +601,7 @@ class PlaylistTab(QTableWidget):
else:
# This is a section header so it must have note text
if plr.note is None:
- log.debug(
- f"insert_row({plr=}) with no track_id and no note"
- )
+ log.debug(f"insert_row({plr=}) with no track_id and no note")
return
# Use one QTableWidgetItem to span all columns from column 1
@@ -618,8 +614,13 @@ class PlaylistTab(QTableWidget):
# Set bold as needed
self._set_row_bold(row_number, bold)
- def insert_track(self, session: scoped_session, track: Tracks,
- note: str = "", repaint: bool = True) -> None:
+ def insert_track(
+ self,
+ session: scoped_session,
+ track: Tracks,
+ note: str = "",
+ repaint: bool = True,
+ ) -> None:
"""
Insert track into playlist tab.
@@ -640,12 +641,12 @@ class PlaylistTab(QTableWidget):
row_number = self.get_new_row_number()
# Check to see whether track is already in playlist
- existing_plr = PlaylistRows.get_track_plr(session, track.id,
- self.playlist_id)
- if existing_plr and ask_yes_no("Duplicate row",
- "Track already in playlist. "
- "Move to new location?",
- default_yes=True):
+ existing_plr = PlaylistRows.get_track_plr(session, track.id, self.playlist_id)
+ if existing_plr and ask_yes_no(
+ "Duplicate row",
+ "Track already in playlist. " "Move to new location?",
+ default_yes=True,
+ ):
# Yes it is and we should reuse it
# If we've been passed a note, we need to add that to the
# existing track
@@ -654,8 +655,7 @@ class PlaylistTab(QTableWidget):
return self._move_row(session, existing_plr, row_number)
# Build playlist_row object
- plr = PlaylistRows(session, self.playlist_id, track.id,
- row_number, note)
+ plr = PlaylistRows(session, self.playlist_id, track.id, row_number, note)
self.insert_row(session, plr)
self.save_playlist(session)
self._update_start_end_times(session)
@@ -698,7 +698,8 @@ class PlaylistTab(QTableWidget):
self._set_row_colour_default(row_number)
self.clear_selection()
self._set_row_last_played_time(
- row_number, self.musicmuster.current_track.start_time)
+ row_number, self.musicmuster.current_track.start_time
+ )
with Session() as session:
self._set_row_note_colour(session, row_number)
@@ -719,11 +720,12 @@ class PlaylistTab(QTableWidget):
current_row = self._get_current_track_row_number()
if current_row is None:
if os.environ["MM_ENV"] == "PRODUCTION":
- send_mail(Config.ERRORS_TO,
- Config.ERRORS_FROM,
- "playlists:play_started:current_row is None",
- stackprinter.format()
- )
+ send_mail(
+ Config.ERRORS_TO,
+ Config.ERRORS_FROM,
+ "playlists:play_started:current_row is None",
+ stackprinter.format(),
+ )
print("playlists:play_started:current_row is None")
stackprinter.show(add_summary=True, style="darkbg")
return
@@ -734,8 +736,7 @@ class PlaylistTab(QTableWidget):
# Set next track
next_row = self._find_next_track_row(session, current_row + 1)
if next_row:
- self.musicmuster.set_next_plr_id(self._get_row_plr_id(next_row),
- self)
+ self.musicmuster.set_next_plr_id(self._get_row_plr_id(next_row), self)
# Display row as current track
self._set_row_colour_current(current_row)
@@ -747,11 +748,13 @@ class PlaylistTab(QTableWidget):
self._obs_change_scene(current_row)
# Update hidden tracks
- QTimer.singleShot(Config.HIDE_AFTER_PLAYING_OFFSET,
- self.hide_or_show_played_tracks)
+ QTimer.singleShot(
+ Config.HIDE_AFTER_PLAYING_OFFSET, self.hide_or_show_played_tracks
+ )
- def populate_display(self, session: scoped_session, playlist_id: int,
- scroll_to_top: bool = True) -> None:
+ def populate_display(
+ self, session: scoped_session, playlist_id: int, scroll_to_top: bool = True
+ ) -> None:
"""
Populate display from the associated playlist ID
"""
@@ -769,25 +772,29 @@ class PlaylistTab(QTableWidget):
playlist = session.get(Playlists, playlist_id)
if not playlist:
if os.environ["MM_ENV"] == "PRODUCTION":
- send_mail(Config.ERRORS_TO,
- Config.ERRORS_FROM,
- "playlists:populate_display:no playlist",
- stackprinter.format()
- )
+ send_mail(
+ Config.ERRORS_TO,
+ Config.ERRORS_FROM,
+ "playlists:populate_display:no playlist",
+ stackprinter.format(),
+ )
print("playlists:populate_display:no playlist")
stackprinter.show(add_summary=True, style="darkbg")
return
for plr in playlist.rows:
- self.insert_row(session, plr, update_track_times=False,
- played=plr.plr_rownum in played_rows)
+ self.insert_row(
+ session,
+ plr,
+ update_track_times=False,
+ played=plr.plr_rownum in played_rows,
+ )
# Scroll to top
if scroll_to_top:
row0_item = self.item(0, 0)
if row0_item:
- self.scrollToItem(row0_item,
- QAbstractItemView.ScrollHint.PositionAtTop)
+ self.scrollToItem(row0_item, QAbstractItemView.ScrollHint.PositionAtTop)
# Set widths
self._set_column_widths(session)
@@ -835,8 +842,7 @@ class PlaylistTab(QTableWidget):
# Any rows in the database for this playlist that has a row
# number equal to or greater than the row count needs to be
# removed.
- PlaylistRows.delete_higher_rows(
- session, self.playlist_id, self.rowCount() - 1)
+ PlaylistRows.delete_higher_rows(session, self.playlist_id, self.rowCount() - 1)
# Get changes into db
session.flush()
@@ -957,7 +963,7 @@ class PlaylistTab(QTableWidget):
# Hide/show rows
self.hide_or_show_played_tracks()
-# # ########## Internally called functions ##########
+ # # ########## Internally called functions ##########
def _add_track(self, row_number: int) -> None:
"""Add a track to a section header making it a normal track row"""
@@ -1004,49 +1010,54 @@ class PlaylistTab(QTableWidget):
# Play with mplayer
if track_row and not current:
- self._add_context_menu("Play with mplayer",
- lambda: self._mplayer_play(row_number))
+ self._add_context_menu(
+ "Play with mplayer", lambda: self._mplayer_play(row_number)
+ )
# Paste
- self._add_context_menu("Paste",
- lambda: self.musicmuster.paste_rows(),
- self.musicmuster.selected_plrs is None)
+ self._add_context_menu(
+ "Paste",
+ lambda: self.musicmuster.paste_rows(),
+ self.musicmuster.selected_plrs is None,
+ )
# Open in Audacity
if track_row and not current:
- self._add_context_menu("Open in Audacity",
- lambda: self._open_in_audacity(row_number)
- )
+ self._add_context_menu(
+ "Open in Audacity", lambda: self._open_in_audacity(row_number)
+ )
# Rescan
if track_row and not current:
self._add_context_menu(
- "Rescan track", lambda: self._rescan(row_number, track_id))
+ "Rescan track", lambda: self._rescan(row_number, track_id)
+ )
# ----------------------
self.menu.addSeparator()
# Remove row
if not current and not next_row:
- self._add_context_menu('Delete row', self._delete_rows)
+ self._add_context_menu("Delete row", self._delete_rows)
# Move to playlist
if not current and not next_row:
- self._add_context_menu('Move to playlist...',
- self.musicmuster.move_selected)
+ self._add_context_menu(
+ "Move to playlist...", self.musicmuster.move_selected
+ )
# ----------------------
self.menu.addSeparator()
# Remove track from row
if track_row and not current and not next_row:
- self._add_context_menu('Remove track from row',
- lambda: self._remove_track(row_number))
+ self._add_context_menu(
+ "Remove track from row", lambda: self._remove_track(row_number)
+ )
# Add track to section header (ie, make this a track row)
if header_row:
- self._add_context_menu('Add a track',
- lambda: self._add_track(row_number))
+ self._add_context_menu("Add a track", lambda: self._add_track(row_number))
# Mark unplayed
if self._get_row_userdata(row_number, self.PLAYED):
@@ -1054,26 +1065,26 @@ class PlaylistTab(QTableWidget):
# Unmark as next
if next_row:
- self._add_context_menu("Unmark as next track",
- self.clear_next)
+ self._add_context_menu("Unmark as next track", self.clear_next)
# ----------------------
self.menu.addSeparator()
# Info
if track_row:
- self._add_context_menu('Info',
- lambda: self._info_row(track_id))
+ self._add_context_menu("Info", lambda: self._info_row(track_id))
# Track path
if track_row:
- self._add_context_menu("Copy track path",
- lambda: self._copy_path(row_number))
+ self._add_context_menu(
+ "Copy track path", lambda: self._copy_path(row_number)
+ )
# return super(PlaylistTab, self).eventFilter(source, event)
- def _calculate_end_time(self, start: Optional[datetime],
- duration: int) -> Optional[datetime]:
+ def _calculate_end_time(
+ self, start: Optional[datetime], duration: int
+ ) -> Optional[datetime]:
"""Return datetime 'duration' ms after 'start'"""
if start is None:
@@ -1099,7 +1110,7 @@ class PlaylistTab(QTableWidget):
attribute_name = f"playlist_{column_name}_col_width"
record = Settings.get_int_settings(session, attribute_name)
if record.f_int != self.columnWidth(idx):
- record.update(session, {'f_int': width})
+ record.update(session, {"f_int": width})
def _context_menu(self, pos):
"""Display right-click menu"""
@@ -1149,9 +1160,8 @@ class PlaylistTab(QTableWidget):
return
# Get confirmation
- plural = 's' if row_count > 1 else ''
- if not ask_yes_no("Delete rows",
- f"Really delete {row_count} row{plural}?"):
+ plural = "s" if row_count > 1 else ""
+ if not ask_yes_no("Delete rows", f"Really delete {row_count} row{plural}?"):
return
rows_to_delete = [plr.plr_rownum for plr in plrs]
@@ -1173,9 +1183,9 @@ class PlaylistTab(QTableWidget):
self._update_start_end_times(session)
- def _find_next_track_row(self, session: scoped_session,
- starting_row: Optional[int] = None) \
- -> Optional[int]:
+ def _find_next_track_row(
+ self, session: scoped_session, starting_row: Optional[int] = None
+ ) -> Optional[int]:
"""
Find next track to play. If a starting row is given, start there;
otherwise, start from top. Skip rows already played.
@@ -1189,12 +1199,12 @@ class PlaylistTab(QTableWidget):
starting_row = 0
track_rows = [
- p.plr_rownum for p in PlaylistRows.get_rows_with_tracks(
- session, self.playlist_id)
+ p.plr_rownum
+ for p in PlaylistRows.get_rows_with_tracks(session, self.playlist_id)
]
played_rows = [
- p.plr_rownum for p in PlaylistRows.get_played_rows(
- session, self.playlist_id)
+ p.plr_rownum
+ for p in PlaylistRows.get_played_rows(session, self.playlist_id)
]
for row_number in range(starting_row, self.rowCount()):
if row_number not in track_rows or row_number in played_rows:
@@ -1239,8 +1249,7 @@ class PlaylistTab(QTableWidget):
return None
try:
- return datetime.strptime(match.group(0)[1:],
- Config.NOTE_TIME_FORMAT)
+ return datetime.strptime(match.group(0)[1:], Config.NOTE_TIME_FORMAT)
except ValueError:
return None
@@ -1250,8 +1259,9 @@ class PlaylistTab(QTableWidget):
"""
return [
- p.plr_rownum for p in PlaylistRows.get_played_rows(
- session, self.playlist_id) if p.plr_rownum is not None
+ p.plr_rownum
+ for p in PlaylistRows.get_played_rows(session, self.playlist_id)
+ if p.plr_rownum is not None
]
def _get_row_artist(self, row_number: int) -> str:
@@ -1296,8 +1306,9 @@ class PlaylistTab(QTableWidget):
return path
- def _get_row_plr(self, session: scoped_session,
- row_number: int) -> Optional[PlaylistRows]:
+ def _get_row_plr(
+ self, session: scoped_session, row_number: int
+ ) -> Optional[PlaylistRows]:
"""
Return PlaylistRows object for this row_number
"""
@@ -1326,8 +1337,9 @@ class PlaylistTab(QTableWidget):
return item_title.text()
- def _get_row_track(self, session: scoped_session,
- row_number: int) -> Optional[Tracks]:
+ def _get_row_track(
+ self, session: scoped_session, row_number: int
+ ) -> Optional[Tracks]:
"""Return the track associated with this row_number or None"""
track_id = self._get_row_track_id(row_number)
@@ -1346,7 +1358,7 @@ class PlaylistTab(QTableWidget):
return int(track_id)
def _get_row_track_path(self, row_number: int) -> str:
- """Return the track path associated with this row_number or '' """
+ """Return the track path associated with this row_number or ''"""
path = self._get_row_userdata(row_number, self.TRACK_PATH)
if not path:
@@ -1354,8 +1366,9 @@ class PlaylistTab(QTableWidget):
else:
return str(path)
- def _get_row_userdata(self, row_number: int,
- role: int) -> Optional[Union[str, int]]:
+ def _get_row_userdata(
+ self, row_number: int, role: int
+ ) -> Optional[Union[str, int]]:
"""
Return the specified userdata, if any.
"""
@@ -1366,15 +1379,14 @@ class PlaylistTab(QTableWidget):
return userdata_item.data(role)
- def _get_section_timing_string(self, ms: int,
- no_end: bool = False) -> str:
+ def _get_section_timing_string(self, ms: int, no_end: bool = False) -> str:
"""Return string describing section duration"""
duration = ms_to_mmss(ms)
caveat = ""
if no_end:
caveat = " (to end of playlist)"
- return ' [' + duration + caveat + ']'
+ return " [" + duration + caveat + "]"
def _get_selected_row(self) -> Optional[int]:
"""
@@ -1393,8 +1405,7 @@ class PlaylistTab(QTableWidget):
# Use a set to deduplicate result (a selected row will have all
# items in that row selected)
return sorted(
- [row_number for row_number in
- set([a.row() for a in self.selectedItems()])]
+ [row_number for row_number in set([a.row() for a in self.selectedItems()])]
)
def _info_row(self, track_id: int) -> None:
@@ -1445,13 +1456,11 @@ class PlaylistTab(QTableWidget):
if website == "wikipedia":
QTimer.singleShot(
- 0,
- lambda: self.musicmuster.tabInfolist.open_in_wikipedia(title)
+ 0, lambda: self.musicmuster.tabInfolist.open_in_wikipedia(title)
)
elif website == "songfacts":
QTimer.singleShot(
- 0,
- lambda: self.musicmuster.tabInfolist.open_in_songfacts(title)
+ 0, lambda: self.musicmuster.tabInfolist.open_in_songfacts(title)
)
else:
return
@@ -1474,8 +1483,9 @@ class PlaylistTab(QTableWidget):
self.clear_selection()
self.hide_or_show_played_tracks()
- def _move_row(self, session: scoped_session, plr: PlaylistRows,
- new_row_number: int) -> None:
+ def _move_row(
+ self, session: scoped_session, plr: PlaylistRows, new_row_number: int
+ ) -> None:
"""Move playlist row to new_row_number using parent copy/paste"""
if plr.plr_rownum is None:
@@ -1506,9 +1516,8 @@ class PlaylistTab(QTableWidget):
)
return
- cmd_list = ['gmplayer', '-vc', 'null', '-vo', 'null', track_path]
- thread = threading.Thread(
- target=self._run_subprocess, args=(cmd_list,))
+ cmd_list = ["gmplayer", "-vc", "null", "-vo", "null", track_path]
+ thread = threading.Thread(target=self._run_subprocess, args=(cmd_list,))
thread.start()
def _obs_change_scene(self, current_row: int) -> None:
@@ -1527,11 +1536,13 @@ class PlaylistTab(QTableWidget):
scene_name = match_obj.group(1)
if scene_name:
try:
- cl = obs.ReqClient(host=Config.OBS_HOST,
- port=Config.OBS_PORT,
- password=Config.OBS_PASSWORD)
+ cl = obs.ReqClient(
+ host=Config.OBS_HOST,
+ port=Config.OBS_PORT,
+ password=Config.OBS_PASSWORD,
+ )
except ConnectionRefusedError:
- log.error(f"OBS connection refused")
+ log.error("OBS connection refused")
return
try:
@@ -1578,8 +1589,9 @@ class PlaylistTab(QTableWidget):
"""Remove track from row, making it a section header"""
# Get confirmation
- if not ask_yes_no("Remove music",
- "Really remove the music track from this row?"):
+ if not ask_yes_no(
+ "Remove music", "Really remove the music track from this row?"
+ ):
return
# Update playlist_rows record
@@ -1635,10 +1647,7 @@ class PlaylistTab(QTableWidget):
else:
note_text += f"{track_id=} not found"
self._set_row_header_text(session, row_number, note_text)
- log.error(
- f"playlists._rescan({track_id=}): "
- "Track not found"
- )
+ log.error(f"playlists._rescan({track_id=}): " "Track not found")
self._set_row_colour_unreadable(row_number)
self._update_start_end_times(session)
@@ -1675,8 +1684,7 @@ class PlaylistTab(QTableWidget):
padding_required -= 1
scroll_item = self.item(top_row, 0)
- self.scrollToItem(scroll_item,
- QAbstractItemView.ScrollHint.PositionAtTop)
+ self.scrollToItem(scroll_item, QAbstractItemView.ScrollHint.PositionAtTop)
def _search(self, next: bool = True) -> None:
"""
@@ -1759,12 +1767,14 @@ class PlaylistTab(QTableWidget):
if ms > 0:
self.musicmuster.lblSumPlaytime.setText(
- f"Selected duration: {ms_to_mmss(ms)}")
+ f"Selected duration: {ms_to_mmss(ms)}"
+ )
else:
self.musicmuster.lblSumPlaytime.setText("")
- def _set_cell_colour(self, row_number: int, column: int,
- colour: Optional[str] = None) -> None:
+ def _set_cell_colour(
+ self, row_number: int, column: int, colour: Optional[str] = None
+ ) -> None:
"""
Set or reset a cell background colour
"""
@@ -1794,8 +1804,9 @@ class PlaylistTab(QTableWidget):
else:
self.setColumnWidth(idx, Config.DEFAULT_COLUMN_WIDTH)
- def _set_item_text(self, row_number: int, column: int,
- text: Optional[str]) -> QTableWidgetItem:
+ def _set_item_text(
+ self, row_number: int, column: int, text: Optional[str]
+ ) -> QTableWidgetItem:
"""
Set text for item if it exists, else create it, and return item
"""
@@ -1849,8 +1860,7 @@ class PlaylistTab(QTableWidget):
self.clear_selection()
- def _set_played_row(self, session: scoped_session,
- row_number: int) -> None:
+ def _set_played_row(self, session: scoped_session, row_number: int) -> None:
"""Mark this row as played"""
_ = self._set_row_userdata(row_number, self.PLAYED, True)
@@ -1863,8 +1873,9 @@ class PlaylistTab(QTableWidget):
plr.played = True
session.flush()
- def _set_row_artist(self, row_number: int,
- artist: Optional[str]) -> QTableWidgetItem:
+ def _set_row_artist(
+ self, row_number: int, artist: Optional[str]
+ ) -> QTableWidgetItem:
"""
Set row artist.
@@ -1876,8 +1887,9 @@ class PlaylistTab(QTableWidget):
return self._set_item_text(row_number, ARTIST, artist)
- def _set_row_bitrate(self, row_number: int,
- bitrate: Optional[int]) -> QTableWidgetItem:
+ def _set_row_bitrate(
+ self, row_number: int, bitrate: Optional[int]
+ ) -> QTableWidgetItem:
"""Set bitrate of this row."""
if not bitrate:
@@ -1915,8 +1927,7 @@ class PlaylistTab(QTableWidget):
if item:
item.setFont(boldfont)
- def _set_row_colour(self, row_number: int,
- colour: Optional[str] = None) -> None:
+ def _set_row_colour(self, row_number: int, colour: Optional[str] = None) -> None:
"""
Set or reset row background colour
"""
@@ -1952,27 +1963,26 @@ class PlaylistTab(QTableWidget):
Set next track row colour
"""
- self._set_row_colour(row_number, Config.COLOUR_NEXT_PLAYLIST)
+ self._set_row_colour(row_number, Config.COLOUR_NEXT_PLAYLIST)
def _set_row_colour_unreadable(self, row_number: int) -> None:
"""
Set unreadable row colour
"""
- self._set_row_colour(row_number, Config.COLOUR_UNREADABLE)
+ self._set_row_colour(row_number, Config.COLOUR_UNREADABLE)
- def _set_row_duration(self, row_number: int,
- ms: Optional[int]) -> QTableWidgetItem:
+ def _set_row_duration(self, row_number: int, ms: Optional[int]) -> QTableWidgetItem:
"""Set duration of this row. Also set in row metadata"""
- duration_item = self._set_item_text(
- row_number, DURATION, ms_to_mmss(ms))
+ duration_item = self._set_item_text(row_number, DURATION, ms_to_mmss(ms))
self._set_row_userdata(row_number, self.ROW_DURATION, ms)
return duration_item
- def _set_row_end_time(self, row_number: int,
- time: Optional[datetime]) -> QTableWidgetItem:
+ def _set_row_end_time(
+ self, row_number: int, time: Optional[datetime]
+ ) -> QTableWidgetItem:
"""Set row end time"""
if not time:
@@ -1985,8 +1995,9 @@ class PlaylistTab(QTableWidget):
return self._set_item_text(row_number, END_TIME, time_str)
- def _set_row_header_text(self, session: scoped_session,
- row_number: int, text: str) -> None:
+ def _set_row_header_text(
+ self, session: scoped_session, row_number: int, text: str
+ ) -> None:
"""
Set header text and row colour
"""
@@ -1999,7 +2010,7 @@ class PlaylistTab(QTableWidget):
Config.ERRORS_TO,
Config.ERRORS_FROM,
"playlists:_set_row_header_text() called on track row",
- stackprinter.format()
+ stackprinter.format(),
)
print("playists:_set_row_header_text() called on track row")
stackprinter.show(add_summary=True, style="darkbg")
@@ -2016,16 +2027,15 @@ class PlaylistTab(QTableWidget):
self._set_row_colour(row_number, note_colour)
def _set_row_last_played_time(
- self, row_number: int,
- last_played: Optional[datetime]) -> QTableWidgetItem:
+ self, row_number: int, last_played: Optional[datetime]
+ ) -> QTableWidgetItem:
"""Set row last played time"""
last_played_str = get_relative_date(last_played)
return self._set_item_text(row_number, LASTPLAYED, last_played_str)
- def _set_row_note_colour(self, session: scoped_session,
- row_number: int) -> None:
+ def _set_row_note_colour(self, session: scoped_session, row_number: int) -> None:
"""
Set row note colour
"""
@@ -2034,11 +2044,12 @@ class PlaylistTab(QTableWidget):
# track associated
if not self._get_row_track_id(row_number):
if os.environ["MM_ENV"] == "PRODUCTION":
- send_mail(Config.ERRORS_TO,
- Config.ERRORS_FROM,
- "playlists:_set_row_note_colour() on header row",
- stackprinter.format()
- )
+ send_mail(
+ Config.ERRORS_TO,
+ Config.ERRORS_FROM,
+ "playlists:_set_row_note_colour() on header row",
+ stackprinter.format(),
+ )
print("playists:_set_row_note_colour() called on header row")
stackprinter.show(add_summary=True, style="darkbg")
return
@@ -2048,8 +2059,9 @@ class PlaylistTab(QTableWidget):
note_colour = NoteColours.get_colour(session, note_text)
self._set_cell_colour(row_number, ROW_NOTES, note_colour)
- def _set_row_note_text(self, session: scoped_session,
- row_number: int, text: str) -> None:
+ def _set_row_note_text(
+ self, session: scoped_session, row_number: int, text: str
+ ) -> None:
"""
Set row note text and note colour
"""
@@ -2062,7 +2074,7 @@ class PlaylistTab(QTableWidget):
Config.ERRORS_TO,
Config.ERRORS_FROM,
"playlists:_set_row_note_text() called on header row",
- stackprinter.format()
+ stackprinter.format(),
)
print("playists:_set_row_note_text() called on header row")
stackprinter.show(add_summary=True, style="darkbg")
@@ -2074,16 +2086,16 @@ class PlaylistTab(QTableWidget):
# Set colour
self._set_row_note_colour(session, row_number)
- def _set_row_plr_id(self, row_number: int,
- plr_id: int) -> QTableWidgetItem:
+ def _set_row_plr_id(self, row_number: int, plr_id: int) -> QTableWidgetItem:
"""
Set PlaylistRows id
"""
return self._set_row_userdata(row_number, self.PLAYLISTROW_ID, plr_id)
- def _set_row_start_gap(self, row_number: int,
- start_gap: Optional[int]) -> QTableWidgetItem:
+ def _set_row_start_gap(
+ self, row_number: int, start_gap: Optional[int]
+ ) -> QTableWidgetItem:
"""
Set start gap on row, set backgroud colour.
@@ -2092,8 +2104,7 @@ class PlaylistTab(QTableWidget):
if not start_gap:
start_gap = 0
- start_gap_item = self._set_item_text(
- row_number, START_GAP, str(start_gap))
+ start_gap_item = self._set_item_text(row_number, START_GAP, str(start_gap))
if start_gap >= 500:
brush = QBrush(QColor(Config.COLOUR_LONG_START))
else:
@@ -2102,8 +2113,9 @@ class PlaylistTab(QTableWidget):
return start_gap_item
- def _set_row_start_time(self, row_number: int,
- time: Optional[datetime]) -> QTableWidgetItem:
+ def _set_row_start_time(
+ self, row_number: int, time: Optional[datetime]
+ ) -> QTableWidgetItem:
"""Set row start time"""
if not time:
@@ -2116,8 +2128,9 @@ class PlaylistTab(QTableWidget):
return self._set_item_text(row_number, START_TIME, time_str)
- def _set_row_times(self, row_number: int, start: datetime,
- duration: int) -> Optional[datetime]:
+ def _set_row_times(
+ self, row_number: int, start: datetime, duration: int
+ ) -> Optional[datetime]:
"""
Set row start and end times, return end time
"""
@@ -2128,8 +2141,7 @@ class PlaylistTab(QTableWidget):
return end_time
- def _set_row_title(self, row_number: int,
- title: Optional[str]) -> QTableWidgetItem:
+ def _set_row_title(self, row_number: int, title: Optional[str]) -> QTableWidgetItem:
"""
Set row title.
"""
@@ -2139,25 +2151,23 @@ class PlaylistTab(QTableWidget):
return self._set_item_text(row_number, TITLE, title)
- def _set_row_track_id(self, row_number: int,
- track_id: int) -> QTableWidgetItem:
+ def _set_row_track_id(self, row_number: int, track_id: int) -> QTableWidgetItem:
"""
Set track id
"""
return self._set_row_userdata(row_number, self.ROW_TRACK_ID, track_id)
- def _set_row_track_path(self, row_number: int,
- path: str) -> QTableWidgetItem:
+ def _set_row_track_path(self, row_number: int, path: str) -> QTableWidgetItem:
"""
Set track path
"""
return self._set_row_userdata(row_number, self.TRACK_PATH, path)
- def _set_row_userdata(self, row_number: int, role: int,
- value: Optional[Union[str, int]]) \
- -> QTableWidgetItem:
+ def _set_row_userdata(
+ self, row_number: int, role: int, value: Optional[Union[str, int]]
+ ) -> QTableWidgetItem:
"""
Set passed userdata in USERDATA column
"""
@@ -2171,25 +2181,26 @@ class PlaylistTab(QTableWidget):
return item
- def _track_time_between_rows(self, session: scoped_session,
- from_plr: PlaylistRows,
- to_plr: PlaylistRows) -> int:
+ def _track_time_between_rows(
+ self, session: scoped_session, from_plr: PlaylistRows, to_plr: PlaylistRows
+ ) -> int:
"""
Returns the total duration of all tracks in rows between
from_row and to_row inclusive
"""
plr_tracks = PlaylistRows.get_rows_with_tracks(
- session, self.playlist_id, from_plr.plr_rownum, to_plr.plr_rownum)
+ session, self.playlist_id, from_plr.plr_rownum, to_plr.plr_rownum
+ )
total_time = 0
- total_time = sum([a.track.duration for a in plr_tracks
- if a.track.duration])
+ total_time = sum([a.track.duration for a in plr_tracks if a.track.duration])
return total_time
- def _update_row_track_info(self, session: scoped_session, row: int,
- track: Tracks) -> None:
+ def _update_row_track_info(
+ self, session: scoped_session, row: int, track: Tracks
+ ) -> None:
"""
Update the passed row with info from the passed track.
"""
@@ -2199,7 +2210,8 @@ class PlaylistTab(QTableWidget):
_ = self._set_row_duration(row, track.duration)
_ = self._set_row_end_time(row, None)
_ = self._set_row_last_played_time(
- row, Playdates.last_played(session, track.id))
+ row, Playdates.last_played(session, track.id)
+ )
_ = self._set_row_start_gap(row, track.start_gap)
_ = self._set_row_start_time(row, None)
_ = self._set_row_title(row, track.title)
@@ -2216,11 +2228,12 @@ class PlaylistTab(QTableWidget):
section_start_rows: List[PlaylistRows] = []
- header_rows = [self._get_row_plr_id(row_number) for row_number in
- range(self.rowCount())
- if self._get_row_track_id(row_number) == 0]
- plrs = PlaylistRows.get_from_id_list(session, self.playlist_id,
- header_rows)
+ header_rows = [
+ self._get_row_plr_id(row_number)
+ for row_number in range(self.rowCount())
+ if self._get_row_track_id(row_number) == 0
+ ]
+ plrs = PlaylistRows.get_from_id_list(session, self.playlist_id, header_rows)
for plr in plrs:
if plr.note.endswith("+"):
section_start_rows.append(plr)
@@ -2230,25 +2243,29 @@ class PlaylistTab(QTableWidget):
from_plr = section_start_rows.pop()
to_plr = plr
total_time = self._track_time_between_rows(
- session, from_plr, to_plr)
+ session, from_plr, to_plr
+ )
time_str = self._get_section_timing_string(total_time)
- self._set_row_header_text(session, from_plr.plr_rownum,
- from_plr.note + time_str)
+ self._set_row_header_text(
+ session, from_plr.plr_rownum, from_plr.note + time_str
+ )
# Update section end
if to_plr.note.strip() == "-":
new_text = (
- "[End " + re.sub(
- section_header_cleanup_re, '', from_plr.note,
- ).strip() + "]"
+ "[End "
+ + re.sub(
+ section_header_cleanup_re,
+ "",
+ from_plr.note,
+ ).strip()
+ + "]"
)
- self._set_row_header_text(session, to_plr.plr_rownum,
- new_text)
+ self._set_row_header_text(session, to_plr.plr_rownum, new_text)
except IndexError:
# This ending row may have a time left from before a
# starting row above was deleted, so replace content
- self._set_row_header_text(session, plr.plr_rownum,
- plr.note)
+ self._set_row_header_text(session, plr.plr_rownum, plr.note)
continue
# If we still have plrs in section_start_rows, there isn't an end
@@ -2257,20 +2274,18 @@ class PlaylistTab(QTableWidget):
if possible_plr:
to_plr = possible_plr
for from_plr in section_start_rows:
- total_time = self._track_time_between_rows(session,
- from_plr, to_plr)
- time_str = self._get_section_timing_string(total_time,
- no_end=True)
- self._set_row_header_text(session, from_plr.plr_rownum,
- from_plr.note + time_str)
+ total_time = self._track_time_between_rows(session, from_plr, to_plr)
+ time_str = self._get_section_timing_string(total_time, no_end=True)
+ self._set_row_header_text(
+ session, from_plr.plr_rownum, from_plr.note + time_str
+ )
def _update_start_end_times(self, session: scoped_session) -> None:
- """ Update track start and end times """
+ """Update track start and end times"""
current_track_end_time = self.musicmuster.current_track.end_time
current_track_row = self._get_current_track_row_number()
- current_track_start_time = (
- self.musicmuster.current_track.start_time)
+ current_track_start_time = self.musicmuster.current_track.start_time
next_start_time = None
next_track_row = self._get_next_track_row_number()
played_rows = self._get_played_rows(session)
@@ -2279,13 +2294,14 @@ class PlaylistTab(QTableWidget):
# Don't change start times for tracks that have been
# played other than current/next row
if row_number in played_rows and row_number not in [
- current_track_row, next_track_row]:
+ current_track_row,
+ next_track_row,
+ ]:
continue
# Get any timing from header row (that's all we need)
if self._get_row_track_id(row_number) == 0:
- note_time = self._get_note_text_time(
- self._get_row_note(row_number))
+ note_time = self._get_note_text_time(self._get_row_note(row_number))
if note_time:
next_start_time = note_time
continue
@@ -2298,16 +2314,17 @@ class PlaylistTab(QTableWidget):
if row_number == next_track_row:
if current_track_end_time:
next_start_time = self._set_row_times(
- row_number, current_track_end_time,
- self._get_row_duration(row_number))
+ row_number,
+ current_track_end_time,
+ self._get_row_duration(row_number),
+ )
continue
# Else set track times below
if row_number == current_track_row:
if not current_track_start_time:
continue
- self._set_row_start_time(row_number,
- current_track_start_time)
+ self._set_row_start_time(row_number, current_track_start_time)
self._set_row_end_time(row_number, current_track_end_time)
# Next track may be above us so only reset
# next_start_time if it's not set
@@ -2323,13 +2340,16 @@ class PlaylistTab(QTableWidget):
# If we're between the current and next row, zero out
# times
- if (current_track_row and next_track_row and
- current_track_row < row_number < next_track_row):
+ if (
+ current_track_row
+ and next_track_row
+ and current_track_row < row_number < next_track_row
+ ):
self._set_row_start_time(row_number, None)
self._set_row_end_time(row_number, None)
else:
next_start_time = self._set_row_times(
- row_number, next_start_time,
- self._get_row_duration(row_number))
+ row_number, next_start_time, self._get_row_duration(row_number)
+ )
self._update_section_headers(session)
diff --git a/app/replace_files.py b/app/replace_files.py
index 50f8037..d645f71 100755
--- a/app/replace_files.py
+++ b/app/replace_files.py
@@ -5,16 +5,12 @@
# parent (eg, bettet bitrate).
import os
-import pydymenu # type: ignore
+import pydymenu # type: ignore
import shutil
import sys
from helpers import (
- fade_point,
- get_audio_segment,
get_tags,
- leading_silence,
- trailing_silence,
set_track_metadata,
)
@@ -29,7 +25,7 @@ process_tag_matches = True
do_processing = True
process_no_matches = True
-source_dir = '/home/kae/music/Singles/tmp'
+source_dir = "/home/kae/music/Singles/tmp"
parent_dir = os.path.dirname(source_dir)
# #########################################################
@@ -46,7 +42,7 @@ def main():
# We only want to run this against the production database because
# we will affect files in the common pool of tracks used by all
# databases
- if 'musicmuster_prod' not in os.environ.get('MM_DB'):
+ if "musicmuster_prod" not in os.environ.get("MM_DB"):
response = input("Not on production database - c to continue: ")
if response != "c":
sys.exit(0)
@@ -68,8 +64,8 @@ def main():
artists_to_path = {}
for k, v in parents.items():
try:
- titles_to_path[v['title'].lower()] = k
- artists_to_path[v['artist'].lower()] = k
+ titles_to_path[v["title"].lower()] = k
+ artists_to_path[v["artist"].lower()] = k
except AttributeError:
continue
@@ -78,44 +74,43 @@ def main():
if not os.path.isfile(new_path):
continue
new_tags = get_tags(new_path)
- new_title = new_tags['title']
- new_artist = new_tags['artist']
- bitrate = new_tags['bitrate']
+ new_title = new_tags["title"]
+ new_artist = new_tags["artist"]
+ bitrate = new_tags["bitrate"]
# If same filename exists in parent direcory, check tags
parent_path = os.path.join(parent_dir, new_fname)
if os.path.exists(parent_path):
parent_tags = get_tags(parent_path)
- parent_title = parent_tags['title']
- parent_artist = parent_tags['artist']
- if (
- (str(parent_title).lower() == str(new_title).lower()) and
- (str(parent_artist).lower() == str(new_artist).lower())
+ parent_title = parent_tags["title"]
+ parent_artist = parent_tags["artist"]
+ if (str(parent_title).lower() == str(new_title).lower()) and (
+ str(parent_artist).lower() == str(new_artist).lower()
):
name_and_tags.append(
f" {new_fname=}, {parent_title} → {new_title}, "
f" {parent_artist} → {new_artist}"
)
if process_name_and_tags_matches:
- process_track(new_path, parent_path, new_title,
- new_artist, bitrate)
+ process_track(new_path, parent_path, new_title, new_artist, bitrate)
continue
# Check for matching tags although filename is different
if new_title.lower() in titles_to_path:
possible_path = titles_to_path[new_title.lower()]
- if parents[possible_path]['artist'].lower() == new_artist.lower():
+ if parents[possible_path]["artist"].lower() == new_artist.lower():
# print(
# f"title={new_title}, artist={new_artist}:\n"
# f" {new_path} → {parent_path}"
# )
tags_not_name.append(
- f"title={new_title}, artist={new_artist}:\n"
- f" {new_path} → {parent_path}"
- )
+ f"title={new_title}, artist={new_artist}:\n"
+ f" {new_path} → {parent_path}"
+ )
if process_tag_matches:
- process_track(new_path, possible_path, new_title,
- new_artist, bitrate)
+ process_track(
+ new_path, possible_path, new_title, new_artist, bitrate
+ )
continue
else:
no_match += 1
@@ -132,8 +127,8 @@ def main():
if choice:
old_file = os.path.join(parent_dir, choice[0])
oldtags = get_tags(old_file)
- old_title = oldtags['title']
- old_artist = oldtags['artist']
+ old_title = oldtags["title"]
+ old_artist = oldtags["artist"]
print()
print(f" File name will change {choice[0]}")
print(f" → {new_fname}")
@@ -232,11 +227,8 @@ def main():
def process_track(src, dst, title, artist, bitrate):
-
new_path = os.path.join(os.path.dirname(dst), os.path.basename(src))
- print(
- f"process_track:\n {src=}\n {dst=}\n {title=}, {artist=}\n"
- )
+ print(f"process_track:\n {src=}\n {dst=}\n {title=}, {artist=}\n")
if not do_processing:
return
diff --git a/app/utilities.py b/app/utilities.py
index b769128..878a7aa 100755
--- a/app/utilities.py
+++ b/app/utilities.py
@@ -4,13 +4,7 @@ import os
from config import Config
from helpers import (
- fade_point,
- get_audio_segment,
get_tags,
- leading_silence,
- normalise_track,
- set_track_metadata,
- trailing_silence,
)
from log import log
from models import Tracks
@@ -72,12 +66,14 @@ def check_db(session):
print("Invalid paths in database")
print("-------------------------")
for t in paths_not_found:
- print(f"""
+ print(
+ f"""
Track ID: {t.id}
Path: {t.path}
Title: {t.title}
Artist: {t.artist}
- """)
+ """
+ )
if more_files_to_report:
print("There were more paths than listed that were not found")
diff --git a/conftest.py b/conftest.py
index db28026..e6c0b36 100644
--- a/conftest.py
+++ b/conftest.py
@@ -1,15 +1,15 @@
# https://itnext.io/setting-up-transactional-tests-with-pytest-and-sqlalchemy-b2d726347629
import pytest
-import sys
-sys.path.append("app")
-import models
+
+# Flake8 doesn't like the sys.append within imports
+# import sys
+# sys.path.append("app")
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
-
@pytest.fixture(scope="session")
def connection():
engine = create_engine(
@@ -21,7 +21,8 @@ def connection():
@pytest.fixture(scope="session")
def setup_database(connection):
- from app.models import Base # noqa E402
+ from app.models import Base # noqa E402
+
Base.metadata.bind = connection
Base.metadata.create_all()
# seed_database()
diff --git a/devnotes.txt b/devnotes.txt
new file mode 100644
index 0000000..08ac9e3
--- /dev/null
+++ b/devnotes.txt
@@ -0,0 +1 @@
+Run Flake8 and Black
diff --git a/docs/build/doctrees/environment.pickle b/docs/build/doctrees/environment.pickle
index 26141455eca6e81f926b6f29a0abcf265b11f402..d89b15e2c7680fae75547a1263d9e6a213235d96 100644
GIT binary patch
delta 3644
zcma);eNa%dST|4e}r#s!<#@deC*|xT8cUO02$JxF2y?lW>yZ_vG
z?z!ju&UxpaoVJ+
zFg1+HVy$A#oT~#H+Bu!cq!SFojAjE<=T46A35}?uLP8ejeV*Jf(p$L7{TaPnODC+b
zGG@BUVd9Kd6GK}~x_Xn*&e>_BndWrmc9>N%30AIEC|_&{i5sG!b*%@+muTVWIt6Sf
zQNz7;9$-rI;qiI}Tq@N<)&|eG1$hq6Ql;Z?W_nr=pKn|SZG1(vg5
zM!5$rF!Nxp9y6FENZk?vcD={_azzS)KNvhR)e0*q6E0Uh)Ea46XpD`c#UUZ=r?^z5
zF7^xj=ooF3il#zhWjP$(BBrK@VX)XHo|W(5IExu}vx{Q2W4!~1>Tx?=&8|jQGW!+A
zvU3SWq82t;gj7wf?S%hSUWZ{*g4o393`TPW+%v_8aWVCmr~#i(0Fn6+^qS+J
zWAml?dsK?S1#uflBL5(PY+-nLjHRVoAy_eJt+mJj1J-G%78@HWI|-kMtk7JYjXK~`
z^>nnCPcL2Y*c~{Z0AJfC!4+F9^jj5Jeg!_Yp5;9!z#Y35hN=}1!>QqmYM(aDk%fK>
z^Bg%KvHNs$YcfH^DPVhz3i3FwHfn0V5x1U|vC(!$Z!sI_WN5CP2{{gh^4X(uBQ6Wa
zs9>XGM2H`ivy3rUa9e20X8PIk-LIpesz&hwBROAV8vk@`oE?5%^DB%*4drmARsmc?
z9z3e`Ch4x{rjGg{I6D7}_8(WYCBi$c9+cwO
zwCqX?a{tY)#Gsv|JrSaJ-hn6WsiHD+HcuI3Z1YB^y5DU@J{(;DYp1K8J#gw7*1D$=E!VWY(NR-Bi%7EfjyD<
zNvndyqxneX{#Q>1!kv)vi~BRu$)OrAc5mh^(Kzki)c7MdRtonQG@%%vk2TBJ5)VFl
zp+9=e>L8^zT1Q6Lssu>uvBeZVd&q9)Q`sz}vYAiCM9hqM@uxyGfmFy5yIM%45YE59
zk2)ZQ6{j^2sg^<0@z+o<+&sPwH^R;1ufetxIp``cJw7oPeMBTw2iB9h)Mrv?zNjIS
z4<4M1&pbe=eWaMZL^?~P0V4Gi=_-*1i1ZPWNSCu1kp0UVbP4hX;=w#^R=ChTKapgL
z;qHKv`cVo!sU)wLpzoBD`c?{>p?HWGm?TPOf52zC6R~&K`6)2xbPO5~1*a?Jlnj&k
z!5k^`I*a8JzcNBb5bo(`C`8Tl8$el~6h*+JzI;kLN=ZWMGNYlXFO~X63V-9GL32$?
z`D2sJcb}!vCLDVmi2CD#;+ObILGjlK%A2zlgh$XTXD8wCB6!%J6U@z~-j>1eaa>>b
z1h_GfLbc1_hC3Uko>Njg1@W(GQB;c{UO%@a*f}0v9aK_R0)Y1hmGZ;>A`B5Ht-RN-
zI4>wp`4xk_BK#ayILg_6c+{Ugn*ZL3iPQrhUxNeY
zYmS^B$`48R@$HcX=|gkJywuPXwBGCe(xsU}-o?DPJm6hK6nSVB3|~qZ4Uw;m6AKX<
zy{MMUX+fxlv8dYTqDWVNQuMMHbtmBD*py$IId$VGMT
zgEtbWX{izTj#|ltNW&7iRDqA_0yYDy3fS53$KgymiMrrX|-ey;lGdV_^~tun^@}w6AWl{u{z=5I}Nqj
zIWHmo_6$rYz8#Nhz;T-$SC1`mn(S8mWVgY?+leu4Sllw79dnX2vQbk*jL5QOew&MJ
pBD-eaj(D+xD?WRh>^fj@WGU|Frz45f(=|YUxCr&SnGZA2e*r%T?o0px
delta 3792
zcmai1dr(u^8RuM#ge0g}Fou`8K32sSpty-uke4zRA22%08wtrpat#S2Hy|#^s}Zq5
z#qU~Qt*e4hP;p~zQM;mccDhwtXFAi}x;3tyaof6J*S6d3Y-h)wb8iTUIR5k9@AdnA
z-}%nH=icky6?m{cP}V3r;Qsis>}ixnX=1Bci>c1qi4P`gQ@gYbHL-g
zfe7u!a$5~k;k5APN((z2WNeU_tA&sPnt5}ImEjGg4#+4NfZ9AAv=`EFD{lb|7JA`s
zekx2TreRuv4swdUs-zSrZ>uoy2D6cYl)|-8vY8(3On@&6L!e_bMi-?)X9*43Vm&-6
z@xs#0^WlF68ec;pq4X=Tl_WsEk%lWJF>uuAh3kf7N*)B?8`7bR^@7&8oYDqCp>ZWO
zJqS*?48ilbzu>`h(Lr!I$L^^$%}118ip-ZH31UPUlTvE7nE6^V$|q$ETqx5-tq;P;
zKVW!~z&*kj_p3<@sVtEREd=lTh0wb}0TXk>>}Z-18Ox|G
zQa6jFZj7XxDk|3vTg)E?Ryr7%#W4_99VM%9I?P6>;091N%+nc>x`R7uI)yucAS3n*x`sB##<4
zR))j8N)4R1(^#JkPwhjI#3~&;a?s$aS^!h|A+OWE6s9_9xNe^ZdCnp4IY$DemcwI*
z9`08Aoa))BX5PV6vUbM78f{h+Gq;qibdaA1pV!cD3je^Vg@xOE9E!zjS?d}ebI?>54>uaT6Jyues#!cl3v1+AQ!Ko!p8>zF*T83u
z^cb;Nh-!MQO1ITmnY4}X;mI+8cBtWSgBsQ}XdttB@>s!{aI)D8M;ephP74ixZ(IVZ
zA9>LdC~A&Gu~6Tvf$kQ~WLfa2sShW-10T5VLqk1{7C}=>sC=khXlf17AdY}^s&cXxal6H2t>@J43`8{(
zd<4kdNyrTu?)lUn;)sV+?iJK-0*s4tkH>Eilg%h_|O`!~BfD-IFmC$bkl6VAx3O*C@Ex|eihYauZs2ttoo)oHIMa5)N?
zl$L^WJQW`-Mkoi4AAd5Zh~(vC;qWbwBc3*guYWPbtyaL3(-+JDeD0}9&%DYy`z8$j0IZL@Z*nDyZJoH3R*Css^_%03yaJgq|v
zFx|QE`_mC98jg4C;NXP_N~46tGkPjYNis(Qdd{d(3I^ms)Y-|(I3=bQAKN4h)hl&=
zd%36XEQ3%kaYMm*1zHYYoJ&P|zpWuQH3cu}+O#q1ygbjKc3j6fqQ>q~HCjco&G8B}
z1(WHaxF=~0rpV54)CYq-cKm>sT*yWqEcNhg5si99E#YFuYpC&X2&4W6_b(chpZM4G
z6u$1&qX+P1Zz}o}VlJm5FZ}mX;%jIbSbC>Yp(=RZvl{O9MyQ4uMTtk^h3h&
z7)vi)x;z%go-Q?&Bylu-IFe(z$f0`R?@KQ^KJ`16iVnFKJH#94<)x+L=5nJqb3AtunV02%dfx3NM-(Fobg6r%ku2XO2y1!|Xue;y*7`Og%
z<_N~SqZohl7Dg?4;p6$NZ_Wsw=c9POdJB&X=5$2LrMZRSPx-#pcq_85FBtLmphLtr
z2R-o25G>O87nX{lKSAvmFhoCAee10iPk^}U`e
z!LIe30XE!BV2q+}5+(WYUC*&tZ{y0a#2@nI(0Q|PGI_}s2~MY*;vC{<00(scEX2XP
z{W{#awO`F}IKN)t_DYu>7?2s@bO+iUybsXTKL-Qu^+%&Fc-GIQpTU{9EAAY=a_6yd
ztJdLvK>TyH#%f}>39UkDtKc5*U=4O-xxih)8Rbdbt0*{ht2nNU5D-zGv{T?N66?~C
zMNmE{j`b1i8qB_(IZOO(keduV%bP1${6|VICf33m1Q0TD@9U1|!hzfA=z-_q?ZxQ-
Dy%8O+
diff --git a/docs/build/doctrees/introduction.doctree b/docs/build/doctrees/introduction.doctree
index 2f2132ca5fccd2008bc168852b9d4b7188c87866..d4938e104646011dc27e634ed616d2fb2f747769 100644
GIT binary patch
delta 1759
zcmah}ZERCj80OsWvX3rZ*LCe?zi!)gUDuCo%?fl~#$cF$fI|l3Lyg>Zce^cJTen;1
zs0N6cvSdFXPyHpR#P|^+id19#L$V+G!(SvOM$nK&4FR%YCz5psT(@Q3(UKVSb5|F({I3}&S?j_Hk+iL{`IBU3o1Yg#6HO?L?4`Vdak
zX|ccFhp+4Ms53-~51R~q_*9?Q^{>k*nF&!5l|)>KL)S2Xef8RZhP!dRp$3KeJe9lSVD-O|5v%n8#VuYH}RcOug9Gn8!(TxTZ)}
ztdJ|8F32XlX>LK#G>a=uyK$*$0j=h|);E|{5}CA^O3fhoh=8b}&h-5~%KU@rcJd>{
z#t841Ey+dH>-#Ia*5?!@lZM$6GyGPjmBRt;Y~gU>#X=jU+)~K_`t>U%ujO_hh1q%z
z7p#uTlq8NO(&PBdW8;-cF}@$owu|HdZraY^J-cn0{AK$^;=qC!7(UcX9`u
zYlwuRGl;A(La_s`KKw3dG4Jxh>|~K9cP{~Uy1THb?ytIIPA>#ak$-J+#mKik9T@M_WPq0JXhc&i9v
zt;~n4&jGKmS#`F9y637+2H8<;CRv|bZ)ThFpo}Gmp#;&2UE=&q17wQp;~s%Uw0eKN-%FT+$Tv-5UupV^3r+apU8Ni}NzD
z6*nWkYEOlVa#rAm%Z?4JJoI)YS8dTdSiE$DHc}OH@rzYDQemc1oae(US!_vzb+HDx
z*Sy7TQ%_&HwG`!|!(;$Y)Y>r}jd2Nv;Y7A
delta 2055
zcmah~ZD>I@-_0NHO
z&htFyyyv{Y`CO8yL`mBHOWpVg`CW!*fhJBOL1wr%rD(l=3jEZ
zRertf;??|L)$f+lGqH!u3e3d+1NU145YY{{I-+b&NKP~C)D)BE_A_#pO}v(4WC?b=
z+h9gVxBoAK$;{Q0gCBKACT!!pw4X`kld_OWafZdH@xM-l^Ok)u48VcHVLwQ+Bu`(+p7?{+rhmw(g(iwz%`Ud#W
zSb)4?0CqOf@TnmLN1F;D8Xd59#$hB9Qi|uM101Ep}3F~q~*D0K`Aa*oMxStiFNr1TWyijz|Z+wFn7waz^o6&LUd$ef#z
z_9)8$$DJwDQz-%?Tzap}V-#2|mfmV3nVigK<)<#Xrfz=7kF&H3S@3s93QeAHjyG85
z@t|{tw6z}E>fpX1sP4zjHv#f?P~v3eUuQj{Rx1HAl>iCUiIWs$!d57DwyJxucNe|{
zjs|gp93D@O1nqh;`uISH322!ZQ9x_(R~(96G@No-;d)m=*F7rj<8VilTteoix?z*M
z7mj-9XZRiPndeCB25EnqSw9{log_~MH#PUVz(kiHieBxa2pfFuJq$5V2ueO0-t+Xp
zRbRorYHrQ(mghJ&n;-*V68UTvzlGgt?tlz}uig9Qdg8yiE`)7n)0GpEK1#0rV_y&zk#O`G50K{{;J=(c>4GKro{nQ{Z{IMl5+^>
z{0-*}!Om`zW-BgQ+y;g2bCgAeuaLJ%hvRK3d>PDq-P}q?D?-m{MD-FtP_q#Od@B5?
z*&9R;0TO&g3T#3s3Lmf9iMD|;=%8LEXfuol-OI)m)EFUI;Y`qp*Y&I5NaHB+E3d|8
ztP-s_L(7pWioI}69ncV8n+l}WA5n*t)MJW=?Tg`5+q72rzHbm7g#Fc`%S5eK^mZ8U
zkD;^hLH}OrBB8rrFzl^AuheoDq_9UF!+K>n9e#!SLP@ZLI8?y)8DZnV7}{PrIUrNi
zQg!8pwRh^?xP`gm4I+@|v-}KyUR*&uO~f20<`^+Q5c3^2`O>m9o8si@43{JSV3F{P
ve7?xf6syc)l~t@Vih15nFjaXimiO`J`2$sLzM=@zWGq!>v0OPg6hr?3G_9oc
diff --git a/docs/build/html/_sources/introduction.rst.txt b/docs/build/html/_sources/introduction.rst.txt
index c07c65c..39e088a 100644
--- a/docs/build/html/_sources/introduction.rst.txt
+++ b/docs/build/html/_sources/introduction.rst.txt
@@ -29,12 +29,10 @@ Features
* Database backed
* Can be almost entirely keyboard driven
-* Playlist management
-* Easily add new tracks to playlists
-* Show multiple playlists on tabs
+* Open multiple playlists in tabs
* Play tracks from any playlist
* Add notes/comments to tracks on playlist
-* Automataic olour-coding of notes/comments according to content
+* Automatatic colour-coding of notes/comments according to content
* Preview tracks before playing to audience
* Time of day clock
* Elapsed track time counter
@@ -42,8 +40,8 @@ Features
* Time to run until track is silent
* Graphic of volume from 5 seconds (configurable) before fade until
track is silent
-* Ability to hide played tracks in playlist
-* Buttone to drop playout volume by 3dB for talkover
+* Optionally hide played tracks in playlist
+* Button to drop playout volume by 3dB for talkover
* Playlist displays:
* Title
* Artist
@@ -51,18 +49,18 @@ Features
* Estimated start time of track
* Estimated end time of track
* When track was last played
- * Bits per second (bps bitrate) of track
- * Length of silence in recording before music starts
+ * Bits per second (bitrate) of track
+ * Length of leading silence in recording before track starts
* Total track length of arbitrary sections of tracks
* Commands that are sent to OBS Studio (eg, for automated scene
changes)
* Playlist templates
-* Move selected/unplayed tracks between playlists
-* Down CSV of played tracks between arbitrary dates/times
+* Move selected or unplayed tracks between playlists
+* Download CSV of tracks played between arbitrary dates/times
* Search for tracks by title or artist
-* Automatic search of current/next track in Wikipedia
-* Optional search of selected track in Wikipedia
-* Optional search of selected track in Songfacts
+* Automatic search of current and next track in Wikipedia
+* Optional search of any track in Wikipedia
+* Optional search of any track in Songfacts
Requirements
diff --git a/docs/build/html/introduction.html b/docs/build/html/introduction.html
index 706bc74..b558719 100644
--- a/docs/build/html/introduction.html
+++ b/docs/build/html/introduction.html
@@ -221,12 +221,10 @@ production of live internet radio shows.
Database backed
Can be almost entirely keyboard driven
-Playlist management
-Easily add new tracks to playlists
-Show multiple playlists on tabs
+Open multiple playlists in tabs
Play tracks from any playlist
Add notes/comments to tracks on playlist
-Automataic olour-coding of notes/comments according to content
+Automatatic colour-coding of notes/comments according to content
Preview tracks before playing to audience
Time of day clock
Elapsed track time counter
@@ -234,8 +232,8 @@ production of live internet radio shows.
Time to run until track is silent
Graphic of volume from 5 seconds (configurable) before fade until
track is silent
-Ability to hide played tracks in playlist
-Buttone to drop playout volume by 3dB for talkover
+Optionally hide played tracks in playlist
+Button to drop playout volume by 3dB for talkover
- Playlist displays:
Title
@@ -244,8 +242,8 @@ track is silent
Estimated start time of track
Estimated end time of track
When track was last played
-Bits per second (bps bitrate) of track
-Length of silence in recording before music starts
+Bits per second (bitrate) of track
+Length of leading silence in recording before track starts
Total track length of arbitrary sections of tracks
Commands that are sent to OBS Studio (eg, for automated scene
changes)
@@ -254,12 +252,12 @@ changes)
Playlist templates
-Move selected/unplayed tracks between playlists
-Down CSV of played tracks between arbitrary dates/times
+Move selected or unplayed tracks between playlists
+Download CSV of tracks played between arbitrary dates/times
Search for tracks by title or artist
-Automatic search of current/next track in Wikipedia
-Optional search of selected track in Wikipedia
-Optional search of selected track in Songfacts
+Automatic search of current and next track in Wikipedia
+Optional search of any track in Wikipedia
+Optional search of any track in Songfacts
diff --git a/docs/build/html/searchindex.js b/docs/build/html/searchindex.js
index 4f93a78..6ba8009 100644
--- a/docs/build/html/searchindex.js
+++ b/docs/build/html/searchindex.js
@@ -1 +1 @@
-Search.setIndex({"docnames": ["development", "index", "installation", "introduction", "reference", "tutorial"], "filenames": ["development.rst", "index.rst", "installation.rst", "introduction.rst", "reference.rst", "tutorial.rst"], "titles": ["Development", "Welcome to MusicMuster\u2019s documentation!", "Installation", "Introduction", "Reference", "Tutorial"], "terms": {"index": 1, "modul": 1, "search": [1, 3], "page": [1, 3], "i": 1, "music": [1, 3], "player": [1, 3], "target": 1, "product": [1, 3], "live": [1, 3], "internet": [1, 3], "radio": [1, 3], "show": [1, 3], "content": 3, "introduct": 1, "instal": [1, 3], "tutori": 1, "refer": 1, "develop": 1, "why": 1, "what": 1, "featur": 1, "requir": 1, "feedback": 1, "bug": 1, "etc": 1, "In": 3, "januari": 3, "2022": 3, "start": 3, "my": 3, "mixcloud": 3, "http": 3, "www": 3, "com": 3, "keithsmusicbox": 3, "until": 3, "had": 3, "been": 3, "an": 3, "station": 3, "which": 3, "me": 3, "us": 3, "window": 3, "playout": 3, "system": 3, "As": 3, "onli": 3, "linux": 3, "set": 3, "up": 3, "pc": 3, "specif": 3, "purpos": 3, "The": 3, "felt": 3, "were": 3, "shortcom": 3, "variou": 3, "area": 3, "onc": 3, "move": 3, "equival": 3, "didn": 3, "t": 3, "have": 3, "same": 3, "wa": 3, "unabl": 3, "find": 3, "one": 3, "met": 3, "criteria": 3, "decid": 3, "see": 3, "how": 3, "practic": 3, "would": 3, "write": 3, "own": 3, "born": 3, "It": 3, "base": 3, "whilst": 3, "could": 3, "gener": 3, "home": 3, "ar": 3, "much": 3, "better": 3, "applic": 3, "role": 3, "ha": 3, "design": 3, "support": 3, "databas": 3, "back": 3, "can": 3, "almost": 3, "entir": 3, "keyboard": 3, "driven": 3, "playlist": 3, "manag": 3, "easili": 3, "add": 3, "new": 3, "track": 3, "multipl": 3, "tab": 3, "plai": 3, "from": 3, "ani": 3, "note": 3, "comment": 3, "automata": 3, "olour": 3, "code": 3, "accord": 3, "preview": 3, "befor": 3, "audienc": 3, "time": 3, "dai": 3, "clock": 3, "elaps": 3, "counter": 3, "run": 3, "fade": 3, "silent": 3, "graphic": 3, "volum": 3, "5": 3, "second": 3, "configur": 3, "abil": 3, "hide": 3, "button": 3, "drop": 3, "3db": 3, "talkov": 3, "titl": 3, "artist": 3, "length": 3, "mm": 3, "ss": 3, "estim": 3, "end": 3, "when": 3, "last": 3, "bit": 3, "per": 3, "bp": 3, "bitrat": 3, "silenc": 3, "record": 3, "total": 3, "arbitrari": 3, "section": 3, "command": 3, "sent": 3, "ob": 3, "studio": 3, "eg": 3, "autom": 3, "scene": 3, "chang": 3, "templat": 3, "select": 3, "unplai": 3, "between": 3, "down": 3, "csv": 3, "date": 3, "automat": 3, "current": 3, "next": 3, "wikipedia": 3, "option": 3, "songfact": 3, "displai": 3, "test": 3, "debian": 3, "12": 3, "bookworm": 3, "howev": 3, "should": 3, "most": 3, "contemporari": 3, "explain": 3, "build": 3, "its": 3, "environ": 3, "automatc": 3, "all": 3, "except": 3, "version": 3, "mariadb": 3, "10": 3, "11": 3, "recent": 3, "suffic": 3, "python": 3, "3": 3, "8": 3, "later": 3, "pleas": 3, "send": 3, "keith": 3, "midnighthax": 3, "edmund": 3, "juli": 3, "2023": 3}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"welcom": 1, "musicmust": [1, 3], "": 1, "document": 1, "indic": 1, "tabl": 1, "content": 1, "develop": 0, "instal": 2, "introduct": 3, "refer": 4, "tutori": 5, "why": 3, "what": 3, "featur": 3, "requir": 3, "feedback": 3, "bug": 3, "etc": 3, "i": 3}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 57}, "alltitles": {"Welcome to MusicMuster\u2019s documentation!": [[1, "welcome-to-musicmuster-s-documentation"]], "Contents": [[1, "contents"]], "Indices and tables": [[1, "indices-and-tables"]], "Development": [[0, "development"]], "Installation": [[2, "installation"]], "Reference": [[4, "reference"]], "Tutorial": [[5, "tutorial"]], "Introduction": [[3, "introduction"]], "Why MusicMuster?": [[3, "why-musicmuster"]], "What is MusicMuster?": [[3, "what-is-musicmuster"]], "Features": [[3, "features"]], "Requirements": [[3, "requirements"]], "Feedback, bugs, etc": [[3, "feedback-bugs-etc"]]}, "indexentries": {}})
\ No newline at end of file
+Search.setIndex({"docnames": ["development", "index", "installation", "introduction", "reference", "tutorial"], "filenames": ["development.rst", "index.rst", "installation.rst", "introduction.rst", "reference.rst", "tutorial.rst"], "titles": ["Development", "Welcome to MusicMuster\u2019s documentation!", "Installation", "Introduction", "Reference", "Tutorial"], "terms": {"index": 1, "modul": 1, "search": [1, 3], "page": [1, 3], "i": 1, "music": [1, 3], "player": [1, 3], "target": 1, "product": [1, 3], "live": [1, 3], "internet": [1, 3], "radio": [1, 3], "show": [1, 3], "content": 3, "introduct": 1, "instal": [1, 3], "tutori": 1, "refer": 1, "develop": 1, "why": 1, "what": 1, "featur": 1, "requir": 1, "feedback": 1, "bug": 1, "etc": 1, "In": 3, "januari": 3, "2022": 3, "start": 3, "my": 3, "mixcloud": 3, "http": 3, "www": 3, "com": 3, "keithsmusicbox": 3, "until": 3, "had": 3, "been": 3, "an": 3, "station": 3, "which": 3, "me": 3, "us": 3, "window": 3, "playout": 3, "system": 3, "As": 3, "onli": 3, "linux": 3, "set": 3, "up": 3, "pc": 3, "specif": 3, "purpos": 3, "The": 3, "felt": 3, "were": 3, "shortcom": 3, "variou": 3, "area": 3, "onc": 3, "move": 3, "equival": 3, "didn": 3, "t": 3, "have": 3, "same": 3, "wa": 3, "unabl": 3, "find": 3, "one": 3, "met": 3, "criteria": 3, "decid": 3, "see": 3, "how": 3, "practic": 3, "would": 3, "write": 3, "own": 3, "born": 3, "It": 3, "base": 3, "whilst": 3, "could": 3, "gener": 3, "home": 3, "ar": 3, "much": 3, "better": 3, "applic": 3, "role": 3, "ha": 3, "design": 3, "support": 3, "databas": 3, "back": 3, "can": 3, "almost": 3, "entir": 3, "keyboard": 3, "driven": 3, "playlist": 3, "manag": [], "easili": [], "add": 3, "new": [], "track": 3, "multipl": 3, "tab": 3, "plai": 3, "from": 3, "ani": 3, "note": 3, "comment": 3, "automata": [], "olour": [], "code": 3, "accord": 3, "preview": 3, "befor": 3, "audienc": 3, "time": 3, "dai": 3, "clock": 3, "elaps": 3, "counter": 3, "run": 3, "fade": 3, "silent": 3, "graphic": 3, "volum": 3, "5": 3, "second": 3, "configur": 3, "abil": [], "hide": 3, "button": 3, "drop": 3, "3db": 3, "talkov": 3, "titl": 3, "artist": 3, "length": 3, "mm": 3, "ss": 3, "estim": 3, "end": 3, "when": 3, "last": 3, "bit": 3, "per": 3, "bp": [], "bitrat": 3, "silenc": 3, "record": 3, "total": 3, "arbitrari": 3, "section": 3, "command": 3, "sent": 3, "ob": 3, "studio": 3, "eg": 3, "autom": 3, "scene": 3, "chang": 3, "templat": 3, "select": 3, "unplai": 3, "between": 3, "down": [], "csv": 3, "date": 3, "automat": 3, "current": 3, "next": 3, "wikipedia": 3, "option": 3, "songfact": 3, "displai": 3, "test": 3, "debian": 3, "12": 3, "bookworm": 3, "howev": 3, "should": 3, "most": 3, "contemporari": 3, "explain": 3, "build": 3, "its": 3, "environ": 3, "automatc": 3, "all": 3, "except": 3, "version": 3, "mariadb": 3, "10": 3, "11": 3, "recent": 3, "suffic": 3, "python": 3, "3": 3, "8": 3, "later": 3, "pleas": 3, "send": 3, "keith": 3, "midnighthax": 3, "edmund": 3, "juli": 3, "2023": 3, "open": 3, "automatat": 3, "colour": 3, "lead": 3, "download": 3}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"welcom": 1, "musicmust": [1, 3], "": 1, "document": 1, "indic": 1, "tabl": 1, "content": 1, "develop": 0, "instal": 2, "introduct": 3, "refer": 4, "tutori": 5, "why": 3, "what": 3, "featur": 3, "requir": 3, "feedback": 3, "bug": 3, "etc": 3, "i": 3}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 57}, "alltitles": {"Welcome to MusicMuster\u2019s documentation!": [[1, "welcome-to-musicmuster-s-documentation"]], "Contents": [[1, "contents"]], "Indices and tables": [[1, "indices-and-tables"]], "Development": [[0, "development"]], "Installation": [[2, "installation"]], "Reference": [[4, "reference"]], "Tutorial": [[5, "tutorial"]], "Introduction": [[3, "introduction"]], "Why MusicMuster?": [[3, "why-musicmuster"]], "What is MusicMuster?": [[3, "what-is-musicmuster"]], "Features": [[3, "features"]], "Requirements": [[3, "requirements"]], "Feedback, bugs, etc": [[3, "feedback-bugs-etc"]]}, "indexentries": {}})
\ No newline at end of file
diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst
index eea2e94..39e088a 100644
--- a/docs/source/introduction.rst
+++ b/docs/source/introduction.rst
@@ -29,11 +29,10 @@ Features
* Database backed
* Can be almost entirely keyboard driven
-* Easily add new tracks to playlists
-* Show multiple playlists on tabs
+* Open multiple playlists in tabs
* Play tracks from any playlist
* Add notes/comments to tracks on playlist
-* Automataic olour-coding of notes/comments according to content
+* Automatatic colour-coding of notes/comments according to content
* Preview tracks before playing to audience
* Time of day clock
* Elapsed track time counter
@@ -41,8 +40,8 @@ Features
* Time to run until track is silent
* Graphic of volume from 5 seconds (configurable) before fade until
track is silent
-* Ability to hide played tracks in playlist
-* Buttone to drop playout volume by 3dB for talkover
+* Optionally hide played tracks in playlist
+* Button to drop playout volume by 3dB for talkover
* Playlist displays:
* Title
* Artist
@@ -50,18 +49,18 @@ Features
* Estimated start time of track
* Estimated end time of track
* When track was last played
- * Bits per second (bps bitrate) of track
- * Length of silence in recording before music starts
+ * Bits per second (bitrate) of track
+ * Length of leading silence in recording before track starts
* Total track length of arbitrary sections of tracks
* Commands that are sent to OBS Studio (eg, for automated scene
changes)
* Playlist templates
-* Move selected/unplayed tracks between playlists
-* Down CSV of played tracks between arbitrary dates/times
+* Move selected or unplayed tracks between playlists
+* Download CSV of tracks played between arbitrary dates/times
* Search for tracks by title or artist
-* Automatic search of current/next track in Wikipedia
-* Optional search of selected track in Wikipedia
-* Optional search of selected track in Songfacts
+* Automatic search of current and next track in Wikipedia
+* Optional search of any track in Wikipedia
+* Optional search of any track in Songfacts
Requirements
diff --git a/poetry.lock b/poetry.lock
index bbc334e..60cb759 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -14,14 +14,14 @@ files = [
[[package]]
name = "alembic"
-version = "1.10.3"
+version = "1.11.1"
description = "A database migration tool for SQLAlchemy."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
- {file = "alembic-1.10.3-py3-none-any.whl", hash = "sha256:b2e0a6cfd3a8ce936a1168320bcbe94aefa3f4463cd773a968a55071beb3cd37"},
- {file = "alembic-1.10.3.tar.gz", hash = "sha256:32a69b13a613aeb7e8093f242da60eff9daed13c0df02fff279c1b06c32965d2"},
+ {file = "alembic-1.11.1-py3-none-any.whl", hash = "sha256:dc871798a601fab38332e38d6ddb38d5e734f60034baeb8e2db5b642fccd8ab8"},
+ {file = "alembic-1.11.1.tar.gz", hash = "sha256:6a810a6b012c88b33458fceb869aef09ac75d6ace5291915ba7fae44de372c01"},
]
[package.dependencies]
@@ -105,6 +105,56 @@ soupsieve = ">1.2"
html5lib = ["html5lib"]
lxml = ["lxml"]
+[[package]]
+name = "black"
+version = "23.3.0"
+description = "The uncompromising code formatter."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"},
+ {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"},
+ {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"},
+ {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"},
+ {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"},
+ {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"},
+ {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"},
+ {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"},
+ {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"},
+ {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"},
+ {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"},
+ {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"},
+ {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"},
+ {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"},
+ {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"},
+ {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"},
+ {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"},
+ {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"},
+ {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"},
+ {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"},
+ {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"},
+ {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"},
+ {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"},
+ {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"},
+ {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"},
+]
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.7.4)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
+
[[package]]
name = "certifi"
version = "2023.5.7"
@@ -202,6 +252,21 @@ files = [
{file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"},
]
+[[package]]
+name = "click"
+version = "8.1.3"
+description = "Composable command line interface toolkit"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+ {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
[[package]]
name = "colorama"
version = "0.4.6"
@@ -252,14 +317,14 @@ files = [
[[package]]
name = "exceptiongroup"
-version = "1.1.1"
+version = "1.1.2"
description = "Backport of PEP 654 (exception groups)"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
- {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"},
- {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"},
+ {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"},
+ {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"},
]
[package.extras]
@@ -282,20 +347,20 @@ tests = ["asttokens", "littleutils", "pytest", "rich"]
[[package]]
name = "flake8"
-version = "6.0.0"
+version = "3.9.0"
description = "the modular source code checker: pep8 pyflakes and co"
category = "dev"
optional = false
-python-versions = ">=3.8.1"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
files = [
- {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"},
- {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"},
+ {file = "flake8-3.9.0-py2.py3-none-any.whl", hash = "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff"},
+ {file = "flake8-3.9.0.tar.gz", hash = "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0"},
]
[package.dependencies]
-mccabe = ">=0.7.0,<0.8.0"
-pycodestyle = ">=2.10.0,<2.11.0"
-pyflakes = ">=3.0.0,<3.1.0"
+mccabe = ">=0.6.0,<0.7.0"
+pycodestyle = ">=2.7.0,<2.8.0"
+pyflakes = ">=2.3.0,<2.4.0"
[[package]]
name = "flakehell"
@@ -488,14 +553,14 @@ tomli = {version = "*", markers = "python_version > \"3.6\" and python_version <
[[package]]
name = "ipython"
-version = "8.12.0"
+version = "8.14.0"
description = "IPython: Productive Interactive Computing"
category = "dev"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
files = [
- {file = "ipython-8.12.0-py3-none-any.whl", hash = "sha256:1c183bf61b148b00bcebfa5d9b39312733ae97f6dad90d7e9b4d86c8647f498c"},
- {file = "ipython-8.12.0.tar.gz", hash = "sha256:a950236df04ad75b5bc7f816f9af3d74dc118fd42f2ff7e80e8e60ca1f182e2d"},
+ {file = "ipython-8.14.0-py3-none-any.whl", hash = "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf"},
+ {file = "ipython-8.14.0.tar.gz", hash = "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1"},
]
[package.dependencies]
@@ -649,14 +714,14 @@ testing = ["pytest"]
[[package]]
name = "markdown-it-py"
-version = "2.2.0"
+version = "3.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"},
- {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"},
+ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
+ {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
]
[package.dependencies]
@@ -669,67 +734,67 @@ compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0
linkify = ["linkify-it-py (>=1,<3)"]
plugins = ["mdit-py-plugins"]
profiling = ["gprof2dot"]
-rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
+rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "markupsafe"
-version = "2.1.2"
+version = "2.1.3"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
- {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"},
- {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"},
- {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"},
- {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"},
- {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"},
- {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"},
- {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"},
- {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"},
- {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"},
- {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"},
- {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"},
- {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"},
- {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"},
- {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"},
- {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"},
- {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"},
- {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"},
- {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"},
- {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"},
- {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"},
- {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"},
- {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"},
- {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"},
- {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"},
- {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"},
- {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"},
- {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"},
- {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"},
- {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"},
- {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"},
- {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"},
- {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"},
- {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"},
- {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"},
- {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"},
- {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"},
- {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"},
- {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"},
- {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"},
- {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"},
- {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"},
- {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"},
- {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"},
- {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"},
- {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"},
- {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"},
- {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"},
- {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"},
- {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"},
- {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
+ {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
+ {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
+ {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
+ {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
+ {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
+ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
]
[[package]]
@@ -749,14 +814,14 @@ traitlets = "*"
[[package]]
name = "mccabe"
-version = "0.7.0"
+version = "0.6.1"
description = "McCabe checker, plugin for flake8"
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = "*"
files = [
- {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
- {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
+ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
+ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
[[package]]
@@ -848,69 +913,66 @@ files = [
[[package]]
name = "mysqlclient"
-version = "2.1.1"
+version = "2.2.0"
description = "Python interface to MySQL"
category = "main"
optional = false
-python-versions = ">=3.5"
-files = [
- {file = "mysqlclient-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1ed71bd6244993b526113cca3df66428609f90e4652f37eb51c33496d478b37"},
- {file = "mysqlclient-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:c812b67e90082a840efb82a8978369e6e69fc62ce1bda4ca8f3084a9d862308b"},
- {file = "mysqlclient-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0d1cd3a5a4d28c222fa199002810e8146cffd821410b67851af4cc80aeccd97c"},
- {file = "mysqlclient-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b355c8b5a7d58f2e909acdbb050858390ee1b0e13672ae759e5e784110022994"},
- {file = "mysqlclient-2.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:996924f3483fd36a34a5812210c69e71dea5a3d5978d01199b78b7f6d485c855"},
- {file = "mysqlclient-2.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dea88c8d3f5a5d9293dfe7f087c16dd350ceb175f2f6631c9cf4caf3e19b7a96"},
- {file = "mysqlclient-2.1.1.tar.gz", hash = "sha256:828757e419fb11dd6c5ed2576ec92c3efaa93a0f7c39e263586d1ee779c3d782"},
-]
-
-[[package]]
-name = "numpy"
-version = "1.24.3"
-description = "Fundamental package for array computing in Python"
-category = "main"
-optional = false
python-versions = ">=3.8"
files = [
- {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"},
- {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"},
- {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"},
- {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"},
- {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"},
- {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"},
- {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"},
- {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"},
- {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"},
- {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"},
- {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"},
- {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"},
- {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"},
- {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"},
- {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"},
- {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"},
- {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"},
- {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"},
- {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"},
- {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"},
- {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"},
- {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"},
- {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"},
- {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"},
- {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"},
- {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"},
- {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"},
- {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"},
+ {file = "mysqlclient-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:68837b6bb23170acffb43ae411e47533a560b6360c06dac39aa55700972c93b2"},
+ {file = "mysqlclient-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5670679ff1be1cc3fef0fa81bf39f0cd70605ba121141050f02743eb878ac114"},
+ {file = "mysqlclient-2.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:004fe1d30d2c2ff8072f8ea513bcec235fd9b896f70dad369461d0ad7e570e98"},
+ {file = "mysqlclient-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9c6b142836c7dba4f723bf9c93cc46b6e5081d65b2af807f400dda9eb85a16d0"},
+ {file = "mysqlclient-2.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:955dba905a7443ce4788c63fdb9f8d688316260cf60b20ff51ac3b1c77616ede"},
+ {file = "mysqlclient-2.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:530ece9995a36cadb6211b9787f0c9e05cdab6702549bdb4236af5e9b535ed6a"},
+ {file = "mysqlclient-2.2.0.tar.gz", hash = "sha256:04368445f9c487d8abb7a878e3d23e923e6072c04a6c320f9e0dc8a82efba14e"},
+]
+
+[[package]]
+name = "numpy"
+version = "1.25.0"
+description = "Fundamental package for array computing in Python"
+category = "main"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "numpy-1.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8aa130c3042052d656751df5e81f6d61edff3e289b5994edcf77f54118a8d9f4"},
+ {file = "numpy-1.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e3f2b96e3b63c978bc29daaa3700c028fe3f049ea3031b58aa33fe2a5809d24"},
+ {file = "numpy-1.25.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6b267f349a99d3908b56645eebf340cb58f01bd1e773b4eea1a905b3f0e4208"},
+ {file = "numpy-1.25.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4aedd08f15d3045a4e9c648f1e04daca2ab1044256959f1f95aafeeb3d794c16"},
+ {file = "numpy-1.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d183b5c58513f74225c376643234c369468e02947b47942eacbb23c1671f25d"},
+ {file = "numpy-1.25.0-cp310-cp310-win32.whl", hash = "sha256:d76a84998c51b8b68b40448ddd02bd1081bb33abcdc28beee6cd284fe11036c6"},
+ {file = "numpy-1.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0dc071017bc00abb7d7201bac06fa80333c6314477b3d10b52b58fa6a6e38f6"},
+ {file = "numpy-1.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c69fe5f05eea336b7a740e114dec995e2f927003c30702d896892403df6dbf0"},
+ {file = "numpy-1.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c7211d7920b97aeca7b3773a6783492b5b93baba39e7c36054f6e749fc7490c"},
+ {file = "numpy-1.25.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecc68f11404930e9c7ecfc937aa423e1e50158317bf67ca91736a9864eae0232"},
+ {file = "numpy-1.25.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e559c6afbca484072a98a51b6fa466aae785cfe89b69e8b856c3191bc8872a82"},
+ {file = "numpy-1.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6c284907e37f5e04d2412950960894b143a648dea3f79290757eb878b91acbd1"},
+ {file = "numpy-1.25.0-cp311-cp311-win32.whl", hash = "sha256:95367ccd88c07af21b379be1725b5322362bb83679d36691f124a16357390153"},
+ {file = "numpy-1.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:b76aa836a952059d70a2788a2d98cb2a533ccd46222558b6970348939e55fc24"},
+ {file = "numpy-1.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b792164e539d99d93e4e5e09ae10f8cbe5466de7d759fc155e075237e0c274e4"},
+ {file = "numpy-1.25.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7cd981ccc0afe49b9883f14761bb57c964df71124dcd155b0cba2b591f0d64b9"},
+ {file = "numpy-1.25.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa48bebfb41f93043a796128854b84407d4df730d3fb6e5dc36402f5cd594c0"},
+ {file = "numpy-1.25.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5177310ac2e63d6603f659fadc1e7bab33dd5a8db4e0596df34214eeab0fee3b"},
+ {file = "numpy-1.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0ac6edfb35d2a99aaf102b509c8e9319c499ebd4978df4971b94419a116d0790"},
+ {file = "numpy-1.25.0-cp39-cp39-win32.whl", hash = "sha256:7412125b4f18aeddca2ecd7219ea2d2708f697943e6f624be41aa5f8a9852cc4"},
+ {file = "numpy-1.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:26815c6c8498dc49d81faa76d61078c4f9f0859ce7817919021b9eba72b425e3"},
+ {file = "numpy-1.25.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b1b90860bf7d8a8c313b372d4f27343a54f415b20fb69dd601b7efe1029c91e"},
+ {file = "numpy-1.25.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85cdae87d8c136fd4da4dad1e48064d700f63e923d5af6c8c782ac0df8044542"},
+ {file = "numpy-1.25.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cc3fda2b36482891db1060f00f881c77f9423eead4c3579629940a3e12095fe8"},
+ {file = "numpy-1.25.0.tar.gz", hash = "sha256:f1accae9a28dc3cda46a91de86acf69de0d1b5f4edd44a9b0c3ceb8036dfff19"},
]
[[package]]
name = "obsws-python"
-version = "1.4.2"
+version = "1.5.2"
description = "A Python SDK for OBS Studio WebSocket v5.0"
category = "main"
optional = false
python-versions = ">=3.9"
files = [
- {file = "obsws_python-1.4.2-py3-none-any.whl", hash = "sha256:e33360b02f5c1525ed37d9034defef7faa15d4496648be77f0d44ac877afa044"},
- {file = "obsws_python-1.4.2.tar.gz", hash = "sha256:46834536b5f690004df66e05245353482bb095cddc583c8b089918bd5d0bef1f"},
+ {file = "obsws_python-1.5.2-py3-none-any.whl", hash = "sha256:f0985de4fc17d85f8b46d4950f3d0757cad6c7ba362b0d1eea31ad71d5fe9f84"},
+ {file = "obsws_python-1.5.2.tar.gz", hash = "sha256:c94775621ae4acab4292a3cee73e198f152775b85f63df569806c61dc8564a19"},
]
[package.dependencies]
@@ -922,14 +984,14 @@ dev = ["black", "isort", "pytest", "pytest-randomly"]
[[package]]
name = "packaging"
-version = "23.0"
+version = "23.1"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
- {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
- {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
+ {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
+ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
]
[[package]]
@@ -948,6 +1010,18 @@ files = [
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
testing = ["docopt", "pytest (<6.0.0)"]
+[[package]]
+name = "pathspec"
+version = "0.11.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
+ {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
+]
+
[[package]]
name = "pexpect"
version = "4.8.0"
@@ -975,16 +1049,32 @@ files = [
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
]
+[[package]]
+name = "platformdirs"
+version = "3.8.0"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"},
+ {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"},
+]
+
+[package.extras]
+docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"]
+
[[package]]
name = "pluggy"
-version = "1.0.0"
+version = "1.2.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
files = [
- {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
- {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
+ {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"},
+ {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"},
]
[package.extras]
@@ -993,14 +1083,14 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "prompt-toolkit"
-version = "3.0.38"
+version = "3.0.39"
description = "Library for building powerful interactive command lines in Python"
category = "dev"
optional = false
python-versions = ">=3.7.0"
files = [
- {file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"},
- {file = "prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"},
+ {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"},
+ {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"},
]
[package.dependencies]
@@ -1008,26 +1098,26 @@ wcwidth = "*"
[[package]]
name = "psutil"
-version = "5.9.4"
+version = "5.9.5"
description = "Cross-platform lib for process and system monitoring in Python."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
- {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"},
- {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"},
- {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"},
- {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"},
- {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"},
- {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"},
- {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"},
- {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"},
- {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"},
- {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"},
- {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"},
- {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"},
- {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"},
- {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"},
+ {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"},
+ {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"},
+ {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"},
+ {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"},
+ {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"},
+ {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"},
+ {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"},
+ {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"},
+ {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"},
+ {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"},
+ {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"},
+ {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"},
+ {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"},
+ {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"},
]
[package.extras]
@@ -1083,14 +1173,14 @@ tests = ["pytest"]
[[package]]
name = "pycodestyle"
-version = "2.10.0"
+version = "2.7.0"
description = "Python style guide checker"
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
- {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"},
- {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"},
+ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
+ {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
]
[[package]]
@@ -1134,14 +1224,14 @@ rich = "*"
[[package]]
name = "pyflakes"
-version = "3.0.1"
+version = "2.3.1"
description = "passive checker of Python programs"
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
- {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"},
- {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"},
+ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
+ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
]
[[package]]
@@ -1158,92 +1248,79 @@ files = [
[[package]]
name = "pygame"
-version = "2.4.0"
+version = "2.5.0"
description = "Python Game Development"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
- {file = "pygame-2.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:63591f381e5b28b90e115ac7a96f8ce5ecb917facb42d79d4f89714f89cc6d8e"},
- {file = "pygame-2.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:13511c7c29f0fc23636f3b95a96ab45f964e84073e7e27dc602a479cd274d89a"},
- {file = "pygame-2.4.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1b7201a44869eb420dd56c8e003251c9e7422c5304b3e78508e767a5634ab31b"},
- {file = "pygame-2.4.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3ab14e06c302921f33d25180813711a920acef386d3992fc21731d2d5e8e86f0"},
- {file = "pygame-2.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:418659c2d42f6a2356e2691006d79b6e07fd4992f9e904a2638c51c992f3e41b"},
- {file = "pygame-2.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e70fd71e0321a805001192e08ae4af45b86c68f155670230c3f6f4dd25089e70"},
- {file = "pygame-2.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab8115af26a9e95f39b08fff416f097803480f265500b218a5ca065d6e73124f"},
- {file = "pygame-2.4.0-cp310-cp310-win32.whl", hash = "sha256:4ffec9661731fb674ccc88d1de92709219047af3d8198d0e6203c21f3f1b54a7"},
- {file = "pygame-2.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:bb5a39320d00fa781959d2d0151e6f0293dd1398c6dc9dc934112ecce7b4fb52"},
- {file = "pygame-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:23543e2d206d8de7d6db4f7b1c74e6fea6c01ead63caf7252e63341e1cdb09f6"},
- {file = "pygame-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b562cfdd8caa76ba47ca2a9211fee6b0a95ceb95c9da94cf60a3909b2300854"},
- {file = "pygame-2.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:751bc57e4c3b7cd92762344562dcbd405e2b54488de1d7a1e083a470bdbc5ae9"},
- {file = "pygame-2.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9b1127f085d09c7c0a976d440e8fc2f7adc579d62abcfc20c23c2699bbe2dc1"},
- {file = "pygame-2.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47fd096ceb68d966681f8d0e820f7305bf05b30421ca562cfdb3c89a5aef26e5"},
- {file = "pygame-2.4.0-cp311-cp311-win32.whl", hash = "sha256:de963a4b8787d93a9fba8f4052d9dde8b12adbeac5781e48035be1557dfadb20"},
- {file = "pygame-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:75ef535ebe541b74a160bb59c3e520f543250d19f69d5973350ec1b9706e1469"},
- {file = "pygame-2.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ca2ecc65126eaaa5b8e6a119913cfb2c2b1ed4c8ee1b980baf333aa9d379f227"},
- {file = "pygame-2.4.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a98ed8c47e367b9233b5ca25c36c2b45ab61959ac543195f0b6349f0a599ec8"},
- {file = "pygame-2.4.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00ecd4688ee25d5d4cf48eddab18749a9bb2b382772f7fa8a987e4d21026c603"},
- {file = "pygame-2.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99c296ecb8ce6ea1f404f4d174fdb215c64515827778525301c29ddf6f8e6e07"},
- {file = "pygame-2.4.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6b94fc99487ce4a45ce00fa9145f4861f6e021254a005101d00bc17a4bb4f5c"},
- {file = "pygame-2.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3020fb98f27a6ea79418a5b332ca07be93884e4a455c8a0a31b2dcf39ee2d96"},
- {file = "pygame-2.4.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f97c8be81b3262ad8dae982485c4a73c9f2374614dfc0db8eb0f754badb29d6"},
- {file = "pygame-2.4.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:be7f948d33d0536c2922289e6f5983251cb0bd0d727db6ff692886c239f47a2c"},
- {file = "pygame-2.4.0-cp36-cp36m-win32.whl", hash = "sha256:a66b314f4a637784d5ca2970745bb2e6e554447dce8f4cfedd9b9fcef5e3ffc6"},
- {file = "pygame-2.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c09323eeae9e0cb2ced0cb3635485ae17f4f1b2b6b908a494ce2d830c609d4be"},
- {file = "pygame-2.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4241e1da3852a955d37a22157ed51b2d30a65f7987eac9d162bb302fb754d87"},
- {file = "pygame-2.4.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:802b03f6c367359c94eb6a90169666efa1aa1d6e24fce37a0b21642ccdfe48cf"},
- {file = "pygame-2.4.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:79b0962a8f09788ca735532cfcf2dd053424fe5eabbda564b74f8e5e2eb11f48"},
- {file = "pygame-2.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:627c8bb007a757da18d32c5d9b7ac50ab0356d9e922d570b0572765778080787"},
- {file = "pygame-2.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f576403c2b14f0eea694365b9018d5bacac70b1550261ffc7a54a92e18967541"},
- {file = "pygame-2.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5210cb09ec31281e16fda008bf8dfe2e024eef58e075dd0c01935d0004fdfffd"},
- {file = "pygame-2.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6060d68d10fafd51c4cb3a7d687d741009881860dfd429c863e570877e2ce9de"},
- {file = "pygame-2.4.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6c0081546749c0b4341ce575622a4f8eee05f675d3a0872ab6aed6e5bd2ba5a8"},
- {file = "pygame-2.4.0-cp37-cp37m-win32.whl", hash = "sha256:fa2531f83e7c5f6f7cc20a1b4e0f982bd608aad81ff6c385148e64256ab0419f"},
- {file = "pygame-2.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:cff815181437add5f3d702e8c7f1d2aa4ed04ed04cde27ec809e7ac516ee6b5f"},
- {file = "pygame-2.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53bfdc1aea619fa8d347be37b08de87089d543375948aacf8b163b0c5eb6d4e4"},
- {file = "pygame-2.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7fa1e65fd559823997f39a592cb49d8c89dd31c8bbde8273fe3922e2c1f294f6"},
- {file = "pygame-2.4.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:65ee75e0e83e393fdc5c06e55e376c7511881a5ebee006ecca89cb1b3b41d6f1"},
- {file = "pygame-2.4.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fbcba1b06f42338fecbd366227025f81729d9f4a577677fd3cd1ceff34d7286a"},
- {file = "pygame-2.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04451e5addae3a078a7a4f494e6b217025f4813dfb364a49c250fc5dfd1d2e2"},
- {file = "pygame-2.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faa3b63b71d555e7a21cecc11c65e059d9cb1902158d863ac3592e1947bc521a"},
- {file = "pygame-2.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef14750fa60b47510cfe9c7c37e7abe67617f5d1f1a8ffa257a59d49836dadda"},
- {file = "pygame-2.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5a2aee4214e5efed2cb3650139010dd4d0b1c29a9760278ab259d0b46281b66a"},
- {file = "pygame-2.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:47be83060a9dbc79763fd230f04d53a29064b5f64d1b59425c432d3570b22623"},
- {file = "pygame-2.4.0-cp38-cp38-win32.whl", hash = "sha256:14492d8c0eaad778bb10b6d53eaea4ef77f4d3431b6b7c857397dc6cf4397ac9"},
- {file = "pygame-2.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:6ba967d0e3fed8611f1face6695dc8fa554ee543d300681f8080f5de9cc7da73"},
- {file = "pygame-2.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9f6b7a604812f447495829751dfe7ab57cb31c2c9acdb07ba4b7157490411a12"},
- {file = "pygame-2.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e2a3176b33b97ebae397f951d254e3155a0afe730e1b76fb35126555c27dd3b5"},
- {file = "pygame-2.4.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6ec870a63295ebff737640c4ef39868312e206dcba655b4ad5c7d0e8c2488b73"},
- {file = "pygame-2.4.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e75d8c2980d719045be366160568bf508cbbed21285efe32468c75abcd4cf8b3"},
- {file = "pygame-2.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e5d32def075e495b4802371fd8cda96ff4957aa39e215f83d87022dedf14cfb"},
- {file = "pygame-2.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cad74cbbefbdb81cb22a9ea22561614b8dc58fcd52cd54126bbb8ee9ee77a5d5"},
- {file = "pygame-2.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c75dd345707da622c78dbd6a63a025f7b89377ddc4e71ba40043929824f5d4"},
- {file = "pygame-2.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075c1282b1d307669c8ef29942564b91acb85623bedba3bfb841079d539ded31"},
- {file = "pygame-2.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1422673a2b485153cbc20dbbd37af791c9842ca98a1b7a89fe3ac115cce79805"},
- {file = "pygame-2.4.0-cp39-cp39-win32.whl", hash = "sha256:fb7bb86c4aedb4382d7f643ff7d21ab4731d59ddb9b448e78b9125ab1addc007"},
- {file = "pygame-2.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:93bb1406125ae9bd7a9bb0d45f11b30f157ea8d2efee1ebe9d781b1d1a9fce6b"},
- {file = "pygame-2.4.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:2946c151691c80ffb9f3f39e1f294d7ed9edaae1814e528d2f5b4751e7e6d903"},
- {file = "pygame-2.4.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80502eb26483b0206d0508475ec7d67a86bc0afc5bb4aad3a6172a7a85a27554"},
- {file = "pygame-2.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9c8bb7b77f97eb49dac900445fbf96a332d2072588949d6396581933843fb04"},
- {file = "pygame-2.4.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b6e1493724d29e46a0e7e8125d9808c9957c652db67afe9497d385509fc5ac5"},
- {file = "pygame-2.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43db3a6c9be3d94eececf7c86cde7584d2bb87f394ade40139c3b4e528fdff24"},
- {file = "pygame-2.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32ed4d4317bce8fe78592a7b5b4a07f2e0ff814e35c66cb5a3b398dae96c3f27"},
- {file = "pygame-2.4.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e5f043751840a07ff0160abe46ed42a88bc29baee93656abb5a050beda176306"},
- {file = "pygame-2.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:867cf19f1c7aa6f187a0a31b702f5668e935e700b46d94bd58e94ec8581cf081"},
- {file = "pygame-2.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a93d368311d40827dc5f0cad2a0e9a8700c1b017346808cfdfd9ea98aee45df"},
- {file = "pygame-2.4.0.tar.gz", hash = "sha256:e3603e70e96ee30af1954ce57d4922a059402f368013e7138e90f1c03d185267"},
+ {file = "pygame-2.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e34a2b5660acc298d0a66ce16f13a7ca1c56c2a685e40afef3a0cf6eaf3f44b3"},
+ {file = "pygame-2.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875dbde88b899fb7f48d6f0e87f70c3dcc8ee87a947c3df817d949a9741dbcf5"},
+ {file = "pygame-2.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:854dc9106210d1a3a83914af53fc234c0bed65a39f5e6098a8eb489da354ad0c"},
+ {file = "pygame-2.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e1898db0fd7b868a31c29204813f447c59390350fd806904d80bebde094f3f8"},
+ {file = "pygame-2.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22d5eac9b9936c7dc2813a750bc8efd53234ad1afc32eb99d6f64bb403c2b9aa"},
+ {file = "pygame-2.5.0-cp310-cp310-win32.whl", hash = "sha256:e9eed550b8921080a3c7524202822fc2cf7226e0ffd3c4e4d16510ba44b24e6f"},
+ {file = "pygame-2.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:93128beb1154c443f05a66bfbf3f1d4eb8659157ab3b45e4a0454e5905440431"},
+ {file = "pygame-2.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c71d5b3ec232113cbd2e23a19eb01eef1818db41892d0d5efbac3901f940da66"},
+ {file = "pygame-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b763062b1996de26a28600e7a8f138d5b36ba0ddd63c65ccbd06124cd95bab70"},
+ {file = "pygame-2.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b6a42109f922352c524565fceb22bf8f8b6e4b00d38306e6f5b4c673048f4a"},
+ {file = "pygame-2.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bcb19c8ee3fc794ab3a7cb5b5fb1ad38da6866dfbba4af3699a84a828c8a4b9"},
+ {file = "pygame-2.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c66b7abc38511c7ca08c5bb58a3bfc14fa51b4e5f85a1786777afc9e584a14dd"},
+ {file = "pygame-2.5.0-cp311-cp311-win32.whl", hash = "sha256:46cf1c9b20fb11c7d836c02dd5fc2ca843b699c0e2bc4130cf4ad2f855db5f7f"},
+ {file = "pygame-2.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:f7b77b5019a9a6342535f53c75cef912b218cd24e98505828418f135aacc0a1b"},
+ {file = "pygame-2.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ddec0c823fd0869fe4a75ba906dcb7889db0e0c289ce8c03d4ce0a67351ab66"},
+ {file = "pygame-2.5.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bae93ce29b8337a5e02507603094c51740c9f496272ef070e2624e9647776568"},
+ {file = "pygame-2.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c80a1ad38d11102b4dfa0519aa2a26fea534503b259872609acc9adb1860884e"},
+ {file = "pygame-2.5.0-cp312-cp312-win32.whl", hash = "sha256:8ffebcafda0add8072f82999498113be37494694fa36e02155cfaf1a0ba56fe2"},
+ {file = "pygame-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:574e310ba708da0c34b71c4158aa7cdca3cf3e16c4100dcd1d3c931a9c6705b4"},
+ {file = "pygame-2.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:275e4fab379620c3b262cd58c457eea80001e91bc2e04d306ddb0ba548c969bf"},
+ {file = "pygame-2.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c116a96a2784bd1724476dbf9c48bfea466ee493a736bdfa04ecbc3f193de0bc"},
+ {file = "pygame-2.5.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4a0787ade8723323a3ba874bb725010bb08990a77327fc85f42474f3a840447"},
+ {file = "pygame-2.5.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cb39b660da1b56a1704ec4aa72bac538030786e23607fb25b8a66f357ffe3a"},
+ {file = "pygame-2.5.0-cp36-cp36m-win32.whl", hash = "sha256:d051420667dd9fc8103b3cf994c03e46ee90b1c4a72c174737b8c14729ddf68e"},
+ {file = "pygame-2.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6b58356510b7c38836eb81cf08983b58f280da99580d4f17e89ed0ddb707c29c"},
+ {file = "pygame-2.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:80f167d8fcec7cd3107f829784ad721b1b7532c19fdf42b3aabbb51f7347850f"},
+ {file = "pygame-2.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef96e9a2d8fd9526b89657d192c42dd7c551bfa381fa98ec52d45443e9713818"},
+ {file = "pygame-2.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e21d53279fb504b267ae06b565b72d9f95ecbf1f2dd8c705329b287f38295d98"},
+ {file = "pygame-2.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147cc0256a5df1316590f351febf6205ef2907564fb0d902935834b91e183486"},
+ {file = "pygame-2.5.0-cp37-cp37m-win32.whl", hash = "sha256:e47696154d689180e4eea8c1d6f2bac923986119219db6ad0d2e60ab1f525e8c"},
+ {file = "pygame-2.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4c87fa8fa1f3ea94069119accd6d4b5fbf869c2b2954a19b45162dfb3b7c885e"},
+ {file = "pygame-2.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:143550078ab10f290cd7c8715a46853e0dc598fd6cdd1561ecb4d6e3116a6b26"},
+ {file = "pygame-2.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:969de806bed49b28972862acba652f05ece9420bbdf5f925c970c6a18a9fd1f9"},
+ {file = "pygame-2.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5d3b0da31ea341b86ef96d6b13c0ddcb25f5320186b7215bc870f08119d2f80"},
+ {file = "pygame-2.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23effd50121468f1dc41022230485bff515154191a9d343224850aa3ed3b7f0"},
+ {file = "pygame-2.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce4116db6924b544ff8ff03f7ef681c8baf9c6e039a1ec21e26b4ebdaa0e994"},
+ {file = "pygame-2.5.0-cp38-cp38-win32.whl", hash = "sha256:50a89c15412506d95e98792435f49a73101788db30ad9c562f611c7aa7b437fa"},
+ {file = "pygame-2.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:df1d8affdbe9f417cc7141581e3d08e4b09f708060d3127d2016fd591b2e4f68"},
+ {file = "pygame-2.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:99965da24d0bf138d9ac6b7494b9a12781c1510cf936616d1d0c46a736777f6a"},
+ {file = "pygame-2.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:daa81057c1beb71a9fb96253457197ad03ee988ba546a166f253bd92a98a9a11"},
+ {file = "pygame-2.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ca85da605f6621c99c05f272a5dcf85bf0badcdca45f16ff2bee9a9d41ae042"},
+ {file = "pygame-2.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:603d403997d46b07022097861c8b0ff76c6192f8a2f5f89f1a6a978d4411b715"},
+ {file = "pygame-2.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7babaeac11544f3e4d7a15756a27f943dc5fff276481fdc9d90415a903ad31a9"},
+ {file = "pygame-2.5.0-cp39-cp39-win32.whl", hash = "sha256:9d2126f91699223f0c36845d1c7b2cdfe2f1753ef85b8410ea613e8bd212ca33"},
+ {file = "pygame-2.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:8062adc409f0b2742d7996b9b470494025c5e3b73d0d03e3798708dcf5d195cd"},
+ {file = "pygame-2.5.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:1bd14adf6151b6ac2f617a8fd71621f1c125209c41a359d3c1cf4bf3904dba5f"},
+ {file = "pygame-2.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b11708f1c7b1671db15246275adcb18cf384f5f7e73532e26999968876c5099"},
+ {file = "pygame-2.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6830e431575697f7a11f7731798445242e37eb07ae9007f7be33083f700e9b1e"},
+ {file = "pygame-2.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7dd80addfdf7dc1f0e04f81c98acb96580726783172256f2ebc955a967e84124"},
+ {file = "pygame-2.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c2cbd9d1a0a3969d6e1c6b0741279c843b4a36ef3804d324874d0a2f0e49816"},
+ {file = "pygame-2.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef373b9865c740f18236f2324e17e7f2111e27c6a4a5b67c490c72a8a8b8de77"},
+ {file = "pygame-2.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3959038a3e2034cee3f15471786a3bac35baeaa1f7503dc7402bb49d25b5ddbc"},
+ {file = "pygame-2.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583d9c8ad033ad51da8485427139d047afb649f49e42d4fa88477f73734ad4b0"},
+ {file = "pygame-2.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b650e925d2e8c82c16bdeae6e7fc5d6ca4ac659a1412da4ecd923ef9d688cb"},
+ {file = "pygame-2.5.0.tar.gz", hash = "sha256:edd5745b79435976d92c0a7318aedcafcb7ac4567125ac6ba88aa473559ef9ab"},
]
[[package]]
name = "pygments"
-version = "2.14.0"
+version = "2.15.1"
description = "Pygments is a syntax highlighting package written in Python."
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
files = [
- {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"},
- {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"},
+ {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"},
+ {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"},
]
[package.extras]
@@ -1284,47 +1361,47 @@ files = [
[[package]]
name = "pyqt5-sip"
-version = "12.12.0"
+version = "12.12.1"
description = "The sip module support for PyQt5"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
- {file = "PyQt5_sip-12.12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e683dcbdc9e7d36d7ccba82cf20e4835b54bb212470a8467f1d4b620ddeef6a"},
- {file = "PyQt5_sip-12.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7ca01f0508782374ad3621296780fbb31ac3c6f6c8ba1e7051cc4c08354c46a3"},
- {file = "PyQt5_sip-12.12.0-cp310-cp310-win32.whl", hash = "sha256:7741a57bf7980ef16ee975ea9790f95616d532053e4a4f16bf35449333cbcc5b"},
- {file = "PyQt5_sip-12.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:660416500991c4eaf20730c2ec4897cd75b476fae9c80fe09fa0f98750693516"},
- {file = "PyQt5_sip-12.12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e77e683916f44b82f880155e0c13566ad285c5708c53287d67b4c2971c4579a3"},
- {file = "PyQt5_sip-12.12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e19fcd0536cb74eda5dfab24490cb20966d371069a95f81f56cb9de7c18c2bee"},
- {file = "PyQt5_sip-12.12.0-cp311-cp311-win32.whl", hash = "sha256:da0c653c3a9843e0bb3bdd43c773c07458a0ae78bd056ebcad0b59ce1aa91e1c"},
- {file = "PyQt5_sip-12.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:56d08c822a8b4f0950285d1b316d81c3b80bf3ba8d98efc035a205051f03a05d"},
- {file = "PyQt5_sip-12.12.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d763dde46d5754c44aed1fbd9ef030c0850b8b341834b4d274d64f8fb1b25a05"},
- {file = "PyQt5_sip-12.12.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21639b03429baae810119239592762da9a30fa0fe3b1c2e26f456cf1a77a3cc0"},
- {file = "PyQt5_sip-12.12.0-cp37-cp37m-win32.whl", hash = "sha256:d33ec957b9c104b56756cc10f82e544d41d64d0e2048c95ba1b64ef6d1ad55dc"},
- {file = "PyQt5_sip-12.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1b0203381b895097c41cd1ca6bba7d88fd0d2fa8c3dde6d55c1bb95141080f35"},
- {file = "PyQt5_sip-12.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:96ec94a7239f83e30b4c37a7f89c75df3504918a372d968e773532b5fbc7d268"},
- {file = "PyQt5_sip-12.12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6e4becfdbbe1da70887cd6baa0ef1ec394d81a39522e54a3118d1757e2fd0e06"},
- {file = "PyQt5_sip-12.12.0-cp38-cp38-win32.whl", hash = "sha256:bc0df0e4a95e2dc394009cf473098deb318ba6c5208390acc762889a253ef802"},
- {file = "PyQt5_sip-12.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:d26078f436952d774c51599b90d5aa4b9533406f7d65e0d80931a87a24268836"},
- {file = "PyQt5_sip-12.12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d66df3702adf375ec8d4d69ec2a80f57ea899180b1db62188c6313a2f81085da"},
- {file = "PyQt5_sip-12.12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:382a85ae7218de9b5ed136d0d865b49c09063e1ca428ed6901b1a5860223b444"},
- {file = "PyQt5_sip-12.12.0-cp39-cp39-win32.whl", hash = "sha256:a04933dacbba804623c5861d214e217e7e3454beab84566ba5e02c86fb86eded"},
- {file = "PyQt5_sip-12.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:1e91834cc98fda25c232666ca2e77b14520b47b1cee8d38bc93dbe0cd951443e"},
- {file = "PyQt5_sip-12.12.0.tar.gz", hash = "sha256:01bc2d443325505b07d35e4a70d239b4f97b77486e29117fb67f927a15cd8061"},
+ {file = "PyQt5_sip-12.12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed598ff1b666f9e5e0214be7840f308f8fb347fe416a2a45fbedab046a7120b"},
+ {file = "PyQt5_sip-12.12.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:644310dcfed4373075bc576717eea60b0f49899beb5cffb204ddaf5f27cddb85"},
+ {file = "PyQt5_sip-12.12.1-cp310-cp310-win32.whl", hash = "sha256:51720277a53d99bac0914fb970970c9c2ec1a6ab3b7cc5580909d37d9cc6b152"},
+ {file = "PyQt5_sip-12.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:2a344855b9c57d37cf000afa46967961122fb1867faee4f53054ebaa1ce51e24"},
+ {file = "PyQt5_sip-12.12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59229c8d30141220a472ba4e4212846e8bf0bed84c32cbeb57f70fe727c6dfc2"},
+ {file = "PyQt5_sip-12.12.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5a9cbcfe8c15d3a34ef33570f0b0130b8ba68b98fd6ec92c28202b186f3ab870"},
+ {file = "PyQt5_sip-12.12.1-cp311-cp311-win32.whl", hash = "sha256:7afc6ec06e79a3e0a7b447e28ef46dad372bdca32e7eff0dcbac6bc53b69a070"},
+ {file = "PyQt5_sip-12.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:b5b7a6c76fe3eb6b245ac6599c807b18e9a718167878a0b547db1d071a914c08"},
+ {file = "PyQt5_sip-12.12.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3dad0b2bbe0bae4916e43610186d425cd186469b2e6c7ff853177c113b6af6ed"},
+ {file = "PyQt5_sip-12.12.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d1378815b15198ce6dddd367fbd81f5c018ce473a89ae938b7a58e1d97f25b10"},
+ {file = "PyQt5_sip-12.12.1-cp37-cp37m-win32.whl", hash = "sha256:2e3d444f5cb81261c22e7c9d3a9a4484bb9db7a1a3077559100175d36297d1da"},
+ {file = "PyQt5_sip-12.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eee684532876775e1d0fa20d4aae1b568aaa6c732d74e6657ee832e427d46947"},
+ {file = "PyQt5_sip-12.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1364f460ae07fc2f4c42dd7a3b3738611b29f5c033025e5e70b03e2687d4bda4"},
+ {file = "PyQt5_sip-12.12.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6cb6139b00e347e7d961467d092e67c47a97893bc6ab83104bcaf50bf4815036"},
+ {file = "PyQt5_sip-12.12.1-cp38-cp38-win32.whl", hash = "sha256:9e21e11eb6fb468affe0d72ff922788c2adc124480bb274941fce93ddb122b8f"},
+ {file = "PyQt5_sip-12.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:f5ac060219c127a5b9009a4cfe33086e36c6bb8e26c0b757b31a6c04d29d630d"},
+ {file = "PyQt5_sip-12.12.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a65f5869f3f35330c920c1b218319140c0b84f8c49a20727b5e3df2acd496833"},
+ {file = "PyQt5_sip-12.12.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e0241b62f5ca9aaff1037f12e6f5ed68168e7200e14e73f05b632381cee0ff4b"},
+ {file = "PyQt5_sip-12.12.1-cp39-cp39-win32.whl", hash = "sha256:0e30f6c9b99161d8a524d8b7aa5a001be5fe002797151c27414066b838beaa4e"},
+ {file = "PyQt5_sip-12.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:8a2e48a331024a225128f94f5d0fb8089e924419693b2e03eda4d5dbc4313b52"},
+ {file = "PyQt5_sip-12.12.1.tar.gz", hash = "sha256:8fdc6e0148abd12d977a1d3828e7b79aae958e83c6cb5adae614916d888a6b10"},
]
[[package]]
name = "pyqt6"
-version = "6.5.0"
+version = "6.5.1"
description = "Python bindings for the Qt cross platform application toolkit"
category = "main"
optional = false
python-versions = ">=3.6.1"
files = [
- {file = "PyQt6-6.5.0-cp37-abi3-macosx_10_14_universal2.whl", hash = "sha256:e3c8289d9a509be897265981b77eb29e64ce29e9d221fdf52545c2c95e819c9b"},
- {file = "PyQt6-6.5.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b0d9628134811fbfc988d1757111ca8e25cb697f136fa54c969fb1a4d4a61d1"},
- {file = "PyQt6-6.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:99ea0e68f548509b7ef97cded0feeaf3dca7d1fe719388569407326be3be38c2"},
- {file = "PyQt6-6.5.0.tar.gz", hash = "sha256:b97cb4be9b2c8997904ea668cf3b0a4ae5822196f7792590d05ecde6216a9fbc"},
+ {file = "PyQt6-6.5.1-cp37-abi3-macosx_10_14_universal2.whl", hash = "sha256:ad91dcb34d4a70add6551745df631b36013e1c50349cf9f1883cd08913e8cd7e"},
+ {file = "PyQt6-6.5.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b8d29f671cba9cfecd9cf6246cd43d10f7d32125b2b8958ad671fbdd1862e097"},
+ {file = "PyQt6-6.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:d136fbf8cf18cafd4e45a55adfd114a29665dcf91f45bb0e82eec1789eecabbb"},
+ {file = "PyQt6-6.5.1.tar.gz", hash = "sha256:e166a0568c27bcc8db00271a5043936226690b6a4a74ce0a5caeb408040a97c3"},
]
[package.dependencies]
@@ -1333,42 +1410,42 @@ PyQt6-sip = ">=13.4,<14"
[[package]]
name = "pyqt6-qt6"
-version = "6.5.0"
+version = "6.5.1"
description = "The subset of a Qt installation needed by PyQt6."
category = "main"
optional = false
python-versions = "*"
files = [
- {file = "PyQt6_Qt6-6.5.0-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:1178fcd5e9590fec4261e06a753a8fa028222ec0bd9a0788b3bd37720fbbe6cf"},
- {file = "PyQt6_Qt6-6.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9d82d8af986a0eef55905f309fdda4303d1354eba10175824ae62ab6547f7a96"},
- {file = "PyQt6_Qt6-6.5.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:5f40ef19eb632731828283361f800928517650c74c914c093af9a364d6843953"},
- {file = "PyQt6_Qt6-6.5.0-py3-none-win_amd64.whl", hash = "sha256:8c1f898f4d02a31615fe7613a38f82b489fb2c8554965c917d551470731635a8"},
+ {file = "PyQt6_Qt6-6.5.1-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:6aa88aaf4e4e9236364d5b4da8ffdf2854dc579966f135e85e34a54a91ec8096"},
+ {file = "PyQt6_Qt6-6.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:dfc06f8da1728fa2ee2a9407d85704e2661415536190ec632dd8f657acc53842"},
+ {file = "PyQt6_Qt6-6.5.1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:7b04daf0e6ca1e79bf8d3e611392be06abc1687879e3475fcbad0d67672d3370"},
+ {file = "PyQt6_Qt6-6.5.1-py3-none-win_amd64.whl", hash = "sha256:fc4de9c51bfa3e8eaef03ecd4e6346f42a3accf69555987d66dd1236f14e8225"},
]
[[package]]
name = "pyqt6-sip"
-version = "13.5.0"
+version = "13.5.1"
description = "The sip module support for PyQt6"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
- {file = "PyQt6_sip-13.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:447c0df1c8796d2dbb9e5c1cef2ba2a59a38a2bce2fa438246c096a52530f331"},
- {file = "PyQt6_sip-13.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cd56a17e51bc84203219023e956ac42ba8aa4195adb1126476f0cb751a22e986"},
- {file = "PyQt6_sip-13.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:c69072f4afc8e75799d3166f5d3b405eaa7bba998f61e3c8f0dd3a78a234015c"},
- {file = "PyQt6_sip-13.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6fed31d93b2ee8115621f2aeb686068ad1b75084df6af5262c4a1818064014d6"},
- {file = "PyQt6_sip-13.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ee6a198346f1d9e2b675232b6d19d1517652594d7fdc72bb32d0bced6cb2e08d"},
- {file = "PyQt6_sip-13.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a12a24ca84c482a8baa07081f73e11cee17c0c9220021319eada087d2ea8267"},
- {file = "PyQt6_sip-13.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:34e9d5a6f2d77fd7829ce93f59406193547dc77316b63a979bf8de84bb2d7d97"},
- {file = "PyQt6_sip-13.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1ffb48367e0a8bcfe6142c039a433905d606785f7085c3dff3f7801f0afd9fec"},
- {file = "PyQt6_sip-13.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:42e802b99293eff99061115b122801574682b950c2f01e68ac14162f35239bce"},
- {file = "PyQt6_sip-13.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d79d1c557d35747feef11e943723d9a662a819070fedf96e85920bfd5ad48d1"},
- {file = "PyQt6_sip-13.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6e72061953b0bd07d6b41c710bb654788ca61a8f336d169b59c96fd15fdf681a"},
- {file = "PyQt6_sip-13.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:640011d5642ee94dce6cfde234631830ca7164bef138772c4ad05b80dcb88e10"},
- {file = "PyQt6_sip-13.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2cbc73dd3a2e0d6669b47fbf0ed5494a3cda996a2d0db465eea2a825a0c12733"},
- {file = "PyQt6_sip-13.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:289c37bf808ecc110c6f85afe29083f90170dbdfb76db412281acabefc0b7ede"},
- {file = "PyQt6_sip-13.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf705dbbf77029c682234cdaa78970899d9d49b40b7b2d942b2af4f0f6c6d566"},
- {file = "PyQt6_sip-13.5.0.tar.gz", hash = "sha256:61c702b7f81796a27c294ba76f1cba3408161f06deb801373c42670ed36f722a"},
+ {file = "PyQt6_sip-13.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2a15f080a994936ed182f4a81343baa19bac9063ec6efc0a93d026f5cfc95ace"},
+ {file = "PyQt6_sip-13.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d90dfec64c35c91644a3e32d3e5680cdee549d00245ea7252cb6298797f9bcef"},
+ {file = "PyQt6_sip-13.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:1e7bb3e45e57dfdb9437043d99b7cb797707e7f2475d122928b13688458f94b7"},
+ {file = "PyQt6_sip-13.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18c0e75e6ebd91dc96dbc6290f044ec37e764890ef2182c82b99ea5b655ea466"},
+ {file = "PyQt6_sip-13.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c3e2f155f92f96d73c680caf3d87f4f9f9aaf6487c125ecbe7140daad7d87245"},
+ {file = "PyQt6_sip-13.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:08b69898aab0fcc73661b212d434f9e9eb50319481bc2ac3aaf1ac06bc9feca6"},
+ {file = "PyQt6_sip-13.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d79957dd0c0ea1a17f0846806ea203dce827df6a9dcd93ebfe98fdd6186d9ecc"},
+ {file = "PyQt6_sip-13.5.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a867601c38acc9b0c7f9aab4f96d9ec8cbedfcd5ae245f82a9c1c48f352413e4"},
+ {file = "PyQt6_sip-13.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:52931307cf06c5ac992df2877e899f8b8ba72464e2828fe442b18fd51c7bf787"},
+ {file = "PyQt6_sip-13.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c71d303ff654ad947d8c0cb5ebfde9a59390aac52eb695a775234a08bee8f44e"},
+ {file = "PyQt6_sip-13.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e0d715bb5b86eb8f09d84b2b4400df7e4c96ef730801bc145a1c23be79f39fac"},
+ {file = "PyQt6_sip-13.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:4b2e70d21069fe6e20bf22de1de2985e064e00d1368e0a171ce38824be4339ab"},
+ {file = "PyQt6_sip-13.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7b4e3cad598afd9b50a32732007184141b400769d425cae86f4e702cbc882b3"},
+ {file = "PyQt6_sip-13.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ad802005e68bb9bb6f869b7f904c73d7c7793d11b83d317c33ff6b0c163d785f"},
+ {file = "PyQt6_sip-13.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:5e8fdb0821c0c556d2a34db1229d4bd711499a1102241b1b9fcf1ee34f87e564"},
+ {file = "PyQt6_sip-13.5.1.tar.gz", hash = "sha256:d1e9141752966669576d04b37ba0b122abbc41cc9c35493751028d7d91c4dd49"},
]
[[package]]
@@ -1392,16 +1469,16 @@ PyQt6-WebEngine-Qt6 = ">=6.5.0"
[[package]]
name = "pyqt6-webengine-qt6"
-version = "6.5.0"
+version = "6.5.1"
description = "The subset of a Qt installation needed by PyQt6-WebEngine."
category = "main"
optional = false
python-versions = "*"
files = [
- {file = "PyQt6_WebEngine_Qt6-6.5.0-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:8d7eee4e864c89d6865ff95394dec3aa5b6620ac20412d09a313e83a5baaecb5"},
- {file = "PyQt6_WebEngine_Qt6-6.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ee2300d372cf38bfb2e426e5036f58bfcaf121e460dc7f89913dc7bd6c3c8953"},
- {file = "PyQt6_WebEngine_Qt6-6.5.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:6f2be9044060ed3e9e0c55e0d8863fae08c815e994bcf17f2ff24945a2264ff7"},
- {file = "PyQt6_WebEngine_Qt6-6.5.0-py3-none-win_amd64.whl", hash = "sha256:5acadcc6608df8d9eba385e04ced2fc88e7eb92e366556ee4ac3c57a02c00088"},
+ {file = "PyQt6_WebEngine_Qt6-6.5.1-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:4d6e4e5b5e4301c1ee86fb3070469f7052325c6ea2ad83987e26d4864851cd9c"},
+ {file = "PyQt6_WebEngine_Qt6-6.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5620e422d26d04df3fe919d069174b769f5d240df44b9a1f19edb1ef04be9f4c"},
+ {file = "PyQt6_WebEngine_Qt6-6.5.1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:7cf9cc03819486e997b4e5ddff645394f9a85d62efe114ee047edd3958670727"},
+ {file = "PyQt6_WebEngine_Qt6-6.5.1-py3-none-win_amd64.whl", hash = "sha256:1ea8bc467a3ada7fe85fcd45318ee79796caefb8f61264ae755c9e771e5a02b9"},
]
[[package]]
@@ -1455,14 +1532,14 @@ files = [
[[package]]
name = "pytest"
-version = "7.3.0"
+version = "7.4.0"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pytest-7.3.0-py3-none-any.whl", hash = "sha256:933051fa1bfbd38a21e73c3960cebdad4cf59483ddba7696c48509727e17f201"},
- {file = "pytest-7.3.0.tar.gz", hash = "sha256:58ecc27ebf0ea643ebfdf7fb1249335da761a00c9f955bcd922349bcb68ee57d"},
+ {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"},
+ {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"},
]
[package.dependencies]
@@ -1474,7 +1551,7 @@ pluggy = ">=0.12,<2.0"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
-testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
+testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-qt"
@@ -1529,14 +1606,14 @@ unidecode = ["Unidecode (>=1.1.1)"]
[[package]]
name = "python-vlc"
-version = "3.0.18121"
+version = "3.0.18122"
description = "VLC bindings for python."
category = "main"
optional = false
python-versions = "*"
files = [
- {file = "python-vlc-3.0.18121.tar.gz", hash = "sha256:24550314a3e6ed55fd347b009491c98b865f9dfa05a92e889d7b0a2210e7485b"},
- {file = "python_vlc-3.0.18121-py3-none-any.whl", hash = "sha256:b8f4bdea22d363377c51996db94cac38d02df02fee9b79c03f1840ff6d376455"},
+ {file = "python-vlc-3.0.18122.tar.gz", hash = "sha256:1039bde287853b4b7b61ba22d83761832434f78506da762dfb060291bf32897d"},
+ {file = "python_vlc-3.0.18122-py3-none-any.whl", hash = "sha256:00e5133886a956ded7362b48926818e5486dfd73b2f9319b76d977acefe6042c"},
]
[[package]]
@@ -1563,18 +1640,18 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "rich"
-version = "13.3.3"
+version = "13.4.2"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "main"
optional = false
python-versions = ">=3.7.0"
files = [
- {file = "rich-13.3.3-py3-none-any.whl", hash = "sha256:540c7d6d26a1178e8e8b37e9ba44573a3cd1464ff6348b99ee7061b95d1c6333"},
- {file = "rich-13.3.3.tar.gz", hash = "sha256:dc84400a9d842b3a9c5ff74addd8eb798d155f36c1c91303888e0a66850d2a15"},
+ {file = "rich-13.4.2-py3-none-any.whl", hash = "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec"},
+ {file = "rich-13.4.2.tar.gz", hash = "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"},
]
[package.dependencies]
-markdown-it-py = ">=2.2.0,<3.0.0"
+markdown-it-py = ">=2.2.0"
pygments = ">=2.13.0,<3.0.0"
[package.extras]
@@ -1582,19 +1659,19 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "setuptools"
-version = "67.6.1"
+version = "68.0.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
- {file = "setuptools-67.6.1-py3-none-any.whl", hash = "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"},
- {file = "setuptools-67.6.1.tar.gz", hash = "sha256:257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a"},
+ {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"},
+ {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
@@ -1784,53 +1861,47 @@ test = ["pytest"]
[[package]]
name = "sqlalchemy"
-version = "1.4.47"
+version = "1.4.49"
description = "Database Abstraction Library"
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
files = [
- {file = "SQLAlchemy-1.4.47-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:dcfb480bfc9e1fab726003ae00a6bfc67a29bad275b63a4e36d17fe7f13a624e"},
- {file = "SQLAlchemy-1.4.47-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:28fda5a69d6182589892422c5a9b02a8fd1125787aab1d83f1392aa955bf8d0a"},
- {file = "SQLAlchemy-1.4.47-cp27-cp27m-win32.whl", hash = "sha256:45e799c1a41822eba6bee4e59b0e38764e1a1ee69873ab2889079865e9ea0e23"},
- {file = "SQLAlchemy-1.4.47-cp27-cp27m-win_amd64.whl", hash = "sha256:10edbb92a9ef611f01b086e271a9f6c1c3e5157c3b0c5ff62310fb2187acbd4a"},
- {file = "SQLAlchemy-1.4.47-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7a4df53472c9030a8ddb1cce517757ba38a7a25699bbcabd57dcc8a5d53f324e"},
- {file = "SQLAlchemy-1.4.47-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:511d4abc823152dec49461209607bbfb2df60033c8c88a3f7c93293b8ecbb13d"},
- {file = "SQLAlchemy-1.4.47-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbe57f39f531c5d68d5594ea4613daa60aba33bb51a8cc42f96f17bbd6305e8d"},
- {file = "SQLAlchemy-1.4.47-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca8ab6748e3ec66afccd8b23ec2f92787a58d5353ce9624dccd770427ee67c82"},
- {file = "SQLAlchemy-1.4.47-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299b5c5c060b9fbe51808d0d40d8475f7b3873317640b9b7617c7f988cf59fda"},
- {file = "SQLAlchemy-1.4.47-cp310-cp310-win32.whl", hash = "sha256:684e5c773222781775c7f77231f412633d8af22493bf35b7fa1029fdf8066d10"},
- {file = "SQLAlchemy-1.4.47-cp310-cp310-win_amd64.whl", hash = "sha256:2bba39b12b879c7b35cde18b6e14119c5f1a16bd064a48dd2ac62d21366a5e17"},
- {file = "SQLAlchemy-1.4.47-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:795b5b9db573d3ed61fae74285d57d396829e3157642794d3a8f72ec2a5c719b"},
- {file = "SQLAlchemy-1.4.47-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:989c62b96596b7938cbc032e39431e6c2d81b635034571d6a43a13920852fb65"},
- {file = "SQLAlchemy-1.4.47-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b67bda733da1dcdccaf354e71ef01b46db483a4f6236450d3f9a61efdba35a"},
- {file = "SQLAlchemy-1.4.47-cp311-cp311-win32.whl", hash = "sha256:9a198f690ac12a3a807e03a5a45df6a30cd215935f237a46f4248faed62e69c8"},
- {file = "SQLAlchemy-1.4.47-cp311-cp311-win_amd64.whl", hash = "sha256:03be6f3cb66e69fb3a09b5ea89d77e4bc942f3bf84b207dba84666a26799c166"},
- {file = "SQLAlchemy-1.4.47-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:16ee6fea316790980779268da47a9260d5dd665c96f225d28e7750b0bb2e2a04"},
- {file = "SQLAlchemy-1.4.47-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:557675e0befafa08d36d7a9284e8761c97490a248474d778373fb96b0d7fd8de"},
- {file = "SQLAlchemy-1.4.47-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb2797fee8a7914fb2c3dc7de404d3f96eb77f20fc60e9ee38dc6b0ca720f2c2"},
- {file = "SQLAlchemy-1.4.47-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28297aa29e035f29cba6b16aacd3680fbc6a9db682258d5f2e7b49ec215dbe40"},
- {file = "SQLAlchemy-1.4.47-cp36-cp36m-win32.whl", hash = "sha256:998e782c8d9fd57fa8704d149ccd52acf03db30d7dd76f467fd21c1c21b414fa"},
- {file = "SQLAlchemy-1.4.47-cp36-cp36m-win_amd64.whl", hash = "sha256:dde4d02213f1deb49eaaf8be8a6425948963a7af84983b3f22772c63826944de"},
- {file = "SQLAlchemy-1.4.47-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e98ef1babe34f37f443b7211cd3ee004d9577a19766e2dbacf62fce73c76245a"},
- {file = "SQLAlchemy-1.4.47-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14a3879853208a242b5913f3a17c6ac0eae9dc210ff99c8f10b19d4a1ed8ed9b"},
- {file = "SQLAlchemy-1.4.47-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7120a2f72599d4fed7c001fa1cbbc5b4d14929436135768050e284f53e9fbe5e"},
- {file = "SQLAlchemy-1.4.47-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:048509d7f3ac27b83ad82fd96a1ab90a34c8e906e4e09c8d677fc531d12c23c5"},
- {file = "SQLAlchemy-1.4.47-cp37-cp37m-win32.whl", hash = "sha256:6572d7c96c2e3e126d0bb27bfb1d7e2a195b68d951fcc64c146b94f088e5421a"},
- {file = "SQLAlchemy-1.4.47-cp37-cp37m-win_amd64.whl", hash = "sha256:a6c3929df5eeaf3867724003d5c19fed3f0c290f3edc7911616616684f200ecf"},
- {file = "SQLAlchemy-1.4.47-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:71d4bf7768169c4502f6c2b0709a02a33703544f611810fb0c75406a9c576ee1"},
- {file = "SQLAlchemy-1.4.47-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd45c60cc4f6d68c30d5179e2c2c8098f7112983532897566bb69c47d87127d3"},
- {file = "SQLAlchemy-1.4.47-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fdbb8e9d4e9003f332a93d6a37bca48ba8095086c97a89826a136d8eddfc455"},
- {file = "SQLAlchemy-1.4.47-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f216a51451a0a0466e082e163591f6dcb2f9ec182adb3f1f4b1fd3688c7582c"},
- {file = "SQLAlchemy-1.4.47-cp38-cp38-win32.whl", hash = "sha256:bd988b3362d7e586ef581eb14771bbb48793a4edb6fcf62da75d3f0f3447060b"},
- {file = "SQLAlchemy-1.4.47-cp38-cp38-win_amd64.whl", hash = "sha256:32ab09f2863e3de51529aa84ff0e4fe89a2cb1bfbc11e225b6dbc60814e44c94"},
- {file = "SQLAlchemy-1.4.47-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:07764b240645627bc3e82596435bd1a1884646bfc0721642d24c26b12f1df194"},
- {file = "SQLAlchemy-1.4.47-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e2a42017984099ef6f56438a6b898ce0538f6fadddaa902870c5aa3e1d82583"},
- {file = "SQLAlchemy-1.4.47-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6b6d807c76c20b4bc143a49ad47782228a2ac98bdcdcb069da54280e138847fc"},
- {file = "SQLAlchemy-1.4.47-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a94632ba26a666e7be0a7d7cc3f7acab622a04259a3aa0ee50ff6d44ba9df0d"},
- {file = "SQLAlchemy-1.4.47-cp39-cp39-win32.whl", hash = "sha256:f80915681ea9001f19b65aee715115f2ad310730c8043127cf3e19b3009892dd"},
- {file = "SQLAlchemy-1.4.47-cp39-cp39-win_amd64.whl", hash = "sha256:fc700b862e0a859a37faf85367e205e7acaecae5a098794aff52fdd8aea77b12"},
- {file = "SQLAlchemy-1.4.47.tar.gz", hash = "sha256:95fc02f7fc1f3199aaa47a8a757437134cf618e9d994c84effd53f530c38586f"},
+ {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"},
+ {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"},
+ {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"},
+ {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"},
+ {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"},
+ {file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"},
+ {file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"},
+ {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"},
+ {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"},
+ {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"},
+ {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"},
+ {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"},
+ {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"},
+ {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"},
+ {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"},
+ {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"},
+ {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"},
+ {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"},
+ {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"},
+ {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"},
+ {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"},
+ {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"},
+ {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"},
+ {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"},
+ {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"},
+ {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"},
+ {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"},
+ {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"},
+ {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"},
+ {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"},
+ {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"},
+ {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"},
+ {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"},
+ {file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"},
+ {file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"},
]
[package.dependencies]
@@ -1859,14 +1930,14 @@ sqlcipher = ["sqlcipher3-binary"]
[[package]]
name = "sqlalchemy2-stubs"
-version = "0.0.2a33"
+version = "0.0.2a34"
description = "Typing Stubs for SQLAlchemy 1.4"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
- {file = "sqlalchemy2-stubs-0.0.2a33.tar.gz", hash = "sha256:5a35a096964dfd985651662b7f175fe1ddcbf5ed4f2d0203e637cec38bed64b4"},
- {file = "sqlalchemy2_stubs-0.0.2a33-py3-none-any.whl", hash = "sha256:9809e7d8ea72cd92ac35aca4b43f588ae24b20376c55a0ef0112a08a6b537180"},
+ {file = "sqlalchemy2-stubs-0.0.2a34.tar.gz", hash = "sha256:2432137ab2fde1a608df4544f6712427b0b7ff25990cfbbc5a9d1db6c8c6f489"},
+ {file = "sqlalchemy2_stubs-0.0.2a34-py3-none-any.whl", hash = "sha256:a313220ac793404349899faf1272e821a62dbe1d3a029bd444faa8d3e966cd07"},
]
[package.dependencies]
@@ -1933,13 +2004,13 @@ speedup = ["python-levenshtein (>=0.12)"]
[[package]]
name = "tinytag"
-version = "1.8.1"
+version = "1.9.0"
description = "Read music meta data and length of MP3, OGG, OPUS, MP4, M4A, FLAC, WMA and Wave files"
category = "main"
optional = false
python-versions = ">=2.7"
files = [
- {file = "tinytag-1.8.1.tar.gz", hash = "sha256:363ab3107831a5598b68aaa061aba915fb1c7b4254d770232e65d5db8487636d"},
+ {file = "tinytag-1.9.0.tar.gz", hash = "sha256:f8d71110e1e680a33d99202e00a5a698481d25d20173b81ba3e863423979e014"},
]
[package.extras]
@@ -1987,44 +2058,45 @@ test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
[[package]]
name = "types-psutil"
-version = "5.9.5.11"
+version = "5.9.5.15"
description = "Typing stubs for psutil"
category = "main"
optional = false
python-versions = "*"
files = [
- {file = "types-psutil-5.9.5.11.tar.gz", hash = "sha256:3d59da0758f056bfb59fef757366e538c5dd5473d81c35b38956624ae2484f31"},
- {file = "types_psutil-5.9.5.11-py3-none-any.whl", hash = "sha256:01cc541b187a11e758d336c4cc89abf71d0098627fa95d5cfaca536be31a7d1a"},
+ {file = "types-psutil-5.9.5.15.tar.gz", hash = "sha256:943a5f9556c1995097c89f50274330dd8d71d17bd73cbc6b09c61dbf4e114cc6"},
+ {file = "types_psutil-5.9.5.15-py3-none-any.whl", hash = "sha256:7f183072d1fdb4e092fd6dd88ea017b957056bf10e58eafcdca26bf4372c0742"},
]
[[package]]
name = "typing-extensions"
-version = "4.5.0"
+version = "4.7.1"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
- {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
- {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
+ {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"},
+ {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"},
]
[[package]]
name = "urllib3"
-version = "1.26.15"
+version = "2.0.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "dev"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+python-versions = ">=3.7"
files = [
- {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"},
- {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"},
+ {file = "urllib3-2.0.3-py3-none-any.whl", hash = "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1"},
+ {file = "urllib3-2.0.3.tar.gz", hash = "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"},
]
[package.extras]
-brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "urwid"
@@ -2068,14 +2140,14 @@ files = [
[[package]]
name = "websocket-client"
-version = "1.5.1"
+version = "1.6.1"
description = "WebSocket client for Python with low level API options"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
- {file = "websocket-client-1.5.1.tar.gz", hash = "sha256:3f09e6d8230892547132177f575a4e3e73cfdf06526e20cc02aa1c3b47184d40"},
- {file = "websocket_client-1.5.1-py3-none-any.whl", hash = "sha256:cdf5877568b7e83aa7cf2244ab56a3213de587bbe0ce9d8b9600fc77b455d89e"},
+ {file = "websocket-client-1.6.1.tar.gz", hash = "sha256:c951af98631d24f8df89ab1019fc365f2227c0892f12fd150e935607c79dd0dd"},
+ {file = "websocket_client-1.6.1-py3-none-any.whl", hash = "sha256:f1f9f2ad5291f0225a49efad77abf9e700b6fef553900623060dad6e26503b9d"},
]
[package.extras]
@@ -2102,4 +2174,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
-content-hash = "48adc85e7a44ebcad0729582bd2a2ea7dab46cf12127861161c778cf4008e200"
+content-hash = "068d52d54597c5fbb5ccbb168ed5b1b76300c5df8621859d7d9b915dbda6e326"
diff --git a/pyproject.toml b/pyproject.toml
index 7d05d29..d83aeae 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,6 +42,8 @@ mypy = "^0.991"
pudb = "^2022.1.3"
sphinx = "^7.0.1"
furo = "^2023.5.20"
+black = "^23.3.0"
+flakehell = "^0.9.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
diff --git a/test.py b/test.py
index 1781c14..3658d12 100755
--- a/test.py
+++ b/test.py
@@ -2,6 +2,7 @@
from PyQt5 import QtGui, QtWidgets
+
class TabBar(QtWidgets.QTabBar):
def paintEvent(self, event):
painter = QtWidgets.QStylePainter(self)
@@ -13,16 +14,18 @@ class TabBar(QtWidgets.QTabBar):
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, option)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, option)
+
class Window(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.setTabBar(TabBar(self))
- for color in 'tomato orange yellow lightgreen skyblue plum'.split():
+ for color in "tomato orange yellow lightgreen skyblue plum".split():
self.addTab(QtWidgets.QWidget(self), color)
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.resize(420, 200)
diff --git a/test_helpers.py b/test_helpers.py
index e6251d4..cbb7c55 100644
--- a/test_helpers.py
+++ b/test_helpers.py
@@ -1,7 +1,12 @@
-from config import Config
from datetime import datetime, timedelta
-from helpers import *
-from models import Tracks
+from helpers import (
+ fade_point,
+ get_audio_segment,
+ get_tags,
+ get_relative_date,
+ leading_silence,
+ ms_to_mmss,
+)
def test_fade_point():
@@ -18,8 +23,8 @@ def test_fade_point():
testdata = eval(f.read())
# Volume detection can vary, so ± 1 second is OK
- assert fade_at < testdata['fade_at'] + 1000
- assert fade_at > testdata['fade_at'] - 1000
+ assert fade_at < testdata["fade_at"] + 1000
+ assert fade_at > testdata["fade_at"] - 1000
def test_get_tags():
@@ -32,8 +37,8 @@ def test_get_tags():
with open(test_track_data) as f:
testdata = eval(f.read())
- assert tags['artist'] == testdata['artist']
- assert tags['title'] == testdata['title']
+ assert tags["artist"] == testdata["artist"]
+ assert tags["title"] == testdata["title"]
def test_get_relative_date():
@@ -44,8 +49,7 @@ def test_get_relative_date():
eight_days_ago = today_at_10 - timedelta(days=8)
assert get_relative_date(eight_days_ago, today_at_11) == "1 week, 1 day ago"
sixteen_days_ago = today_at_10 - timedelta(days=16)
- assert get_relative_date(
- sixteen_days_ago, today_at_11) == "2 weeks, 2 days ago"
+ assert get_relative_date(sixteen_days_ago, today_at_11) == "2 weeks, 2 days ago"
def test_leading_silence():
@@ -62,8 +66,8 @@ def test_leading_silence():
testdata = eval(f.read())
# Volume detection can vary, so ± 1 second is OK
- assert silence_at < testdata['leading_silence'] + 1000
- assert silence_at > testdata['leading_silence'] - 1000
+ assert silence_at < testdata["leading_silence"] + 1000
+ assert silence_at > testdata["leading_silence"] - 1000
def test_ms_to_mmss():
diff --git a/test_models.py b/test_models.py
index 901f119..9cd3ce2 100644
--- a/test_models.py
+++ b/test_models.py
@@ -135,7 +135,6 @@ def test_playdates_remove_track(session):
track_path = "/a/b/c"
track = Tracks(session, track_path)
- playdate = Playdates(session, track.id)
Playdates.remove_track(session, track.id)
last_played = Playdates.last_played(session, track.id)
@@ -149,10 +148,8 @@ def test_playlist_create(session):
def test_playlist_add_note(session):
note_text = "my note"
- note_row = 2
playlist = Playlists(session, "my playlist")
- note = playlist.add_note(session, note_row, note_text)
assert len(playlist.notes) == 1
playlist_note = playlist.notes[0]
@@ -356,9 +353,7 @@ def test_tracks_get_all_paths(session):
def test_tracks_get_all_tracks(session):
# Need two tracks
track1_path = "/a/b/c"
- track1 = Tracks(session, track1_path)
track2_path = "/m/n/o"
- track2 = Tracks(session, track2_path)
result = Tracks.get_all_tracks(session)
assert track1_path in [a.path for a in result]
@@ -369,9 +364,7 @@ def test_tracks_by_filename(session):
track1_path = "/a/b/c"
track1 = Tracks(session, track1_path)
- assert Tracks.get_by_filename(
- session, os.path.basename(track1_path)
- ) is track1
+ assert Tracks.get_by_filename(session, os.path.basename(track1_path)) is track1
def test_tracks_by_path(session):
@@ -403,26 +396,24 @@ def test_tracks_rescan(session):
# Re-read the track
track_read = Tracks.get_by_path(session, test_track_path)
- assert track_read.duration == testdata['duration']
- assert track_read.start_gap == testdata['leading_silence']
+ assert track_read.duration == testdata["duration"]
+ assert track_read.start_gap == testdata["leading_silence"]
# Silence detection can vary, so ± 1 second is OK
- assert track_read.fade_at < testdata['fade_at'] + 1000
- assert track_read.fade_at > testdata['fade_at'] - 1000
- assert track_read.silence_at < testdata['trailing_silence'] + 1000
- assert track_read.silence_at > testdata['trailing_silence'] - 1000
+ assert track_read.fade_at < testdata["fade_at"] + 1000
+ assert track_read.fade_at > testdata["fade_at"] - 1000
+ assert track_read.silence_at < testdata["trailing_silence"] + 1000
+ assert track_read.silence_at > testdata["trailing_silence"] - 1000
def test_tracks_remove_by_path(session):
track1_path = "/a/b/c"
- track1 = Tracks(session, track1_path)
assert len(Tracks.get_all_tracks(session)) == 1
Tracks.remove_by_path(session, track1_path)
assert len(Tracks.get_all_tracks(session)) == 0
def test_tracks_search_artists(session):
-
track1_path = "/a/b/c"
track1_artist = "Artist One"
track1 = Tracks(session, track1_path)
@@ -435,7 +426,6 @@ def test_tracks_search_artists(session):
session.commit()
- x = Tracks.get_all_tracks(session)
artist_first_word = track1_artist.split()[0].lower()
assert len(Tracks.search_artists(session, artist_first_word)) == 2
assert len(Tracks.search_artists(session, track1_artist)) == 1
@@ -454,7 +444,6 @@ def test_tracks_search_titles(session):
session.commit()
- x = Tracks.get_all_tracks(session)
title_first_word = track1_title.split()[0].lower()
assert len(Tracks.search_titles(session, title_first_word)) == 2
assert len(Tracks.search_titles(session, track1_title)) == 1
diff --git a/test_playlists.py b/test_playlists.py
index 24bb7e0..57e63d3 100644
--- a/test_playlists.py
+++ b/test_playlists.py
@@ -3,7 +3,6 @@ from PyQt5.QtCore import Qt
from app import playlists
from app import models
from app import musicmuster
-from app import dbconfig
def seed2tracks(session):
@@ -78,7 +77,6 @@ def test_save_and_restore(qtbot, session):
def test_meta_all_clear(qtbot, session):
-
# Create playlist
playlist = models.Playlists(session, "my playlist")
playlist_tab = playlists.PlaylistTab(None, session, playlist.id)
@@ -107,7 +105,6 @@ def test_meta_all_clear(qtbot, session):
def test_meta(qtbot, session):
-
# Create playlist
playlist = playlists.Playlists(session, "my playlist")
playlist_tab = playlists.PlaylistTab(None, session, playlist.id)
@@ -141,7 +138,7 @@ def test_meta(qtbot, session):
# Add a note
note_text = "my note"
- note_row = 7 # will be added as row 3
+ note_row = 7 # will be added as row 3
note = models.Notes(session, playlist.id, note_row, note_text)
playlist_tab._insert_note(session, note)
@@ -211,7 +208,6 @@ def test_clear_next(qtbot, session):
def test_get_selected_row(qtbot, monkeypatch, session):
-
monkeypatch.setattr(musicmuster, "Session", session)
monkeypatch.setattr(playlists, "Session", session)
@@ -236,15 +232,12 @@ def test_get_selected_row(qtbot, monkeypatch, session):
row0_item0 = playlist_tab.item(0, 0)
assert row0_item0 is not None
rect = playlist_tab.visualItemRect(row0_item0)
- qtbot.mouseClick(
- playlist_tab.viewport(), Qt.LeftButton, pos=rect.center()
- )
+ qtbot.mouseClick(playlist_tab.viewport(), Qt.LeftButton, pos=rect.center())
row_number = playlist_tab.get_selected_row()
assert row_number == 0
def test_set_next(qtbot, monkeypatch, session):
-
monkeypatch.setattr(musicmuster, "Session", session)
monkeypatch.setattr(playlists, "Session", session)
seed2tracks(session)
@@ -274,14 +267,11 @@ def test_set_next(qtbot, monkeypatch, session):
row0_item2 = playlist_tab.item(0, 2)
assert row0_item2 is not None
rect = playlist_tab.visualItemRect(row0_item2)
- qtbot.mouseClick(
- playlist_tab.viewport(), Qt.LeftButton, pos=rect.center()
- )
+ qtbot.mouseClick(playlist_tab.viewport(), Qt.LeftButton, pos=rect.center())
selected_title = playlist_tab.get_selected_title()
assert selected_title == track1_title
- qtbot.keyPress(playlist_tab.viewport(), "N",
- modifier=Qt.ControlModifier)
+ qtbot.keyPress(playlist_tab.viewport(), "N", modifier=Qt.ControlModifier)
qtbot.wait(1000)
diff --git a/tree.py b/tree.py
index 4cbba1b..19c51c7 100755
--- a/tree.py
+++ b/tree.py
@@ -9,9 +9,10 @@ datas = {
],
"No Category": [
("New Game", "Playnite", "", "", "Never", "Not Plated", ""),
- ]
+ ],
}
+
class GroupDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(GroupDelegate, self).__init__(parent)
@@ -25,6 +26,7 @@ class GroupDelegate(QtWidgets.QStyledItemDelegate):
option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
option.icon = self._minus_icon if is_open else self._plus_icon
+
class GroupView(QtWidgets.QTreeView):
def __init__(self, model, parent=None):
super(GroupView, self).__init__(parent)
@@ -48,7 +50,18 @@ class GroupModel(QtGui.QStandardItemModel):
def __init__(self, parent=None):
super(GroupModel, self).__init__(parent)
self.setColumnCount(8)
- self.setHorizontalHeaderLabels(["", "Name", "Library", "Release Date", "Genre(s)", "Last Played", "Time Played", ""])
+ self.setHorizontalHeaderLabels(
+ [
+ "",
+ "Name",
+ "Library",
+ "Release Date",
+ "Genre(s)",
+ "Last Played",
+ "Time Played",
+ "",
+ ]
+ )
for i in range(self.columnCount()):
it = self.horizontalHeaderItem(i)
it.setForeground(QtGui.QColor("#F2F2F2"))
@@ -84,7 +97,7 @@ class GroupModel(QtGui.QStandardItemModel):
item.setEditable(False)
item.setBackground(QtGui.QColor("#0D1225"))
item.setForeground(QtGui.QColor("#F2F2F2"))
- group_item.setChild(j, i+1, item)
+ group_item.setChild(j, i + 1, item)
class MainWindow(QtWidgets.QMainWindow):
@@ -100,11 +113,12 @@ class MainWindow(QtWidgets.QMainWindow):
for children in childrens:
model.append_element_to_group(group_item, children)
-if __name__ == '__main__':
+
+if __name__ == "__main__":
import sys
+
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(720, 240)
w.show()
sys.exit(app.exec_())
-
diff --git a/web.py b/web.py
index 5527215..ec0e365 100755
--- a/web.py
+++ b/web.py
@@ -1,6 +1,5 @@
#!/usr/bin/env python3
import sys
-from pprint import pprint
from PyQt6.QtWidgets import QApplication, QLabel
from PyQt6.QtGui import QColor, QPalette