diff --git a/main.py b/main.py index e77708b..6b61b3a 100755 --- a/main.py +++ b/main.py @@ -4538,6 +4538,43 @@ class MainWindow(QMainWindow): "Audition the current audio area (with the edits applied)") self._btn_audio_play.toggled.connect(self._on_audio_audition) self._audition_proc = None # QProcess while auditioning, else None + + # ── Merge (crossfade) pane widgets ─────────────────────────── + self._merge_list = QListWidget() + self._merge_list.setSelectionMode( + QAbstractItemView.SelectionMode.SingleSelection) + self._merge_list.setToolTip("Clips to merge, top to bottom") + self._spn_crossfade = QDoubleSpinBox() + self._spn_crossfade.setRange(0.0, 10.0) + self._spn_crossfade.setDecimals(2) + self._spn_crossfade.setSingleStep(0.1) + self._spn_crossfade.setSuffix(" s") + self._spn_crossfade.setToolTip("Crossfade applied at every join (0 = butt-join)") + self._spn_crossfade.setValue(float(self._settings.value("audio_crossfade", 0.5))) + self._spn_crossfade.valueChanged.connect( + lambda v: self._settings.setValue("audio_crossfade", v)) + self._btn_merge_add_sel = QPushButton("+ Selection") + self._btn_merge_add_sel.setToolTip( + "Add the current audio area (with edits) as a clip") + self._btn_merge_add_sel.clicked.connect(self._on_merge_add_selection) + self._btn_merge_add_file = QPushButton("+ File…") + self._btn_merge_add_file.setToolTip("Add audio file(s) as clips") + self._btn_merge_add_file.clicked.connect(self._on_merge_add_file) + self._btn_merge_up = QPushButton("▲") + self._btn_merge_up.setToolTip("Move selected clip up") + self._btn_merge_up.clicked.connect(lambda: self._merge_move(-1)) + self._btn_merge_down = QPushButton("▼") + self._btn_merge_down.setToolTip("Move selected clip down") + self._btn_merge_down.clicked.connect(lambda: self._merge_move(1)) + self._btn_merge_remove = QPushButton("✕") + self._btn_merge_remove.setToolTip("Remove selected clip") + self._btn_merge_remove.clicked.connect(self._merge_remove_selected) + self._btn_merge_preview = QPushButton("▶ Preview") + self._btn_merge_preview.setToolTip("Merge to a temp file and audition") + self._btn_merge_preview.clicked.connect(self._on_merge_preview) + self._btn_merge_save = QPushButton("Merge && Save…") + self._btn_merge_save.setToolTip("Merge the clips and save the result") + self._btn_merge_save.clicked.connect(self._on_merge_save) self._transport_row = transport_row # Row 1b — subcategory (subprofile) export buttons live on their own @@ -4864,6 +4901,27 @@ class MainWindow(QMainWindow): sg.setColumnStretch(3, 1) tb.addItem(scan_page, "Scan / Classify") + # ── Page 3: Merge (crossfade) ──────────────────────────── + merge_page = QWidget() + mv = QVBoxLayout(merge_page) + mv.setContentsMargins(8, 6, 8, 6); mv.setSpacing(6) + mv.addWidget(self._merge_list) + xrow = QHBoxLayout() + xrow.addWidget(QLabel("Crossfade:")) + xrow.addWidget(self._spn_crossfade) + xrow.addStretch() + mv.addLayout(xrow) + brow = QHBoxLayout() + for b in (self._btn_merge_add_sel, self._btn_merge_add_file, + self._btn_merge_up, self._btn_merge_down, self._btn_merge_remove): + brow.addWidget(b) + mv.addLayout(brow) + arow = QHBoxLayout() + arow.addWidget(self._btn_merge_preview) + arow.addWidget(self._btn_merge_save) + mv.addLayout(arow) + tb.addItem(merge_page, "Merge") + outer.addWidget(tb) # ── Menu bar ───────────────────────────────────────────── @@ -6521,6 +6579,24 @@ class MainWindow(QMainWindow): start = self._cursor self._timeline.set_audio_region(start, start + self._spn_audio_len.value()) + def _on_merge_add_selection(self) -> None: + pass # implemented in M3 + + def _on_merge_add_file(self) -> None: + pass # implemented in M3 + + def _merge_move(self, delta: int) -> None: + pass # implemented in M3 + + def _merge_remove_selected(self) -> None: + pass # implemented in M3 + + def _on_merge_preview(self) -> None: + pass # implemented in M4 + + def _on_merge_save(self) -> None: + pass # implemented in M4 + def _on_wave_refresh(self) -> None: """Decode the current audio area and repaint the waveform strip.""" if not self._file_path: diff --git a/tests/test_ui_structure.py b/tests/test_ui_structure.py index d901484..a1ba0d8 100644 --- a/tests/test_ui_structure.py +++ b/tests/test_ui_structure.py @@ -310,6 +310,16 @@ def test_audio_toolbox_has_two_panes(win): assert win._btn_scan in tb.findChildren(QPushButton) +def test_merge_pane_present(win): + from PyQt6.QtWidgets import QToolBox, QListWidget, QDoubleSpinBox + tb = win._tab_audio.findChild(QToolBox) + titles = [tb.itemText(i) for i in range(tb.count())] + assert "Merge" in titles + assert isinstance(win._merge_list, QListWidget) + assert isinstance(win._spn_crossfade, QDoubleSpinBox) + assert win._spn_crossfade.value() == 0.5 + + def test_extract_honors_format_and_edits(win, monkeypatch, tmp_path): import main as m import pytest