Compare commits
No commits in common. "d050fa0d8475cdea8df151c954fc99daad3b7165" and "6aa09bf28aa3ce459bc1eba2248ecabaf1f28730" have entirely different histories.
d050fa0d84
...
6aa09bf28a
@ -1,6 +1,6 @@
|
||||
# Standard library imports
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
import datetime as dt
|
||||
|
||||
# PyQt imports
|
||||
@ -203,19 +203,6 @@ class PlaylistTrack:
|
||||
self.fade_graph_start_updates = now + dt.timedelta(milliseconds=update_graph_at_ms)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrackFileData:
|
||||
"""
|
||||
Simple class to track details changes to a track file
|
||||
"""
|
||||
|
||||
source_file_path: str
|
||||
track_id: int = 0
|
||||
track_path: Optional[str] = None
|
||||
tags: dict[str, Any] = field(default_factory=dict)
|
||||
audio_metadata: dict[str, str | int | float] = field(default_factory=dict)
|
||||
|
||||
|
||||
class AddFadeCurve(QObject):
|
||||
"""
|
||||
Initialising a fade curve introduces a noticeable delay so carry out in
|
||||
|
||||
@ -39,7 +39,6 @@ class Config(object):
|
||||
DEFAULT_COLUMN_WIDTH = 200
|
||||
DISPLAY_SQL = False
|
||||
EPOCH = dt.datetime(1970, 1, 1)
|
||||
ENGINE_OPTIONS = dict(pool_pre_ping=True)
|
||||
ERRORS_FROM = ["noreply@midnighthax.com"]
|
||||
ERRORS_TO = ["kae@midnighthax.com"]
|
||||
FADE_CURVE_BACKGROUND = "lightyellow"
|
||||
@ -79,7 +78,6 @@ class Config(object):
|
||||
OBS_PASSWORD = "auster"
|
||||
OBS_PORT = 4455
|
||||
PLAY_SETTLE = 500000
|
||||
REPLACE_FILES_DEFAULT_SOURCE = "/home/kae/music/Singles/tmp"
|
||||
RETURN_KEY_DEBOUNCE_MS = 500
|
||||
ROOT = os.environ.get("ROOT") or "/home/kae/music"
|
||||
ROWS_FROM_ZERO = True
|
||||
@ -96,4 +94,3 @@ class Config(object):
|
||||
|
||||
# These rely on earlier definitions
|
||||
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
||||
REPLACE_FILES_DEFAULT_DESTINATION = os.path.dirname(REPLACE_FILES_DEFAULT_SOURCE)
|
||||
|
||||
189
app/dialogs.py
189
app/dialogs.py
@ -1,203 +1,24 @@
|
||||
# Standard library imports
|
||||
from typing import Optional
|
||||
import os
|
||||
|
||||
# PyQt imports
|
||||
from PyQt6.QtCore import QEvent, Qt
|
||||
from PyQt6.QtWidgets import (
|
||||
QDialog,
|
||||
QListWidgetItem,
|
||||
QMainWindow,
|
||||
QTableWidgetItem,
|
||||
)
|
||||
from PyQt6.QtWidgets import QDialog, QListWidgetItem
|
||||
|
||||
# Third party imports
|
||||
import pydymenu # type: ignore
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
# App imports
|
||||
from classes import MusicMusterSignals, TrackFileData
|
||||
from config import Config
|
||||
from classes import MusicMusterSignals
|
||||
from helpers import (
|
||||
ask_yes_no,
|
||||
get_relative_date,
|
||||
get_tags,
|
||||
ms_to_mmss,
|
||||
show_warning,
|
||||
)
|
||||
from log import log
|
||||
from models import db, Settings, Tracks
|
||||
from models import Settings, Tracks
|
||||
from playlistmodel import PlaylistModel
|
||||
from ui import dlg_TrackSelect_ui
|
||||
from ui import dlg_replace_files_ui
|
||||
|
||||
|
||||
class ReplaceFilesDialog(QDialog):
|
||||
"""Import files as new or replacements"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session: Session,
|
||||
main_window: QMainWindow,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.session = session
|
||||
self.main_window = main_window
|
||||
self.ui = dlg_replace_files_ui.Ui_Dialog()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.ui.lblSourceDirectory.setText(Config.REPLACE_FILES_DEFAULT_SOURCE)
|
||||
self.ui.lblDestinationDirectory.setText(
|
||||
Config.REPLACE_FILES_DEFAULT_DESTINATION
|
||||
)
|
||||
self.replacement_files: list[TrackFileData] = []
|
||||
|
||||
# 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
|
||||
dburi = os.environ.get("ALCHEMICAL_DATABASE_URI")
|
||||
if not dburi or "musicmuster_prod" not in dburi:
|
||||
if not ask_yes_no(
|
||||
"Not production database",
|
||||
"Not on production database - continue?",
|
||||
default_yes=False,
|
||||
):
|
||||
return
|
||||
if self.ui.lblSourceDirectory.text() == self.ui.lblDestinationDirectory.text():
|
||||
show_warning(
|
||||
parent=self.main_window,
|
||||
title="Error",
|
||||
msg="Cannot import into source directory",
|
||||
)
|
||||
return
|
||||
|
||||
self.ui.tableWidget.setHorizontalHeaderLabels(["Path", "Title", "Artist"])
|
||||
|
||||
# Work through new files
|
||||
source_dir = self.ui.lblSourceDirectory.text()
|
||||
with db.Session() as session:
|
||||
for new_basename in os.listdir(source_dir):
|
||||
new_path = os.path.join(source_dir, new_basename)
|
||||
if not os.path.isfile(new_path):
|
||||
continue
|
||||
rf = TrackFileData(source_file_path=new_path)
|
||||
rf.tags = get_tags(new_path)
|
||||
if not rf.tags['title'] or not rf.tags['artist']:
|
||||
show_warning(
|
||||
parent=self.main_window,
|
||||
title="Error",
|
||||
msg=(
|
||||
f"File {new_path} missing tags\n\n:"
|
||||
f"Title={rf.tags['title']}\n"
|
||||
f"Artist={rf.tags['artist']}\n"
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
# Check for same filename
|
||||
match_track = self.check_by_basename(
|
||||
session, new_path, rf.tags['artist'], rf.tags['title']
|
||||
)
|
||||
if not match_track:
|
||||
match_track = self.check_by_title(
|
||||
session, new_path, rf.tags['artist'], rf.tags['title']
|
||||
)
|
||||
if not match_track:
|
||||
match_track = self.get_fuzzy_match(session, new_basename)
|
||||
|
||||
# Build summary
|
||||
rf.track_path = os.path.join(Config.REPLACE_FILES_DEFAULT_DESTINATION,
|
||||
new_basename)
|
||||
if match_track:
|
||||
rf.track_id = match_track.id
|
||||
match_basename = os.path.basename(match_track.path)
|
||||
if match_basename == new_basename:
|
||||
path_text = " " + new_basename + " (no change)"
|
||||
else:
|
||||
path_text = f" {match_basename} →\n {new_basename}"
|
||||
filename_item = QTableWidgetItem(path_text)
|
||||
|
||||
if match_track.title == rf.tags['title']:
|
||||
title_text = " " + rf.tags['title'] + " (no change)"
|
||||
else:
|
||||
title_text = f" {match_track.title} →\n {rf.tags['title']}"
|
||||
title_item = QTableWidgetItem(title_text)
|
||||
|
||||
if match_track.artist == rf.tags['artist']:
|
||||
artist_text = " " + rf.tags['artist'] + " (no change)"
|
||||
else:
|
||||
artist_text = f" {match_track.artist} →\n {rf.tags['artist']}"
|
||||
artist_item = QTableWidgetItem(artist_text)
|
||||
|
||||
else:
|
||||
filename_item = QTableWidgetItem(" " + new_basename + " (new)")
|
||||
title_item = QTableWidgetItem(" " + rf.tags['title'])
|
||||
artist_item = QTableWidgetItem(" " + rf.tags['artist'])
|
||||
|
||||
self.replacement_files.append(rf)
|
||||
row = self.ui.tableWidget.rowCount()
|
||||
self.ui.tableWidget.insertRow(row)
|
||||
self.ui.tableWidget.setItem(row, 0, filename_item)
|
||||
self.ui.tableWidget.setItem(row, 1, title_item)
|
||||
self.ui.tableWidget.setItem(row, 2, artist_item)
|
||||
|
||||
self.ui.tableWidget.resizeColumnsToContents()
|
||||
self.ui.tableWidget.resizeRowsToContents()
|
||||
|
||||
def check_by_basename(
|
||||
self, session: Session, new_path: str, new_path_artist: str, new_path_title: str
|
||||
) -> Optional[Tracks]:
|
||||
"""
|
||||
Return Track that matches basename and tags
|
||||
"""
|
||||
|
||||
match_track = None
|
||||
candidates_by_basename = Tracks.get_by_basename(session, new_path)
|
||||
if candidates_by_basename:
|
||||
# Check tags are the same
|
||||
for cbbn in candidates_by_basename:
|
||||
cbbn_tags = get_tags(cbbn.path)
|
||||
if (
|
||||
cbbn_tags["title"].lower() == new_path_title.lower()
|
||||
and cbbn_tags["artist"].lower() == new_path_artist.lower()
|
||||
):
|
||||
match_track = cbbn
|
||||
break
|
||||
|
||||
return match_track
|
||||
|
||||
def check_by_title(
|
||||
self, session: Session, new_path: str, new_path_artist: str, new_path_title: str
|
||||
) -> Optional[Tracks]:
|
||||
"""
|
||||
Return Track that mathces title and artist
|
||||
"""
|
||||
|
||||
match_track = None
|
||||
candidates_by_title = Tracks.search_titles(session, new_path_title)
|
||||
if candidates_by_title:
|
||||
# Check artist tag
|
||||
for cbt in candidates_by_title:
|
||||
cbt_artist = get_tags(cbt.path)["artist"]
|
||||
if cbt_artist.lower() == new_path_artist.lower():
|
||||
match_track = cbt
|
||||
break
|
||||
|
||||
return match_track
|
||||
|
||||
def get_fuzzy_match(self, session: Session, fname: str) -> Optional[Tracks]:
|
||||
"""
|
||||
Return Track that matches fuzzy filename search
|
||||
"""
|
||||
|
||||
match_track = None
|
||||
choice = pydymenu.rofi([a.path for a in Tracks.get_all(session)], prompt=fname)
|
||||
if choice:
|
||||
match_track = Tracks.get_by_path(session, choice[0])
|
||||
|
||||
return match_track
|
||||
from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
|
||||
|
||||
|
||||
class TrackSelectDialog(QDialog):
|
||||
@ -221,7 +42,7 @@ class TrackSelectDialog(QDialog):
|
||||
self.new_row_number = new_row_number
|
||||
self.source_model = source_model
|
||||
self.add_to_header = add_to_header
|
||||
self.ui = dlg_TrackSelect_ui.Ui_Dialog()
|
||||
self.ui = Ui_Dialog()
|
||||
self.ui.setupUi(self)
|
||||
self.ui.btnAdd.clicked.connect(self.add_selected)
|
||||
self.ui.btnAddClose.clicked.connect(self.add_selected_and_close)
|
||||
|
||||
@ -115,19 +115,11 @@ def get_embedded_time(text: str) -> Optional[dt.datetime]:
|
||||
return None
|
||||
|
||||
|
||||
def get_all_track_metadata(filepath: str) -> Dict[str, str | int | float]:
|
||||
"""Return all track metadata"""
|
||||
def get_file_metadata(filepath: str) -> dict:
|
||||
"""Return track metadata"""
|
||||
|
||||
return (
|
||||
get_audio_metadata(filepath)
|
||||
| get_tags(filepath)
|
||||
| dict(path=filepath)
|
||||
)
|
||||
|
||||
def get_audio_metadata(filepath: str) -> Dict[str, str | int | float]:
|
||||
"""Return audio metadata"""
|
||||
|
||||
metadata: Dict[str, str | int | float] = {}
|
||||
# Get title, artist, bitrate, duration, path
|
||||
metadata: Dict[str, str | int | float] = get_tags(filepath)
|
||||
|
||||
metadata["mtime"] = os.path.getmtime(filepath)
|
||||
|
||||
@ -204,6 +196,7 @@ 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,
|
||||
)
|
||||
|
||||
|
||||
@ -351,13 +344,10 @@ def send_mail(to_addr, from_addr, subj, body):
|
||||
def set_track_metadata(track):
|
||||
"""Set/update track metadata in database"""
|
||||
|
||||
audio_metadata = get_audio_metadata(track.path)
|
||||
tags = get_tags(track.path)
|
||||
metadata = get_file_metadata(track.path)
|
||||
|
||||
for audio_key in audio_metadata:
|
||||
setattr(track, audio_key, audio_metadata[audio_key])
|
||||
for tag_key in tags:
|
||||
setattr(track, tag_key, tags[tag_key])
|
||||
for key in metadata:
|
||||
setattr(track, key, metadata[key])
|
||||
|
||||
|
||||
def show_OK(parent: QMainWindow, title: str, msg: str) -> None:
|
||||
|
||||
@ -33,7 +33,7 @@ if ALCHEMICAL_DATABASE_URI is None:
|
||||
raise ValueError("ALCHEMICAL_DATABASE_URI is undefined")
|
||||
if "unittest" in sys.modules and "sqlite" not in ALCHEMICAL_DATABASE_URI:
|
||||
raise ValueError("Unit tests running on non-Sqlite database")
|
||||
db = Alchemical(ALCHEMICAL_DATABASE_URI, engine_options=Config.ENGINE_OPTIONS)
|
||||
db = Alchemical(ALCHEMICAL_DATABASE_URI)
|
||||
|
||||
|
||||
# Database classes
|
||||
@ -130,9 +130,7 @@ class Playdates(dbtables.PlaydatesTable):
|
||||
session.commit()
|
||||
|
||||
@staticmethod
|
||||
def last_playdates(
|
||||
session: Session, track_id: int, limit=5
|
||||
) -> Sequence["Playdates"]:
|
||||
def last_playdates(session: Session, track_id: int, limit=5) -> Sequence["Playdates"]:
|
||||
"""
|
||||
Return a list of the last limit playdates for this track, sorted
|
||||
earliest to latest.
|
||||
@ -171,7 +169,9 @@ class Playdates(dbtables.PlaydatesTable):
|
||||
"""
|
||||
|
||||
return session.scalars(
|
||||
Playdates.select().order_by(Playdates.lastplayed.desc()).limit(limit)
|
||||
Playdates.select()
|
||||
.order_by(Playdates.lastplayed.desc())
|
||||
.limit(limit)
|
||||
).all()
|
||||
|
||||
@staticmethod
|
||||
@ -673,21 +673,6 @@ class Tracks(dbtables.TracksTable):
|
||||
|
||||
return session.scalars(select(cls)).unique().all()
|
||||
|
||||
@classmethod
|
||||
def get_by_basename(
|
||||
cls, session: Session, basename: str
|
||||
) -> Optional[Sequence["Tracks"]]:
|
||||
"""
|
||||
Return track(s) with passed basename, or None.
|
||||
"""
|
||||
|
||||
try:
|
||||
return session.scalars(
|
||||
Tracks.select().where(Tracks.path.like("%/" + basename))
|
||||
).all()
|
||||
except NoResultFound:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_by_path(cls, session: Session, path: str) -> Optional["Tracks"]:
|
||||
"""
|
||||
|
||||
@ -7,7 +7,6 @@ from typing import cast, List, Optional
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
@ -49,7 +48,6 @@ from PyQt6.QtWidgets import (
|
||||
# Third party imports
|
||||
from pygame import mixer
|
||||
import pipeclient
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm.session import Session
|
||||
import stackprinter # type: ignore
|
||||
|
||||
@ -59,10 +57,9 @@ from classes import (
|
||||
FadeCurve,
|
||||
MusicMusterSignals,
|
||||
PlaylistTrack,
|
||||
TrackFileData,
|
||||
)
|
||||
from config import Config
|
||||
from dialogs import TrackSelectDialog, ReplaceFilesDialog
|
||||
from dialogs import TrackSelectDialog
|
||||
from log import log
|
||||
from models import db, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
|
||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||
@ -149,12 +146,12 @@ class ImportTrack(QObject):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
track_files: List[TrackFileData],
|
||||
filenames: List[str],
|
||||
source_model: PlaylistModel,
|
||||
row_number: Optional[int],
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.track_files = track_files
|
||||
self.filenames = filenames
|
||||
self.source_model = source_model
|
||||
if row_number is None:
|
||||
self.next_row_number = source_model.rowCount()
|
||||
@ -162,50 +159,25 @@ class ImportTrack(QObject):
|
||||
self.next_row_number = row_number
|
||||
self.signals = MusicMusterSignals()
|
||||
|
||||
# Sanity check
|
||||
for tf in track_files:
|
||||
if not tf.tags:
|
||||
raise Exception(f"ImportTrack: no tags for {tf.source_file_path}")
|
||||
if not tf.audio_metadata:
|
||||
raise Exception(
|
||||
f"ImportTrack: no audio_metadata for {tf.source_file_path}"
|
||||
)
|
||||
if tf.track_path is None:
|
||||
raise Exception(f"ImportTrack: no track_path for {tf.source_file_path}")
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Create track objects from passed files and add to visible playlist
|
||||
"""
|
||||
|
||||
with db.Session() as session:
|
||||
for tf in self.track_files:
|
||||
for fname in self.filenames:
|
||||
self.signals.status_message_signal.emit(
|
||||
f"Importing {basename(tf.source_file_path)}", 5000
|
||||
f"Importing {basename(fname)}", 5000
|
||||
)
|
||||
|
||||
# Sanity check
|
||||
if not os.path.exists(tf.source_file_path):
|
||||
log.error(f"ImportTrack: file not found: {tf.source_file_path=}")
|
||||
continue
|
||||
|
||||
# Move the track file. Check that we're not importing a
|
||||
# file that's already in its final destination.
|
||||
if os.path.exists(tf.track_path) and tf.track_path != tf.source_file_path:
|
||||
os.unlink(tf.track_path)
|
||||
shutil.move(tf.source_file_path, tf.track_path)
|
||||
|
||||
# Import track
|
||||
metadata = helpers.get_file_metadata(fname)
|
||||
try:
|
||||
track = Tracks(
|
||||
session, path=tf.track_path, **tf.audio_metadata | tf.tags
|
||||
)
|
||||
track = Tracks(session, **metadata)
|
||||
except Exception as e:
|
||||
self.signals.show_warning_signal.emit(
|
||||
"Error importing track", str(e)
|
||||
)
|
||||
return
|
||||
helpers.normalise_track(tf.track_path)
|
||||
helpers.normalise_track(track.path)
|
||||
# We're importing potentially multiple tracks in a loop.
|
||||
# If there's an error adding the track to the Tracks
|
||||
# table, the session will rollback, thus losing any
|
||||
@ -215,7 +187,7 @@ class ImportTrack(QObject):
|
||||
self.source_model.insert_row(self.next_row_number, track.id, "")
|
||||
self.next_row_number += 1
|
||||
self.signals.status_message_signal.emit(
|
||||
f"{len(self.track_files)} tracks imported", 10000
|
||||
f"{len(self.filenames)} tracks imported", 10000
|
||||
)
|
||||
self.import_finished.emit()
|
||||
|
||||
@ -487,14 +459,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
record.update(session, {"f_int": splitter_bottom})
|
||||
|
||||
# Save tab number of open playlists
|
||||
open_playlist_ids: dict[int, int] = {}
|
||||
for idx in range(self.tabPlaylist.count()):
|
||||
open_playlist_ids[self.tabPlaylist.widget(idx).playlist_id] = idx
|
||||
Playlists.clear_tabs(session, list(open_playlist_ids.keys()))
|
||||
for playlist_id, idx in open_playlist_ids.items():
|
||||
playlist_id = self.tabPlaylist.widget(idx).playlist_id
|
||||
playlist = session.get(Playlists, playlist_id)
|
||||
if playlist:
|
||||
log.debug(f"Set {playlist=} tab to {idx=}")
|
||||
playlist.tab = idx
|
||||
session.flush()
|
||||
|
||||
@ -563,7 +531,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.actionPaste.triggered.connect(self.paste_rows)
|
||||
self.actionPlay_next.triggered.connect(self.play_next)
|
||||
self.actionRenamePlaylist.triggered.connect(self.rename_playlist)
|
||||
self.actionReplace_files.triggered.connect(self.replace_files)
|
||||
self.actionResume.triggered.connect(self.resume)
|
||||
self.actionSave_as_template.triggered.connect(self.save_as_template)
|
||||
self.actionSearch_title_in_Songfacts.triggered.connect(
|
||||
@ -636,7 +603,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
add tab to display. Return index number of tab.
|
||||
"""
|
||||
|
||||
log.debug(f"create_playlist_tab({playlist=})")
|
||||
log.info(f"create_playlist_tab({playlist=})")
|
||||
|
||||
playlist_tab = PlaylistTab(
|
||||
musicmuster=self,
|
||||
@ -644,7 +611,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
)
|
||||
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
||||
|
||||
log.debug(f"create_playlist_tab() returned: {idx=}")
|
||||
log.info(f"create_playlist_tab() returned: {idx=}")
|
||||
return idx
|
||||
|
||||
def cut_rows(self) -> None:
|
||||
@ -823,29 +790,56 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
return
|
||||
|
||||
with db.Session() as session:
|
||||
track_files: list[TrackFileData] = []
|
||||
for fpath in dlg.selectedFiles():
|
||||
tf = TrackFileData(fpath)
|
||||
tf.tags = helpers.get_tags(fpath)
|
||||
do_import = self.ok_to_import(session, fpath, tf.tags)
|
||||
if do_import:
|
||||
tf.track_path = os.path.join(
|
||||
Config.IMPORT_DESTINATION, os.path.basename(fpath)
|
||||
new_tracks = []
|
||||
for fname in dlg.selectedFiles():
|
||||
txt = ""
|
||||
tags = helpers.get_tags(fname)
|
||||
title = tags["title"]
|
||||
if not title:
|
||||
helpers.show_warning(
|
||||
self,
|
||||
"Problem with track file",
|
||||
f"{fname} does not have a title tag",
|
||||
)
|
||||
tf.audio_metadata = helpers.get_audio_metadata(fpath)
|
||||
track_files.append(tf)
|
||||
|
||||
self.import_filenames(track_files)
|
||||
|
||||
def import_filenames(self, track_files: list[TrackFileData]) -> None:
|
||||
"""
|
||||
Import the list of filenames as new tracks
|
||||
"""
|
||||
continue
|
||||
artist = tags["artist"]
|
||||
if not artist:
|
||||
helpers.show_warning(
|
||||
self,
|
||||
"Problem with track file",
|
||||
f"{fname} does not have an artist tag",
|
||||
)
|
||||
continue
|
||||
count = 0
|
||||
possible_matches = Tracks.search_titles(session, title)
|
||||
if possible_matches:
|
||||
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"
|
||||
count += 1
|
||||
if count >= Config.MAX_IMPORT_MATCHES:
|
||||
txt += "\nThere are more similar-looking tracks"
|
||||
break
|
||||
txt += "\n"
|
||||
# Check whether to proceed if there were potential matches
|
||||
txt += "Proceed with import?"
|
||||
result = QMessageBox.question(
|
||||
self,
|
||||
"Possible duplicates",
|
||||
txt,
|
||||
QMessageBox.StandardButton.Ok,
|
||||
QMessageBox.StandardButton.Cancel,
|
||||
)
|
||||
if result == QMessageBox.StandardButton.Cancel:
|
||||
continue
|
||||
new_tracks.append(fname)
|
||||
|
||||
# Import in separate thread
|
||||
self.import_thread = QThread()
|
||||
self.worker = ImportTrack(
|
||||
track_files,
|
||||
new_tracks,
|
||||
self.active_proxy_model(),
|
||||
self.active_tab().source_model_selected_row_number(),
|
||||
)
|
||||
@ -856,58 +850,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.import_thread.finished.connect(self.import_thread.deleteLater)
|
||||
self.import_thread.start()
|
||||
|
||||
def ok_to_import(self, session: Session, fname: str, tags: dict[str, str]) -> bool:
|
||||
"""
|
||||
Check file has tags, check it's not a duplicate. Return True if this filenam
|
||||
is OK to import, False if not.
|
||||
"""
|
||||
|
||||
title = tags["title"]
|
||||
if not title:
|
||||
helpers.show_warning(
|
||||
self,
|
||||
"Problem with track file",
|
||||
f"{fname} does not have a title tag",
|
||||
)
|
||||
return False
|
||||
|
||||
artist = tags["artist"]
|
||||
if not artist:
|
||||
helpers.show_warning(
|
||||
self,
|
||||
"Problem with track file",
|
||||
f"{fname} does not have an artist tag",
|
||||
)
|
||||
return False
|
||||
|
||||
txt = ""
|
||||
count = 0
|
||||
possible_matches = Tracks.search_titles(session, title)
|
||||
if possible_matches:
|
||||
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"
|
||||
count += 1
|
||||
if count >= Config.MAX_IMPORT_MATCHES:
|
||||
txt += "\nThere are more similar-looking tracks"
|
||||
break
|
||||
txt += "\n"
|
||||
# Check whether to proceed if there were potential matches
|
||||
txt += "Proceed with import?"
|
||||
result = QMessageBox.question(
|
||||
self,
|
||||
"Possible duplicates",
|
||||
txt,
|
||||
QMessageBox.StandardButton.Ok,
|
||||
QMessageBox.StandardButton.Cancel,
|
||||
)
|
||||
if result == QMessageBox.StandardButton.Cancel:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def initialise_audacity(self) -> None:
|
||||
"""
|
||||
Initialise access to audacity
|
||||
@ -915,7 +857,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
try:
|
||||
self.audacity_client = pipeclient.PipeClient()
|
||||
log.debug(f"{hex(id(self.audacity_client))=}")
|
||||
log.info(f"{hex(id(self.audacity_client))=}")
|
||||
except RuntimeError as e:
|
||||
log.error(f"Unable to initialise Audacity: {str(e)}")
|
||||
|
||||
@ -1157,8 +1099,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
if self.catch_return_key:
|
||||
# Suppress inadvertent double press
|
||||
if (
|
||||
track_sequence.now.start_time
|
||||
and track_sequence.now.start_time
|
||||
track_sequence.now.start_time and track_sequence.now.start_time
|
||||
+ dt.timedelta(milliseconds=Config.RETURN_KEY_DEBOUNCE_MS)
|
||||
> dt.datetime.now()
|
||||
):
|
||||
@ -1286,64 +1227,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.tabBar.setTabText(idx, new_name)
|
||||
session.commit()
|
||||
|
||||
def replace_files(self) -> None:
|
||||
"""
|
||||
Scan source directory and offer to replace existing files with "similar"
|
||||
files, or import the source file as a new track.
|
||||
"""
|
||||
|
||||
import_files: list[TrackFileData] = []
|
||||
|
||||
with db.Session() as session:
|
||||
dlg = ReplaceFilesDialog(
|
||||
session=session,
|
||||
main_window=self,
|
||||
)
|
||||
status = dlg.exec()
|
||||
if status:
|
||||
for rf in dlg.replacement_files:
|
||||
if rf.track_id:
|
||||
# We're updating an existing track
|
||||
if rf.track_path:
|
||||
if os.path.exists(rf.track_path):
|
||||
os.unlink(rf.track_path)
|
||||
shutil.move(rf.source_file_path, rf.track_path)
|
||||
track = session.get(Tracks, rf.track_id)
|
||||
if not track:
|
||||
raise Exception(
|
||||
f"replace_files: could not retrieve track {rf.track_id}"
|
||||
)
|
||||
|
||||
track.artist = rf.tags["artist"]
|
||||
track.title = rf.tags["title"]
|
||||
if track.path != rf.track_path:
|
||||
track.path = rf.track_path
|
||||
try:
|
||||
session.commit()
|
||||
except IntegrityError:
|
||||
# https://jira.mariadb.org/browse/MDEV-29345 workaround
|
||||
session.rollback()
|
||||
track.path = "DUMMY"
|
||||
session.commit()
|
||||
track.path = rf.track_path
|
||||
session.commit()
|
||||
else:
|
||||
# We're importing a new track
|
||||
do_import = self.ok_to_import(
|
||||
session,
|
||||
os.path.basename(rf.source_file_path),
|
||||
rf.tags
|
||||
)
|
||||
if do_import:
|
||||
rf.audio_metadata = helpers.get_audio_metadata(rf.source_file_path)
|
||||
import_files.append(rf)
|
||||
|
||||
# self.import_filenames(dlg.replacement_files)
|
||||
self.import_filenames(import_files)
|
||||
else:
|
||||
session.rollback()
|
||||
session.close()
|
||||
|
||||
def resume(self) -> None:
|
||||
"""
|
||||
Resume playing last track. We may be playing the next track
|
||||
|
||||
@ -172,7 +172,7 @@ class PlaylistTab(QTableView):
|
||||
# Save passed settings
|
||||
self.musicmuster = musicmuster
|
||||
self.playlist_id = playlist_id
|
||||
log.debug(f"PlaylistTab.__init__({playlist_id=})")
|
||||
log.info(f"PlaylistTab.__init__({playlist_id=})")
|
||||
|
||||
# Set up widget
|
||||
self.source_model = PlaylistModel(playlist_id)
|
||||
@ -531,7 +531,7 @@ class PlaylistTab(QTableView):
|
||||
Called when column width changes. Save new width to database.
|
||||
"""
|
||||
|
||||
log.debug(f"_column_resize({column_number=}, {_old=}, {_new=}")
|
||||
log.info(f"_column_resize({column_number=}, {_old=}, {_new=}")
|
||||
|
||||
header = self.horizontalHeader()
|
||||
if not header:
|
||||
@ -635,7 +635,7 @@ class PlaylistTab(QTableView):
|
||||
)
|
||||
)
|
||||
|
||||
log.debug(f"get_selected_rows() returned: {result=}")
|
||||
log.info(f"get_selected_rows() returned: {result=}")
|
||||
return result
|
||||
|
||||
def _import_from_audacity(self, row_number: int) -> None:
|
||||
@ -726,7 +726,7 @@ class PlaylistTab(QTableView):
|
||||
If playlist_id is us, resize rows
|
||||
"""
|
||||
|
||||
log.debug(f"resize_rows({playlist_id=}) {self.playlist_id=}")
|
||||
log.info(f"resize_rows({playlist_id=}) {self.playlist_id=}")
|
||||
|
||||
if playlist_id and playlist_id != self.playlist_id:
|
||||
return
|
||||
@ -828,7 +828,7 @@ class PlaylistTab(QTableView):
|
||||
def _set_column_widths(self) -> None:
|
||||
"""Column widths from settings"""
|
||||
|
||||
log.debug("_set_column_widths()")
|
||||
log.info("_set_column_widths()")
|
||||
|
||||
header = self.horizontalHeader()
|
||||
if not header:
|
||||
|
||||
@ -134,7 +134,7 @@ def main():
|
||||
if process_no_matches:
|
||||
prompt = f"file={new_fname}\n title={new_title}\n artist={new_artist}: "
|
||||
# Use fzf to search
|
||||
choice = pydymenu.rofi(parent_fnames, prompt=prompt)
|
||||
choice = pydymenu.fzf(parent_fnames, prompt=prompt)
|
||||
if choice:
|
||||
old_file = os.path.join(parent_dir, choice[0])
|
||||
oldtags = get_tags(old_file)
|
||||
|
||||
@ -1,145 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1038</width>
|
||||
<height>774</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>680</x>
|
||||
<y>730</y>
|
||||
<width>341</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>15</y>
|
||||
<width>181</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Source directory:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>50</y>
|
||||
<width>181</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Destination directory:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="lblSourceDirectory">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
<y>15</y>
|
||||
<width>811</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>lblSourceDirectory</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="lblDestinationDirectory">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
<y>50</y>
|
||||
<width>811</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>lblDestinationDirectory</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="tableWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>90</y>
|
||||
<width>1001</width>
|
||||
<height>621</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<column/>
|
||||
<column/>
|
||||
<column/>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@ -1,53 +0,0 @@
|
||||
# Form implementation generated from reading ui file 'app/ui/dlgReplaceFiles.ui'
|
||||
#
|
||||
# Created by: PyQt6 UI code generator 6.7.0
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_Dialog(object):
|
||||
def setupUi(self, Dialog):
|
||||
Dialog.setObjectName("Dialog")
|
||||
Dialog.resize(1038, 774)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog)
|
||||
self.buttonBox.setGeometry(QtCore.QRect(680, 730, 341, 32))
|
||||
self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
|
||||
self.buttonBox.setObjectName("buttonBox")
|
||||
self.label = QtWidgets.QLabel(parent=Dialog)
|
||||
self.label.setGeometry(QtCore.QRect(10, 15, 181, 24))
|
||||
self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.label.setObjectName("label")
|
||||
self.label_2 = QtWidgets.QLabel(parent=Dialog)
|
||||
self.label_2.setGeometry(QtCore.QRect(10, 50, 181, 24))
|
||||
self.label_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.lblSourceDirectory = QtWidgets.QLabel(parent=Dialog)
|
||||
self.lblSourceDirectory.setGeometry(QtCore.QRect(200, 15, 811, 24))
|
||||
self.lblSourceDirectory.setObjectName("lblSourceDirectory")
|
||||
self.lblDestinationDirectory = QtWidgets.QLabel(parent=Dialog)
|
||||
self.lblDestinationDirectory.setGeometry(QtCore.QRect(200, 50, 811, 24))
|
||||
self.lblDestinationDirectory.setObjectName("lblDestinationDirectory")
|
||||
self.tableWidget = QtWidgets.QTableWidget(parent=Dialog)
|
||||
self.tableWidget.setGeometry(QtCore.QRect(20, 90, 1001, 621))
|
||||
self.tableWidget.setAlternatingRowColors(True)
|
||||
self.tableWidget.setColumnCount(3)
|
||||
self.tableWidget.setObjectName("tableWidget")
|
||||
self.tableWidget.setRowCount(0)
|
||||
|
||||
self.retranslateUi(Dialog)
|
||||
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
|
||||
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(Dialog)
|
||||
|
||||
def retranslateUi(self, Dialog):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
|
||||
self.label.setText(_translate("Dialog", "Source directory:"))
|
||||
self.label_2.setText(_translate("Dialog", "Destination directory:"))
|
||||
self.lblSourceDirectory.setText(_translate("Dialog", "lblSourceDirectory"))
|
||||
self.lblDestinationDirectory.setText(_translate("Dialog", "lblDestinationDirectory"))
|
||||
@ -753,8 +753,6 @@ padding-left: 8px;</string>
|
||||
<addaction name="actionDownload_CSV_of_played_tracks"/>
|
||||
<addaction name="actionSave_as_template"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionReplace_files"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionE_xit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuPlaylist">
|
||||
@ -1134,11 +1132,6 @@ padding-left: 8px;</string>
|
||||
<string>Select duplicate rows...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReplace_files">
|
||||
<property name="text">
|
||||
<string>Replace files...</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Form implementation generated from reading ui file 'app/ui/main_window.ui'
|
||||
#
|
||||
# Created by: PyQt6 UI code generator 6.7.0
|
||||
# Created by: PyQt6 UI code generator 6.6.1
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
@ -15,7 +15,11 @@ class Ui_MainWindow(object):
|
||||
MainWindow.resize(1280, 857)
|
||||
MainWindow.setMinimumSize(QtCore.QSize(1280, 0))
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/icons/musicmuster"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon.addPixmap(
|
||||
QtGui.QPixmap(":/icons/musicmuster"),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
MainWindow.setWindowIcon(icon)
|
||||
MainWindow.setStyleSheet("")
|
||||
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
|
||||
@ -27,39 +31,62 @@ class Ui_MainWindow(object):
|
||||
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
|
||||
self.verticalLayout_3.setObjectName("verticalLayout_3")
|
||||
self.previous_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.previous_track_2.sizePolicy().hasHeightForWidth())
|
||||
sizePolicy.setHeightForWidth(
|
||||
self.previous_track_2.sizePolicy().hasHeightForWidth()
|
||||
)
|
||||
self.previous_track_2.setSizePolicy(sizePolicy)
|
||||
self.previous_track_2.setMaximumSize(QtCore.QSize(230, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Sans")
|
||||
font.setPointSize(20)
|
||||
self.previous_track_2.setFont(font)
|
||||
self.previous_track_2.setStyleSheet("background-color: #f8d7da;\n"
|
||||
"border: 1px solid rgb(85, 87, 83);")
|
||||
self.previous_track_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.previous_track_2.setStyleSheet(
|
||||
"background-color: #f8d7da;\n" "border: 1px solid rgb(85, 87, 83);"
|
||||
)
|
||||
self.previous_track_2.setAlignment(
|
||||
QtCore.Qt.AlignmentFlag.AlignRight
|
||||
| QtCore.Qt.AlignmentFlag.AlignTrailing
|
||||
| QtCore.Qt.AlignmentFlag.AlignVCenter
|
||||
)
|
||||
self.previous_track_2.setObjectName("previous_track_2")
|
||||
self.verticalLayout_3.addWidget(self.previous_track_2)
|
||||
self.current_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.current_track_2.sizePolicy().hasHeightForWidth())
|
||||
sizePolicy.setHeightForWidth(
|
||||
self.current_track_2.sizePolicy().hasHeightForWidth()
|
||||
)
|
||||
self.current_track_2.setSizePolicy(sizePolicy)
|
||||
self.current_track_2.setMaximumSize(QtCore.QSize(230, 16777215))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Sans")
|
||||
font.setPointSize(20)
|
||||
self.current_track_2.setFont(font)
|
||||
self.current_track_2.setStyleSheet("background-color: #d4edda;\n"
|
||||
"border: 1px solid rgb(85, 87, 83);")
|
||||
self.current_track_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.current_track_2.setStyleSheet(
|
||||
"background-color: #d4edda;\n" "border: 1px solid rgb(85, 87, 83);"
|
||||
)
|
||||
self.current_track_2.setAlignment(
|
||||
QtCore.Qt.AlignmentFlag.AlignRight
|
||||
| QtCore.Qt.AlignmentFlag.AlignTrailing
|
||||
| QtCore.Qt.AlignmentFlag.AlignVCenter
|
||||
)
|
||||
self.current_track_2.setObjectName("current_track_2")
|
||||
self.verticalLayout_3.addWidget(self.current_track_2)
|
||||
self.next_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.next_track_2.sizePolicy().hasHeightForWidth())
|
||||
@ -69,19 +96,29 @@ class Ui_MainWindow(object):
|
||||
font.setFamily("Sans")
|
||||
font.setPointSize(20)
|
||||
self.next_track_2.setFont(font)
|
||||
self.next_track_2.setStyleSheet("background-color: #fff3cd;\n"
|
||||
"border: 1px solid rgb(85, 87, 83);")
|
||||
self.next_track_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.next_track_2.setStyleSheet(
|
||||
"background-color: #fff3cd;\n" "border: 1px solid rgb(85, 87, 83);"
|
||||
)
|
||||
self.next_track_2.setAlignment(
|
||||
QtCore.Qt.AlignmentFlag.AlignRight
|
||||
| QtCore.Qt.AlignmentFlag.AlignTrailing
|
||||
| QtCore.Qt.AlignmentFlag.AlignVCenter
|
||||
)
|
||||
self.next_track_2.setObjectName("next_track_2")
|
||||
self.verticalLayout_3.addWidget(self.next_track_2)
|
||||
self.horizontalLayout_3.addLayout(self.verticalLayout_3)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.hdrPreviousTrack = QtWidgets.QLabel(parent=self.centralwidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.hdrPreviousTrack.sizePolicy().hasHeightForWidth())
|
||||
sizePolicy.setHeightForWidth(
|
||||
self.hdrPreviousTrack.sizePolicy().hasHeightForWidth()
|
||||
)
|
||||
self.hdrPreviousTrack.setSizePolicy(sizePolicy)
|
||||
self.hdrPreviousTrack.setMinimumSize(QtCore.QSize(0, 0))
|
||||
self.hdrPreviousTrack.setMaximumSize(QtCore.QSize(16777215, 16777215))
|
||||
@ -89,32 +126,43 @@ class Ui_MainWindow(object):
|
||||
font.setFamily("Sans")
|
||||
font.setPointSize(20)
|
||||
self.hdrPreviousTrack.setFont(font)
|
||||
self.hdrPreviousTrack.setStyleSheet("background-color: #f8d7da;\n"
|
||||
"border: 1px solid rgb(85, 87, 83);")
|
||||
self.hdrPreviousTrack.setStyleSheet(
|
||||
"background-color: #f8d7da;\n" "border: 1px solid rgb(85, 87, 83);"
|
||||
)
|
||||
self.hdrPreviousTrack.setText("")
|
||||
self.hdrPreviousTrack.setWordWrap(False)
|
||||
self.hdrPreviousTrack.setObjectName("hdrPreviousTrack")
|
||||
self.verticalLayout.addWidget(self.hdrPreviousTrack)
|
||||
self.hdrCurrentTrack = QtWidgets.QPushButton(parent=self.centralwidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.hdrCurrentTrack.sizePolicy().hasHeightForWidth())
|
||||
sizePolicy.setHeightForWidth(
|
||||
self.hdrCurrentTrack.sizePolicy().hasHeightForWidth()
|
||||
)
|
||||
self.hdrCurrentTrack.setSizePolicy(sizePolicy)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(20)
|
||||
self.hdrCurrentTrack.setFont(font)
|
||||
self.hdrCurrentTrack.setStyleSheet("background-color: #d4edda;\n"
|
||||
"border: 1px solid rgb(85, 87, 83);\n"
|
||||
"text-align: left;\n"
|
||||
"padding-left: 8px;\n"
|
||||
"")
|
||||
self.hdrCurrentTrack.setStyleSheet(
|
||||
"background-color: #d4edda;\n"
|
||||
"border: 1px solid rgb(85, 87, 83);\n"
|
||||
"text-align: left;\n"
|
||||
"padding-left: 8px;\n"
|
||||
""
|
||||
)
|
||||
self.hdrCurrentTrack.setText("")
|
||||
self.hdrCurrentTrack.setFlat(True)
|
||||
self.hdrCurrentTrack.setObjectName("hdrCurrentTrack")
|
||||
self.verticalLayout.addWidget(self.hdrCurrentTrack)
|
||||
self.hdrNextTrack = QtWidgets.QPushButton(parent=self.centralwidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.hdrNextTrack.sizePolicy().hasHeightForWidth())
|
||||
@ -122,10 +170,12 @@ class Ui_MainWindow(object):
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(20)
|
||||
self.hdrNextTrack.setFont(font)
|
||||
self.hdrNextTrack.setStyleSheet("background-color: #fff3cd;\n"
|
||||
"border: 1px solid rgb(85, 87, 83);\n"
|
||||
"text-align: left;\n"
|
||||
"padding-left: 8px;")
|
||||
self.hdrNextTrack.setStyleSheet(
|
||||
"background-color: #fff3cd;\n"
|
||||
"border: 1px solid rgb(85, 87, 83);\n"
|
||||
"text-align: left;\n"
|
||||
"padding-left: 8px;"
|
||||
)
|
||||
self.hdrNextTrack.setText("")
|
||||
self.hdrNextTrack.setFlat(True)
|
||||
self.hdrNextTrack.setObjectName("hdrNextTrack")
|
||||
@ -160,7 +210,12 @@ class Ui_MainWindow(object):
|
||||
self.cartsWidget.setObjectName("cartsWidget")
|
||||
self.horizontalLayout_Carts = QtWidgets.QHBoxLayout(self.cartsWidget)
|
||||
self.horizontalLayout_Carts.setObjectName("horizontalLayout_Carts")
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
|
||||
spacerItem = QtWidgets.QSpacerItem(
|
||||
40,
|
||||
20,
|
||||
QtWidgets.QSizePolicy.Policy.Expanding,
|
||||
QtWidgets.QSizePolicy.Policy.Minimum,
|
||||
)
|
||||
self.horizontalLayout_Carts.addItem(spacerItem)
|
||||
self.gridLayout_4.addWidget(self.cartsWidget, 2, 0, 1, 1)
|
||||
self.frame_6 = QtWidgets.QFrame(parent=self.centralwidget)
|
||||
@ -205,7 +260,11 @@ class Ui_MainWindow(object):
|
||||
self.btnPreview = QtWidgets.QPushButton(parent=self.FadeStopInfoFrame)
|
||||
self.btnPreview.setMinimumSize(QtCore.QSize(132, 41))
|
||||
icon1 = QtGui.QIcon()
|
||||
icon1.addPixmap(QtGui.QPixmap(":/icons/headphones"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon1.addPixmap(
|
||||
QtGui.QPixmap(":/icons/headphones"),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
self.btnPreview.setIcon(icon1)
|
||||
self.btnPreview.setIconSize(QtCore.QSize(30, 30))
|
||||
self.btnPreview.setCheckable(True)
|
||||
@ -289,10 +348,15 @@ class Ui_MainWindow(object):
|
||||
self.label_silent_timer.setObjectName("label_silent_timer")
|
||||
self.horizontalLayout.addWidget(self.frame_silent)
|
||||
self.widgetFadeVolume = PlotWidget(parent=self.InfoFooterFrame)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
|
||||
sizePolicy = QtWidgets.QSizePolicy(
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
QtWidgets.QSizePolicy.Policy.Preferred,
|
||||
)
|
||||
sizePolicy.setHorizontalStretch(1)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.widgetFadeVolume.sizePolicy().hasHeightForWidth())
|
||||
sizePolicy.setHeightForWidth(
|
||||
self.widgetFadeVolume.sizePolicy().hasHeightForWidth()
|
||||
)
|
||||
self.widgetFadeVolume.setSizePolicy(sizePolicy)
|
||||
self.widgetFadeVolume.setMinimumSize(QtCore.QSize(0, 0))
|
||||
self.widgetFadeVolume.setObjectName("widgetFadeVolume")
|
||||
@ -309,7 +373,11 @@ class Ui_MainWindow(object):
|
||||
self.btnFade.setMinimumSize(QtCore.QSize(132, 32))
|
||||
self.btnFade.setMaximumSize(QtCore.QSize(164, 16777215))
|
||||
icon2 = QtGui.QIcon()
|
||||
icon2.addPixmap(QtGui.QPixmap(":/icons/fade"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon2.addPixmap(
|
||||
QtGui.QPixmap(":/icons/fade"),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
self.btnFade.setIcon(icon2)
|
||||
self.btnFade.setIconSize(QtCore.QSize(30, 30))
|
||||
self.btnFade.setObjectName("btnFade")
|
||||
@ -317,7 +385,11 @@ class Ui_MainWindow(object):
|
||||
self.btnStop = QtWidgets.QPushButton(parent=self.frame)
|
||||
self.btnStop.setMinimumSize(QtCore.QSize(0, 36))
|
||||
icon3 = QtGui.QIcon()
|
||||
icon3.addPixmap(QtGui.QPixmap(":/icons/stopsign"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon3.addPixmap(
|
||||
QtGui.QPixmap(":/icons/stopsign"),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
self.btnStop.setIcon(icon3)
|
||||
self.btnStop.setObjectName("btnStop")
|
||||
self.verticalLayout_5.addWidget(self.btnStop)
|
||||
@ -343,39 +415,69 @@ class Ui_MainWindow(object):
|
||||
MainWindow.setStatusBar(self.statusbar)
|
||||
self.actionPlay_next = QtGui.QAction(parent=MainWindow)
|
||||
icon4 = QtGui.QIcon()
|
||||
icon4.addPixmap(QtGui.QPixmap("app/ui/../../../../.designer/backup/icon-play.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon4.addPixmap(
|
||||
QtGui.QPixmap("app/ui/../../../../.designer/backup/icon-play.png"),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
self.actionPlay_next.setIcon(icon4)
|
||||
self.actionPlay_next.setObjectName("actionPlay_next")
|
||||
self.actionSkipToNext = QtGui.QAction(parent=MainWindow)
|
||||
icon5 = QtGui.QIcon()
|
||||
icon5.addPixmap(QtGui.QPixmap(":/icons/next"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon5.addPixmap(
|
||||
QtGui.QPixmap(":/icons/next"),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
self.actionSkipToNext.setIcon(icon5)
|
||||
self.actionSkipToNext.setObjectName("actionSkipToNext")
|
||||
self.actionInsertTrack = QtGui.QAction(parent=MainWindow)
|
||||
icon6 = QtGui.QIcon()
|
||||
icon6.addPixmap(QtGui.QPixmap("app/ui/../../../../.designer/backup/icon_search_database.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon6.addPixmap(
|
||||
QtGui.QPixmap(
|
||||
"app/ui/../../../../.designer/backup/icon_search_database.png"
|
||||
),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
self.actionInsertTrack.setIcon(icon6)
|
||||
self.actionInsertTrack.setObjectName("actionInsertTrack")
|
||||
self.actionAdd_file = QtGui.QAction(parent=MainWindow)
|
||||
icon7 = QtGui.QIcon()
|
||||
icon7.addPixmap(QtGui.QPixmap("app/ui/../../../../.designer/backup/icon_open_file.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon7.addPixmap(
|
||||
QtGui.QPixmap("app/ui/../../../../.designer/backup/icon_open_file.png"),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
self.actionAdd_file.setIcon(icon7)
|
||||
self.actionAdd_file.setObjectName("actionAdd_file")
|
||||
self.actionFade = QtGui.QAction(parent=MainWindow)
|
||||
icon8 = QtGui.QIcon()
|
||||
icon8.addPixmap(QtGui.QPixmap("app/ui/../../../../.designer/backup/icon-fade.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon8.addPixmap(
|
||||
QtGui.QPixmap("app/ui/../../../../.designer/backup/icon-fade.png"),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
self.actionFade.setIcon(icon8)
|
||||
self.actionFade.setObjectName("actionFade")
|
||||
self.actionStop = QtGui.QAction(parent=MainWindow)
|
||||
icon9 = QtGui.QIcon()
|
||||
icon9.addPixmap(QtGui.QPixmap(":/icons/stop"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon9.addPixmap(
|
||||
QtGui.QPixmap(":/icons/stop"),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
self.actionStop.setIcon(icon9)
|
||||
self.actionStop.setObjectName("actionStop")
|
||||
self.action_Clear_selection = QtGui.QAction(parent=MainWindow)
|
||||
self.action_Clear_selection.setObjectName("action_Clear_selection")
|
||||
self.action_Resume_previous = QtGui.QAction(parent=MainWindow)
|
||||
icon10 = QtGui.QIcon()
|
||||
icon10.addPixmap(QtGui.QPixmap(":/icons/previous"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||
icon10.addPixmap(
|
||||
QtGui.QPixmap(":/icons/previous"),
|
||||
QtGui.QIcon.Mode.Normal,
|
||||
QtGui.QIcon.State.Off,
|
||||
)
|
||||
self.action_Resume_previous.setIcon(icon10)
|
||||
self.action_Resume_previous.setObjectName("action_Resume_previous")
|
||||
self.actionE_xit = QtGui.QAction(parent=MainWindow)
|
||||
@ -422,7 +524,9 @@ class Ui_MainWindow(object):
|
||||
self.actionImport = QtGui.QAction(parent=MainWindow)
|
||||
self.actionImport.setObjectName("actionImport")
|
||||
self.actionDownload_CSV_of_played_tracks = QtGui.QAction(parent=MainWindow)
|
||||
self.actionDownload_CSV_of_played_tracks.setObjectName("actionDownload_CSV_of_played_tracks")
|
||||
self.actionDownload_CSV_of_played_tracks.setObjectName(
|
||||
"actionDownload_CSV_of_played_tracks"
|
||||
)
|
||||
self.actionSearch = QtGui.QAction(parent=MainWindow)
|
||||
self.actionSearch.setObjectName("actionSearch")
|
||||
self.actionInsertSectionHeader = QtGui.QAction(parent=MainWindow)
|
||||
@ -450,13 +554,15 @@ class Ui_MainWindow(object):
|
||||
self.actionResume = QtGui.QAction(parent=MainWindow)
|
||||
self.actionResume.setObjectName("actionResume")
|
||||
self.actionSearch_title_in_Wikipedia = QtGui.QAction(parent=MainWindow)
|
||||
self.actionSearch_title_in_Wikipedia.setObjectName("actionSearch_title_in_Wikipedia")
|
||||
self.actionSearch_title_in_Wikipedia.setObjectName(
|
||||
"actionSearch_title_in_Wikipedia"
|
||||
)
|
||||
self.actionSearch_title_in_Songfacts = QtGui.QAction(parent=MainWindow)
|
||||
self.actionSearch_title_in_Songfacts.setObjectName("actionSearch_title_in_Songfacts")
|
||||
self.actionSearch_title_in_Songfacts.setObjectName(
|
||||
"actionSearch_title_in_Songfacts"
|
||||
)
|
||||
self.actionSelect_duplicate_rows = QtGui.QAction(parent=MainWindow)
|
||||
self.actionSelect_duplicate_rows.setObjectName("actionSelect_duplicate_rows")
|
||||
self.actionReplace_files = QtGui.QAction(parent=MainWindow)
|
||||
self.actionReplace_files.setObjectName("actionReplace_files")
|
||||
self.menuFile.addAction(self.actionNewPlaylist)
|
||||
self.menuFile.addAction(self.actionNew_from_template)
|
||||
self.menuFile.addAction(self.actionOpenPlaylist)
|
||||
@ -472,8 +578,6 @@ class Ui_MainWindow(object):
|
||||
self.menuFile.addAction(self.actionDownload_CSV_of_played_tracks)
|
||||
self.menuFile.addAction(self.actionSave_as_template)
|
||||
self.menuFile.addSeparator()
|
||||
self.menuFile.addAction(self.actionReplace_files)
|
||||
self.menuFile.addSeparator()
|
||||
self.menuFile.addAction(self.actionE_xit)
|
||||
self.menuPlaylist.addSeparator()
|
||||
self.menuPlaylist.addAction(self.actionPlay_next)
|
||||
@ -507,7 +611,7 @@ class Ui_MainWindow(object):
|
||||
self.retranslateUi(MainWindow)
|
||||
self.tabPlaylist.setCurrentIndex(-1)
|
||||
self.tabInfolist.setCurrentIndex(-1)
|
||||
self.actionE_xit.triggered.connect(MainWindow.close) # type: ignore
|
||||
self.actionE_xit.triggered.connect(MainWindow.close) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
@ -543,38 +647,58 @@ class Ui_MainWindow(object):
|
||||
self.actionFade.setShortcut(_translate("MainWindow", "Ctrl+Z"))
|
||||
self.actionStop.setText(_translate("MainWindow", "S&top"))
|
||||
self.actionStop.setShortcut(_translate("MainWindow", "Ctrl+Alt+S"))
|
||||
self.action_Clear_selection.setText(_translate("MainWindow", "Clear &selection"))
|
||||
self.action_Clear_selection.setText(
|
||||
_translate("MainWindow", "Clear &selection")
|
||||
)
|
||||
self.action_Clear_selection.setShortcut(_translate("MainWindow", "Esc"))
|
||||
self.action_Resume_previous.setText(_translate("MainWindow", "&Resume previous"))
|
||||
self.action_Resume_previous.setText(
|
||||
_translate("MainWindow", "&Resume previous")
|
||||
)
|
||||
self.actionE_xit.setText(_translate("MainWindow", "E&xit"))
|
||||
self.actionTest.setText(_translate("MainWindow", "&Test"))
|
||||
self.actionOpenPlaylist.setText(_translate("MainWindow", "O&pen..."))
|
||||
self.actionNewPlaylist.setText(_translate("MainWindow", "&New..."))
|
||||
self.actionTestFunction.setText(_translate("MainWindow", "&Test function"))
|
||||
self.actionSkipToFade.setText(_translate("MainWindow", "&Skip to start of fade"))
|
||||
self.actionSkipToFade.setText(
|
||||
_translate("MainWindow", "&Skip to start of fade")
|
||||
)
|
||||
self.actionSkipToEnd.setText(_translate("MainWindow", "Skip to &end of track"))
|
||||
self.actionClosePlaylist.setText(_translate("MainWindow", "&Close"))
|
||||
self.actionRenamePlaylist.setText(_translate("MainWindow", "&Rename..."))
|
||||
self.actionDeletePlaylist.setText(_translate("MainWindow", "Dele&te..."))
|
||||
self.actionMoveSelected.setText(_translate("MainWindow", "Mo&ve selected tracks to..."))
|
||||
self.actionMoveSelected.setText(
|
||||
_translate("MainWindow", "Mo&ve selected tracks to...")
|
||||
)
|
||||
self.actionExport_playlist.setText(_translate("MainWindow", "E&xport..."))
|
||||
self.actionSetNext.setText(_translate("MainWindow", "Set &next"))
|
||||
self.actionSetNext.setShortcut(_translate("MainWindow", "Ctrl+N"))
|
||||
self.actionSelect_next_track.setText(_translate("MainWindow", "Select next track"))
|
||||
self.actionSelect_next_track.setText(
|
||||
_translate("MainWindow", "Select next track")
|
||||
)
|
||||
self.actionSelect_next_track.setShortcut(_translate("MainWindow", "J"))
|
||||
self.actionSelect_previous_track.setText(_translate("MainWindow", "Select previous track"))
|
||||
self.actionSelect_previous_track.setText(
|
||||
_translate("MainWindow", "Select previous track")
|
||||
)
|
||||
self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K"))
|
||||
self.actionSelect_played_tracks.setText(_translate("MainWindow", "Select played tracks"))
|
||||
self.actionMoveUnplayed.setText(_translate("MainWindow", "Move &unplayed tracks to..."))
|
||||
self.actionSelect_played_tracks.setText(
|
||||
_translate("MainWindow", "Select played tracks")
|
||||
)
|
||||
self.actionMoveUnplayed.setText(
|
||||
_translate("MainWindow", "Move &unplayed tracks to...")
|
||||
)
|
||||
self.actionAdd_note.setText(_translate("MainWindow", "Add note..."))
|
||||
self.actionAdd_note.setShortcut(_translate("MainWindow", "Ctrl+T"))
|
||||
self.actionEnable_controls.setText(_translate("MainWindow", "Enable controls"))
|
||||
self.actionImport.setText(_translate("MainWindow", "Import track..."))
|
||||
self.actionImport.setShortcut(_translate("MainWindow", "Ctrl+Shift+I"))
|
||||
self.actionDownload_CSV_of_played_tracks.setText(_translate("MainWindow", "Download CSV of played tracks..."))
|
||||
self.actionDownload_CSV_of_played_tracks.setText(
|
||||
_translate("MainWindow", "Download CSV of played tracks...")
|
||||
)
|
||||
self.actionSearch.setText(_translate("MainWindow", "Search..."))
|
||||
self.actionSearch.setShortcut(_translate("MainWindow", "/"))
|
||||
self.actionInsertSectionHeader.setText(_translate("MainWindow", "Insert §ion header..."))
|
||||
self.actionInsertSectionHeader.setText(
|
||||
_translate("MainWindow", "Insert §ion header...")
|
||||
)
|
||||
self.actionInsertSectionHeader.setShortcut(_translate("MainWindow", "Ctrl+H"))
|
||||
self.actionRemove.setText(_translate("MainWindow", "&Remove track"))
|
||||
self.actionFind_next.setText(_translate("MainWindow", "Find next"))
|
||||
@ -582,8 +706,12 @@ class Ui_MainWindow(object):
|
||||
self.actionFind_previous.setText(_translate("MainWindow", "Find previous"))
|
||||
self.actionFind_previous.setShortcut(_translate("MainWindow", "P"))
|
||||
self.action_About.setText(_translate("MainWindow", "&About"))
|
||||
self.actionSave_as_template.setText(_translate("MainWindow", "Save as template..."))
|
||||
self.actionNew_from_template.setText(_translate("MainWindow", "New from template..."))
|
||||
self.actionSave_as_template.setText(
|
||||
_translate("MainWindow", "Save as template...")
|
||||
)
|
||||
self.actionNew_from_template.setText(
|
||||
_translate("MainWindow", "New from template...")
|
||||
)
|
||||
self.actionDebug.setText(_translate("MainWindow", "Debug"))
|
||||
self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1..."))
|
||||
self.actionMark_for_moving.setText(_translate("MainWindow", "Mark for moving"))
|
||||
@ -592,11 +720,22 @@ class Ui_MainWindow(object):
|
||||
self.actionPaste.setShortcut(_translate("MainWindow", "Ctrl+V"))
|
||||
self.actionResume.setText(_translate("MainWindow", "Resume"))
|
||||
self.actionResume.setShortcut(_translate("MainWindow", "Ctrl+R"))
|
||||
self.actionSearch_title_in_Wikipedia.setText(_translate("MainWindow", "Search title in Wikipedia"))
|
||||
self.actionSearch_title_in_Wikipedia.setShortcut(_translate("MainWindow", "Ctrl+W"))
|
||||
self.actionSearch_title_in_Songfacts.setText(_translate("MainWindow", "Search title in Songfacts"))
|
||||
self.actionSearch_title_in_Songfacts.setShortcut(_translate("MainWindow", "Ctrl+S"))
|
||||
self.actionSelect_duplicate_rows.setText(_translate("MainWindow", "Select duplicate rows..."))
|
||||
self.actionReplace_files.setText(_translate("MainWindow", "Replace files..."))
|
||||
self.actionSearch_title_in_Wikipedia.setText(
|
||||
_translate("MainWindow", "Search title in Wikipedia")
|
||||
)
|
||||
self.actionSearch_title_in_Wikipedia.setShortcut(
|
||||
_translate("MainWindow", "Ctrl+W")
|
||||
)
|
||||
self.actionSearch_title_in_Songfacts.setText(
|
||||
_translate("MainWindow", "Search title in Songfacts")
|
||||
)
|
||||
self.actionSearch_title_in_Songfacts.setShortcut(
|
||||
_translate("MainWindow", "Ctrl+S")
|
||||
)
|
||||
self.actionSelect_duplicate_rows.setText(
|
||||
_translate("MainWindow", "Select duplicate rows...")
|
||||
)
|
||||
|
||||
|
||||
from infotabs import InfoTabs
|
||||
from pyqtgraph import PlotWidget
|
||||
|
||||
233
poetry.lock
generated
233
poetry.lock
generated
@ -79,6 +79,27 @@ files = [
|
||||
[package.extras]
|
||||
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.12.3"
|
||||
description = "Screen-scraping library"
|
||||
optional = false
|
||||
python-versions = ">=3.6.0"
|
||||
files = [
|
||||
{file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"},
|
||||
{file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
soupsieve = ">1.2"
|
||||
|
||||
[package.extras]
|
||||
cchardet = ["cchardet"]
|
||||
chardet = ["chardet"]
|
||||
charset-normalizer = ["charset-normalizer"]
|
||||
html5lib = ["html5lib"]
|
||||
lxml = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "24.4.2"
|
||||
@ -439,6 +460,23 @@ urllib3 = "*"
|
||||
dev = ["dlint", "flake8-2020", "flake8-aaa", "flake8-absolute-import", "flake8-alfred", "flake8-annotations-complexity", "flake8-bandit", "flake8-black", "flake8-broken-line", "flake8-bugbear", "flake8-builtins", "flake8-coding", "flake8-cognitive-complexity", "flake8-commas", "flake8-comprehensions", "flake8-debugger", "flake8-django", "flake8-docstrings", "flake8-eradicate", "flake8-executable", "flake8-expression-complexity", "flake8-fixme", "flake8-functions", "flake8-future-import", "flake8-import-order", "flake8-isort", "flake8-logging-format", "flake8-mock", "flake8-mutable", "flake8-mypy", "flake8-pep3101", "flake8-pie", "flake8-print", "flake8-printf-formatting", "flake8-pyi", "flake8-pytest", "flake8-pytest-style", "flake8-quotes", "flake8-requirements", "flake8-rst-docstrings", "flake8-scrapy", "flake8-spellcheck", "flake8-sql", "flake8-strict", "flake8-string-format", "flake8-tidy-imports", "flake8-todo", "flake8-use-fstring", "flake8-variables-names", "isort[pyproject]", "mccabe", "pandas-vet", "pep8-naming", "pylint", "pytest", "typing-extensions", "wemake-python-styleguide"]
|
||||
docs = ["alabaster", "pygments-github-lexers", "recommonmark", "sphinx"]
|
||||
|
||||
[[package]]
|
||||
name = "furo"
|
||||
version = "2023.9.10"
|
||||
description = "A clean customisable Sphinx documentation theme."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "furo-2023.9.10-py3-none-any.whl", hash = "sha256:513092538537dc5c596691da06e3c370714ec99bc438680edc1debffb73e5bfc"},
|
||||
{file = "furo-2023.9.10.tar.gz", hash = "sha256:5707530a476d2a63b8cad83b4f961f3739a69f4b058bcf38a03a39fa537195b2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
beautifulsoup4 = "*"
|
||||
pygments = ">=2.7"
|
||||
sphinx = ">=6.0,<8.0"
|
||||
sphinx-basic-ng = "*"
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.0.3"
|
||||
@ -633,75 +671,75 @@ i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "line-profiler"
|
||||
version = "4.1.3"
|
||||
version = "4.1.2"
|
||||
description = "Line-by-line profiler"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "line_profiler-4.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b26cccca30c0f859c585cd4a6c75ffde4dca80ba98a858d3d04b44a6b560c65"},
|
||||
{file = "line_profiler-4.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e8a1ed7bf88049cb8d069a2dac96c91b25b5a77cb712c207b7f484ab86f8b134"},
|
||||
{file = "line_profiler-4.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320a8ccb2b9d0df85b8f19000242407d0cb1ea5804b4967fe6f755824c81a87"},
|
||||
{file = "line_profiler-4.1.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5751939d9dd95b1ec74e0aee428fe17d037fcb346fd23a7bf928b71c2dca2d19"},
|
||||
{file = "line_profiler-4.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b45f405d63730e5284403c1ff293f1e7f8ac7a39486db4c55a858712cec333d"},
|
||||
{file = "line_profiler-4.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9e24d61810ad153ab6a795d68f735812de4131f282128b799467f7fa56cac94f"},
|
||||
{file = "line_profiler-4.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f961465381e5bdc9fa7e5597af6714ada700d3e6ca61cca56763477f1047ff23"},
|
||||
{file = "line_profiler-4.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:6112436cb48ab635bc64e3dbfd80f67b56967e72aa7853e5084a64e11be5fe65"},
|
||||
{file = "line_profiler-4.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:16c8d2830e9daf0bcd49422e9367db5c825b02b88c383b9228c281ce14a5ad80"},
|
||||
{file = "line_profiler-4.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e3ed5dd55bda1b0f65893ff377b6aedae69490f7be4fd5d818dd5bcc75553bf"},
|
||||
{file = "line_profiler-4.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f0ad37589b270e59f65ec6704435f02ece6d4246af112c0413095a5d3b13285b"},
|
||||
{file = "line_profiler-4.1.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c29ef65e3e0085f20ffedcddfa8d02f6f6eaa0dacec29129cd74d206f9f6c"},
|
||||
{file = "line_profiler-4.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ef054e1b6fd2443341911a2ddad0f8b6ed24903fa6a7e5e8201cd4272132e3a"},
|
||||
{file = "line_profiler-4.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:02bc0650ef8f87a489d6fbafcc0040ca76144d2a4c40e4044babccfe769b5525"},
|
||||
{file = "line_profiler-4.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f032c0973f0c1150440dce5f9b91509fce474c11b10c2c93a2109e1e0dab8a45"},
|
||||
{file = "line_profiler-4.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec8a34285338aadc6a74e91b022b6d8ea19ac5deaaa0c9b880a1ab7b4ed45c43"},
|
||||
{file = "line_profiler-4.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8ae10578f1325772ccfa2833288d826e4bc781214d74b87331a6b7e5793252ca"},
|
||||
{file = "line_profiler-4.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b7c89c68379879d3a11c5e76499f0f7a08683436762af6bf51db126d3cb9cdd9"},
|
||||
{file = "line_profiler-4.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9f4abf9ecb8b508d96420dde44d54a8484e73468132229bbba2229283a7e9fb"},
|
||||
{file = "line_profiler-4.1.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d12bf40ed654ad1d5c132be172054b9ec5ae3ba138ca2099002075fb14396a64"},
|
||||
{file = "line_profiler-4.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56d17f3bf22b9c7d72b3cb2d283d71152f4cc98e8ba88e720c743b2e3d9be6ad"},
|
||||
{file = "line_profiler-4.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9d7c7593ae86215d99d1d32e4b92ed6ace2ac8388aab781b74bf97d44e72ff1f"},
|
||||
{file = "line_profiler-4.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:248f16ba356ac1e19be834b0bdaf29c95c1c9229beaa63e0e3aad9aa3edfc012"},
|
||||
{file = "line_profiler-4.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:b85468d30ed16e362e8a044df0f331796c6ec5a76a55e88aae57078a2eec6afa"},
|
||||
{file = "line_profiler-4.1.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:82d5333d1ffac08b34828213bd674165e50876610061faa97660928b346a620d"},
|
||||
{file = "line_profiler-4.1.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f56985a885e2936eab6303fc82f1a20e5e0bb6d4d8f44f8a3825179d261053e"},
|
||||
{file = "line_profiler-4.1.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:713d43be1382f47c2f04d5d25ba3c65978292249849f85746a8476d6a8863717"},
|
||||
{file = "line_profiler-4.1.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6a3dd7ba3a17da254338313ec1d4ce4bdd723812e5cb58f4d05b78c1c5dbe4"},
|
||||
{file = "line_profiler-4.1.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:481bbace88b2e15fb63a16e578a48faa28eba7399afe7da6ce1bde569780c346"},
|
||||
{file = "line_profiler-4.1.3-cp36-cp36m-win_amd64.whl", hash = "sha256:654b16f9e82b0ce7f7657ef859bf2324275e9cd70c8169414922c9cb37d5589f"},
|
||||
{file = "line_profiler-4.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:39332137af7a562c44524cef7c37de9860428ce2cde8b9c51047ccad9fd5eca4"},
|
||||
{file = "line_profiler-4.1.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dad96626acd5804c818c374d34ce1debea07b1e100b160499f4dfbcf5fc1cbe6"},
|
||||
{file = "line_profiler-4.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7125846d636959907e307c1f0bbf6f05fe5b7ca195b929f7b676fd20cf0763f2"},
|
||||
{file = "line_profiler-4.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a89de2a09363dd1a62a0a49e82a7157854b6e92b1893627b14e952412357db60"},
|
||||
{file = "line_profiler-4.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9e11f5831a251d3a3551372b523b3bc0da1e912ab2ade2c4d9d8e0b225eed6ab"},
|
||||
{file = "line_profiler-4.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:66d856975284dc62ac6f5a97757e160c1eb9898078014385cf74b829d8d806b7"},
|
||||
{file = "line_profiler-4.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3fb0f43900d36d7ccd8b30b8506498440d5ec610f2f1d40de3de11c3e304fb90"},
|
||||
{file = "line_profiler-4.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7394227bfb5bf15002d3695e674916fe82c38957cd2f56fccd43b71dc3447d1e"},
|
||||
{file = "line_profiler-4.1.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8e19a0ca3198b173a5b7caa304be3b39d122f89b0cfc2a134c5cbb4105ee2fd6"},
|
||||
{file = "line_profiler-4.1.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ad57e3c80fb0aee0c86a25d738e3556063eb3d57d0a43217de13f134417915d"},
|
||||
{file = "line_profiler-4.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cca919a8199236326f14f3719e992f30dd43a272b0e8fcb98e436a66e4a96fc"},
|
||||
{file = "line_profiler-4.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d6753834e1ea03ea19015d0553f0ce0d61bbf2269b85fc0f42833d616369488b"},
|
||||
{file = "line_profiler-4.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a32559afd550852f2054a441d33afe16e8b68b167ffb15373ec2b521c6fdc51f"},
|
||||
{file = "line_profiler-4.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:e526f9dfad5e8e21cd5345d5213757cfc26af33f072042f3ccff36b10c46a23c"},
|
||||
{file = "line_profiler-4.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5aec873bea3a1357c1a21f788b44d29e288df2a579b4433c8a85fc2b0a8c229d"},
|
||||
{file = "line_profiler-4.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6059a8960487fc1e7b333178d39c53d3de5fd3c7da04477019e70d13c4c8520c"},
|
||||
{file = "line_profiler-4.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ac815ba3cdc8603de6b0ea57a725f4aea1e0a2b7d8c99fabb43f6f2b1670dc0"},
|
||||
{file = "line_profiler-4.1.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ebd58a953fa86384150b79638331133ef0c22d8d68f046e00fe97e62053edae"},
|
||||
{file = "line_profiler-4.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c91e4cb038496e771220daccb512dab5311619392fec59ea916e9316630e9825"},
|
||||
{file = "line_profiler-4.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b4e4a49a42d4d9e1dce122dd0a5a427f9a337c22cf8a82712f006cae038870bf"},
|
||||
{file = "line_profiler-4.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:209d41401238eb0da340f92dfaf60dd84500be475b2b6738cf0ef28579b4df9a"},
|
||||
{file = "line_profiler-4.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:68684974e81344344174caf723bb4ab6659bc186d05c8f7e2453002e6bf74cff"},
|
||||
{file = "line_profiler-4.1.3.tar.gz", hash = "sha256:e5f1123c3672c3218ba063c23bd64a51159e44649fed6780b993c781fb5ed318"},
|
||||
{file = "line_profiler-4.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4344c1504ad1a57029a8ab30812d967a0917cad7b654077e8787e4a7d7ea3469"},
|
||||
{file = "line_profiler-4.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0720b356db3e9ca297c3260f280c5be3bb4b230eda61ce73b4df5e553418d37a"},
|
||||
{file = "line_profiler-4.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:09f742af37166768f92495bd3d3a71da1ba41d3004307a66c108f29ed947d6e1"},
|
||||
{file = "line_profiler-4.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:443a5df10eb7910df694340c8a81c1668a88bb59ca44149a3291f7b2ae960891"},
|
||||
{file = "line_profiler-4.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a906f9d1687eea7e5b22e3bd367d4b63706fcea1906baaad76b1cc4c1142553d"},
|
||||
{file = "line_profiler-4.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3b2c8cc34a776c5cfaa4a4a09a51541efcc9082dce15b19e494000e82576ced"},
|
||||
{file = "line_profiler-4.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:55ca0a78eb8d52515486c374ec53fa9e65e3c4128e8bbc909d8bfce267a91fdd"},
|
||||
{file = "line_profiler-4.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:f4a11389f06831d7984b63be0743fbbbae1ffb56fad04b4e538d3e6933b5c265"},
|
||||
{file = "line_profiler-4.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32fa07f6fecfd209329559e4ae945dc7bdc0703355c8924bbf19101495b2373f"},
|
||||
{file = "line_profiler-4.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6f8e9e8af6660629f214e424613c56a6622cf36d9c638c569c926b21374d7029"},
|
||||
{file = "line_profiler-4.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4753113c4e2c30a547937dbc456900d7f3a1b99bc8bc81a640a89306cd729c0f"},
|
||||
{file = "line_profiler-4.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f0989302404850a2a041ba60afe6c7240aea10fdd9432d5c1d464aca39a0369"},
|
||||
{file = "line_profiler-4.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f4b25ee412b0cd624614edd16c4c0af02dbeb73db2a08a49a14b120005a5630"},
|
||||
{file = "line_profiler-4.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93c6a49009ee75dcd8ff644c5fd39eeb8bb672d5a41bacdd239db14ae1ba3098"},
|
||||
{file = "line_profiler-4.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b96964cdb306741a01b95d210d634cc79ed70d2904336cbd8f69a9b5f284426d"},
|
||||
{file = "line_profiler-4.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:46a8cad2cb4b6a1229ddccf06694b1d01fd5acd1cf8c502caf937765a7c877de"},
|
||||
{file = "line_profiler-4.1.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a102fd8e13abd367379e39fd9426fd60e1e3a39fcd80fa25641618969464c022"},
|
||||
{file = "line_profiler-4.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44ee51bce974d6b2269492299d4abae6db1b06ae7617760c7436c597dbdbd032"},
|
||||
{file = "line_profiler-4.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e4cafd9a1effe1b9646f6a86716dbd291684fde1f8a297930d845d8a9340299"},
|
||||
{file = "line_profiler-4.1.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b433a2918e522d6dd0e6bdcf1216cede15c4f201f7eeb0d816114fbac5031cd7"},
|
||||
{file = "line_profiler-4.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad96accb1f5cdedfe2e6607f9be86d28196d3f743229e2b67bd28a40f76f133"},
|
||||
{file = "line_profiler-4.1.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4eb9df035861f7c2e9852773dff72a3324e2e5aebc0b8c7c2ba22437387ef5e7"},
|
||||
{file = "line_profiler-4.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e733c0e6626d0e9f1b434da40b93ed1c00ea503f3ced04f5a58c22d1163fe1c1"},
|
||||
{file = "line_profiler-4.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:8cc0c24384e29e99da5627669dbf312a23d11138de0169aa58d4ea5187522ba0"},
|
||||
{file = "line_profiler-4.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:900ad7be6d609fb1442200c7757de3534b381d6eeac22fa0135c5d0a900b5787"},
|
||||
{file = "line_profiler-4.1.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49c6c6e19c3c0d7cc8f1641ece9e52fec5e99c56472e26156c16473b7568d374"},
|
||||
{file = "line_profiler-4.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7ed1edd85f9a005a3e1316b3962a5fc42a159257cf2dfd13d10fcbefaece8ce"},
|
||||
{file = "line_profiler-4.1.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:2ed7027f7d1b3ae9a379a2f407f512b84ccf82d6a3a7b53a90bb17ada61928a9"},
|
||||
{file = "line_profiler-4.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e8537be16b46133ab86d6e805ca83b012b17ef36a7445dd5c89c45ba70b97aad"},
|
||||
{file = "line_profiler-4.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:934870b5e451c938f149c5475cc0286133d8718ba99ff4ec04fb1a87f7bfb985"},
|
||||
{file = "line_profiler-4.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbda8e0bb98b1790ba8819d0a72ee3e11e669c79fc703eaf0e5ed747cac2d441"},
|
||||
{file = "line_profiler-4.1.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78cfd263c79927f74f174e32b83e4692e26ada2fefcdfef0c1dae5cfabb37a37"},
|
||||
{file = "line_profiler-4.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:390f5e5dc047a62ffb7dbd236b4d44c6175d4f66aabe654f4b35df9b9aa79d02"},
|
||||
{file = "line_profiler-4.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dce014572ee599b2d571cf45fbd0c7d5f1a1e822dabe82581e18dd0229b16799"},
|
||||
{file = "line_profiler-4.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4fe92a239d8097a3a0cacb280e0a2455be6633da3c844b784ba011043d090b36"},
|
||||
{file = "line_profiler-4.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3df9b30cdd8b3652e658acb38a9533bac47f2b8f5c320c5a03dbdd378ac11b35"},
|
||||
{file = "line_profiler-4.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5643cb19c89f6749039452913803a8cfb554c07676f6c00bc96e0632a054abb6"},
|
||||
{file = "line_profiler-4.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:163d26586511b68551735052a1bcca173c9d8366573ab4a91c470c7f7bd89967"},
|
||||
{file = "line_profiler-4.1.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8fa3128e93e49ad8b5216e40dc9d2bc2e354e896c1512feead3d6db1668ce649"},
|
||||
{file = "line_profiler-4.1.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a1eb88cec273300377b364eee9ceffce2e639906bf210e7d7233c88dc87e62f"},
|
||||
{file = "line_profiler-4.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7f213eeb846c9bc950fd210dfcd0fa93b1d2991f218b8788c0759f06bd00557"},
|
||||
{file = "line_profiler-4.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ec6f137dbbdc0af6b88a1053c1430681c07a3b2d1719dc1f59be70d464851a23"},
|
||||
{file = "line_profiler-4.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3af457b2dfad6e2019f7e5bbe9eabac9b2c34824fb2ea574aee7b17998c48c98"},
|
||||
{file = "line_profiler-4.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:9dd72adc753019788ff0498dd686068c4d8e65d38c0eca1b4b58b5719c14fa7d"},
|
||||
{file = "line_profiler-4.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:62776d67dfc6c358de5c19d606eccbd95e6feb75928064850be0232e9276f751"},
|
||||
{file = "line_profiler-4.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:060d71ba11ff5476d7c10774a34955566bab545ab5ff39231306b4d84081725d"},
|
||||
{file = "line_profiler-4.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ad13e1d5a174336508bbf275202822c8898cd1f014881059103b748310d5bc84"},
|
||||
{file = "line_profiler-4.1.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77824dfc1f58dc7fe62fb053aa54586979ef60fea221dcdbba2022608c1314f"},
|
||||
{file = "line_profiler-4.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c8ffc44a030789f7bc6594de581b39e8da0591fc6c598dd4243cf140b200528"},
|
||||
{file = "line_profiler-4.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4729820d8da3ed92f14e30dbd28a851eeefe2ba70b8b897f2d9c886ade8007c1"},
|
||||
{file = "line_profiler-4.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0bce5c04d0daf6dd19348540012b0a6d69206ae40db096de222e6d5f824922e8"},
|
||||
{file = "line_profiler-4.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:a65b70d6ecef4f2e61cf504a5c77085718f1dae9508b21c9058ad483ae7e16ee"},
|
||||
{file = "line_profiler-4.1.2.tar.gz", hash = "sha256:aa56578b0ff5a756fe180b3fda7bd67c27bbd478b3d0124612d8cf00e4a21df2"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
all = ["Cython (>=3.0.3)", "IPython (>=7.14.0)", "IPython (>=7.18.0)", "IPython (>=8.12.2)", "IPython (>=8.14.0)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.8.1)", "cmake (>=3.21.2)", "coverage[toml] (>=6.1.1)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=7.3.0)", "ninja (>=1.10.2)", "pytest (>=6.2.5)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest-cov (>=3.0.0)", "rich (>=12.3.0)", "scikit-build (>=0.11.1)", "setuptools (>=41.0.1)", "setuptools (>=68.2.2)", "ubelt (>=1.3.4)", "xdoctest (>=1.1.3)"]
|
||||
all-strict = ["Cython (==3.0.3)", "IPython (==7.14.0)", "IPython (==7.18.0)", "IPython (==8.12.2)", "IPython (==8.14.0)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.8.1)", "cmake (==3.21.2)", "coverage[toml] (==6.1.1)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==7.3.0)", "ninja (==1.10.2)", "pytest (==6.2.5)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest-cov (==3.0.0)", "rich (==12.3.0)", "scikit-build (==0.11.1)", "setuptools (==41.0.1)", "setuptools (==68.2.2)", "ubelt (==1.3.4)", "xdoctest (==1.1.3)"]
|
||||
all = ["Cython (>=3.0.3)", "IPython (>=7.14.0)", "IPython (>=7.18.0)", "IPython (>=8.12.2)", "IPython (>=8.14.0)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.8.1)", "cmake (>=3.21.2)", "coverage[toml] (>=5.3)", "ninja (>=1.10.2)", "pytest (>=4.6.0)", "pytest (>=4.6.0)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=6.1.2)", "pytest (>=6.2.5)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.9.0)", "pytest-cov (>=3.0.0)", "rich (>=12.3.0)", "scikit-build (>=0.11.1)", "setuptools (>=41.0.1)", "setuptools (>=68.2.2)", "ubelt (>=1.3.4)", "xdoctest (>=1.1.2)"]
|
||||
all-strict = ["Cython (==3.0.3)", "IPython (==7.14.0)", "IPython (==7.18.0)", "IPython (==8.12.2)", "IPython (==8.14.0)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.8.1)", "cmake (==3.21.2)", "coverage[toml] (==5.3)", "ninja (==1.10.2)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==2.8.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.9.0)", "pytest-cov (==3.0.0)", "rich (==12.3.0)", "scikit-build (==0.11.1)", "setuptools (==41.0.1)", "setuptools (==68.2.2)", "ubelt (==1.3.4)", "xdoctest (==1.1.2)"]
|
||||
ipython = ["IPython (>=7.14.0)", "IPython (>=7.18.0)", "IPython (>=8.12.2)", "IPython (>=8.14.0)"]
|
||||
ipython-strict = ["IPython (==7.14.0)", "IPython (==7.18.0)", "IPython (==8.12.2)", "IPython (==8.14.0)"]
|
||||
optional = ["IPython (>=7.14.0)", "IPython (>=7.18.0)", "IPython (>=8.12.2)", "IPython (>=8.14.0)", "rich (>=12.3.0)"]
|
||||
optional-strict = ["IPython (==7.14.0)", "IPython (==7.18.0)", "IPython (==8.12.2)", "IPython (==8.14.0)", "rich (==12.3.0)"]
|
||||
tests = ["coverage[toml] (>=6.1.1)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=7.3.0)", "pytest (>=6.2.5)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest-cov (>=3.0.0)", "ubelt (>=1.3.4)", "xdoctest (>=1.1.3)"]
|
||||
tests-strict = ["coverage[toml] (==6.1.1)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==7.3.0)", "pytest (==6.2.5)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest-cov (==3.0.0)", "ubelt (==1.3.4)", "xdoctest (==1.1.3)"]
|
||||
tests = ["coverage[toml] (>=5.3)", "pytest (>=4.6.0)", "pytest (>=4.6.0)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=4.6.11)", "pytest (>=4.6.0,<=6.1.2)", "pytest (>=6.2.5)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.8.1)", "pytest-cov (>=2.9.0)", "pytest-cov (>=3.0.0)", "ubelt (>=1.3.4)", "xdoctest (>=1.1.2)"]
|
||||
tests-strict = ["coverage[toml] (==5.3)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==2.8.1)", "pytest-cov (==2.8.1)", "pytest-cov (==2.9.0)", "pytest-cov (==3.0.0)", "ubelt (==1.3.4)", "xdoctest (==1.1.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "mako"
|
||||
@ -1426,13 +1464,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pyqtgraph"
|
||||
version = "0.13.7"
|
||||
version = "0.13.6"
|
||||
description = "Scientific Graphics and GUI Library for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pyqtgraph-0.13.7-py3-none-any.whl", hash = "sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a"},
|
||||
{file = "pyqtgraph-0.13.7.tar.gz", hash = "sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3"},
|
||||
{file = "pyqtgraph-0.13.6-py3-none-any.whl", hash = "sha256:35740eb7e5908e490c6e06ef6ef15738dfba27ee35b0b0417638d515da6f9226"},
|
||||
{file = "pyqtgraph-0.13.6.tar.gz", hash = "sha256:2397a2197e7ac66920329bf28fb346b038d85a351f8988ccc806ffb79d11573d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1506,6 +1544,19 @@ pytest = "*"
|
||||
dev = ["pre-commit", "tox"]
|
||||
doc = ["sphinx", "sphinx-rtd-theme"]
|
||||
|
||||
[[package]]
|
||||
name = "python-levenshtein"
|
||||
version = "0.12.2"
|
||||
description = "Python extension for computing string edit distances and similarities."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "python-Levenshtein-0.12.2.tar.gz", hash = "sha256:dc2395fbd148a1ab31090dd113c366695934b9e85fe5a4b2a032745efd0346f6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
setuptools = "*"
|
||||
|
||||
[[package]]
|
||||
name = "python-slugify"
|
||||
version = "8.0.4"
|
||||
@ -1573,6 +1624,22 @@ pygments = ">=2.13.0,<3.0.0"
|
||||
[package.extras]
|
||||
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "69.5.1"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"},
|
||||
{file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
@ -1616,6 +1683,17 @@ files = [
|
||||
{file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soupsieve"
|
||||
version = "2.5"
|
||||
description = "A modern CSS selector implementation for Beautiful Soup."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"},
|
||||
{file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx"
|
||||
version = "7.3.7"
|
||||
@ -1650,6 +1728,23 @@ docs = ["sphinxcontrib-websupport"]
|
||||
lint = ["flake8 (>=3.5.0)", "importlib_metadata", "mypy (==1.9.0)", "pytest (>=6.0)", "ruff (==0.3.7)", "sphinx-lint", "tomli", "types-docutils", "types-requests"]
|
||||
test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=6.0)", "setuptools (>=67.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-basic-ng"
|
||||
version = "1.0.0b2"
|
||||
description = "A modern skeleton for Sphinx themes."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"},
|
||||
{file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
sphinx = ">=4.0"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"]
|
||||
|
||||
[[package]]
|
||||
name = "sphinxcontrib-applehelp"
|
||||
version = "1.0.8"
|
||||
@ -1886,6 +1981,20 @@ files = [
|
||||
{file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thefuzz"
|
||||
version = "0.19.0"
|
||||
description = "Fuzzy string matching in python"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "thefuzz-0.19.0-py2.py3-none-any.whl", hash = "sha256:4fcdde8e40f5ca5e8106bc7665181f9598a9c8b18b0a4d38c41a095ba6788972"},
|
||||
{file = "thefuzz-0.19.0.tar.gz", hash = "sha256:6f7126db2f2c8a54212b05e3a740e45f4291c497d75d20751728f635bb74aa3d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
speedup = ["python-levenshtein (>=0.12)"]
|
||||
|
||||
[[package]]
|
||||
name = "tinytag"
|
||||
version = "1.10.1"
|
||||
@ -2036,4 +2145,4 @@ test = ["websockets"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "b33fb0a465d9bea7ec2bf14800405452d2af35e377d4c02022e33d318d8f190c"
|
||||
content-hash = "1788eb932877661b33482412cbe7bb623a8bd8364db55bf2a1ad48d0e37cc737"
|
||||
|
||||
@ -16,6 +16,8 @@ psutil = "^5.9.8"
|
||||
pydub = "^0.25.1"
|
||||
types-psutil = "^5.9.5.20240423"
|
||||
python-slugify = "^8.0.4"
|
||||
thefuzz = "^0.19.0"
|
||||
python-Levenshtein = "^0.12.2"
|
||||
pyfzf = "^0.3.1"
|
||||
pydymenu = "^0.5.2"
|
||||
stackprinter = "^0.2.10"
|
||||
@ -31,12 +33,13 @@ obs-websocket-py = "^1.0"
|
||||
ipdb = "^0.13.9"
|
||||
pytest-qt = "^4.4.0"
|
||||
pydub-stubs = "^0.25.1"
|
||||
line-profiler = "^4.1.3"
|
||||
line-profiler = "^4.1.2"
|
||||
flakehell = "^0.9.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pudb = "*"
|
||||
sphinx = "^7.0.1"
|
||||
furo = "^2023.5.20"
|
||||
flakehell = "^0.9.0"
|
||||
mypy = "^1.7.0"
|
||||
pdbp = "^1.5.0"
|
||||
|
||||
@ -33,10 +33,12 @@ class TestMMModels(unittest.TestCase):
|
||||
|
||||
with db.Session() as session:
|
||||
track1_path = "testdata/isa.mp3"
|
||||
self.track1 = Tracks(session, **helpers.get_all_track_metadata(track1_path))
|
||||
metadata1 = helpers.get_file_metadata(track1_path)
|
||||
self.track1 = Tracks(session, **metadata1)
|
||||
|
||||
track2_path = "testdata/mom.mp3"
|
||||
self.track2 = Tracks(session, **helpers.get_all_track_metadata(track2_path))
|
||||
metadata2 = helpers.get_file_metadata(track2_path)
|
||||
self.track2 = Tracks(session, **metadata2)
|
||||
|
||||
def tearDown(self):
|
||||
db.drop_all()
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
# Standard library imports
|
||||
import os
|
||||
import unittest
|
||||
from typing import Optional
|
||||
|
||||
# PyQt imports
|
||||
from PyQt6.QtCore import Qt, QModelIndex
|
||||
|
||||
# Third party imports
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
# App imports
|
||||
from app.helpers import get_all_track_metadata
|
||||
from app.log import log
|
||||
from app.helpers import get_file_metadata
|
||||
|
||||
# Set up test database before importing db
|
||||
# Mark subsequent lines to ignore E402, imports not at top of file
|
||||
@ -20,6 +23,7 @@ from app import playlistmodel # noqa: E402
|
||||
from app.models import ( # noqa: E402
|
||||
db,
|
||||
Playlists,
|
||||
Settings,
|
||||
Tracks,
|
||||
)
|
||||
|
||||
@ -47,7 +51,8 @@ class TestMMMiscTracks(unittest.TestCase):
|
||||
|
||||
for row in range(len(self.test_tracks)):
|
||||
track_path = self.test_tracks[row % len(self.test_tracks)]
|
||||
track = Tracks(session, **get_all_track_metadata(track_path))
|
||||
metadata = get_file_metadata(track_path)
|
||||
track = Tracks(session, **metadata)
|
||||
self.model.insert_row(
|
||||
proposed_row_number=row, track_id=track.id, note=f"{row=}"
|
||||
)
|
||||
@ -108,7 +113,7 @@ class TestMMMiscNoPlaylist(unittest.TestCase):
|
||||
_ = str(model)
|
||||
|
||||
track_path = self.test_tracks[0]
|
||||
metadata = get_all_track_metadata(track_path)
|
||||
metadata = get_file_metadata(track_path)
|
||||
track = Tracks(session, **metadata)
|
||||
model.insert_row(proposed_row_number=0, track_id=track.id)
|
||||
|
||||
|
||||
@ -3,12 +3,14 @@ import os
|
||||
import unittest
|
||||
|
||||
# PyQt imports
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
# Third party imports
|
||||
import pytest
|
||||
from pytestqt.plugin import QtBot # type: ignore
|
||||
|
||||
# App imports
|
||||
from app import helpers
|
||||
|
||||
# Set up test database before importing db
|
||||
# Mark subsequent lines to ignore E402, imports not at top of file
|
||||
|
||||
Loading…
Reference in New Issue
Block a user