From 5fe90af2461beaf70a8b779444a632c472bc2705 Mon Sep 17 00:00:00 2001 From: Honney OC Date: Sun, 13 Sep 2026 23:23:41 +0000 Subject: [PATCH] fix: pre-transcode video to H.264 and try VAAPI encoder --- Makefile | 4 +-- verify_sync.py | 94 ++++++++++++++++++++++---------------------------- 2 files changed, 44 insertions(+), 54 deletions(-) diff --git a/Makefile b/Makefile index a60baca..967df04 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ sync: @if [ -x $(PY) ]; then $(PY) apply_sync.py --config $(CONFIG) --lag $(LAG); else python3 apply_sync.py --config $(CONFIG) --lag $(LAG); fi verify: - @if [ -x $(PY) ]; then QT_QPA_PLATFORM=xcb QT_FFMPEG_DECODING_HW_DEVICE_TYPES=vaapi QT_XCB_GL_INTEGRATION=xcb_egl QT_STYLE_OVERRIDE=Fusion $(PY) verify_sync.py --config $(CONFIG) --lag $(LAG); else QT_QPA_PLATFORM=xcb QT_FFMPEG_DECODING_HW_DEVICE_TYPES=vaapi QT_XCB_GL_INTEGRATION=xcb_egl QT_STYLE_OVERRIDE=Fusion python3 verify_sync.py --config $(CONFIG) --lag $(LAG); fi + @if [ -x $(PY) ]; then QT_STYLE_OVERRIDE=Fusion $(PY) verify_sync.py --config $(CONFIG) --lag $(LAG); else QT_STYLE_OVERRIDE=Fusion python3 verify_sync.py --config $(CONFIG) --lag $(LAG); fi ast: @AUDIO_A=$$(python3 -c "import tomllib; c=tomllib.load(open('config.toml','rb')); print(c['extract'].get('file_a',''))" 2>/dev/null); \ @@ -100,7 +100,7 @@ ast: else \ echo "=> extracted wavs exist ($$OUT/$${STEM_A}_audio.wav, $$OUT/$${STEM_B}_audio.wav), skipping extract"; \ fi - @if [ -x $(PY) ]; then QT_QPA_PLATFORM=xcb QT_FFMPEG_DECODING_HW_DEVICE_TYPES=vaapi QT_XCB_GL_INTEGRATION=xcb_egl QT_STYLE_OVERRIDE=Fusion $(PY) sync_audio_test.py --config $(CONFIG); else QT_QPA_PLATFORM=xcb QT_FFMPEG_DECODING_HW_DEVICE_TYPES=vaapi QT_XCB_GL_INTEGRATION=xcb_egl QT_STYLE_OVERRIDE=Fusion python3 sync_audio_test.py --config $(CONFIG); fi + @if [ -x $(PY) ]; then QT_STYLE_OVERRIDE=Fusion $(PY) sync_audio_test.py --config $(CONFIG); else QT_STYLE_OVERRIDE=Fusion python3 sync_audio_test.py --config $(CONFIG); fi clean: rm -rf $(VENV) __pycache__ .pytest_cache diff --git a/verify_sync.py b/verify_sync.py index e76344a..eaf1bbf 100644 --- a/verify_sync.py +++ b/verify_sync.py @@ -27,13 +27,6 @@ import numpy as np warnings.filterwarnings("ignore", category=RuntimeWarning, module="pyqtgraph") -# Force X11/xcb on Wayland without EGL — QVideoWidget can't render without it -for k, v in {"QT_QPA_PLATFORM": "xcb", - "QT_STYLE_OVERRIDE": "Fusion", - "QT_FFMPEG_DECODING_HW_DEVICE_TYPES": "vaapi", - "QT_XCB_GL_INTEGRATION": "xcb_egl"}.items(): - os.environ.setdefault(k, v) - try: import tomllib except ModuleNotFoundError: @@ -117,24 +110,33 @@ def extract_audio_from_mkv(mkv_path: str, stream_index: int, out_wav: str, sr: i def transcode_to_h264_sdr(src: str, dst: str, duration: float = 0) -> bool: """Transcode video to H.264 SDR MP4 for QMediaPlayer compatibility. - If duration > 0, only transcode that many seconds.""" - cmd = ["ffmpeg", "-y", "-i", src] - if duration > 0: - cmd += ["-t", f"{duration:.3f}"] - cmd += [ - "-map", "0:v:0", - "-an", - "-c:v", "libx264", "-preset", "ultrafast", "-crf", "20", - "-pix_fmt", "yuv420p", - "-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2", - "-movflags", "+faststart", - dst, - ] - try: - r = subprocess.run(cmd, capture_output=True, timeout=600) - return r.returncode == 0 and os.path.exists(dst) - except Exception: - return False + Tries VAAPI hardware encoding first, falls back to CPU x264.""" + for encoder in ["h264_vaapi", "libx264"]: + cmd = ["ffmpeg", "-y", "-i", src] + if duration > 0: + cmd += ["-t", f"{duration:.3f}"] + cmd += ["-map", "0:v:0", "-an"] + if encoder == "h264_vaapi": + cmd += ["-vaapi_device", "/dev/dri/renderD128", + "-vf", "format=nv12,hwupload", + "-c:v", "h264_vaapi", "-qp", "20"] + else: + cmd += ["-c:v", "libx264", "-preset", "ultrafast", "-crf", "20", + "-pix_fmt", "yuv420p", + "-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2"] + cmd += ["-movflags", "+faststart", dst] + try: + r = subprocess.run(cmd, capture_output=True, timeout=600) + if r.returncode == 0 and os.path.exists(dst): + print(f" encoded with {encoder}") + return True + print(f" {encoder} failed, trying next...", file=sys.stderr) + if os.path.exists(dst): + os.remove(dst) + except Exception: + if os.path.exists(dst): + os.remove(dst) + return False def apply_lag(a: np.ndarray, b: np.ndarray, lag_samples: int) -> tuple[np.ndarray, np.ndarray]: @@ -406,9 +408,6 @@ class VerifyWindow(QtWidgets.QMainWindow): # Wait for media loaded to get actual duration self.media_player.durationChanged.connect(self._on_duration_changed) - # Handle video decode errors — transcode and retry - self.media_player.errorOccurred.connect(self._on_media_error) - def _on_duration_changed(self, dur_ms): """QMediaPlayer reports actual video duration in ms.""" vid_dur = dur_ms / 1000.0 @@ -427,30 +426,6 @@ class VerifyWindow(QtWidgets.QMainWindow): self.btn_play.setText("Play") self.audio_player.stop() - def _on_media_error(self, error, message): - """Handle video decode failure — transcode to H.264 and retry.""" - if getattr(self, "_transcode_attempted", False): - print(f"Video error even after transcode: {message}", file=sys.stderr) - return - print(f"Video decode error ({error.name}): {message}", file=sys.stderr) - print("Attempting fallback: transcoding to H.264 SDR...", file=sys.stderr) - self._transcode_attempted = True - - if self.temp_dir is None: - self.temp_dir = tempfile.mkdtemp(prefix="verify_sync_") - preview_path = os.path.join(self.temp_dir, "preview.mp4") - transcode_dur = 30.0 if self.mode == "short" else 0 - - if not transcode_to_h264_sdr(self.video_path, preview_path, transcode_dur): - print("Transcode also failed — video will remain black", file=sys.stderr) - return - - # Reload with the transcoded file - self.video_path = preview_path - from PyQt6.QtCore import QUrl - self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(preview_path))) - print(f"Retrying with transcoded preview: {preview_path}") - def _draw_waveforms(self): max_pts = 200_000 if self.mode == "full" else 80_000 @@ -688,6 +663,21 @@ def main(): print(f"WARNING: extracted video not found ({extracted_video}), using original", file=sys.stderr) temp_dir = None + # Always transcode to H.264 SDR — QMediaPlayer can't reliably decode + # HEVC HDR/DV, FFV1 yuv444p, or other exotic codecs + temp_dir = temp_dir or tempfile.mkdtemp(prefix="verify_sync_") + preview_path = os.path.join(temp_dir, "preview.mp4") + if not os.path.exists(preview_path): + transcode_dur = 30.0 if args.mode == "short" else 0 + print(f"Transcoding to H.264 SDR ({'%.1fs' % transcode_dur if transcode_dur else 'full'})...") + if not transcode_to_h264_sdr(video_path, preview_path, transcode_dur): + print(f"ERROR: transcode failed", file=sys.stderr) + sys.exit(1) + print(f"Preview ready: {preview_path}") + else: + print(f"Using cached preview: {preview_path}") + video_path = preview_path + print(f"Mode: {args.mode}") print(f"Video: {video_path}") print(f"WAV A: {wav_a}")