fix: apply_sync ffmpeg streamcpy and filter
This commit is contained in:
1 parent
06dd13d907
commit
4cf7227a6c
2 files changed
+51
-7
No files matched your search
+25
-3
@@ -57,6 +57,23 @@ def probe_audio_streams(path: str) -> list[dict]:
|
||||
return []
|
||||
|
||||
|
||||
def _codec_args(codec: str, out_idx: int) -> list[str]:
|
||||
"""Return ffmpeg codec args for a given audio codec string."""
|
||||
lossless = {"flac", "alac", "pcm_s16le", "pcm_s24le", "pcm_s32le", "pcm_f32le", "pcm_f64le"}
|
||||
if codec in lossless:
|
||||
return [f"-c:a:{out_idx}", codec]
|
||||
# lossy codecs: use original where possible
|
||||
codec_map = {
|
||||
"aac": "aac", "mp3": "libmp3lame", "opus": "libopus",
|
||||
"vorbis": "libvorbis", "ac3": "ac3", "eac3": "eac3",
|
||||
}
|
||||
enc = codec_map.get(codec, "aac")
|
||||
args = [f"-c:a:{out_idx}", enc]
|
||||
if enc != codec or codec not in lossless:
|
||||
args += [f"-b:a:{out_idx}", "192k"]
|
||||
return args
|
||||
|
||||
|
||||
def build_ffmpeg_cmd(file_a: str, file_b: str, lag_sec: float, out_path: str) -> list[str]:
|
||||
"""Build ffmpeg command to mux A video + A audio + delayed B audio streams."""
|
||||
b_streams = probe_audio_streams(file_b)
|
||||
@@ -67,6 +84,7 @@ def build_ffmpeg_cmd(file_a: str, file_b: str, lag_sec: float, out_path: str) ->
|
||||
set_german = (len(b_streams) == 1 and b_streams[0]["language"] in ("und", "unknown", ""))
|
||||
|
||||
# Build filter_complex for B audio streams
|
||||
# Negative adelay trims the start (B ahead of A), positive adds silence (B behind A)
|
||||
filters = []
|
||||
for i, s in enumerate(b_streams):
|
||||
filters.append(f"[1:a:{i}]adelay={lag_ms:.0f}|{lag_ms:.0f}:all=1[d{i}]")
|
||||
@@ -81,11 +99,15 @@ def build_ffmpeg_cmd(file_a: str, file_b: str, lag_sec: float, out_path: str) ->
|
||||
for i in range(len(b_streams)):
|
||||
cmd += ["-map", f"[d{i}]"]
|
||||
|
||||
# Codecs: copy everything (lossless)
|
||||
cmd += ["-c:v", "copy", "-c:a", "copy"]
|
||||
# Codecs: copy video + A's audio, re-encode B's filtered audio with same codec
|
||||
n_a_audio = len(probe_audio_streams(file_a))
|
||||
cmd += ["-c:v", "copy"]
|
||||
for i in range(n_a_audio):
|
||||
cmd += [f"-c:a:{i}", "copy"]
|
||||
for i, s in enumerate(b_streams):
|
||||
cmd += _codec_args(s["codec"], n_a_audio + i)
|
||||
|
||||
# Language metadata for B audio streams (they become the last N audio streams in output)
|
||||
n_a_audio = len(probe_audio_streams(file_a))
|
||||
for i, s in enumerate(b_streams):
|
||||
out_audio_idx = n_a_audio + i # 0-based index in output audio streams
|
||||
if set_german and i == 0:
|
||||
|
||||
+26
-4
@@ -111,7 +111,8 @@ def find_wavs_from_config(cfg: dict) -> tuple[str, str, int, float, float, int]:
|
||||
|
||||
class SyncWindow(QtWidgets.QMainWindow):
|
||||
def __init__(self, a_path: str, b_path: str, sr: int = 48000, method: str = "phat", device: str = "auto",
|
||||
video_a: str = "", video_b: str = "", out_dir: str = "output", volume: int = 30):
|
||||
video_a: str = "", video_b: str = "", out_dir: str = "output", volume: int = 30,
|
||||
duration: float = 90.0):
|
||||
super().__init__()
|
||||
self.setWindowTitle(f"movie_sync — audio sync test — {device_info()}")
|
||||
self.resize(1400, 900)
|
||||
@@ -121,6 +122,7 @@ class SyncWindow(QtWidgets.QMainWindow):
|
||||
self.video_b = video_b
|
||||
self.out_dir = out_dir
|
||||
self.sr = sr
|
||||
self.duration = duration
|
||||
self.method = method
|
||||
self.device_pref = device
|
||||
self._updating = False
|
||||
@@ -193,6 +195,10 @@ class SyncWindow(QtWidgets.QMainWindow):
|
||||
self.p3.addItem(self.peak_line)
|
||||
self.p3.setXRange(self.lag_sec-2, self.lag_sec+2)
|
||||
|
||||
self.xcorr_val_label = QtWidgets.QLabel()
|
||||
self.xcorr_val_label.setStyleSheet("color: #ffd166; font-size: 12px;")
|
||||
lay.addWidget(self.xcorr_val_label)
|
||||
|
||||
# controls
|
||||
ctr = QtWidgets.QHBoxLayout()
|
||||
lay.addLayout(ctr)
|
||||
@@ -232,7 +238,7 @@ class SyncWindow(QtWidgets.QMainWindow):
|
||||
btn_export = QtWidgets.QPushButton("Export data")
|
||||
ctr.addWidget(btn_export)
|
||||
|
||||
btn_verify = QtWidgets.QPushButton("Verify (30s)")
|
||||
btn_verify = QtWidgets.QPushButton(f"Verify ({duration:g}s)")
|
||||
ctr.addWidget(btn_verify)
|
||||
|
||||
btn_apply = QtWidgets.QPushButton("Apply sync")
|
||||
@@ -294,6 +300,7 @@ class SyncWindow(QtWidgets.QMainWindow):
|
||||
self.btn_stop.clicked.connect(self.stop_play)
|
||||
|
||||
self.update_labels()
|
||||
self._update_xcorr_label()
|
||||
|
||||
def _set_lag(self, lag_sec: float, source: str = ""):
|
||||
if self._updating:
|
||||
@@ -314,9 +321,23 @@ class SyncWindow(QtWidgets.QMainWindow):
|
||||
# pyqtgraph setPos shifts items; we recreate x
|
||||
# Instead shift data: plot t_b + lag
|
||||
self.curve_b.setData(self.t_b + shift, self.b_plot)
|
||||
self._update_xcorr_label()
|
||||
self.update_labels()
|
||||
self._updating = False
|
||||
|
||||
def _update_xcorr_label(self):
|
||||
N = len(self.corr)
|
||||
idx = N // 2 + self.lag_samples
|
||||
if 0 <= idx < N:
|
||||
val = np.abs(self.corr[idx])
|
||||
peak_val = np.abs(self.peak)
|
||||
pct = (val / peak_val * 100) if peak_val > 0 else 0
|
||||
self.xcorr_val_label.setText(
|
||||
f"Xcorr at lag: {val:.6f} (peak: {peak_val:.6f} = {pct:.1f}%)"
|
||||
)
|
||||
else:
|
||||
self.xcorr_val_label.setText("Xcorr at lag: (out of range)")
|
||||
|
||||
def update_labels(self):
|
||||
lag_ms = self.lag_sec * 1000
|
||||
stem = os.path.splitext(os.path.basename(self.video_a))[0]
|
||||
@@ -498,7 +519,7 @@ def main():
|
||||
args = ap.parse_args()
|
||||
|
||||
cfg = load_config(args.config)
|
||||
cfg_a, cfg_b, sr, _, _, volume = find_wavs_from_config(cfg)
|
||||
cfg_a, cfg_b, sr, _, duration, volume = find_wavs_from_config(cfg)
|
||||
a_path = args.a or cfg_a
|
||||
b_path = args.b or cfg_b
|
||||
method = args.method or cfg.get("sync", {}).get("method", "phat")
|
||||
@@ -546,7 +567,8 @@ def main():
|
||||
app.setPalette(dark)
|
||||
pg.setConfigOptions(antialias=True, background="#1e1e1e", foreground="#ddd")
|
||||
w = SyncWindow(a_path, b_path, sr=sr, method=method, device=device,
|
||||
video_a=video_a, video_b=video_b, out_dir=out_dir, volume=volume)
|
||||
video_a=video_a, video_b=video_b, out_dir=out_dir, volume=volume,
|
||||
duration=duration)
|
||||
w.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
Reference in new issue
Block a user