Compare commits

..

No commits in common. "ecd5c65695def300cd315b3aecc528ff98ef6343" and "28897500c80c9c0484e10c77dbb61149fed4b095" have entirely different histories.

5 changed files with 40 additions and 69 deletions

View File

@ -37,6 +37,11 @@ class AudacityController:
user_uid = os.getuid() # Get the user's UID user_uid = os.getuid() # Get the user's UID
self.pipe_to = f"/tmp/audacity_script_pipe.to.{user_uid}" self.pipe_to = f"/tmp/audacity_script_pipe.to.{user_uid}"
self.pipe_from = f"/tmp/audacity_script_pipe.from.{user_uid}" self.pipe_from = f"/tmp/audacity_script_pipe.from.{user_uid}"
if not (os.path.exists(self.pipe_to) and os.path.exists(self.pipe_from)):
raise ApplicationError(
"AudacityController: Audacity pipes not found. Ensure scripting is enabled "
f"and pipes exist at {self.pipe_to} and {self.pipe_from}."
)
elif method == "socket": elif method == "socket":
self.socket_host = socket_host self.socket_host = socket_host
self.socket_port = socket_port self.socket_port = socket_port
@ -49,8 +54,6 @@ class AudacityController:
else: else:
raise ApplicationError("Invalid method. Use 'pipe' or 'socket'.") raise ApplicationError("Invalid method. Use 'pipe' or 'socket'.")
self._sanity_check()
def close(self): def close(self):
""" """
Close the connection (for sockets). Close the connection (for sockets).
@ -63,14 +66,16 @@ class AudacityController:
Export file from Audacity Export file from Audacity
""" """
log.info("export()")
self._sanity_check() self._sanity_check()
select_status = self._send_command("SelectAll") select_status = self._send_command("SelectAll")
log.debug(f"{select_status=}") log.info(f"{select_status=}")
export_cmd = f'Export2: Filename="{self.path}" NumChannels=2' export_cmd = f'Export2: Filename="{self.path}" NumChannels=2'
export_status = self._send_command(export_cmd) export_status = self._send_command(export_cmd)
log.debug(f"{export_status=}") log.info(f"{export_status=}")
self.path = "" self.path = ""
if not export_status.startswith("Exported"): if not export_status.startswith("Exported"):
raise ApplicationError(f"Error writing from Audacity: {export_status=}") raise ApplicationError(f"Error writing from Audacity: {export_status=}")
@ -80,6 +85,8 @@ class AudacityController:
Open path in Audacity. Escape filename. Open path in Audacity. Escape filename.
""" """
log.info(f"open({path=})")
self._sanity_check() self._sanity_check()
escaped_path = path.replace('"', '\\"') escaped_path = path.replace('"', '\\"')
@ -87,13 +94,15 @@ class AudacityController:
status = self._send_command(cmd) status = self._send_command(cmd)
self.path = path self.path = path
log.debug(f"_open_in_audacity {path=}, {status=}") log.info(f"_open_in_audacity {path=}, {status=}")
def _sanity_check(self) -> None: def _sanity_check(self) -> None:
""" """
Check Audactity running and basic connectivity. Check Audactity running and basic connectivity.
""" """
log.info("_sanity_check")
# Check Audacity is running # Check Audacity is running
if "audacity" not in [i.name() for i in psutil.process_iter()]: if "audacity" not in [i.name() for i in psutil.process_iter()]:
log.warning("Audactity not running") log.warning("Audactity not running")
@ -122,7 +131,7 @@ class AudacityController:
:param command: Command to send (e.g., 'SelectAll'). :param command: Command to send (e.g., 'SelectAll').
:return: Response from Audacity. :return: Response from Audacity.
""" """
log.debug(f"_send_command({command=})") log.info(f"_send_command({command=})")
if self.method == "pipe": if self.method == "pipe":
try: try:

View File

@ -8,7 +8,6 @@ import sys
# PyQt imports # PyQt imports
# Third party imports # Third party imports
import line_profiler
from sqlalchemy import ( from sqlalchemy import (
bindparam, bindparam,
delete, delete,
@ -564,9 +563,8 @@ class PlaylistRows(dbtables.PlaylistRowsTable):
) )
@staticmethod @staticmethod
@line_profiler.profile
def update_plr_row_numbers( def update_plr_row_numbers(
session: Session, playlist_id: int, sqla_map: List[dict[str, int]], dummy_for_profiling=None session: Session, playlist_id: int, sqla_map: List[dict[str, int]]
) -> None: ) -> None:
""" """
Take a {plrid: row_number} dictionary and update the row numbers accordingly Take a {plrid: row_number} dictionary and update the row numbers accordingly

View File

