feat: add background apply_sync with progress bar
This commit is contained in:
1 parent
885560ef5f
commit
068f059cc1
1 file changed
+125
-19
+125
-19
@@ -41,6 +41,79 @@ from sync_audio import load_wav, correlate, device_info, lag_to_seconds
|
|||||||
from waveform_cache import cached_load_wav
|
from waveform_cache import cached_load_wav
|
||||||
|
|
||||||
|
|
||||||
|
class ApplySyncWorker(QtCore.QThread):
|
||||||
|
"""Background thread for apply_sync.py — runs ffmpeg and parses frame progress."""
|
||||||
|
progress = QtCore.pyqtSignal(int, int) # frames_done, frames_total
|
||||||
|
status = QtCore.pyqtSignal(str)
|
||||||
|
finished = QtCore.pyqtSignal(bool) # success
|
||||||
|
|
||||||
|
def __init__(self, lag_sec: float, video_a: str, video_b: str, config: str = "config.toml"):
|
||||||
|
super().__init__()
|
||||||
|
self.lag_sec = lag_sec
|
||||||
|
self.video_a = video_a
|
||||||
|
self.video_b = video_b
|
||||||
|
self.config = config
|
||||||
|
self._abort = False
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
self._abort = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
import subprocess
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
apply_script = os.path.join(script_dir, "apply_sync.py")
|
||||||
|
|
||||||
|
cmd = [sys.executable, apply_script, "--config", self.config,
|
||||||
|
"--a", self.video_a, "--b", self.video_b,
|
||||||
|
"--lag", f"{self.lag_sec:.6f}"]
|
||||||
|
self.status.emit("starting apply_sync...")
|
||||||
|
|
||||||
|
# Probe total frames from file A for progress
|
||||||
|
total_frames = 0
|
||||||
|
try:
|
||||||
|
r = subprocess.run(
|
||||||
|
["ffprobe", "-v", "error", "-count_frames",
|
||||||
|
"-select_streams", "v:0", "-show_entries", "stream=nb_read_frames",
|
||||||
|
"-of", "default=noprint_wrappers=1:nokey=1", self.video_a],
|
||||||
|
capture_output=True, text=True, timeout=15
|
||||||
|
)
|
||||||
|
if r.returncode == 0:
|
||||||
|
total_frames = int(r.stdout.strip())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
cmd, cwd=script_dir,
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
frames_done = 0
|
||||||
|
for line in proc.stderr:
|
||||||
|
if self._abort:
|
||||||
|
proc.kill()
|
||||||
|
self.finished.emit(False)
|
||||||
|
return
|
||||||
|
line = line.strip()
|
||||||
|
# ffmpeg outputs "frame= 123 fps=..." on stderr
|
||||||
|
if line.startswith("frame="):
|
||||||
|
try:
|
||||||
|
parts = line.split()
|
||||||
|
for p in parts:
|
||||||
|
if p.isdigit():
|
||||||
|
frames_done = int(p)
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self.progress.emit(frames_done, total_frames)
|
||||||
|
self.status.emit(f"encoding frame {frames_done}/{total_frames or '?'}")
|
||||||
|
elif "error" in line.lower() or "failed" in line.lower():
|
||||||
|
self.status.emit(line)
|
||||||
|
|
||||||
|
rc = proc.wait()
|
||||||
|
self.finished.emit(rc == 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def load_config(path: str) -> dict:
|
def load_config(path: str) -> dict:
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
@@ -252,8 +325,21 @@ class SyncWindow(QtWidgets.QMainWindow):
|
|||||||
ctr.addWidget(btn_verify)
|
ctr.addWidget(btn_verify)
|
||||||
|
|
||||||
btn_apply = QtWidgets.QPushButton("Apply sync")
|
btn_apply = QtWidgets.QPushButton("Apply sync")
|
||||||
|
self.btn_apply = btn_apply
|
||||||
ctr.addWidget(btn_apply)
|
ctr.addWidget(btn_apply)
|
||||||
|
|
||||||
|
self.apply_progress = QtWidgets.QProgressBar()
|
||||||
|
self.apply_progress.setRange(0, 100)
|
||||||
|
self.apply_progress.setValue(0)
|
||||||
|
self.apply_progress.setMaximumWidth(150)
|
||||||
|
self.apply_progress.setVisible(False)
|
||||||
|
ctr.addWidget(self.apply_progress)
|
||||||
|
|
||||||
|
self.apply_status = QtWidgets.QLabel("")
|
||||||
|
self.apply_status.setStyleSheet("color: #aaa; font-size: 11px;")
|
||||||
|
self.apply_status.setMaximumWidth(300)
|
||||||
|
ctr.addWidget(self.apply_status)
|
||||||
|
|
||||||
# volume + playback
|
# volume + playback
|
||||||
ctr2 = QtWidgets.QHBoxLayout()
|
ctr2 = QtWidgets.QHBoxLayout()
|
||||||
lay.addLayout(ctr2)
|
lay.addLayout(ctr2)
|
||||||
@@ -449,27 +535,47 @@ class SyncWindow(QtWidgets.QMainWindow):
|
|||||||
subprocess.Popen(cmd, cwd=os.path.dirname(os.path.abspath(__file__)))
|
subprocess.Popen(cmd, cwd=os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
def on_apply_sync(self):
|
def on_apply_sync(self):
|
||||||
"""Run apply_sync.py on full files, then launch verify_sync.py in full mode."""
|
"""Run apply_sync.py on a background thread with progress tracking."""
|
||||||
import subprocess
|
self.btn_apply.setEnabled(False)
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
self.apply_progress.setVisible(True)
|
||||||
apply_script = os.path.join(script_dir, "apply_sync.py")
|
self.apply_progress.setRange(0, 0) # indeterminate until we get frame count
|
||||||
verify_script = os.path.join(script_dir, "verify_sync.py")
|
self.apply_progress.setValue(0)
|
||||||
|
self.apply_status.setText("starting...")
|
||||||
|
|
||||||
# Run apply_sync
|
self._apply_worker = ApplySyncWorker(
|
||||||
cmd = [sys.executable, apply_script, "--config", "config.toml",
|
self.lag_sec, self.video_a, self.video_b
|
||||||
"--lag", f"{self.lag_sec:.6f}"]
|
)
|
||||||
print(f"Running: {' '.join(cmd)}")
|
self._apply_worker.progress.connect(self._on_apply_progress)
|
||||||
r = subprocess.run(cmd, cwd=script_dir)
|
self._apply_worker.status.connect(lambda s: self.apply_status.setText(s))
|
||||||
if r.returncode != 0:
|
self._apply_worker.finished.connect(self._on_apply_done)
|
||||||
print(f"apply_sync failed with code {r.returncode}", file=sys.stderr)
|
self._apply_worker.start()
|
||||||
return
|
|
||||||
|
|
||||||
# Launch verify in full mode
|
def _on_apply_progress(self, frames_done: int, frames_total: int):
|
||||||
cmd2 = [sys.executable, verify_script, "--config", "config.toml",
|
if frames_total > 0:
|
||||||
"--a", self.video_a, "--b", self.video_b,
|
self.apply_progress.setRange(0, frames_total)
|
||||||
"--lag", f"{self.lag_sec:.6f}", "--mode", "full"]
|
self.apply_progress.setValue(frames_done)
|
||||||
print(f"Launching: {' '.join(cmd2)}")
|
pct = frames_done * 100 // frames_total
|
||||||
subprocess.Popen(cmd2, cwd=script_dir)
|
self.apply_status.setText(f"encoding frame {frames_done}/{frames_total} ({pct}%)")
|
||||||
|
else:
|
||||||
|
self.apply_progress.setRange(0, 0) # indeterminate
|
||||||
|
self.apply_status.setText(f"encoding frame {frames_done}...")
|
||||||
|
|
||||||
|
def _on_apply_done(self, success: bool):
|
||||||
|
self.btn_apply.setEnabled(True)
|
||||||
|
self.apply_progress.setVisible(False)
|
||||||
|
if success:
|
||||||
|
self.apply_status.setText("done")
|
||||||
|
# Launch verify in full mode
|
||||||
|
import subprocess
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
verify_script = os.path.join(script_dir, "verify_sync.py")
|
||||||
|
cmd = [sys.executable, verify_script, "--config", "config.toml",
|
||||||
|
"--a", self.video_a, "--b", self.video_b,
|
||||||
|
"--lag", f"{self.lag_sec:.6f}", "--mode", "full"]
|
||||||
|
print(f"Launching: {' '.join(cmd)}")
|
||||||
|
subprocess.Popen(cmd, cwd=script_dir)
|
||||||
|
else:
|
||||||
|
self.apply_status.setText("apply_sync failed")
|
||||||
|
|
||||||
def play(self, which: str):
|
def play(self, which: str):
|
||||||
if not HAS_SD:
|
if not HAS_SD:
|
||||||
|
|||||||
Reference in new issue
Block a user