From e61c4a449d55dbfc796a1c8670c7beab6edf7c0c Mon Sep 17 00:00:00 2001 From: Honney OC Date: Tue, 15 Sep 2026 13:06:14 +0000 Subject: [PATCH] feat: add independent volume controls and refine audio filters --- extract.py | 8 ++++---- verify_sync.py | 42 ++++++++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/extract.py b/extract.py index e5f239e..165a671 100644 --- a/extract.py +++ b/extract.py @@ -323,7 +323,7 @@ def extract_audio_loudnorm(input_file: str, duration: int, start_sec: float, map_args = ["-map", f"0:a:{stream_idx}"] if stream_idx is not None else [] base = ["ffmpeg", "-y", *ss_args, "-i", input_file, "-t", str(duration), "-vn", *map_args] measure_af = f"loudnorm=I={TARGET_LUFS}:TP={TARGET_TP}:LRA={TARGET_LRA}:print_format=json" - gate_filter = ",lowpass=f=3000,agate=threshold=0.02:ratio=20:attack=5:release=50" if loud_gate else "" + gate_filter = ",lowpass=f=2000,highpass=f=100,agate=threshold=0.02:ratio=20:attack=5:release=50" if loud_gate else "" # ─── Pass 1: measure ─── print(f"[loudness] {stem}: pass 1/2 measuring…") @@ -332,7 +332,7 @@ def extract_audio_loudnorm(input_file: str, duration: int, start_sec: float, if r.returncode != 0: print(f"[loudness] {stem}: pass 1 failed ({r.stderr.strip()[-80:]}), falling back to simple extract", file=sys.stderr) if loud_gate: - cmd_fallback = base + ["-af", "lowpass=f=3000,agate=threshold=0.02:ratio=20:attack=5:release=50", *AUDIO_CODEC, out_path] + cmd_fallback = base + ["-af", "lowpass=f=2000,highpass=f=100,agate=threshold=0.02:ratio=20:attack=5:release=50", *AUDIO_CODEC, out_path] else: cmd_fallback = base + [*AUDIO_CODEC, out_path] r2 = subprocess.run(cmd_fallback, capture_output=True, text=True) @@ -344,7 +344,7 @@ def extract_audio_loudnorm(input_file: str, duration: int, start_sec: float, if measured is None: print(f"[loudness] {stem}: could not parse measurement, falling back to simple extract", file=sys.stderr) if loud_gate: - cmd_fallback = base + ["-af", "lowpass=f=3000,agate=threshold=0.02:ratio=20:attack=5:release=50", *AUDIO_CODEC, out_path] + cmd_fallback = base + ["-af", "lowpass=f=2000,highpass=f=100,agate=threshold=0.02:ratio=20:attack=5:release=50", *AUDIO_CODEC, out_path] else: cmd_fallback = base + [*AUDIO_CODEC, out_path] r2 = subprocess.run(cmd_fallback, capture_output=True, text=True) @@ -495,7 +495,7 @@ for _file_idx, INPUT_FILE in enumerate(INPUT_FILES): cmd_audio = [ "ffmpeg", "-y", *ss_args, "-i", INPUT_FILE, "-t", str(DURATION), "-vn", *map_args, "-af", - "lowpass=f=3000,agate=threshold=0.02:ratio=20:attack=5:release=50", + "lowpass=f=2000,highpass=f=100,agate=threshold=0.02:ratio=20:attack=5:release=50", *AUDIO_CODEC, audio_out ] else: diff --git a/verify_sync.py b/verify_sync.py index 1a1625b..badf326 100644 --- a/verify_sync.py +++ b/verify_sync.py @@ -428,16 +428,29 @@ class VerifyWindow(QtWidgets.QMainWindow): self.play_mode.setFixedWidth(90) ctrl.addWidget(self.play_mode) - ctrl.addWidget(QtWidgets.QLabel("Vol:")) - self.vol_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal) - self.vol_slider.setRange(0, 100) - self.vol_slider.setValue(self.default_volume) - self.vol_slider.setMaximumWidth(100) - ctrl.addWidget(self.vol_slider) - self.vol_label = QtWidgets.QLabel(f"{self.default_volume}%") - self.vol_label.setMinimumWidth(35) - ctrl.addWidget(self.vol_label) - self.vol_slider.valueChanged.connect(lambda v: self.vol_label.setText(f"{v}%")) + ctrl.addSpacing(10) + + ctrl.addWidget(QtWidgets.QLabel("Vol A:")) + self.vol_a_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal) + self.vol_a_slider.setRange(0, 200) + self.vol_a_slider.setValue(self.default_volume) + self.vol_a_slider.setMaximumWidth(100) + ctrl.addWidget(self.vol_a_slider) + self.vol_a_label = QtWidgets.QLabel(f"{self.default_volume}%") + self.vol_a_label.setMinimumWidth(35) + ctrl.addWidget(self.vol_a_label) + self.vol_a_slider.valueChanged.connect(lambda v: self.vol_a_label.setText(f"{v}%")) + + ctrl.addWidget(QtWidgets.QLabel("Vol B:")) + self.vol_b_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal) + self.vol_b_slider.setRange(0, 200) + self.vol_b_slider.setValue(self.default_volume) + self.vol_b_slider.setMaximumWidth(100) + ctrl.addWidget(self.vol_b_slider) + self.vol_b_label = QtWidgets.QLabel(f"{self.default_volume}%") + self.vol_b_label.setMinimumWidth(35) + ctrl.addWidget(self.vol_b_label) + self.vol_b_slider.valueChanged.connect(lambda v: self.vol_b_label.setText(f"{v}%")) ctrl.addSpacing(10) @@ -578,16 +591,17 @@ class VerifyWindow(QtWidgets.QMainWindow): a_seg = self.a_aligned[start_samp:] b_seg = self.b_aligned[start_samp:] - vol = self.vol_slider.value() / 100.0 + vol_a = self.vol_a_slider.value() / 100.0 + vol_b = self.vol_b_slider.value() / 100.0 mode = self.play_mode.currentIndex() if mode == 0: # A+B mix ml = min(len(a_seg), len(b_seg)) - mix = ((a_seg[:ml] + b_seg[:ml]) / 2.0) * vol + mix = (a_seg[:ml] * vol_a + b_seg[:ml] * vol_b) / 2.0 elif mode == 1: # A only - mix = a_seg * vol + mix = a_seg * vol_a else: # B only - mix = b_seg * vol + mix = b_seg * vol_b self.audio_player.play(mix, self.sr) self.playing_a_b = True