@ -44,7 +44,6 @@ from PyQt6.QtWidgets import (
) )
# Third party imports # Third party imports
import line_profiler
from pygame import mixer from pygame import mixer
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
@ -1054,8 +1053,7 @@ class Window(QMainWindow, Ui_MainWindow):
else: else:
webbrowser.get("browser").open_new_tab(url) webbrowser.get("browser").open_new_tab(url)
@line_profiler.profile def paste_rows(self) -> None:
def paste_rows(self, dummy_for_profiling=None) -> None:
""" """
Paste earlier cut rows. Paste earlier cut rows.
""" """

View File

@ -26,7 +26,6 @@ from PyQt6.QtGui import (
) )
# Third party imports # Third party imports
import line_profiler
import obswebsocket # type: ignore import obswebsocket # type: ignore
# import snoop # type: ignore # import snoop # type: ignore
@ -737,8 +736,7 @@ class PlaylistModel(QAbstractTableModel):
self.update_track_times() self.update_track_times()
self.invalidate_rows(row_numbers) self.invalidate_rows(row_numbers)
@line_profiler.profile def move_rows(self, from_rows: list[int], to_row_number: int) -> None:
def move_rows(self, from_rows: list[int], to_row_number: int, dummy_for_profiling=None) -> None:
""" """
Move the playlist rows given to to_row and below. Move the playlist rows given to to_row and below.
""" """
@ -977,8 +975,7 @@ class PlaylistModel(QAbstractTableModel):
# Update display # Update display
self.invalidate_row(track_sequence.previous.row_number) self.invalidate_row(track_sequence.previous.row_number)
@line_profiler.profile def refresh_data(self, session: db.session) -> None:
def refresh_data(self, session: db.session, dummy_for_profiling=None) -> None:
"""Populate self.playlist_rows with playlist data""" """Populate self.playlist_rows with playlist data"""
# We used to clear self.playlist_rows each time but that's # We used to clear self.playlist_rows each time but that's

View File

