Build in replace_file functionality
Major rewrite of file importing Fixes #141
This commit is contained in:
parent
6aa09bf28a
commit
a24ff76b6b
@ -1,6 +1,6 @@
|
||||
# Standard library imports
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional
|
||||
import datetime as dt
|
||||
|
||||
# PyQt imports
|
||||
@ -203,6 +203,19 @@ 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
|
||||
|
||||
@ -78,6 +78,7 @@ 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
|
||||
@ -94,3 +95,4 @@ 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,24 +1,203 @@
|
||||
# Standard library imports
|
||||
from typing import Optional
|
||||
import os
|
||||
|
||||
# PyQt imports
|
||||
from PyQt6.QtCore import QEvent, Qt
|
||||
from PyQt6.QtWidgets import QDialog, QListWidgetItem
|
||||
from PyQt6.QtWidgets import (
|
||||
QDialog,
|
||||
QListWidgetItem,
|
||||
QMainWindow,
|
||||
QTableWidgetItem,
|
||||
)
|
||||
|
||||
# Third party imports
|
||||
import pydymenu # type: ignore
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
# App imports
|
||||
from classes import MusicMusterSignals
|
||||
from classes import MusicMusterSignals, TrackFileData
|
||||
from config import Config
|
||||
from helpers import (
|
||||
ask_yes_no,
|
||||
get_relative_date,
|
||||
get_tags,
|
||||
ms_to_mmss,
|
||||
show_warning,
|
||||
)
|
||||
from log import log
|
||||
from models import Settings, Tracks
|
||||
from models import db, Settings, Tracks
|
||||
from playlistmodel import PlaylistModel
|
||||
from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
|
||||
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
|
||||
|
||||
|
||||
class TrackSelectDialog(QDialog):
|
||||
@ -42,7 +221,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 = Ui_Dialog()
|
||||
self.ui = dlg_TrackSelect_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,11 +115,10 @@ def get_embedded_time(text: str) -> Optional[dt.datetime]:
|
||||
return None
|
||||
|
||||
|
||||
def get_file_metadata(filepath: str) -> dict:
|
||||
def get_audio_metadata(filepath: str) -> Dict[str, str | int | float]:
|
||||
"""Return track metadata"""
|
||||
|
||||
# Get title, artist, bitrate, duration, path
|
||||
metadata: Dict[str, str | int | float] = get_tags(filepath)
|
||||
metadata: Dict[str, str | int | float] = {}
|
||||
|
||||
metadata["mtime"] = os.path.getmtime(filepath)
|
||||
|
||||
@ -196,7 +195,6 @@ 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,
|
||||
)
|
||||
|
||||
|
||||
@ -344,10 +342,13 @@ def send_mail(to_addr, from_addr, subj, body):
|
||||
def set_track_metadata(track):
|
||||
"""Set/update track metadata in database"""
|
||||
|
||||
metadata = get_file_metadata(track.path)
|
||||
audio_metadata = get_audio_metadata(track.path)
|
||||
tags = get_tags(track.path)
|
||||
|
||||
for key in metadata:
|
||||
setattr(track, key, metadata[key])
|
||||
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])
|
||||
|
||||
|
||||
def show_OK(parent: QMainWindow, title: str, msg: str) -> None:
|
||||
|
||||
@ -130,7 +130,9 @@ 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.
|
||||
@ -169,9 +171,7 @@ 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,6 +673,21 @@ 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,6 +7,7 @@ from typing import cast, List, Optional
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
@ -48,6 +49,7 @@ 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
|
||||
|
||||
@ -57,9 +59,10 @@ from classes import (
|
||||
FadeCurve,
|
||||
MusicMusterSignals,
|
||||
PlaylistTrack,
|
||||
TrackFileData,
|
||||
)
|
||||
from config import Config
|
||||
from dialogs import TrackSelectDialog
|
||||
from dialogs import TrackSelectDialog, ReplaceFilesDialog
|
||||
from log import log
|
||||
from models import db, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
|
||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||
@ -146,12 +149,12 @@ class ImportTrack(QObject):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
filenames: List[str],
|
||||
track_files: List[TrackFileData],
|
||||
source_model: PlaylistModel,
|
||||
row_number: Optional[int],
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.filenames = filenames
|
||||
self.track_files = track_files
|
||||
self.source_model = source_model
|
||||
if row_number is None:
|
||||
self.next_row_number = source_model.rowCount()
|
||||
@ -159,25 +162,49 @@ 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 fname in self.filenames:
|
||||
for tf in self.track_files:
|
||||
self.signals.status_message_signal.emit(
|
||||
f"Importing {basename(fname)}", 5000
|
||||
f"Importing {basename(tf.source_file_path)}", 5000
|
||||
)
|
||||
metadata = helpers.get_file_metadata(fname)
|
||||
|
||||
# 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 os.path.exists(tf.source_file_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
|
||||
try:
|
||||
track = Tracks(session, **metadata)
|
||||
track = Tracks(
|
||||
session, path=tf.track_path, **tf.audio_metadata | tf.tags
|
||||
)
|
||||
except Exception as e:
|
||||
self.signals.show_warning_signal.emit(
|
||||
"Error importing track", str(e)
|
||||
)
|
||||
return
|
||||
helpers.normalise_track(track.path)
|
||||
helpers.normalise_track(tf.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
|
||||
@ -187,7 +214,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.filenames)} tracks imported", 10000
|
||||
f"{len(self.track_files)} tracks imported", 10000
|
||||
)
|
||||
self.import_finished.emit()
|
||||
|
||||
@ -531,6 +558,7 @@ 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(
|
||||
@ -790,56 +818,29 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
return
|
||||
|
||||
with db.Session() as session:
|
||||
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",
|
||||
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)
|
||||
)
|
||||
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)
|
||||
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
|
||||
"""
|
||||
|
||||
# Import in separate thread
|
||||
self.import_thread = QThread()
|
||||
self.worker = ImportTrack(
|
||||
new_tracks,
|
||||
track_files,
|
||||
self.active_proxy_model(),
|
||||
self.active_tab().source_model_selected_row_number(),
|
||||
)
|
||||
@ -850,6 +851,58 @@ 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
|
||||
@ -1099,7 +1152,8 @@ 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()
|
||||
):
|
||||
@ -1227,6 +1281,64 @@ 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
|
||||
|
||||
@ -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.fzf(parent_fnames, prompt=prompt)
|
||||
choice = pydymenu.rofi(parent_fnames, prompt=prompt)
|
||||
if choice:
|
||||
old_file = os.path.join(parent_dir, choice[0])
|
||||
oldtags = get_tags(old_file)
|
||||
|
||||
145
app/ui/dlgReplaceFiles.ui
Normal file
145
app/ui/dlgReplaceFiles.ui
Normal file
@ -0,0 +1,145 @@
|
||||
<?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>
|
||||
53
app/ui/dlg_replace_files_ui.py
Normal file
53
app/ui/dlg_replace_files_ui.py
Normal file
@ -0,0 +1,53 @@
|
||||
# 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,6 +753,8 @@ 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">
|
||||
@ -1132,6 +1134,11 @@ 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.6.1
|
||||
# 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.
|
||||
@ -15,11 +15,7 @@ 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)
|
||||
@ -31,62 +27,39 @@ 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())
|
||||
@ -96,29 +69,19 @@ 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))
|
||||
@ -126,43 +89,32 @@ 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())
|
||||
@ -170,12 +122,10 @@ 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")
|
||||
@ -210,12 +160,7 @@ 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)
|
||||
@ -260,11 +205,7 @@ 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)
|
||||
@ -348,15 +289,10 @@ 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")
|
||||
@ -373,11 +309,7 @@ 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")
|
||||
@ -385,11 +317,7 @@ 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)
|
||||
@ -415,69 +343,39 @@ 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)
|
||||
@ -524,9 +422,7 @@ 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)
|
||||
@ -554,15 +450,13 @@ 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)
|
||||
@ -578,6 +472,8 @@ 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)
|
||||
@ -611,7 +507,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):
|
||||
@ -647,58 +543,38 @@ 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"))
|
||||
@ -706,12 +582,8 @@ 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"))
|
||||
@ -720,22 +592,11 @@ 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.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..."))
|
||||
from infotabs import InfoTabs
|
||||
from pyqtgraph import PlotWidget
|
||||
|
||||
@ -33,11 +33,11 @@ class TestMMModels(unittest.TestCase):
|
||||
|
||||
with db.Session() as session:
|
||||
track1_path = "testdata/isa.mp3"
|
||||
metadata1 = helpers.get_file_metadata(track1_path)
|
||||
metadata1 = helpers.get_audio_metadata(track1_path)
|
||||
self.track1 = Tracks(session, **metadata1)
|
||||
|
||||
track2_path = "testdata/mom.mp3"
|
||||
metadata2 = helpers.get_file_metadata(track2_path)
|
||||
metadata2 = helpers.get_audio_metadata(track2_path)
|
||||
self.track2 = Tracks(session, **metadata2)
|
||||
|
||||
def tearDown(self):
|
||||
|
||||
@ -11,7 +11,7 @@ from sqlalchemy.orm.session import Session
|
||||
|
||||
# App imports
|
||||
from app.log import log
|
||||
from app.helpers import get_file_metadata
|
||||
from app.helpers import get_audio_metadata
|
||||
|
||||
# Set up test database before importing db
|
||||
# Mark subsequent lines to ignore E402, imports not at top of file
|
||||
@ -51,7 +51,7 @@ class TestMMMiscTracks(unittest.TestCase):
|
||||
|
||||
for row in range(len(self.test_tracks)):
|
||||
track_path = self.test_tracks[row % len(self.test_tracks)]
|
||||
metadata = get_file_metadata(track_path)
|
||||
metadata = get_audio_metadata(track_path)
|
||||
track = Tracks(session, **metadata)
|
||||
self.model.insert_row(
|
||||
proposed_row_number=row, track_id=track.id, note=f"{row=}"
|
||||
@ -113,7 +113,7 @@ class TestMMMiscNoPlaylist(unittest.TestCase):
|
||||
_ = str(model)
|
||||
|
||||
track_path = self.test_tracks[0]
|
||||
metadata = get_file_metadata(track_path)
|
||||
metadata = get_audio_metadata(track_path)
|
||||
track = Tracks(session, **metadata)
|
||||
model.insert_row(proposed_row_number=0, track_id=track.id)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user