fix: pre-transcode video to H.264 and try VAAPI encoder
This commit is contained in:
1 parent
25117062e8
commit
53b22ce25d
2 files changed
+44
-54
No files matched your search
@@ -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
|
@if [ -x $(PY) ]; then $(PY) apply_sync.py --config $(CONFIG) --lag $(LAG); else python3 apply_sync.py --config $(CONFIG) --lag $(LAG); fi
|
||||||
|
|
||||||
verify:
|
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:
|
ast:
|
||||||
@AUDIO_A=$$(python3 -c "import tomllib; c=tomllib.load(open('config.toml','rb')); print(c['extract'].get('file_a',''))" 2>/dev/null); \
|
@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 \
|
else \
|
||||||
echo "=> extracted wavs exist ($$OUT/$${STEM_A}_audio.wav, $$OUT/$${STEM_B}_audio.wav), skipping extract"; \
|
echo "=> extracted wavs exist ($$OUT/$${STEM_A}_audio.wav, $$OUT/$${STEM_B}_audio.wav), skipping extract"; \
|
||||||
fi
|
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:
|
clean:
|
||||||
rm -rf $(VENV) __pycache__ .pytest_cache
|
rm -rf $(VENV) __pycache__ .pytest_cache
|
||||||
+42
-52
@@ -27,13 +27,6 @@ import numpy as np
|
|||||||
|
|
||||||
warnings.filterwarnings("ignore", category=RuntimeWarning, module="pyqtgraph")
|
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:
|
try:
|
||||||
import tomllib
|
import tomllib
|
||||||
except ModuleNotFoundError:
|
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:
|
def transcode_to_h264_sdr(src: str, dst: str, duration: float = 0) -> bool:
|
||||||
"""Transcode video to H.264 SDR MP4 for QMediaPlayer compatibility.
|
"""Transcode video to H.264 SDR MP4 for QMediaPlayer compatibility.
|
||||||
If duration > 0, only transcode that many seconds."""
|
Tries VAAPI hardware encoding first, falls back to CPU x264."""
|
||||||
cmd = ["ffmpeg", "-y", "-i", src]
|
for encoder in ["h264_vaapi", "libx264"]:
|
||||||
if duration > 0:
|
cmd = ["ffmpeg", "-y", "-i", src]
|
||||||
cmd += ["-t", f"{duration:.3f}"]
|
if duration > 0:
|
||||||
cmd += [
|
cmd += ["-t", f"{duration:.3f}"]
|
||||||
"-map", "0:v:0",
|
cmd += ["-map", "0:v:0", "-an"]
|
||||||
"-an",
|
if encoder == "h264_vaapi":
|
||||||
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "20",
|
cmd += ["-vaapi_device", "/dev/dri/renderD128",
|
||||||
"-pix_fmt", "yuv420p",
|
"-vf", "format=nv12,hwupload",
|
||||||
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
"-c:v", "h264_vaapi", "-qp", "20"]
|
||||||
"-movflags", "+faststart",
|
else:
|
||||||
dst,
|
cmd += ["-c:v", "libx264", "-preset", "ultrafast", "-crf", "20",
|
||||||
]
|
"-pix_fmt", "yuv420p",
|
||||||
try:
|
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2"]
|
||||||
r = subprocess.run(cmd, capture_output=True, timeout=600)
|
cmd += ["-movflags", "+faststart", dst]
|
||||||
return r.returncode == 0 and os.path.exists(dst)
|
try:
|
||||||
except Exception:
|
r = subprocess.run(cmd, capture_output=True, timeout=600)
|
||||||
return False
|
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]:
|
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
|
# Wait for media loaded to get actual duration
|
||||||
self.media_player.durationChanged.connect(self._on_duration_changed)
|
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):
|
def _on_duration_changed(self, dur_ms):
|
||||||
"""QMediaPlayer reports actual video duration in ms."""
|
"""QMediaPlayer reports actual video duration in ms."""
|
||||||
vid_dur = dur_ms / 1000.0
|
vid_dur = dur_ms / 1000.0
|
||||||
@@ -427,30 +426,6 @@ class VerifyWindow(QtWidgets.QMainWindow):
|
|||||||
self.btn_play.setText("Play")
|
self.btn_play.setText("Play")
|
||||||
self.audio_player.stop()
|
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):
|
def _draw_waveforms(self):
|
||||||
max_pts = 200_000 if self.mode == "full" else 80_000
|
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)
|
print(f"WARNING: extracted video not found ({extracted_video}), using original", file=sys.stderr)
|
||||||
temp_dir = None
|
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"Mode: {args.mode}")
|
||||||
print(f"Video: {video_path}")
|
print(f"Video: {video_path}")
|
||||||
print(f"WAV A: {wav_a}")
|
print(f"WAV A: {wav_a}")
|
||||||
|
|||||||
Reference in new issue
Block a user