@ -1,9 +1,9 @@
# Standard library imports # Standard library imports
from typing import Callable, cast, List, Optional, TYPE_CHECKING from typing import Callable, cast, List, Optional, TYPE_CHECKING
import time
# PyQt imports # PyQt imports
from PyQt6.QtCore import ( from PyQt6.QtCore import (
QAbstractItemModel,
QEvent, QEvent,
QModelIndex, QModelIndex,
QObject, QObject,
@ -57,46 +57,40 @@ class EscapeDelegate(QStyledItemDelegate):
- increases the height of a row when editing to make editing easier - increases the height of a row when editing to make editing easier
- closes the edit on control-return - closes the edit on control-return
- checks with user before abandoning edit on Escape - checks with user before abandoning edit on Escape
- positions cursor where double-click occurs
""" """
def __init__(self, parent: QWidget, source_model: PlaylistModel) -> None: def __init__(self, parent: QWidget, source_model: PlaylistModel) -> None:
super().__init__(parent) super().__init__(parent)
self.source_model = source_model self.source_model = source_model
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
self.click_position = None # Store the mouse click position
def createEditor( def createEditor(
self, self,
parent: Optional[QWidget], parent: Optional[QWidget],
option: QStyleOptionViewItem, option: QStyleOptionViewItem,
index: QModelIndex, index: QModelIndex,
) -> Optional[QDoubleSpinBox | QPlainTextEdit]: ) -> Optional[QWidget]:
""" """
Intercept createEditor call and make row just a little bit taller Intercept createEditor call and make row just a little bit taller
""" """
editor: QDoubleSpinBox | QPlainTextEdit self.editor: QDoubleSpinBox | QPlainTextEdit
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
self.signals.enable_escape_signal.emit(False) self.signals.enable_escape_signal.emit(False)
if isinstance(self.parent(), PlaylistTab): if isinstance(self.parent(), PlaylistTab):
p = cast(PlaylistTab, self.parent()) p = cast(PlaylistTab, self.parent())
if index.column() == Col.INTRO.value: if index.column() == Col.INTRO.value:
editor = QDoubleSpinBox(parent) self.editor = QDoubleSpinBox(parent)
editor.setDecimals(1) self.editor.setDecimals(1)
editor.setSingleStep(0.1) self.editor.setSingleStep(0.1)
return editor return self.editor
elif isinstance(index.data(), str): elif isinstance(index.data(), str):
editor = QPlainTextEdit(parent) self.editor = QPlainTextEdit(parent)
editor.setGeometry(option.rect) # Match the cell geometry
row = index.row() row = index.row()
row_height = p.rowHeight(row) row_height = p.rowHeight(row)
p.setRowHeight(row, row_height + Config.MINIMUM_ROW_HEIGHT) p.setRowHeight(row, row_height + Config.MINIMUM_ROW_HEIGHT)
return editor return self.editor
return super().createEditor(parent, option, index) return super().createEditor(parent, option, index)
def destroyEditor(self, editor: Optional[QWidget], index: QModelIndex) -> None: def destroyEditor(self, editor: Optional[QWidget], index: QModelIndex) -> None:
@ -107,53 +101,31 @@ class EscapeDelegate(QStyledItemDelegate):
self.signals.enable_escape_signal.emit(True) self.signals.enable_escape_signal.emit(True)
return super().destroyEditor(editor, index) return super().destroyEditor(editor, index)
def editorEvent(
self, event: QEvent, model: QAbstractItemModel, option, index: QModelIndex
) -> bool:
"""Capture mouse click position."""
if event.type() == QEvent.Type.MouseButtonPress:
self.click_position = event.pos()
return super().editorEvent(event, model, option, index)
def eventFilter(self, editor: Optional[QObject], event: Optional[QEvent]) -> bool: def eventFilter(self, editor: Optional[QObject], event: Optional[QEvent]) -> bool:
"""By default, QPlainTextEdit doesn't handle enter or return""" """By default, QPlainTextEdit doesn't handle enter or return"""
if editor is None or event is None: if editor is None or event is None:
return super().eventFilter(editor, event) return False
if event.type() == QEvent.Type.Show: if event.type() == QEvent.Type.KeyPress:
if self.click_position and isinstance(editor, QPlainTextEdit):
# Map click position to editor's local space
local_click_position = editor.mapFromParent(self.click_position)
# Move cursor to the calculated position
cursor = editor.cursorForPosition(local_click_position)
editor.setTextCursor(cursor)
# Reset click position
self.click_position = None
return super().eventFilter(editor, event)
elif event.type() == QEvent.Type.KeyPress:
key_event = cast(QKeyEvent, event) key_event = cast(QKeyEvent, event)
if key_event.key() == Qt.Key.Key_Return: if key_event.key() == Qt.Key.Key_Return:
if key_event.modifiers() == (Qt.KeyboardModifier.ControlModifier): if key_event.modifiers() == (Qt.KeyboardModifier.ControlModifier):
self.commitData.emit(editor) self.commitData.emit(editor)
self.closeEditor.emit(editor) self.closeEditor.emit(editor)
return True return True
elif key_event.key() == Qt.Key.Key_Escape: elif key_event.key() == Qt.Key.Key_Escape:
# Close editor if no changes have been made # Close editor if no changes have been made
data_modified = False data_modified = False
if isinstance(editor, QPlainTextEdit): if isinstance(self.editor, QPlainTextEdit):
data_modified = self.original_model_data != editor.toPlainText()
elif isinstance(editor, QDoubleSpinBox):
data_modified = ( data_modified = (
self.original_model_data != int(editor.value()) * 1000 self.original_model_data == self.editor.toPlainText()
) )
if not data_modified: elif isinstance(self.editor, QDoubleSpinBox):
data_modified = (
self.original_model_data == int(self.editor.value()) * 1000
)
if data_modified:
self.closeEditor.emit(editor) self.closeEditor.emit(editor)
return True return True
@ -163,7 +135,6 @@ class EscapeDelegate(QStyledItemDelegate):
if discard_edits == QMessageBox.StandardButton.Yes: if discard_edits == QMessageBox.StandardButton.Yes:
self.closeEditor.emit(editor) self.closeEditor.emit(editor)
return True return True
return False return False
def setEditorData(self, editor, index): def setEditorData(self, editor, index):
@ -182,9 +153,9 @@ class EscapeDelegate(QStyledItemDelegate):
proxy_model = index.model() proxy_model = index.model()
edit_index = proxy_model.mapToSource(index) edit_index = proxy_model.mapToSource(index)
if isinstance(editor, QPlainTextEdit): if isinstance(self.editor, QPlainTextEdit):
value = editor.toPlainText().strip() value = editor.toPlainText().strip()
elif isinstance(editor, QDoubleSpinBox): elif isinstance(self.editor, QDoubleSpinBox):
value = editor.value() value = editor.value()
self.source_model.setData(edit_index, value, Qt.ItemDataRole.EditRole) self.source_model.setData(edit_index, value, Qt.ItemDataRole.EditRole)
@ -463,7 +434,7 @@ class PlaylistTab(QTableView):
# Open/import in/from Audacity # Open/import in/from Audacity
if track_row and not this_is_current_row: if track_row and not this_is_current_row:
if self.ac and track_path == self.ac.path: if 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",
@ -751,8 +722,6 @@ class PlaylistTab(QTableView):
return return
try: try:
if not self.ac:
self.ac = AudacityController()
self.ac.open(path) 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))