diff --git a/main.py b/main.py index a2b1964..91aed85 100755 --- a/main.py +++ b/main.py @@ -4460,8 +4460,41 @@ class MainWindow(QMainWindow): "Extract this exact length of audio from the playhead and save it") self._btn_extract_audio.setEnabled(False) self._btn_extract_audio.clicked.connect(self._on_extract_audio) - transport_row.addWidget(self._spn_audio_len) - transport_row.addWidget(self._btn_extract_audio) + + # Output format (persisted) — drives the extract extension + save filter. + self._cmb_audio_fmt = QComboBox() + for label, ext in (("WAV", ".wav"), ("MP3", ".mp3"), ("FLAC", ".flac"), + ("M4A", ".m4a"), ("OGG", ".ogg"), ("OPUS", ".opus")): + self._cmb_audio_fmt.addItem(label, ext) + saved_fmt = self._settings.value("audio_extract_fmt", ".wav") + idx = self._cmb_audio_fmt.findData(saved_fmt) + self._cmb_audio_fmt.setCurrentIndex(idx if idx >= 0 else 0) + self._cmb_audio_fmt.currentIndexChanged.connect( + lambda _i: self._settings.setValue( + "audio_extract_fmt", self._cmb_audio_fmt.currentData())) + + # Non-destructive edit controls (applied via ffmpeg -af at extract time). + # Edit params are per-extract (intentionally not persisted); only the format is sticky. + self._spn_fade_in = QDoubleSpinBox() + self._spn_fade_in.setRange(0.0, 30.0) + self._spn_fade_in.setDecimals(2) + self._spn_fade_in.setSingleStep(0.1) + self._spn_fade_in.setSuffix(" s") + self._spn_fade_in.setToolTip("Fade-in duration (0 = none)") + self._spn_fade_out = QDoubleSpinBox() + self._spn_fade_out.setRange(0.0, 30.0) + self._spn_fade_out.setDecimals(2) + self._spn_fade_out.setSingleStep(0.1) + self._spn_fade_out.setSuffix(" s") + self._spn_fade_out.setToolTip("Fade-out duration (0 = none)") + self._chk_normalize = QCheckBox("Normalize") + self._chk_normalize.setToolTip("Apply EBU R128 loudness normalization (loudnorm)") + self._spn_gain = QDoubleSpinBox() + self._spn_gain.setRange(-30.0, 30.0) + self._spn_gain.setDecimals(1) + self._spn_gain.setSingleStep(0.5) + self._spn_gain.setSuffix(" dB") + self._spn_gain.setToolTip("Gain applied to the extracted audio (0 = unchanged)") self._transport_row = transport_row # Row 1b — subcategory (subprofile) export buttons live on their own diff --git a/tests/test_ui_structure.py b/tests/test_ui_structure.py index 7a52eff..08689bf 100644 --- a/tests/test_ui_structure.py +++ b/tests/test_ui_structure.py @@ -282,3 +282,16 @@ def test_audio_deck_tab_exists(win): labels = [win._control_deck.tabText(i) for i in range(win._control_deck.count())] assert "Audio" in labels and "Scan" not in labels + + +def test_audio_edit_widgets_exist(win): + from PyQt6.QtWidgets import QComboBox, QCheckBox, QDoubleSpinBox + assert isinstance(win._cmb_audio_fmt, QComboBox) + assert win._cmb_audio_fmt.count() >= 3 # wav/mp3/flac at least + assert isinstance(win._spn_fade_in, QDoubleSpinBox) + assert isinstance(win._spn_fade_out, QDoubleSpinBox) + assert isinstance(win._chk_normalize, QCheckBox) + assert isinstance(win._spn_gain, QDoubleSpinBox) + assert win._cmb_audio_fmt.findData(".wav") >= 0 + assert win._cmb_audio_fmt.findData(".mp3") >= 0 + assert win._cmb_audio_fmt.findData(".flac") >= 0