Compare commits
6 Commits
206a02214e
...
2f9fcae05f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f9fcae05f | ||
|
|
4978dcf5c3 | ||
|
|
19b1bf3fde | ||
|
|
316b4708c6 | ||
|
|
4fd9a0381f | ||
|
|
88cce738d7 |
@ -143,6 +143,6 @@ class Config(object):
|
||||
WIKIPEDIA_ON_NEXT = False
|
||||
|
||||
# These rely on earlier definitions
|
||||
HIDE_PLAYED_MODE = HIDE_PLAYED_MODE_SECTIONS
|
||||
HIDE_PLAYED_MODE = HIDE_PLAYED_MODE_TRACKS
|
||||
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
||||
REPLACE_FILES_DEFAULT_DESTINATION = os.path.dirname(REPLACE_FILES_DEFAULT_SOURCE)
|
||||
|
||||
@ -35,6 +35,7 @@ from classes import (
|
||||
)
|
||||
from config import Config
|
||||
from helpers import (
|
||||
audio_file_extension,
|
||||
file_is_unreadable,
|
||||
get_all_track_metadata,
|
||||
get_audio_metadata,
|
||||
@ -196,6 +197,7 @@ 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
|
||||
|
||||
@ -231,6 +233,27 @@ 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
|
||||
|
||||
extension = audio_file_extension(tfd.source_path)
|
||||
if extension and tfd.file_path_to_remove.endswith(extension):
|
||||
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
|
||||
@ -441,7 +464,8 @@ class FileImporter:
|
||||
if tfd.track_id == 0 and tfd.destination_path != tfd.file_path_to_remove:
|
||||
while os.path.exists(tfd.destination_path):
|
||||
msg = (
|
||||
f"New import requested but default destination path ({tfd.destination_path})"
|
||||
"New import requested but default destination path"
|
||||
f" ({tfd.destination_path})"
|
||||
" already exists. Click OK and choose where to save this track"
|
||||
)
|
||||
show_OK(title="Desintation path exists", msg=msg, parent=None)
|
||||
@ -488,7 +512,8 @@ class FileImporter:
|
||||
msgs: list[str] = []
|
||||
for tfd in tfds:
|
||||
msgs.append(
|
||||
f"{os.path.basename(tfd.source_path)} will not be imported because {tfd.error}"
|
||||
f"{os.path.basename(tfd.source_path)} will not be imported "
|
||||
f"because {tfd.error}"
|
||||
)
|
||||
if msgs:
|
||||
show_OK("File not imported", "\r\r".join(msgs))
|
||||
@ -511,7 +536,8 @@ class FileImporter:
|
||||
filename = os.path.basename(tfd.source_path)
|
||||
log.debug(f"Processing {filename}")
|
||||
log.debug(
|
||||
f"remaining files: {[a.source_path for a in self.import_files_data]}"
|
||||
"remaining files: "
|
||||
f"{[a.source_path for a in self.import_files_data]}"
|
||||
)
|
||||
self.signals.status_message_signal.emit(
|
||||
f"Importing {filename}", 10000
|
||||
@ -608,7 +634,10 @@ class DoTrackImport(QThread):
|
||||
self.signals = MusicMusterSignals()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<DoTrackImport(id={hex(id(self))}, import_file_path={self.import_file_path}"
|
||||
return (
|
||||
f"<DoTrackImport(id={hex(id(self))}, "
|
||||
f"import_file_path={self.import_file_path}"
|
||||
)
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
@ -622,7 +651,8 @@ 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 = get_audio_metadata(self.import_file_path)
|
||||
|
||||
# Remove old file if so requested
|
||||
|
||||
@ -13,6 +13,7 @@ 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
|
||||
@ -49,6 +50,14 @@ 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,
|
||||
@ -71,7 +80,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
|
||||
@ -93,6 +102,9 @@ 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)
|
||||
|
||||
@ -63,6 +63,7 @@ from pygame import mixer
|
||||
import stackprinter # type: ignore
|
||||
|
||||
# App imports
|
||||
from audacity_controller import AudacityController
|
||||
from classes import (
|
||||
ApplicationError,
|
||||
Filter,
|
||||
@ -79,6 +80,7 @@ from dialogs import TrackInsertDialog
|
||||
from file_importer import FileImporter
|
||||
from helpers import file_is_unreadable, get_name
|
||||
from log import log, log_call
|
||||
from helpers import ask_yes_no, file_is_unreadable, get_name, show_warning
|
||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||
from playlistrow import PlaylistRow, TrackSequence
|
||||
from playlists import PlaylistTab
|
||||
@ -1232,6 +1234,13 @@ class Window(QMainWindow):
|
||||
# Load playlists
|
||||
self.load_last_playlists()
|
||||
|
||||
# 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: QCloseEvent | None) -> None:
|
||||
|
||||
@ -34,7 +34,6 @@ from PyQt6.QtWidgets import (
|
||||
# import line_profiler
|
||||
|
||||
# App imports
|
||||
from audacity_controller import AudacityController
|
||||
from classes import (
|
||||
ApplicationError,
|
||||
Col,
|
||||
@ -315,13 +314,6 @@ 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()
|
||||
@ -554,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:
|
||||
if self.ac and track_path == self.ac.path:
|
||||
if track_row and not this_is_current_row and self.musicmuster.ac:
|
||||
if track_path == self.musicmuster.ac.path:
|
||||
# This track was opened in Audacity
|
||||
self._add_context_menu(
|
||||
"Update from Audacity",
|
||||
@ -665,8 +657,8 @@ class PlaylistTab(QTableView):
|
||||
that we have an edit open.
|
||||
"""
|
||||
|
||||
if self.ac:
|
||||
self.ac.path = None
|
||||
if self.musicmuster.ac:
|
||||
self.musicmuster.ac.path = None
|
||||
|
||||
def clear_selection(self) -> None:
|
||||
"""Unselect all tracks and reset drag mode"""
|
||||
@ -877,10 +869,10 @@ class PlaylistTab(QTableView):
|
||||
Import current Audacity track to passed row
|
||||
"""
|
||||
|
||||
if not self.ac:
|
||||
if not self.musicmuster.ac:
|
||||
return
|
||||
try:
|
||||
self.ac.export()
|
||||
self.ac.musicmuster.export()
|
||||
self._rescan(row_number)
|
||||
except ApplicationError as e:
|
||||
show_warning(self.musicmuster, "Audacity error", str(e))
|
||||
@ -937,15 +929,16 @@ 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:
|
||||
if not self.ac:
|
||||
self.ac = AudacityController()
|
||||
self.ac.open(path)
|
||||
self.musicmuster.ac.open(path)
|
||||
except ApplicationError as e:
|
||||
show_warning(self.musicmuster, "Audacity error", str(e))
|
||||
|
||||
|
||||
@ -33,6 +33,8 @@ 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]
|
||||
@ -63,6 +65,9 @@ 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