Compare commits
No commits in common. "19b1bf3fde5a3ac397cf3037213eeac16fa87705" and "1749f0a0b84b981186cc6448621b896ce5baa8bc" have entirely different histories.
19b1bf3fde
...
1749f0a0b8
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,4 +14,3 @@ StudioPlaylist.png
|
||||
tmp/
|
||||
.coverage
|
||||
profile_output*
|
||||
kae.py
|
||||
|
||||
@ -140,6 +140,6 @@ class Config(object):
|
||||
WIKIPEDIA_ON_NEXT = False
|
||||
|
||||
# These rely on earlier definitions
|
||||
HIDE_PLAYED_MODE = HIDE_PLAYED_MODE_TRACKS
|
||||
HIDE_PLAYED_MODE = HIDE_PLAYED_MODE_SECTIONS
|
||||
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
||||
REPLACE_FILES_DEFAULT_DESTINATION = os.path.dirname(REPLACE_FILES_DEFAULT_SOURCE)
|
||||
|
||||
@ -35,7 +35,6 @@ from classes import (
|
||||
)
|
||||
from config import Config
|
||||
from helpers import (
|
||||
audio_file_extension,
|
||||
file_is_unreadable,
|
||||
get_tags,
|
||||
show_OK,
|
||||
@ -105,14 +104,16 @@ class FileImporter:
|
||||
# variable or an instance variable are effectively the same thing.
|
||||
workers: dict[str, DoTrackImport] = {}
|
||||
|
||||
def __init__(self, base_model: PlaylistModel, row_number: int) -> None:
|
||||
def __init__(
|
||||
self, base_model: PlaylistModel, row_number: Optional[int] = None
|
||||
) -> None:
|
||||
"""
|
||||
Initialise the FileImporter singleton instance.
|
||||
"""
|
||||
|
||||
log.debug(f"FileImporter.__init__({base_model=}, {row_number=})")
|
||||
|
||||
# Create ModelData
|
||||
if not row_number:
|
||||
row_number = base_model.rowCount()
|
||||
self.model_data = ThreadData(base_model=base_model, row_number=row_number)
|
||||
|
||||
# Data structure to track files to import
|
||||
@ -201,9 +202,8 @@ class FileImporter:
|
||||
self.sort_track_match_data(tfd)
|
||||
selection = self.get_user_choices(tfd)
|
||||
if self.process_selection(tfd, selection):
|
||||
if self.extension_check(tfd):
|
||||
if self.validate_file_data(tfd):
|
||||
tfd.import_this_file = True
|
||||
if self.validate_file_data(tfd):
|
||||
tfd.import_this_file = True
|
||||
|
||||
return tfd
|
||||
|
||||
@ -237,26 +237,6 @@ class FileImporter:
|
||||
|
||||
return True
|
||||
|
||||
def extension_check(self, tfd: TrackFileData) -> bool:
|
||||
"""
|
||||
If we are replacing an existing file, check that the correct file
|
||||
extension of the replacement file matches the existing file
|
||||
extension and return True if it does (or if there is no exsting
|
||||
file), else False.
|
||||
"""
|
||||
|
||||
if not tfd.file_path_to_remove:
|
||||
return True
|
||||
|
||||
if tfd.file_path_to_remove.endswith(audio_file_extension(tfd.source_path)):
|
||||
return True
|
||||
|
||||
tfd.error = (
|
||||
f"Existing file ({tfd.file_path_to_remove}) has a different "
|
||||
f"extension to replacement file ({tfd.source_path})"
|
||||
)
|
||||
return False
|
||||
|
||||
def find_similar(self, tfd: TrackFileData) -> None:
|
||||
"""
|
||||
- Search title in existing tracks
|
||||
@ -465,8 +445,7 @@ class FileImporter:
|
||||
if tfd.track_id == 0 and tfd.destination_path != tfd.file_path_to_remove:
|
||||
while os.path.exists(tfd.destination_path):
|
||||
msg = (
|
||||
"New import requested but default destination path"
|
||||
f" ({tfd.destination_path})"
|
||||
f"New import requested but default destination path ({tfd.destination_path})"
|
||||
" already exists. Click OK and choose where to save this track"
|
||||
)
|
||||
show_OK(title="Desintation path exists", msg=msg, parent=None)
|
||||
@ -648,8 +627,7 @@ class DoTrackImport(QThread):
|
||||
f"Importing {os.path.basename(self.import_file_path)}", 5000
|
||||
)
|
||||
|
||||
# Get audio metadata in this thread rather than calling
|
||||
# function to save interactive time
|
||||
# Get audio metadata in this thread rather than calling function to save interactive time
|
||||
self.audio_metadata = helpers.get_audio_metadata(self.import_file_path)
|
||||
|
||||
# Remove old file if so requested
|
||||
|
||||
@ -13,7 +13,6 @@ import tempfile
|
||||
from PyQt6.QtWidgets import QInputDialog, QMainWindow, QMessageBox, QWidget
|
||||
|
||||
# Third party imports
|
||||
import filetype
|
||||
from mutagen.flac import FLAC # type: ignore
|
||||
from mutagen.mp3 import MP3 # type: ignore
|
||||
from pydub import AudioSegment, effects
|
||||
@ -51,14 +50,6 @@ def ask_yes_no(
|
||||
return button == QMessageBox.StandardButton.Yes
|
||||
|
||||
|
||||
def audio_file_extension(fpath: str) -> str | None:
|
||||
"""
|
||||
Return the correct extension for this type of file.
|
||||
"""
|
||||
|
||||
return filetype.guess(fpath).extension
|
||||
|
||||
|
||||
def fade_point(
|
||||
audio_segment: AudioSegment,
|
||||
fade_threshold: float = 0.0,
|
||||
@ -81,7 +72,7 @@ def fade_point(
|
||||
fade_threshold = max_vol
|
||||
|
||||
while (
|
||||
audio_segment[trim_ms: trim_ms + chunk_size].dBFS < fade_threshold
|
||||
audio_segment[trim_ms : trim_ms + chunk_size].dBFS < fade_threshold
|
||||
and trim_ms > 0
|
||||
): # noqa W503
|
||||
trim_ms -= chunk_size
|
||||
@ -103,9 +94,6 @@ def file_is_unreadable(path: Optional[str]) -> bool:
|
||||
|
||||
|
||||
def get_audio_segment(path: str) -> Optional[AudioSegment]:
|
||||
if not path.endswith(audio_file_extension(path)):
|
||||
return None
|
||||
|
||||
try:
|
||||
if path.endswith(".mp3"):
|
||||
return AudioSegment.from_mp3(path)
|
||||
|
||||
27
app/log.py
27
app/log.py
@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# Standard library imports
|
||||
from collections import defaultdict
|
||||
from functools import wraps
|
||||
import logging
|
||||
import logging.config
|
||||
import logging.handlers
|
||||
@ -109,30 +108,4 @@ def handle_exception(exc_type, exc_value, exc_traceback):
|
||||
QMessageBox.critical(None, "Application Error", msg)
|
||||
|
||||
|
||||
def truncate_large(obj, limit=5):
|
||||
"""Helper to truncate large lists or other iterables."""
|
||||
if isinstance(obj, (list, tuple, set)):
|
||||
if len(obj) > limit:
|
||||
return f"{type(obj).__name__}(len={len(obj)}, items={list(obj)[:limit]}...)"
|
||||
|
||||
return repr(obj)
|
||||
|
||||
|
||||
def log_call(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
args_repr = [truncate_large(a) for a in args]
|
||||
kwargs_repr = [f"{k}={truncate_large(v)}" for k, v in kwargs.items()]
|
||||
params_repr = ", ".join(args_repr + kwargs_repr)
|
||||
log.debug(f"call {func.__name__}({params_repr})")
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
log.debug(f"return {func.__name__}: {truncate_large(result)}")
|
||||
return result
|
||||
except Exception as e:
|
||||
log.debug(f"exception in {func.__name__}: {e}")
|
||||
raise
|
||||
return wrapper
|
||||
|
||||
|
||||
sys.excepthook = handle_exception
|
||||
|
||||
@ -63,7 +63,6 @@ from sqlalchemy.orm.session import Session
|
||||
import stackprinter # type: ignore
|
||||
|
||||
# App imports
|
||||
from audacity_controller import AudacityController
|
||||
from classes import (
|
||||
ApplicationError,
|
||||
Filter,
|
||||
@ -73,8 +72,8 @@ from classes import (
|
||||
from config import Config
|
||||
from dialogs import TrackSelectDialog
|
||||
from file_importer import FileImporter
|
||||
from log import log, log_call
|
||||
from helpers import ask_yes_no, file_is_unreadable, get_name, show_warning
|
||||
from helpers import ask_yes_no, file_is_unreadable, get_name
|
||||
from log import log
|
||||
from models import db, Playdates, PlaylistRows, Playlists, Queries, Settings, Tracks
|
||||
from music_manager import RowAndTrack, track_sequence
|
||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||
@ -1209,13 +1208,6 @@ class Window(QMainWindow):
|
||||
self.load_last_playlists()
|
||||
self.stop_autoplay = False
|
||||
|
||||
# Set up for Audacity
|
||||
try:
|
||||
self.ac: Optional[AudacityController] = AudacityController()
|
||||
except ApplicationError as e:
|
||||
self.ac = None
|
||||
show_warning(self, "Audacity error", str(e))
|
||||
|
||||
# # # # # # # # # # Overrides # # # # # # # # # #
|
||||
|
||||
def closeEvent(self, event: Optional[QCloseEvent]) -> None:
|
||||
@ -1475,7 +1467,6 @@ class Window(QMainWindow):
|
||||
|
||||
return Playlists(session, name, template_id)
|
||||
|
||||
@log_call
|
||||
def _open_playlist(self, playlist: Playlists, is_template: bool = False) -> int:
|
||||
"""
|
||||
With passed playlist:
|
||||
@ -1998,7 +1989,6 @@ class Window(QMainWindow):
|
||||
dlg.exec()
|
||||
session.commit()
|
||||
|
||||
@log_call
|
||||
def load_last_playlists(self) -> None:
|
||||
"""Load the playlists that were open when the last session closed"""
|
||||
|
||||
@ -2735,7 +2725,6 @@ class Window(QMainWindow):
|
||||
self.catch_return_key = False
|
||||
self.show_status_message("Play controls: Enabled", 0)
|
||||
# Re-enable 10ms timer (see above)
|
||||
log.debug(f"issue287: {self.timer10.isActive()=}")
|
||||
if not self.timer10.isActive():
|
||||
self.timer10.start(10)
|
||||
log.debug("issue223: update_clocks: 10ms timer enabled")
|
||||
|
||||
@ -46,7 +46,7 @@ from helpers import (
|
||||
remove_substring_case_insensitive,
|
||||
set_track_metadata,
|
||||
)
|
||||
from log import log, log_call
|
||||
from log import log
|
||||
from models import db, NoteColours, Playdates, PlaylistRows, Tracks
|
||||
from music_manager import RowAndTrack, track_sequence
|
||||
|
||||
|
||||
@ -34,6 +34,7 @@ from PyQt6.QtWidgets import (
|
||||
# import line_profiler
|
||||
|
||||
# App imports
|
||||
from audacity_controller import AudacityController
|
||||
from classes import ApplicationError, Col, MusicMusterSignals, PlaylistStyle, TrackInfo
|
||||
from config import Config
|
||||
from dialogs import TrackSelectDialog
|
||||
@ -43,7 +44,7 @@ from helpers import (
|
||||
show_OK,
|
||||
show_warning,
|
||||
)
|
||||
from log import log, log_call
|
||||
from log import log
|
||||
from models import db, Settings
|
||||
from music_manager import track_sequence
|
||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||
@ -307,6 +308,13 @@ class PlaylistTab(QTableView):
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
|
||||
# Set up for Audacity
|
||||
try:
|
||||
self.ac: Optional[AudacityController] = AudacityController()
|
||||
except ApplicationError as e:
|
||||
self.ac = None
|
||||
show_warning(self.musicmuster, "Audacity error", str(e))
|
||||
|
||||
# Load model, set column widths
|
||||
self.setModel(model)
|
||||
self._set_column_widths()
|
||||
@ -538,8 +546,8 @@ class PlaylistTab(QTableView):
|
||||
track_path = base_model.get_row_info(model_row_number).path
|
||||
|
||||
# Open/import in/from Audacity
|
||||
if track_row and not this_is_current_row and self.musicmuster.ac:
|
||||
if track_path == self.musicmuster.ac.path:
|
||||
if track_row and not this_is_current_row:
|
||||
if self.ac and track_path == self.ac.path:
|
||||
# This track was opened in Audacity
|
||||
self._add_context_menu(
|
||||
"Update from Audacity",
|
||||
@ -649,8 +657,8 @@ class PlaylistTab(QTableView):
|
||||
that we have an edit open.
|
||||
"""
|
||||
|
||||
if self.musicmuster.ac:
|
||||
self.musicmuster.ac.path = None
|
||||
if self.ac:
|
||||
self.ac.path = None
|
||||
|
||||
def clear_selection(self) -> None:
|
||||
"""Unselect all tracks and reset drag mode"""
|
||||
@ -852,10 +860,10 @@ class PlaylistTab(QTableView):
|
||||
Import current Audacity track to passed row
|
||||
"""
|
||||
|
||||
if not self.musicmuster.ac:
|
||||
if not self.ac:
|
||||
return
|
||||
try:
|
||||
self.ac.musicmuster.export()
|
||||
self.ac.export()
|
||||
self._rescan(row_number)
|
||||
except ApplicationError as e:
|
||||
show_warning(self.musicmuster, "Audacity error", str(e))
|
||||
@ -912,16 +920,15 @@ class PlaylistTab(QTableView):
|
||||
Open track in passed row in Audacity
|
||||
"""
|
||||
|
||||
if not self.musicmuster.ac:
|
||||
return
|
||||
|
||||
path = self.get_base_model().get_row_track_path(row_number)
|
||||
if not path:
|
||||
log.error(f"_open_in_audacity: can't get path for {row_number=}")
|
||||
return
|
||||
|
||||
try:
|
||||
self.musicmuster.ac.open(path)
|
||||
if not self.ac:
|
||||
self.ac = AudacityController()
|
||||
self.ac.open(path)
|
||||
except ApplicationError as e:
|
||||
show_warning(self.musicmuster, "Audacity error", str(e))
|
||||
|
||||
|
||||
@ -33,8 +33,6 @@ dependencies = [
|
||||
"types-pyyaml>=6.0.12.20241230",
|
||||
"dogpile-cache>=1.3.4",
|
||||
"pdbpp>=0.10.3",
|
||||
"filetype>=1.2.0",
|
||||
"black>=25.1.0",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
@ -65,9 +63,6 @@ python_version = 3.11
|
||||
warn_unused_configs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
||||
[tool.pylsp.plugins.pycodestyle]
|
||||
maxLineLength = 88
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "--exitfirst --showlocals --capture=no"
|
||||
pythonpath = [".", "app"]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user