Compare commits
86 Commits
2f9fcae05f
...
206a02214e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
206a02214e | ||
|
|
7918bbb237 | ||
|
|
8621a37af3 | ||
|
|
ad391aedc6 | ||
|
|
735c927864 | ||
|
|
e2aac65dac | ||
|
|
86c3c3fd80 | ||
|
|
a4ba013306 | ||
|
|
6d012d7b5a | ||
|
|
c58eb47cc1 | ||
|
|
847840251c | ||
|
|
f172eaaeb2 | ||
|
|
25771f5235 | ||
|
|
c93d24aef5 | ||
|
|
a2a9afb04f | ||
|
|
db6fb7b367 | ||
|
|
5d7b55a0ab | ||
|
|
0124ca6018 | ||
|
|
af40e419ff | ||
|
|
223c7cd3ab | ||
|
|
edd8c36c53 | ||
|
|
c6be215bd4 | ||
|
|
22e16144e1 | ||
|
|
1932719fea | ||
|
|
e9f4ecf5ef | ||
|
|
ce224a41d1 | ||
|
|
bf89172f8a | ||
|
|
9f234bb007 | ||
|
|
6fb6339843 | ||
|
|
bf5563f2f1 | ||
|
|
8fa98a2207 | ||
|
|
e7af25ad6e | ||
|
|
d6bb3d04d8 | ||
|
|
a0ded4b73d | ||
|
|
6496ea2ac4 | ||
|
|
c61df17dd5 | ||
|
|
747f28f4f9 | ||
|
|
5f0da55a24 | ||
|
|
498923c3b3 | ||
|
|
b34e0a014a | ||
|
|
f9c33120f5 | ||
|
|
ffb1b238f4 | ||
|
|
83780bfb68 | ||
|
|
cd793f9668 | ||
|
|
7f3e235e9d | ||
|
|
324dd770df | ||
|
|
11400536b5 | ||
|
|
d596792375 | ||
|
|
a8791f925d | ||
|
|
5317ecdf18 | ||
|
|
a2baf489c3 | ||
|
|
aec994bafd | ||
|
|
0c717241ff | ||
|
|
728d012257 | ||
|
|
ac685426d9 | ||
|
|
38c49b32d7 | ||
|
|
9a6bb038e1 | ||
|
|
0478e25109 | ||
|
|
199abc9c0c | ||
|
|
f9c8541b17 | ||
|
|
5e492f4569 | ||
|
|
f3b1e05e83 | ||
|
|
e71f1d072f | ||
|
|
c182a69a5d | ||
|
|
e39518e5ee | ||
|
|
4eaab98745 | ||
|
|
2fce0b34be | ||
|
|
ed7ac0758c | ||
|
|
098ce7198e | ||
|
|
38b166737b | ||
|
|
0ea12eb9d9 | ||
|
|
75cc7a3f19 | ||
|
|
f64671d126 | ||
|
|
2bf1bc64e7 | ||
|
|
3c7fc20e5a | ||
|
|
52d2269ece | ||
|
|
3cd764c893 | ||
|
|
65878b0b75 | ||
|
|
4e89d72a8f | ||
|
|
92ecb632b5 | ||
|
|
6296566c2c | ||
|
|
7e5b170f5e | ||
|
|
3db71a08ae | ||
|
|
7b0e2b2c6c | ||
|
|
4265472d73 | ||
|
|
c94cadf24f |
@ -143,6 +143,6 @@ class Config(object):
|
|||||||
WIKIPEDIA_ON_NEXT = False
|
WIKIPEDIA_ON_NEXT = False
|
||||||
|
|
||||||
# These rely on earlier definitions
|
# 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")
|
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
||||||
REPLACE_FILES_DEFAULT_DESTINATION = os.path.dirname(REPLACE_FILES_DEFAULT_SOURCE)
|
REPLACE_FILES_DEFAULT_DESTINATION = os.path.dirname(REPLACE_FILES_DEFAULT_SOURCE)
|
||||||
|
|||||||
@ -35,7 +35,6 @@ from classes import (
|
|||||||
)
|
)
|
||||||
from config import Config
|
from config import Config
|
||||||
from helpers import (
|
from helpers import (
|
||||||
audio_file_extension,
|
|
||||||
file_is_unreadable,
|
file_is_unreadable,
|
||||||
get_all_track_metadata,
|
get_all_track_metadata,
|
||||||
get_audio_metadata,
|
get_audio_metadata,
|
||||||
@ -197,9 +196,8 @@ class FileImporter:
|
|||||||
self.sort_track_match_data(tfd)
|
self.sort_track_match_data(tfd)
|
||||||
selection = self.get_user_choices(tfd)
|
selection = self.get_user_choices(tfd)
|
||||||
if self.process_selection(tfd, selection):
|
if self.process_selection(tfd, selection):
|
||||||
if self.extension_check(tfd):
|
if self.validate_file_data(tfd):
|
||||||
if self.validate_file_data(tfd):
|
tfd.import_this_file = True
|
||||||
tfd.import_this_file = True
|
|
||||||
|
|
||||||
return tfd
|
return tfd
|
||||||
|
|
||||||
@ -233,27 +231,6 @@ class FileImporter:
|
|||||||
|
|
||||||
return True
|
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:
|
def find_similar(self, tfd: TrackFileData) -> None:
|
||||||
"""
|
"""
|
||||||
- Search title in existing tracks
|
- Search title in existing tracks
|
||||||
@ -464,8 +441,7 @@ class FileImporter:
|
|||||||
if tfd.track_id == 0 and tfd.destination_path != tfd.file_path_to_remove:
|
if tfd.track_id == 0 and tfd.destination_path != tfd.file_path_to_remove:
|
||||||
while os.path.exists(tfd.destination_path):
|
while os.path.exists(tfd.destination_path):
|
||||||
msg = (
|
msg = (
|
||||||
"New import requested but default destination path"
|
f"New import requested but default destination path ({tfd.destination_path})"
|
||||||
f" ({tfd.destination_path})"
|
|
||||||
" already exists. Click OK and choose where to save this track"
|
" already exists. Click OK and choose where to save this track"
|
||||||
)
|
)
|
||||||
show_OK(title="Desintation path exists", msg=msg, parent=None)
|
show_OK(title="Desintation path exists", msg=msg, parent=None)
|
||||||
@ -512,8 +488,7 @@ class FileImporter:
|
|||||||
msgs: list[str] = []
|
msgs: list[str] = []
|
||||||
for tfd in tfds:
|
for tfd in tfds:
|
||||||
msgs.append(
|
msgs.append(
|
||||||
f"{os.path.basename(tfd.source_path)} will not be imported "
|
f"{os.path.basename(tfd.source_path)} will not be imported because {tfd.error}"
|
||||||
f"because {tfd.error}"
|
|
||||||
)
|
)
|
||||||
if msgs:
|
if msgs:
|
||||||
show_OK("File not imported", "\r\r".join(msgs))
|
show_OK("File not imported", "\r\r".join(msgs))
|
||||||
@ -536,8 +511,7 @@ class FileImporter:
|
|||||||
filename = os.path.basename(tfd.source_path)
|
filename = os.path.basename(tfd.source_path)
|
||||||
log.debug(f"Processing {filename}")
|
log.debug(f"Processing {filename}")
|
||||||
log.debug(
|
log.debug(
|
||||||
"remaining files: "
|
f"remaining files: {[a.source_path for a in self.import_files_data]}"
|
||||||
f"{[a.source_path for a in self.import_files_data]}"
|
|
||||||
)
|
)
|
||||||
self.signals.status_message_signal.emit(
|
self.signals.status_message_signal.emit(
|
||||||
f"Importing {filename}", 10000
|
f"Importing {filename}", 10000
|
||||||
@ -634,10 +608,7 @@ class DoTrackImport(QThread):
|
|||||||
self.signals = MusicMusterSignals()
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return f"<DoTrackImport(id={hex(id(self))}, import_file_path={self.import_file_path}"
|
||||||
f"<DoTrackImport(id={hex(id(self))}, "
|
|
||||||
f"import_file_path={self.import_file_path}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -651,8 +622,7 @@ class DoTrackImport(QThread):
|
|||||||
f"Importing {os.path.basename(self.import_file_path)}", 5000
|
f"Importing {os.path.basename(self.import_file_path)}", 5000
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get audio metadata in this thread rather than calling
|
# Get audio metadata in this thread rather than calling function to save interactive time
|
||||||
# function to save interactive time
|
|
||||||
self.audio_metadata = get_audio_metadata(self.import_file_path)
|
self.audio_metadata = get_audio_metadata(self.import_file_path)
|
||||||
|
|
||||||
# Remove old file if so requested
|
# Remove old file if so requested
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import tempfile
|
|||||||
from PyQt6.QtWidgets import QInputDialog, QMainWindow, QMessageBox, QWidget
|
from PyQt6.QtWidgets import QInputDialog, QMainWindow, QMessageBox, QWidget
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
import filetype
|
|
||||||
from mutagen.flac import FLAC # type: ignore
|
from mutagen.flac import FLAC # type: ignore
|
||||||
from mutagen.mp3 import MP3 # type: ignore
|
from mutagen.mp3 import MP3 # type: ignore
|
||||||
from pydub import AudioSegment, effects
|
from pydub import AudioSegment, effects
|
||||||
@ -50,14 +49,6 @@ def ask_yes_no(
|
|||||||
return button == QMessageBox.StandardButton.Yes
|
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(
|
def fade_point(
|
||||||
audio_segment: AudioSegment,
|
audio_segment: AudioSegment,
|
||||||
fade_threshold: float = 0.0,
|
fade_threshold: float = 0.0,
|
||||||
@ -80,7 +71,7 @@ def fade_point(
|
|||||||
fade_threshold = max_vol
|
fade_threshold = max_vol
|
||||||
|
|
||||||
while (
|
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
|
and trim_ms > 0
|
||||||
): # noqa W503
|
): # noqa W503
|
||||||
trim_ms -= chunk_size
|
trim_ms -= chunk_size
|
||||||
@ -102,9 +93,6 @@ def file_is_unreadable(path: Optional[str]) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def get_audio_segment(path: str) -> Optional[AudioSegment]:
|
def get_audio_segment(path: str) -> Optional[AudioSegment]:
|
||||||
if not path.endswith(audio_file_extension(path)):
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if path.endswith(".mp3"):
|
if path.endswith(".mp3"):
|
||||||
return AudioSegment.from_mp3(path)
|
return AudioSegment.from_mp3(path)
|
||||||
|
|||||||
@ -63,7 +63,6 @@ from pygame import mixer
|
|||||||
import stackprinter # type: ignore
|
import stackprinter # type: ignore
|
||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
from audacity_controller import AudacityController
|
|
||||||
from classes import (
|
from classes import (
|
||||||
ApplicationError,
|
ApplicationError,
|
||||||
Filter,
|
Filter,
|
||||||
@ -80,7 +79,6 @@ from dialogs import TrackInsertDialog
|
|||||||
from file_importer import FileImporter
|
from file_importer import FileImporter
|
||||||
from helpers import file_is_unreadable, get_name
|
from helpers import file_is_unreadable, get_name
|
||||||
from log import log, log_call
|
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 playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||||
from playlistrow import PlaylistRow, TrackSequence
|
from playlistrow import PlaylistRow, TrackSequence
|
||||||
from playlists import PlaylistTab
|
from playlists import PlaylistTab
|
||||||
@ -1234,13 +1232,6 @@ class Window(QMainWindow):
|
|||||||
# Load playlists
|
# Load playlists
|
||||||
self.load_last_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 # # # # # # # # # #
|
# # # # # # # # # # Overrides # # # # # # # # # #
|
||||||
|
|
||||||
def closeEvent(self, event: QCloseEvent | None) -> None:
|
def closeEvent(self, event: QCloseEvent | None) -> None:
|
||||||
|
|||||||
@ -34,6 +34,7 @@ from PyQt6.QtWidgets import (
|
|||||||
# import line_profiler
|
# import line_profiler
|
||||||
|
|
||||||
# App imports
|
# App imports
|
||||||
|
from audacity_controller import AudacityController
|
||||||
from classes import (
|
from classes import (
|
||||||
ApplicationError,
|
ApplicationError,
|
||||||
Col,
|
Col,
|
||||||
@ -314,6 +315,13 @@ class PlaylistTab(QTableView):
|
|||||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
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
|
# Load model, set column widths
|
||||||
self.setModel(model)
|
self.setModel(model)
|
||||||
self._set_column_widths()
|
self._set_column_widths()
|
||||||
@ -546,8 +554,8 @@ class PlaylistTab(QTableView):
|
|||||||
track_path = base_model.get_row_info(model_row_number).path
|
track_path = base_model.get_row_info(model_row_number).path
|
||||||
|
|
||||||
# Open/import in/from Audacity
|
# Open/import in/from Audacity
|
||||||
if track_row and not this_is_current_row and self.musicmuster.ac:
|
if track_row and not this_is_current_row:
|
||||||
if track_path == self.musicmuster.ac.path:
|
if self.ac and track_path == self.ac.path:
|
||||||
# This track was opened in Audacity
|
# This track was opened in Audacity
|
||||||
self._add_context_menu(
|
self._add_context_menu(
|
||||||
"Update from Audacity",
|
"Update from Audacity",
|
||||||
@ -657,8 +665,8 @@ class PlaylistTab(QTableView):
|
|||||||
that we have an edit open.
|
that we have an edit open.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.musicmuster.ac:
|
if self.ac:
|
||||||
self.musicmuster.ac.path = None
|
self.ac.path = None
|
||||||
|
|
||||||
def clear_selection(self) -> None:
|
def clear_selection(self) -> None:
|
||||||
"""Unselect all tracks and reset drag mode"""
|
"""Unselect all tracks and reset drag mode"""
|
||||||
@ -869,10 +877,10 @@ class PlaylistTab(QTableView):
|
|||||||
Import current Audacity track to passed row
|
Import current Audacity track to passed row
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.musicmuster.ac:
|
if not self.ac:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self.ac.musicmuster.export()
|
self.ac.export()
|
||||||
self._rescan(row_number)
|
self._rescan(row_number)
|
||||||
except ApplicationError as e:
|
except ApplicationError as e:
|
||||||
show_warning(self.musicmuster, "Audacity error", str(e))
|
show_warning(self.musicmuster, "Audacity error", str(e))
|
||||||
@ -929,16 +937,15 @@ class PlaylistTab(QTableView):
|
|||||||
Open track in passed row in Audacity
|
Open track in passed row in Audacity
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.musicmuster.ac:
|
|
||||||
return
|
|
||||||
|
|
||||||
path = self.get_base_model().get_row_track_path(row_number)
|
path = self.get_base_model().get_row_track_path(row_number)
|
||||||
if not path:
|
if not path:
|
||||||
log.error(f"_open_in_audacity: can't get path for {row_number=}")
|
log.error(f"_open_in_audacity: can't get path for {row_number=}")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.musicmuster.ac.open(path)
|
if not self.ac:
|
||||||
|
self.ac = AudacityController()
|
||||||
|
self.ac.open(path)
|
||||||
except ApplicationError as e:
|
except ApplicationError as e:
|
||||||
show_warning(self.musicmuster, "Audacity error", str(e))
|
show_warning(self.musicmuster, "Audacity error", str(e))
|
||||||
|
|
||||||
|
|||||||
@ -33,8 +33,6 @@ dependencies = [
|
|||||||
"types-pyyaml>=6.0.12.20241230",
|
"types-pyyaml>=6.0.12.20241230",
|
||||||
"dogpile-cache>=1.3.4",
|
"dogpile-cache>=1.3.4",
|
||||||
"pdbpp>=0.10.3",
|
"pdbpp>=0.10.3",
|
||||||
"filetype>=1.2.0",
|
|
||||||
"black>=25.1.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
@ -65,9 +63,6 @@ python_version = 3.11
|
|||||||
warn_unused_configs = true
|
warn_unused_configs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
|
|
||||||
[tool.pylsp.plugins.pycodestyle]
|
|
||||||
maxLineLength = 88
|
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "--exitfirst --showlocals --capture=no"
|
addopts = "--exitfirst --showlocals --capture=no"
|
||||||
pythonpath = [".", "app"]
|
pythonpath = [".", "app"]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user