From 99bbc3e1263e27448b422f9a1c0426012364e3a8 Mon Sep 17 00:00:00 2001 From: Honney OC Date: Sun, 13 Sep 2026 23:18:24 +0000 Subject: [PATCH] fix: transcode video for QMediaPlayer and force xcb platform --- Makefile | 4 ++-- verify_sync.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 87a3a01..a60baca 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_STYLE_OVERRIDE=Fusion $(PY) verify_sync.py --config $(CONFIG) --lag $(LAG); else QT_QPA_PLATFORM=xcb QT_STYLE_OVERRIDE=Fusion python3 verify_sync.py --config $(CONFIG) --lag $(LAG); 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) 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 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_STYLE_OVERRIDE=Fusion $(PY) sync_audio_test.py --config $(CONFIG); else QT_STYLE_OVERRIDE=Fusion python3 sync_audio_test.py --config $(CONFIG); 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 clean: rm -rf $(VENV) __pycache__ .pytest_cache diff --git a/verify_sync.py b/verify_sync.py index 643586b..f6c3f0e 100644 --- a/verify_sync.py +++ b/verify_sync.py @@ -27,6 +27,13 @@ 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: @@ -108,6 +115,28 @@ def extract_audio_from_mkv(mkv_path: str, stream_index: int, out_wav: str, sr: i return False +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 + + def apply_lag(a: np.ndarray, b: np.ndarray, lag_samples: int) -> tuple[np.ndarray, np.ndarray]: if lag_samples >= 0: a_out = a[lag_samples:] @@ -621,6 +650,15 @@ def main(): print(f"ERROR: wav B not found: {wav_b}", file=sys.stderr); sys.exit(1) video_path = video_a + # In short mode, use the extracted FFV1 video instead of the original HEVC source + if args.mode == "short": + stem = os.path.splitext(os.path.basename(video_a))[0] + extracted_video = os.path.join(out_dir_extract, f"{stem}_video.mkv") + if os.path.exists(extracted_video): + video_path = extracted_video + print(f"Using extracted video: {video_path}") + else: + print(f"WARNING: extracted video not found ({extracted_video}), using original", file=sys.stderr) temp_dir = None print(f"Mode: {args.mode}") @@ -632,6 +670,31 @@ def main(): app = QtWidgets.QApplication(sys.argv) apply_dark_theme(app) + # Check if QMediaPlayer supports the video codec; only transcode if needed + probe = QtMultimedia.QMediaPlayer() + probe.setSource(QtCore.QUrl.fromLocalFile(os.path.abspath(video_path))) + support = probe.support() + del probe + + if support != QtMultimedia.QMediaPlayer.Support.Supported: + if not temp_dir: + temp_dir = 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"Codec not supported by QMediaPlayer (support={support.name}), " + 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"WARNING: transcode failed, using original video (may show black)", file=sys.stderr) + else: + video_path = preview_path + print(f"Preview ready: {preview_path}") + else: + video_path = preview_path + print(f"Using cached preview: {preview_path}") + else: + print(f"Codec supported by QMediaPlayer, using directly") + w = VerifyWindow(video_path, wav_a, wav_b, sr=sr, lag_sec=lag_sec, mode=args.mode, out_dir=out_dir, temp_dir=temp_dir, volume=volume)