feat: '+ To library' — add the current audio area straight to the clip library

This commit is contained in:
2026-07-04 01:11:05 +02:00
parent 0e82a82cde
commit 158483ce42
2 changed files with 80 additions and 1 deletions
+48 -1
View File
@@ -5170,6 +5170,10 @@ class MainWindow(QMainWindow):
self._btn_edit_clip.setToolTip( self._btn_edit_clip.setToolTip(
"Open the current audio area in the destructive editor") "Open the current audio area in the destructive editor")
self._btn_edit_clip.clicked.connect(self._on_edit_clip) self._btn_edit_clip.clicked.connect(self._on_edit_clip)
self._btn_add_library = QPushButton(" To library")
self._btn_add_library.setToolTip(
"Add the current audio area (with edits) to the clip library")
self._btn_add_library.clicked.connect(self._on_add_selection_to_library)
self._wave.selection_changed.connect(self._on_wave_selection_changed) self._wave.selection_changed.connect(self._on_wave_selection_changed)
self._wave.view_changed.connect(self._on_wave_view_changed) self._wave.view_changed.connect(self._on_wave_view_changed)
self._wave_pending_view = None self._wave_pending_view = None
@@ -5546,7 +5550,8 @@ class MainWindow(QMainWindow):
g.addWidget(self._wave, 4, 0, 1, 4) g.addWidget(self._wave, 4, 0, 1, 4)
g.addWidget(self._btn_wave_refresh, 5, 0, 1, 2) g.addWidget(self._btn_wave_refresh, 5, 0, 1, 2)
g.addWidget(self._btn_audio_play, 5, 2, 1, 2) g.addWidget(self._btn_audio_play, 5, 2, 1, 2)
g.addWidget(self._btn_edit_clip, 6, 0, 1, 4) # own row: destructive editor entry g.addWidget(self._btn_edit_clip, 6, 0, 1, 2) # destructive editor entry
g.addWidget(self._btn_add_library, 6, 2, 1, 2) # straight into the clip library
g.addWidget(self._btn_extract_audio, 7, 0, 1, 4) g.addWidget(self._btn_extract_audio, 7, 0, 1, 4)
g.setColumnStretch(4, 1) # spare gutter column clusters label/field pairs to the left g.setColumnStretch(4, 1) # spare gutter column clusters label/field pairs to the left
g.setRowStretch(8, 1) # anchor content to the top g.setRowStretch(8, 1) # anchor content to the top
@@ -7388,6 +7393,48 @@ class MainWindow(QMainWindow):
self._merge_temps.add(tmp) # reuse the close-time temp sweep self._merge_temps.add(tmp) # reuse the close-time temp sweep
self._open_audio_editor(tmp) self._open_audio_editor(tmp)
def _on_add_selection_to_library(self) -> None:
"""Render the current audio area (with edits) into a managed library
folder and add it to the clip library no save dialog."""
if not self._file_path:
self._show_status("Load a video first", 3000)
return
start = self._cursor
dur = self._spn_audio_len.value()
base = (self._settings.value("audio_extract_dir", "")
or self._tab_export_folder()
or os.path.dirname(self._file_path))
lib_dir = (self._settings.value("audio_library_dir", "")
or os.path.join(base, "audio_library"))
self._settings.setValue("audio_library_dir", lib_dir)
try:
os.makedirs(lib_dir, exist_ok=True)
except OSError:
self._show_status("Could not create the library folder", 4000)
return
stem = os.path.splitext(os.path.basename(self._file_path))[0]
out = os.path.join(lib_dir, f"{stem}_{start:.2f}-{start + dur:.2f}s.wav")
cmd = build_audio_clip_command(self._file_path, start, dur, out,
filters=self._current_edit_filters() or None)
self._show_status("Adding to library…")
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
try:
proc = subprocess.run(cmd, capture_output=True, timeout=120)
except Exception:
proc = None
finally:
QApplication.restoreOverrideCursor()
if proc is not None and proc.returncode == 0 and os.path.exists(out):
self._scan_panel._library.add_clip(out)
self._show_status(f"Added to library: {os.path.basename(out)}", 4000)
else:
try:
if os.path.exists(out):
os.remove(out)
except OSError:
pass
self._show_status("Could not add the clip to the library", 3000)
def _open_audio_editor(self, path: str) -> None: def _open_audio_editor(self, path: str) -> None:
"""Open the destructive editor on *path*; if the user Saves-as, add the """Open the destructive editor on *path*; if the user Saves-as, add the
result to the clip library.""" result to the clip library."""
+32
View File
@@ -448,6 +448,38 @@ def test_extract_no_edits_passes_no_filters(win, monkeypatch, tmp_path):
assert seen["filters"] is None assert seen["filters"] is None
def test_add_selection_to_library(win, tmp_path, monkeypatch):
import main as m
win._file_path = "/vids/Chloe.mp4"
win._cursor = 7.0
win._spn_audio_len.setValue(3.0)
win._settings.setValue("audio_extract_dir", str(tmp_path))
win._settings.setValue("audio_library_dir", "") # force derive
seen = {}
def fake_cmd(inp, start, dur, out, filters=None):
seen["out"] = out; seen["start"] = start; seen["dur"] = dur; return ["true"]
monkeypatch.setattr(m, "build_audio_clip_command", fake_cmd)
monkeypatch.setattr(m.subprocess, "run",
lambda *a, **k: type("P", (), {"returncode": 0})())
monkeypatch.setattr(m.os.path, "exists", lambda p: True)
monkeypatch.setattr(m, "probe_duration", lambda p: 3.0)
lib = win._scan_panel._library
lib._key = "audio_library_addsel_test"
win._settings.setValue(lib._key, [])
lib._list.clear()
win._on_add_selection_to_library()
# rendered the current area with the managed-folder auto-name
assert seen["start"] == 7.0 and seen["dur"] == 3.0
assert os.path.join("audio_library", "Chloe_7.00-10.00s.wav") in seen["out"]
# and it was added to the library
assert seen["out"] in lib.clips()
def test_add_selection_to_library_no_file_safe(win):
win._file_path = ""
win._on_add_selection_to_library() # must not raise
def test_waveform_strip_present_and_safe(win): def test_waveform_strip_present_and_safe(win):
from PyQt6.QtWidgets import QPushButton from PyQt6.QtWidgets import QPushButton
assert win._wave is not None assert win._wave is not None