Build in replace_file functionality

Major rewrite of file importing

Fixes #141
This commit is contained in:
Keith Edmunds 2024-05-03 22:40:21 +01:00
parent 6aa09bf28a
commit a24ff76b6b
13 changed files with 677 additions and 289 deletions

View File

@ -1,6 +1,6 @@
# Standard library imports # Standard library imports
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import Optional from typing import Any, Optional
import datetime as dt import datetime as dt
# PyQt imports # PyQt imports
@ -203,6 +203,19 @@ class PlaylistTrack:
self.fade_graph_start_updates = now + dt.timedelta(milliseconds=update_graph_at_ms) 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): class AddFadeCurve(QObject):
""" """
Initialising a fade curve introduces a noticeable delay so carry out in Initialising a fade curve introduces a noticeable delay so carry out in

View File

@ -78,6 +78,7 @@ class Config(object):
OBS_PASSWORD = "auster" OBS_PASSWORD = "auster"
OBS_PORT = 4455 OBS_PORT = 4455
PLAY_SETTLE = 500000 PLAY_SETTLE = 500000
REPLACE_FILES_DEFAULT_SOURCE = "/home/kae/music/Singles/tmp"
RETURN_KEY_DEBOUNCE_MS = 500 RETURN_KEY_DEBOUNCE_MS = 500
ROOT = os.environ.get("ROOT") or "/home/kae/music" ROOT = os.environ.get("ROOT") or "/home/kae/music"
ROWS_FROM_ZERO = True ROWS_FROM_ZERO = True
@ -94,3 +95,4 @@ class Config(object):
# These rely on earlier definitions # These rely on earlier definitions
IMPORT_DESTINATION = os.path.join(ROOT, "Singles") IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
REPLACE_FILES_DEFAULT_DESTINATION = os.path.dirname(REPLACE_FILES_DEFAULT_SOURCE)

View File

@ -1,24 +1,203 @@
# Standard library imports # Standard library imports
from typing import Optional from typing import Optional
import os
# PyQt imports # PyQt imports
from PyQt6.QtCore import QEvent, Qt from PyQt6.QtCore import QEvent, Qt
from PyQt6.QtWidgets import QDialog, QListWidgetItem from PyQt6.QtWidgets import (
QDialog,
QListWidgetItem,
QMainWindow,
QTableWidgetItem,
)
# Third party imports # Third party imports
import pydymenu # type: ignore
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
# App imports # App imports
from classes import MusicMusterSignals from classes import MusicMusterSignals, TrackFileData
from config import Config
from helpers import ( from helpers import (
ask_yes_no, ask_yes_no,
get_relative_date, get_relative_date,
get_tags,
ms_to_mmss, ms_to_mmss,
show_warning,
) )
from log import log from log import log
from models import Settings, Tracks from models import db, Settings, Tracks
from playlistmodel import PlaylistModel 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): class TrackSelectDialog(QDialog):
@ -42,7 +221,7 @@ class TrackSelectDialog(QDialog):
self.new_row_number = new_row_number self.new_row_number = new_row_number
self.source_model = source_model self.source_model = source_model
self.add_to_header = add_to_header self.add_to_header = add_to_header
self.ui = Ui_Dialog() self.ui = dlg_TrackSelect_ui.Ui_Dialog()
self.ui.setupUi(self) self.ui.setupUi(self)
self.ui.btnAdd.clicked.connect(self.add_selected) self.ui.btnAdd.clicked.connect(self.add_selected)
self.ui.btnAddClose.clicked.connect(self.add_selected_and_close) self.ui.btnAddClose.clicked.connect(self.add_selected_and_close)

View File

