Compare commits
3 Commits
28897500c8
...
ecd5c65695
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecd5c65695 | ||
|
|
8c33db170d | ||
|
|
5d5277b028 |
@ -37,11 +37,6 @@ 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
|
||||||
@ -54,6 +49,8 @@ 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).
|
||||||
@ -66,16 +63,14 @@ 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.info(f"{select_status=}")
|
log.debug(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.info(f"{export_status=}")
|
log.debug(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=}")
|
||||||
@ -85,8 +80,6 @@ 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('"', '\\"')
|
||||||
@ -94,15 +87,13 @@ class AudacityController:
|
|||||||
status = self._send_command(cmd)
|
status = self._send_command(cmd)
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
log.info(f"_open_in_audacity {path=}, {status=}")
|
log.debug(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")
|
||||||
@ -131,7 +122,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.info(f"_send_command({command=})")
|
log.debug(f"_send_command({command=})")
|
||||||
|
|
||||||
if self.method == "pipe":
|
if self.method == "pipe":
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -8,6 +8,7 @@ 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,
|
||||||
@ -563,8 +564,9 @@ 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]]
|
session: Session, playlist_id: int, sqla_map: List[dict[str, int]], dummy_for_profiling=None
|
||||||
) -> 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
|
||||||
|
|||||||
@ -44,6 +44,7 @@ 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
|
||||||
@ -1053,7 +1054,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
else:
|
else:
|
||||||
webbrowser.get("browser").open_new_tab(url)
|
webbrowser.get("browser").open_new_tab(url)
|
||||||
|
|
||||||
def paste_rows(self) -> None:
|
@line_profiler.profile
|
||||||
|
def paste_rows(self, dummy_for_profiling=None) -> None:
|
||||||
"""
|
"""
|
||||||
Paste earlier cut rows.
|
Paste earlier cut rows.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -26,6 +26,7 @@ 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
|
||||||
@ -736,7 +737,8 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
self.update_track_times()
|
self.update_track_times()
|
||||||
self.invalidate_rows(row_numbers)
|
self.invalidate_rows(row_numbers)
|
||||||
|
|
||||||
def move_rows(self, from_rows: list[int], to_row_number: int) -> None:
|
@line_profiler.profile
|
||||||
|
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.
|
||||||
"""
|
"""
|
||||||
@ -975,7 +977,8 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# Update display
|
# Update display
|
||||||
self.invalidate_row(track_sequence.previous.row_number)
|
self.invalidate_row(track_sequence.previous.row_number)
|
||||||
|
|
||||||
def refresh_data(self, session: db.session) -> None:
|
@line_profiler.profile
|
||||||
|
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
|
||||||
|
|||||||
@ -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,40 +57,46 @@ 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[QWidget]:
|
) -> Optional[QDoubleSpinBox | QPlainTextEdit]:
|
||||||
"""
|
"""
|
||||||
Intercept createEditor call and make row just a little bit taller
|
Intercept createEditor call and make row just a little bit taller
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.editor: QDoubleSpinBox | QPlainTextEdit
|
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:
|
||||||
self.editor = QDoubleSpinBox(parent)
|
editor = QDoubleSpinBox(parent)
|
||||||
self.editor.setDecimals(1)
|
editor.setDecimals(1)
|
||||||
self.editor.setSingleStep(0.1)
|
editor.setSingleStep(0.1)
|
||||||
return self.editor
|
return editor
|
||||||
elif isinstance(index.data(), str):
|
elif isinstance(index.data(), str):
|
||||||
self.editor = QPlainTextEdit(parent)
|
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 self.editor
|
return 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:
|
||||||
@ -101,31 +107,53 @@ 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 False
|
return super().eventFilter(editor, event)
|
||||||
|
|
||||||
if event.type() == QEvent.Type.KeyPress:
|
if event.type() == QEvent.Type.Show:
|
||||||
|
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(self.editor, QPlainTextEdit):
|
if isinstance(editor, QPlainTextEdit):
|
||||||
|
data_modified = self.original_model_data != editor.toPlainText()
|
||||||
|
elif isinstance(editor, QDoubleSpinBox):
|
||||||
data_modified = (
|
data_modified = (
|
||||||
self.original_model_data == self.editor.toPlainText()
|
self.original_model_data != int(editor.value()) * 1000
|
||||||
)
|
)
|
||||||
elif isinstance(self.editor, QDoubleSpinBox):
|
if not data_modified:
|
||||||
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
|
||||||
|
|
||||||
@ -135,6 +163,7 @@ 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):
|
||||||
@ -153,9 +182,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(self.editor, QPlainTextEdit):
|
if isinstance(editor, QPlainTextEdit):
|
||||||
value = editor.toPlainText().strip()
|
value = editor.toPlainText().strip()
|
||||||
elif isinstance(self.editor, QDoubleSpinBox):
|
elif isinstance(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)
|
||||||
|
|
||||||
@ -434,7 +463,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 track_path == self.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",
|
||||||
@ -722,6 +751,8 @@ 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))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user