@ -115,11 +115,10 @@ def get_embedded_time(text: str) -> Optional[dt.datetime]:
return None return None
def get_file_metadata(filepath: str) -> dict: def get_audio_metadata(filepath: str) -> Dict[str, str | int | float]:
"""Return track metadata""" """Return track metadata"""
# Get title, artist, bitrate, duration, path metadata: Dict[str, str | int | float] = {}
metadata: Dict[str, str | int | float] = get_tags(filepath)
metadata["mtime"] = os.path.getmtime(filepath) metadata["mtime"] = os.path.getmtime(filepath)
@ -196,7 +195,6 @@ def get_tags(path: str) -> Dict[str, Any]:
artist=tag.artist, artist=tag.artist,
bitrate=round(tag.bitrate), bitrate=round(tag.bitrate),
duration=int(round(tag.duration, Config.MILLISECOND_SIGFIGS) * 1000), 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): def set_track_metadata(track):
"""Set/update track metadata in database""" """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: for audio_key in audio_metadata:
setattr(track, key, metadata[key]) 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: def show_OK(parent: QMainWindow, title: str, msg: str) -> None:

View File

@ -130,7 +130,9 @@ class Playdates(dbtables.PlaydatesTable):
session.commit() session.commit()
@staticmethod @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 Return a list of the last limit playdates for this track, sorted
earliest to latest. earliest to latest.
@ -169,9 +171,7 @@ class Playdates(dbtables.PlaydatesTable):
""" """
return session.scalars( return session.scalars(
Playdates.select() Playdates.select().order_by(Playdates.lastplayed.desc()).limit(limit)
.order_by(Playdates.lastplayed.desc())
.limit(limit)
).all() ).all()
@staticmethod @staticmethod
@ -673,6 +673,21 @@ class Tracks(dbtables.TracksTable):
return session.scalars(select(cls)).unique().all() 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 @classmethod
def get_by_path(cls, session: Session, path: str) -> Optional["Tracks"]: def get_by_path(cls, session: Session, path: str) -> Optional["Tracks"]:
""" """

View File

@ -7,6 +7,7 @@ from typing import cast, List, Optional
import argparse import argparse
import datetime as dt import datetime as dt
import os import os
import shutil
import subprocess import subprocess
import sys import sys
import threading import threading
@ -48,6 +49,7 @@ from PyQt6.QtWidgets import (
# Third party imports # Third party imports
from pygame import mixer from pygame import mixer
import pipeclient import pipeclient
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
import stackprinter # type: ignore import stackprinter # type: ignore
@ -57,9 +59,10 @@ from classes import (
FadeCurve, FadeCurve,
MusicMusterSignals, MusicMusterSignals,
PlaylistTrack, PlaylistTrack,
TrackFileData,
) )
from config import Config from config import Config
from dialogs import TrackSelectDialog from dialogs import TrackSelectDialog, ReplaceFilesDialog
from log import log from log import log
from models import db, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks from models import db, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
from playlistmodel import PlaylistModel, PlaylistProxyModel from playlistmodel import PlaylistModel, PlaylistProxyModel
@ -146,12 +149,12 @@ class ImportTrack(QObject):
def __init__( def __init__(
self, self,
filenames: List[str], track_files: List[TrackFileData],
source_model: PlaylistModel, source_model: PlaylistModel,
row_number: Optional[int], row_number: Optional[int],
) -> None: ) -> None:
super().__init__() super().__init__()
self.filenames = filenames self.track_files = track_files
self.source_model = source_model self.source_model = source_model
if row_number is None: if row_number is None:
self.next_row_number = source_model.rowCount() self.next_row_number = source_model.rowCount()
@ -159,25 +162,49 @@ class ImportTrack(QObject):
self.next_row_number = row_number self.next_row_number = row_number
self.signals = MusicMusterSignals() 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): def run(self):
""" """
Create track objects from passed files and add to visible playlist Create track objects from passed files and add to visible playlist
""" """
with db.Session() as session: with db.Session() as session:
for fname in self.filenames: for tf in self.track_files:
self.signals.status_message_signal.emit( 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: try:
track = Tracks(session, **metadata) track = Tracks(
session, path=tf.track_path, **tf.audio_metadata | tf.tags
)
except Exception as e: except Exception as e:
self.signals.show_warning_signal.emit( self.signals.show_warning_signal.emit(
"Error importing track", str(e) "Error importing track", str(e)
) )
return return
helpers.normalise_track(track.path) helpers.normalise_track(tf.track_path)
# We're importing potentially multiple tracks in a loop. # We're importing potentially multiple tracks in a loop.
# If there's an error adding the track to the Tracks # If there's an error adding the track to the Tracks
# table, the session will rollback, thus losing any # 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.source_model.insert_row(self.next_row_number, track.id, "")
self.next_row_number += 1 self.next_row_number += 1
self.signals.status_message_signal.emit( 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() self.import_finished.emit()
@ -531,6 +558,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionPaste.triggered.connect(self.paste_rows) self.actionPaste.triggered.connect(self.paste_rows)
self.actionPlay_next.triggered.connect(self.play_next) self.actionPlay_next.triggered.connect(self.play_next)
self.actionRenamePlaylist.triggered.connect(self.rename_playlist) self.actionRenamePlaylist.triggered.connect(self.rename_playlist)
self.actionReplace_files.triggered.connect(self.replace_files)
self.actionResume.triggered.connect(self.resume) self.actionResume.triggered.connect(self.resume)
self.actionSave_as_template.triggered.connect(self.save_as_template) self.actionSave_as_template.triggered.connect(self.save_as_template)
self.actionSearch_title_in_Songfacts.triggered.connect( self.actionSearch_title_in_Songfacts.triggered.connect(
@ -790,10 +818,45 @@ class Window(QMainWindow, Ui_MainWindow):
return return
with db.Session() as session: with db.Session() as session:
new_tracks = [] track_files: list[TrackFileData] = []
for fname in dlg.selectedFiles(): for fpath in dlg.selectedFiles():
txt = "" tf = TrackFileData(fpath)
tags = helpers.get_tags(fname) 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)
)
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(
track_files,
self.active_proxy_model(),
self.active_tab().source_model_selected_row_number(),
)
self.worker.moveToThread(self.import_thread)
self.import_thread.started.connect(self.worker.run)
self.worker.import_finished.connect(self.import_thread.quit)
self.worker.import_finished.connect(self.worker.deleteLater)
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"] title = tags["title"]
if not title: if not title:
helpers.show_warning( helpers.show_warning(
@ -801,7 +864,8 @@ class Window(QMainWindow, Ui_MainWindow):
"Problem with track file", "Problem with track file",
f"{fname} does not have a title tag", f"{fname} does not have a title tag",
) )
continue return False
artist = tags["artist"] artist = tags["artist"]
if not artist: if not artist:
helpers.show_warning( helpers.show_warning(
@ -809,7 +873,9 @@ class Window(QMainWindow, Ui_MainWindow):
"Problem with track file", "Problem with track file",
f"{fname} does not have an artist tag", f"{fname} does not have an artist tag",
) )
continue return False
txt = ""
count = 0 count = 0
possible_matches = Tracks.search_titles(session, title) possible_matches = Tracks.search_titles(session, title)
if possible_matches: if possible_matches:
@ -833,22 +899,9 @@ class Window(QMainWindow, Ui_MainWindow):
QMessageBox.StandardButton.Cancel, QMessageBox.StandardButton.Cancel,
) )
if result == QMessageBox.StandardButton.Cancel: if result == QMessageBox.StandardButton.Cancel:
continue return False
new_tracks.append(fname)
# Import in separate thread return True
self.import_thread = QThread()
self.worker = ImportTrack(
new_tracks,
self.active_proxy_model(),
self.active_tab().source_model_selected_row_number(),
)
self.worker.moveToThread(self.import_thread)
self.import_thread.started.connect(self.worker.run)
self.worker.import_finished.connect(self.import_thread.quit)
self.worker.import_finished.connect(self.worker.deleteLater)
self.import_thread.finished.connect(self.import_thread.deleteLater)
self.import_thread.start()
def initialise_audacity(self) -> None: def initialise_audacity(self) -> None:
""" """
@ -1099,7 +1152,8 @@ class Window(QMainWindow, Ui_MainWindow):
if self.catch_return_key: if self.catch_return_key:
# Suppress inadvertent double press # Suppress inadvertent double press
if ( 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.timedelta(milliseconds=Config.RETURN_KEY_DEBOUNCE_MS)
> dt.datetime.now() > dt.datetime.now()
): ):
@ -1227,6 +1281,64 @@ class Window(QMainWindow, Ui_MainWindow):
self.tabBar.setTabText(idx, new_name) self.tabBar.setTabText(idx, new_name)
session.commit() 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: def resume(self) -> None:
""" """
Resume playing last track. We may be playing the next track Resume playing last track. We may be playing the next track

View File

@ -134,7 +134,7 @@ def main():
if process_no_matches: if process_no_matches:
prompt = f"file={new_fname}\n title={new_title}\n artist={new_artist}: " prompt = f"file={new_fname}\n title={new_title}\n artist={new_artist}: "
# Use fzf to search # Use fzf to search
choice = pydymenu.fzf(parent_fnames, prompt=prompt) choice = pydymenu.rofi(parent_fnames, prompt=prompt)
if choice: if choice:
old_file = os.path.join(parent_dir, choice[0]) old_file = os.path.join(parent_dir, choice[0])
oldtags = get_tags(old_file) oldtags = get_tags(old_file)

145
app/ui/dlgReplaceFiles.ui Normal file
View 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>

View 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"))

View File

@ -753,6 +753,8 @@ padding-left: 8px;</string>
<addaction name="actionDownload_CSV_of_played_tracks"/> <addaction name="actionDownload_CSV_of_played_tracks"/>
<addaction name="actionSave_as_template"/> <addaction name="actionSave_as_template"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionReplace_files"/>
<addaction name="separator"/>
<addaction name="actionE_xit"/> <addaction name="actionE_xit"/>
</widget> </widget>
<widget class="QMenu" name="menuPlaylist"> <widget class="QMenu" name="menuPlaylist">
@ -1132,6 +1134,11 @@ padding-left: 8px;</string>
<string>Select duplicate rows...</string> <string>Select duplicate rows...</string>
</property> </property>
</action> </action>
<action name="actionReplace_files">
<property name="text">
<string>Replace files...</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View File

@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'app/ui/main_window.ui' # 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 # 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. # 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.resize(1280, 857)
MainWindow.setMinimumSize(QtCore.QSize(1280, 0)) MainWindow.setMinimumSize(QtCore.QSize(1280, 0))
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap( icon.addPixmap(QtGui.QPixmap(":/icons/musicmuster"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
QtGui.QPixmap(":/icons/musicmuster"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
MainWindow.setWindowIcon(icon) MainWindow.setWindowIcon(icon)
MainWindow.setStyleSheet("") MainWindow.setStyleSheet("")
self.centralwidget = QtWidgets.QWidget(parent=MainWindow) self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
@ -31,62 +27,39 @@ class Ui_MainWindow(object):
self.verticalLayout_3 = QtWidgets.QVBoxLayout() self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3") self.verticalLayout_3.setObjectName("verticalLayout_3")
self.previous_track_2 = QtWidgets.QLabel(parent=self.centralwidget) self.previous_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth( sizePolicy.setHeightForWidth(self.previous_track_2.sizePolicy().hasHeightForWidth())
self.previous_track_2.sizePolicy().hasHeightForWidth()
)
self.previous_track_2.setSizePolicy(sizePolicy) self.previous_track_2.setSizePolicy(sizePolicy)
self.previous_track_2.setMaximumSize(QtCore.QSize(230, 16777215)) self.previous_track_2.setMaximumSize(QtCore.QSize(230, 16777215))
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.previous_track_2.setFont(font) self.previous_track_2.setFont(font)
self.previous_track_2.setStyleSheet( self.previous_track_2.setStyleSheet("background-color: #f8d7da;\n"
"background-color: #f8d7da;\n" "border: 1px solid rgb(85, 87, 83);" "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.setAlignment(
QtCore.Qt.AlignmentFlag.AlignRight
| QtCore.Qt.AlignmentFlag.AlignTrailing
| QtCore.Qt.AlignmentFlag.AlignVCenter
)
self.previous_track_2.setObjectName("previous_track_2") self.previous_track_2.setObjectName("previous_track_2")
self.verticalLayout_3.addWidget(self.previous_track_2) self.verticalLayout_3.addWidget(self.previous_track_2)
self.current_track_2 = QtWidgets.QLabel(parent=self.centralwidget) self.current_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth( sizePolicy.setHeightForWidth(self.current_track_2.sizePolicy().hasHeightForWidth())
self.current_track_2.sizePolicy().hasHeightForWidth()
)
self.current_track_2.setSizePolicy(sizePolicy) self.current_track_2.setSizePolicy(sizePolicy)
self.current_track_2.setMaximumSize(QtCore.QSize(230, 16777215)) self.current_track_2.setMaximumSize(QtCore.QSize(230, 16777215))
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.current_track_2.setFont(font) self.current_track_2.setFont(font)
self.current_track_2.setStyleSheet( self.current_track_2.setStyleSheet("background-color: #d4edda;\n"
"background-color: #d4edda;\n" "border: 1px solid rgb(85, 87, 83);" "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.setAlignment(
QtCore.Qt.AlignmentFlag.AlignRight
| QtCore.Qt.AlignmentFlag.AlignTrailing
| QtCore.Qt.AlignmentFlag.AlignVCenter
)
self.current_track_2.setObjectName("current_track_2") self.current_track_2.setObjectName("current_track_2")
self.verticalLayout_3.addWidget(self.current_track_2) self.verticalLayout_3.addWidget(self.current_track_2)
self.next_track_2 = QtWidgets.QLabel(parent=self.centralwidget) self.next_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.next_track_2.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.next_track_2.sizePolicy().hasHeightForWidth())
@ -96,29 +69,19 @@ class Ui_MainWindow(object):
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.next_track_2.setFont(font) self.next_track_2.setFont(font)
self.next_track_2.setStyleSheet( self.next_track_2.setStyleSheet("background-color: #fff3cd;\n"
"background-color: #fff3cd;\n" "border: 1px solid rgb(85, 87, 83);" "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.setAlignment(
QtCore.Qt.AlignmentFlag.AlignRight
| QtCore.Qt.AlignmentFlag.AlignTrailing
| QtCore.Qt.AlignmentFlag.AlignVCenter
)
self.next_track_2.setObjectName("next_track_2") self.next_track_2.setObjectName("next_track_2")
self.verticalLayout_3.addWidget(self.next_track_2) self.verticalLayout_3.addWidget(self.next_track_2)
self.horizontalLayout_3.addLayout(self.verticalLayout_3) self.horizontalLayout_3.addLayout(self.verticalLayout_3)
self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setObjectName("verticalLayout")
self.hdrPreviousTrack = QtWidgets.QLabel(parent=self.centralwidget) self.hdrPreviousTrack = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth( sizePolicy.setHeightForWidth(self.hdrPreviousTrack.sizePolicy().hasHeightForWidth())
self.hdrPreviousTrack.sizePolicy().hasHeightForWidth()
)
self.hdrPreviousTrack.setSizePolicy(sizePolicy) self.hdrPreviousTrack.setSizePolicy(sizePolicy)
self.hdrPreviousTrack.setMinimumSize(QtCore.QSize(0, 0)) self.hdrPreviousTrack.setMinimumSize(QtCore.QSize(0, 0))
self.hdrPreviousTrack.setMaximumSize(QtCore.QSize(16777215, 16777215)) self.hdrPreviousTrack.setMaximumSize(QtCore.QSize(16777215, 16777215))
@ -126,43 +89,32 @@ class Ui_MainWindow(object):
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.hdrPreviousTrack.setFont(font) self.hdrPreviousTrack.setFont(font)
self.hdrPreviousTrack.setStyleSheet( self.hdrPreviousTrack.setStyleSheet("background-color: #f8d7da;\n"
"background-color: #f8d7da;\n" "border: 1px solid rgb(85, 87, 83);" "border: 1px solid rgb(85, 87, 83);")
)
self.hdrPreviousTrack.setText("") self.hdrPreviousTrack.setText("")
self.hdrPreviousTrack.setWordWrap(False) self.hdrPreviousTrack.setWordWrap(False)
self.hdrPreviousTrack.setObjectName("hdrPreviousTrack") self.hdrPreviousTrack.setObjectName("hdrPreviousTrack")
self.verticalLayout.addWidget(self.hdrPreviousTrack) self.verticalLayout.addWidget(self.hdrPreviousTrack)
self.hdrCurrentTrack = QtWidgets.QPushButton(parent=self.centralwidget) self.hdrCurrentTrack = QtWidgets.QPushButton(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth( sizePolicy.setHeightForWidth(self.hdrCurrentTrack.sizePolicy().hasHeightForWidth())
self.hdrCurrentTrack.sizePolicy().hasHeightForWidth()
)
self.hdrCurrentTrack.setSizePolicy(sizePolicy) self.hdrCurrentTrack.setSizePolicy(sizePolicy)
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(20) font.setPointSize(20)
self.hdrCurrentTrack.setFont(font) self.hdrCurrentTrack.setFont(font)
self.hdrCurrentTrack.setStyleSheet( self.hdrCurrentTrack.setStyleSheet("background-color: #d4edda;\n"
"background-color: #d4edda;\n" "border: 1px solid rgb(85, 87, 83);\n"
"border: 1px solid rgb(85, 87, 83);\n" "text-align: left;\n"
"text-align: left;\n" "padding-left: 8px;\n"
"padding-left: 8px;\n" "")
""
)
self.hdrCurrentTrack.setText("") self.hdrCurrentTrack.setText("")
self.hdrCurrentTrack.setFlat(True) self.hdrCurrentTrack.setFlat(True)
self.hdrCurrentTrack.setObjectName("hdrCurrentTrack") self.hdrCurrentTrack.setObjectName("hdrCurrentTrack")
self.verticalLayout.addWidget(self.hdrCurrentTrack) self.verticalLayout.addWidget(self.hdrCurrentTrack)
self.hdrNextTrack = QtWidgets.QPushButton(parent=self.centralwidget) self.hdrNextTrack = QtWidgets.QPushButton(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.hdrNextTrack.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.hdrNextTrack.sizePolicy().hasHeightForWidth())
@ -170,12 +122,10 @@ class Ui_MainWindow(object):
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(20) font.setPointSize(20)
self.hdrNextTrack.setFont(font) self.hdrNextTrack.setFont(font)
self.hdrNextTrack.setStyleSheet( self.hdrNextTrack.setStyleSheet("background-color: #fff3cd;\n"
"background-color: #fff3cd;\n" "border: 1px solid rgb(85, 87, 83);\n"
"border: 1px solid rgb(85, 87, 83);\n" "text-align: left;\n"
"text-align: left;\n" "padding-left: 8px;")
"padding-left: 8px;"
)
self.hdrNextTrack.setText("") self.hdrNextTrack.setText("")
self.hdrNextTrack.setFlat(True) self.hdrNextTrack.setFlat(True)
self.hdrNextTrack.setObjectName("hdrNextTrack") self.hdrNextTrack.setObjectName("hdrNextTrack")
@ -210,12 +160,7 @@ class Ui_MainWindow(object):
self.cartsWidget.setObjectName("cartsWidget") self.cartsWidget.setObjectName("cartsWidget")
self.horizontalLayout_Carts = QtWidgets.QHBoxLayout(self.cartsWidget) self.horizontalLayout_Carts = QtWidgets.QHBoxLayout(self.cartsWidget)
self.horizontalLayout_Carts.setObjectName("horizontalLayout_Carts") self.horizontalLayout_Carts.setObjectName("horizontalLayout_Carts")
spacerItem = QtWidgets.QSpacerItem( spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_Carts.addItem(spacerItem) self.horizontalLayout_Carts.addItem(spacerItem)
self.gridLayout_4.addWidget(self.cartsWidget, 2, 0, 1, 1) self.gridLayout_4.addWidget(self.cartsWidget, 2, 0, 1, 1)
self.frame_6 = QtWidgets.QFrame(parent=self.centralwidget) 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 = QtWidgets.QPushButton(parent=self.FadeStopInfoFrame)
self.btnPreview.setMinimumSize(QtCore.QSize(132, 41)) self.btnPreview.setMinimumSize(QtCore.QSize(132, 41))
icon1 = QtGui.QIcon() icon1 = QtGui.QIcon()
icon1.addPixmap( icon1.addPixmap(QtGui.QPixmap(":/icons/headphones"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
QtGui.QPixmap(":/icons/headphones"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.btnPreview.setIcon(icon1) self.btnPreview.setIcon(icon1)
self.btnPreview.setIconSize(QtCore.QSize(30, 30)) self.btnPreview.setIconSize(QtCore.QSize(30, 30))
self.btnPreview.setCheckable(True) self.btnPreview.setCheckable(True)
@ -348,15 +289,10 @@ class Ui_MainWindow(object):
self.label_silent_timer.setObjectName("label_silent_timer") self.label_silent_timer.setObjectName("label_silent_timer")
self.horizontalLayout.addWidget(self.frame_silent) self.horizontalLayout.addWidget(self.frame_silent)
self.widgetFadeVolume = PlotWidget(parent=self.InfoFooterFrame) self.widgetFadeVolume = PlotWidget(parent=self.InfoFooterFrame)
sizePolicy = QtWidgets.QSizePolicy( sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(1) sizePolicy.setHorizontalStretch(1)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth( sizePolicy.setHeightForWidth(self.widgetFadeVolume.sizePolicy().hasHeightForWidth())
self.widgetFadeVolume.sizePolicy().hasHeightForWidth()
)
self.widgetFadeVolume.setSizePolicy(sizePolicy) self.widgetFadeVolume.setSizePolicy(sizePolicy)
self.widgetFadeVolume.setMinimumSize(QtCore.QSize(0, 0)) self.widgetFadeVolume.setMinimumSize(QtCore.QSize(0, 0))
self.widgetFadeVolume.setObjectName("widgetFadeVolume") self.widgetFadeVolume.setObjectName("widgetFadeVolume")
@ -373,11 +309,7 @@ class Ui_MainWindow(object):
self.btnFade.setMinimumSize(QtCore.QSize(132, 32)) self.btnFade.setMinimumSize(QtCore.QSize(132, 32))
self.btnFade.setMaximumSize(QtCore.QSize(164, 16777215)) self.btnFade.setMaximumSize(QtCore.QSize(164, 16777215))
icon2 = QtGui.QIcon() icon2 = QtGui.QIcon()
icon2.addPixmap( icon2.addPixmap(QtGui.QPixmap(":/icons/fade"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
QtGui.QPixmap(":/icons/fade"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.btnFade.setIcon(icon2) self.btnFade.setIcon(icon2)
self.btnFade.setIconSize(QtCore.QSize(30, 30)) self.btnFade.setIconSize(QtCore.QSize(30, 30))
self.btnFade.setObjectName("btnFade") self.btnFade.setObjectName("btnFade")
@ -385,11 +317,7 @@ class Ui_MainWindow(object):
self.btnStop = QtWidgets.QPushButton(parent=self.frame) self.btnStop = QtWidgets.QPushButton(parent=self.frame)
self.btnStop.setMinimumSize(QtCore.QSize(0, 36)) self.btnStop.setMinimumSize(QtCore.QSize(0, 36))
icon3 = QtGui.QIcon() icon3 = QtGui.QIcon()
icon3.addPixmap( icon3.addPixmap(QtGui.QPixmap(":/icons/stopsign"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
QtGui.QPixmap(":/icons/stopsign"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.btnStop.setIcon(icon3) self.btnStop.setIcon(icon3)
self.btnStop.setObjectName("btnStop") self.btnStop.setObjectName("btnStop")
self.verticalLayout_5.addWidget(self.btnStop) self.verticalLayout_5.addWidget(self.btnStop)
@ -415,69 +343,39 @@ class Ui_MainWindow(object):
MainWindow.setStatusBar(self.statusbar) MainWindow.setStatusBar(self.statusbar)
self.actionPlay_next = QtGui.QAction(parent=MainWindow) self.actionPlay_next = QtGui.QAction(parent=MainWindow)
icon4 = QtGui.QIcon() icon4 = QtGui.QIcon()
icon4.addPixmap( icon4.addPixmap(QtGui.QPixmap("app/ui/../../../../.designer/backup/icon-play.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
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.setIcon(icon4)
self.actionPlay_next.setObjectName("actionPlay_next") self.actionPlay_next.setObjectName("actionPlay_next")
self.actionSkipToNext = QtGui.QAction(parent=MainWindow) self.actionSkipToNext = QtGui.QAction(parent=MainWindow)
icon5 = QtGui.QIcon() icon5 = QtGui.QIcon()
icon5.addPixmap( icon5.addPixmap(QtGui.QPixmap(":/icons/next"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
QtGui.QPixmap(":/icons/next"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionSkipToNext.setIcon(icon5) self.actionSkipToNext.setIcon(icon5)
self.actionSkipToNext.setObjectName("actionSkipToNext") self.actionSkipToNext.setObjectName("actionSkipToNext")
self.actionInsertTrack = QtGui.QAction(parent=MainWindow) self.actionInsertTrack = QtGui.QAction(parent=MainWindow)
icon6 = QtGui.QIcon() icon6 = QtGui.QIcon()
icon6.addPixmap( icon6.addPixmap(QtGui.QPixmap("app/ui/../../../../.designer/backup/icon_search_database.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
QtGui.QPixmap(
"app/ui/../../../../.designer/backup/icon_search_database.png"
),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionInsertTrack.setIcon(icon6) self.actionInsertTrack.setIcon(icon6)
self.actionInsertTrack.setObjectName("actionInsertTrack") self.actionInsertTrack.setObjectName("actionInsertTrack")
self.actionAdd_file = QtGui.QAction(parent=MainWindow) self.actionAdd_file = QtGui.QAction(parent=MainWindow)
icon7 = QtGui.QIcon() icon7 = QtGui.QIcon()
icon7.addPixmap( icon7.addPixmap(QtGui.QPixmap("app/ui/../../../../.designer/backup/icon_open_file.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
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.setIcon(icon7)
self.actionAdd_file.setObjectName("actionAdd_file") self.actionAdd_file.setObjectName("actionAdd_file")
self.actionFade = QtGui.QAction(parent=MainWindow) self.actionFade = QtGui.QAction(parent=MainWindow)
icon8 = QtGui.QIcon() icon8 = QtGui.QIcon()
icon8.addPixmap( icon8.addPixmap(QtGui.QPixmap("app/ui/../../../../.designer/backup/icon-fade.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
QtGui.QPixmap("app/ui/../../../../.designer/backup/icon-fade.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionFade.setIcon(icon8) self.actionFade.setIcon(icon8)
self.actionFade.setObjectName("actionFade") self.actionFade.setObjectName("actionFade")
self.actionStop = QtGui.QAction(parent=MainWindow) self.actionStop = QtGui.QAction(parent=MainWindow)
icon9 = QtGui.QIcon() icon9 = QtGui.QIcon()
icon9.addPixmap( icon9.addPixmap(QtGui.QPixmap(":/icons/stop"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
QtGui.QPixmap(":/icons/stop"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionStop.setIcon(icon9) self.actionStop.setIcon(icon9)
self.actionStop.setObjectName("actionStop") self.actionStop.setObjectName("actionStop")
self.action_Clear_selection = QtGui.QAction(parent=MainWindow) self.action_Clear_selection = QtGui.QAction(parent=MainWindow)
self.action_Clear_selection.setObjectName("action_Clear_selection") self.action_Clear_selection.setObjectName("action_Clear_selection")
self.action_Resume_previous = QtGui.QAction(parent=MainWindow) self.action_Resume_previous = QtGui.QAction(parent=MainWindow)
icon10 = QtGui.QIcon() icon10 = QtGui.QIcon()
icon10.addPixmap( icon10.addPixmap(QtGui.QPixmap(":/icons/previous"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
QtGui.QPixmap(":/icons/previous"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.action_Resume_previous.setIcon(icon10) self.action_Resume_previous.setIcon(icon10)
self.action_Resume_previous.setObjectName("action_Resume_previous") self.action_Resume_previous.setObjectName("action_Resume_previous")
self.actionE_xit = QtGui.QAction(parent=MainWindow) self.actionE_xit = QtGui.QAction(parent=MainWindow)
@ -524,9 +422,7 @@ class Ui_MainWindow(object):
self.actionImport = QtGui.QAction(parent=MainWindow) self.actionImport = QtGui.QAction(parent=MainWindow)
self.actionImport.setObjectName("actionImport") self.actionImport.setObjectName("actionImport")
self.actionDownload_CSV_of_played_tracks = QtGui.QAction(parent=MainWindow) self.actionDownload_CSV_of_played_tracks = QtGui.QAction(parent=MainWindow)
self.actionDownload_CSV_of_played_tracks.setObjectName( self.actionDownload_CSV_of_played_tracks.setObjectName("actionDownload_CSV_of_played_tracks")
"actionDownload_CSV_of_played_tracks"
)
self.actionSearch = QtGui.QAction(parent=MainWindow) self.actionSearch = QtGui.QAction(parent=MainWindow)
self.actionSearch.setObjectName("actionSearch") self.actionSearch.setObjectName("actionSearch")
self.actionInsertSectionHeader = QtGui.QAction(parent=MainWindow) self.actionInsertSectionHeader = QtGui.QAction(parent=MainWindow)
@ -554,15 +450,13 @@ class Ui_MainWindow(object):
self.actionResume = QtGui.QAction(parent=MainWindow) self.actionResume = QtGui.QAction(parent=MainWindow)
self.actionResume.setObjectName("actionResume") self.actionResume.setObjectName("actionResume")
self.actionSearch_title_in_Wikipedia = QtGui.QAction(parent=MainWindow) self.actionSearch_title_in_Wikipedia = QtGui.QAction(parent=MainWindow)
self.actionSearch_title_in_Wikipedia.setObjectName( self.actionSearch_title_in_Wikipedia.setObjectName("actionSearch_title_in_Wikipedia")
"actionSearch_title_in_Wikipedia"
)
self.actionSearch_title_in_Songfacts = QtGui.QAction(parent=MainWindow) self.actionSearch_title_in_Songfacts = QtGui.QAction(parent=MainWindow)
self.actionSearch_title_in_Songfacts.setObjectName( self.actionSearch_title_in_Songfacts.setObjectName("actionSearch_title_in_Songfacts")
"actionSearch_title_in_Songfacts"
)
self.actionSelect_duplicate_rows = QtGui.QAction(parent=MainWindow) self.actionSelect_duplicate_rows = QtGui.QAction(parent=MainWindow)
self.actionSelect_duplicate_rows.setObjectName("actionSelect_duplicate_rows") 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.actionNewPlaylist)
self.menuFile.addAction(self.actionNew_from_template) self.menuFile.addAction(self.actionNew_from_template)
self.menuFile.addAction(self.actionOpenPlaylist) 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.actionDownload_CSV_of_played_tracks)
self.menuFile.addAction(self.actionSave_as_template) self.menuFile.addAction(self.actionSave_as_template)
self.menuFile.addSeparator() self.menuFile.addSeparator()
self.menuFile.addAction(self.actionReplace_files)
self.menuFile.addSeparator()
self.menuFile.addAction(self.actionE_xit) self.menuFile.addAction(self.actionE_xit)
self.menuPlaylist.addSeparator() self.menuPlaylist.addSeparator()
self.menuPlaylist.addAction(self.actionPlay_next) self.menuPlaylist.addAction(self.actionPlay_next)
@ -647,58 +543,38 @@ class Ui_MainWindow(object):
self.actionFade.setShortcut(_translate("MainWindow", "Ctrl+Z")) self.actionFade.setShortcut(_translate("MainWindow", "Ctrl+Z"))
self.actionStop.setText(_translate("MainWindow", "S&top")) self.actionStop.setText(_translate("MainWindow", "S&top"))
self.actionStop.setShortcut(_translate("MainWindow", "Ctrl+Alt+S")) self.actionStop.setShortcut(_translate("MainWindow", "Ctrl+Alt+S"))
self.action_Clear_selection.setText( self.action_Clear_selection.setText(_translate("MainWindow", "Clear &selection"))
_translate("MainWindow", "Clear &selection")
)
self.action_Clear_selection.setShortcut(_translate("MainWindow", "Esc")) self.action_Clear_selection.setShortcut(_translate("MainWindow", "Esc"))
self.action_Resume_previous.setText( self.action_Resume_previous.setText(_translate("MainWindow", "&Resume previous"))
_translate("MainWindow", "&Resume previous")
)
self.actionE_xit.setText(_translate("MainWindow", "E&xit")) self.actionE_xit.setText(_translate("MainWindow", "E&xit"))
self.actionTest.setText(_translate("MainWindow", "&Test")) self.actionTest.setText(_translate("MainWindow", "&Test"))
self.actionOpenPlaylist.setText(_translate("MainWindow", "O&pen...")) self.actionOpenPlaylist.setText(_translate("MainWindow", "O&pen..."))
self.actionNewPlaylist.setText(_translate("MainWindow", "&New...")) self.actionNewPlaylist.setText(_translate("MainWindow", "&New..."))
self.actionTestFunction.setText(_translate("MainWindow", "&Test function")) self.actionTestFunction.setText(_translate("MainWindow", "&Test function"))
self.actionSkipToFade.setText( self.actionSkipToFade.setText(_translate("MainWindow", "&Skip to start of fade"))
_translate("MainWindow", "&Skip to start of fade")
)
self.actionSkipToEnd.setText(_translate("MainWindow", "Skip to &end of track")) self.actionSkipToEnd.setText(_translate("MainWindow", "Skip to &end of track"))
self.actionClosePlaylist.setText(_translate("MainWindow", "&Close")) self.actionClosePlaylist.setText(_translate("MainWindow", "&Close"))
self.actionRenamePlaylist.setText(_translate("MainWindow", "&Rename...")) self.actionRenamePlaylist.setText(_translate("MainWindow", "&Rename..."))
self.actionDeletePlaylist.setText(_translate("MainWindow", "Dele&te...")) self.actionDeletePlaylist.setText(_translate("MainWindow", "Dele&te..."))
self.actionMoveSelected.setText( self.actionMoveSelected.setText(_translate("MainWindow", "Mo&ve selected tracks to..."))
_translate("MainWindow", "Mo&ve selected tracks to...")
)
self.actionExport_playlist.setText(_translate("MainWindow", "E&xport...")) self.actionExport_playlist.setText(_translate("MainWindow", "E&xport..."))
self.actionSetNext.setText(_translate("MainWindow", "Set &next")) self.actionSetNext.setText(_translate("MainWindow", "Set &next"))
self.actionSetNext.setShortcut(_translate("MainWindow", "Ctrl+N")) self.actionSetNext.setShortcut(_translate("MainWindow", "Ctrl+N"))
self.actionSelect_next_track.setText( self.actionSelect_next_track.setText(_translate("MainWindow", "Select next track"))
_translate("MainWindow", "Select next track")
)
self.actionSelect_next_track.setShortcut(_translate("MainWindow", "J")) self.actionSelect_next_track.setShortcut(_translate("MainWindow", "J"))
self.actionSelect_previous_track.setText( self.actionSelect_previous_track.setText(_translate("MainWindow", "Select previous track"))
_translate("MainWindow", "Select previous track")
)
self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K")) self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K"))
self.actionSelect_played_tracks.setText( self.actionSelect_played_tracks.setText(_translate("MainWindow", "Select played tracks"))
_translate("MainWindow", "Select played tracks") self.actionMoveUnplayed.setText(_translate("MainWindow", "Move &unplayed tracks to..."))
)
self.actionMoveUnplayed.setText(
_translate("MainWindow", "Move &unplayed tracks to...")
)
self.actionAdd_note.setText(_translate("MainWindow", "Add note...")) self.actionAdd_note.setText(_translate("MainWindow", "Add note..."))
self.actionAdd_note.setShortcut(_translate("MainWindow", "Ctrl+T")) self.actionAdd_note.setShortcut(_translate("MainWindow", "Ctrl+T"))
self.actionEnable_controls.setText(_translate("MainWindow", "Enable controls")) self.actionEnable_controls.setText(_translate("MainWindow", "Enable controls"))
self.actionImport.setText(_translate("MainWindow", "Import track...")) self.actionImport.setText(_translate("MainWindow", "Import track..."))
self.actionImport.setShortcut(_translate("MainWindow", "Ctrl+Shift+I")) self.actionImport.setShortcut(_translate("MainWindow", "Ctrl+Shift+I"))
self.actionDownload_CSV_of_played_tracks.setText( self.actionDownload_CSV_of_played_tracks.setText(_translate("MainWindow", "Download CSV of played tracks..."))
_translate("MainWindow", "Download CSV of played tracks...")
)
self.actionSearch.setText(_translate("MainWindow", "Search...")) self.actionSearch.setText(_translate("MainWindow", "Search..."))
self.actionSearch.setShortcut(_translate("MainWindow", "/")) self.actionSearch.setShortcut(_translate("MainWindow", "/"))
self.actionInsertSectionHeader.setText( self.actionInsertSectionHeader.setText(_translate("MainWindow", "Insert &section header..."))
_translate("MainWindow", "Insert &section header...")
)
self.actionInsertSectionHeader.setShortcut(_translate("MainWindow", "Ctrl+H")) self.actionInsertSectionHeader.setShortcut(_translate("MainWindow", "Ctrl+H"))
self.actionRemove.setText(_translate("MainWindow", "&Remove track")) self.actionRemove.setText(_translate("MainWindow", "&Remove track"))
self.actionFind_next.setText(_translate("MainWindow", "Find next")) 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.setText(_translate("MainWindow", "Find previous"))
self.actionFind_previous.setShortcut(_translate("MainWindow", "P")) self.actionFind_previous.setShortcut(_translate("MainWindow", "P"))
self.action_About.setText(_translate("MainWindow", "&About")) self.action_About.setText(_translate("MainWindow", "&About"))
self.actionSave_as_template.setText( self.actionSave_as_template.setText(_translate("MainWindow", "Save as template..."))
_translate("MainWindow", "Save as template...") self.actionNew_from_template.setText(_translate("MainWindow", "New from template..."))
)
self.actionNew_from_template.setText(
_translate("MainWindow", "New from template...")
)
self.actionDebug.setText(_translate("MainWindow", "Debug")) self.actionDebug.setText(_translate("MainWindow", "Debug"))
self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1...")) self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1..."))
self.actionMark_for_moving.setText(_translate("MainWindow", "Mark for moving")) 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.actionPaste.setShortcut(_translate("MainWindow", "Ctrl+V"))
self.actionResume.setText(_translate("MainWindow", "Resume")) self.actionResume.setText(_translate("MainWindow", "Resume"))
self.actionResume.setShortcut(_translate("MainWindow", "Ctrl+R")) self.actionResume.setShortcut(_translate("MainWindow", "Ctrl+R"))
self.actionSearch_title_in_Wikipedia.setText( self.actionSearch_title_in_Wikipedia.setText(_translate("MainWindow", "Search title in Wikipedia"))
_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_Wikipedia.setShortcut( self.actionSearch_title_in_Songfacts.setShortcut(_translate("MainWindow", "Ctrl+S"))
_translate("MainWindow", "Ctrl+W") self.actionSelect_duplicate_rows.setText(_translate("MainWindow", "Select duplicate rows..."))
) self.actionReplace_files.setText(_translate("MainWindow", "Replace files..."))
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 infotabs import InfoTabs
from pyqtgraph import PlotWidget from pyqtgraph import PlotWidget

View File

@ -33,11 +33,11 @@ class TestMMModels(unittest.TestCase):
with db.Session() as session: with db.Session() as session:
track1_path = "testdata/isa.mp3" track1_path = "testdata/isa.mp3"
metadata1 = helpers.get_file_metadata(track1_path) metadata1 = helpers.get_audio_metadata(track1_path)
self.track1 = Tracks(session, **metadata1) self.track1 = Tracks(session, **metadata1)
track2_path = "testdata/mom.mp3" track2_path = "testdata/mom.mp3"
metadata2 = helpers.get_file_metadata(track2_path) metadata2 = helpers.get_audio_metadata(track2_path)
self.track2 = Tracks(session, **metadata2) self.track2 = Tracks(session, **metadata2)
def tearDown(self): def tearDown(self):

View File

@ -11,7 +11,7 @@ from sqlalchemy.orm.session import Session
# App imports # App imports
from app.log import log 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 # Set up test database before importing db
# Mark subsequent lines to ignore E402, imports not at top of file # 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)): for row in range(len(self.test_tracks)):
track_path = self.test_tracks[row % 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) track = Tracks(session, **metadata)
self.model.insert_row( self.model.insert_row(
proposed_row_number=row, track_id=track.id, note=f"{row=}" proposed_row_number=row, track_id=track.id, note=f"{row=}"
@ -113,7 +113,7 @@ class TestMMMiscNoPlaylist(unittest.TestCase):
_ = str(model) _ = str(model)
track_path = self.test_tracks[0] track_path = self.test_tracks[0]
metadata = get_file_metadata(track_path) metadata = get_audio_metadata(track_path)
track = Tracks(session, **metadata) track = Tracks(session, **metadata)
model.insert_row(proposed_row_number=0, track_id=track.id) model.insert_row(proposed_row_number=0, track_id=track.id)