import subprocess import numpy as np import matplotlib.pyplot as plt from scipy.interpolate import interp1d # --- Setup --- video_a = "/home/honney/projects/mkv/Saga of Tanya the Evil/Season 1/Saga Of Tanya The Evil - S01E01 - The Devil Of The Rhine.mkv" video_b = "/home/honney/projects/mkv/Saga of Tanya the Evil (2017-2017) [imdbid-tt6455986]/Season 01/Saga of Tanya the Evil S01E001.mkv" FPS_NATIVE = 24000/1001 WIDTH = 160 HEIGHT = int((WIDTH/16)*9) MAX_OFFSET_SECONDS = 1 # FAKTOR FÜR PRÄZISION: # 2 = Doppelte FPS für die Rechnung (Sub-Frame) # 10 = Zehnfache Präzision (reicht für Mikrosekunden-Sync) UPSAMPLE_FACTOR = 10 FPS_VIRTUAL = FPS_NATIVE * UPSAMPLE_FACTOR def extract_hist_signal(video): proc = subprocess.Popen([ "ffmpeg", "-loglevel", "error", "-i", video, "-vf", f"scale={WIDTH}:{HEIGHT},format=gray", "-f", "rawvideo", "-" ], stdout=subprocess.PIPE) frame_size = WIDTH * HEIGHT hist_signal = [] while True: data = proc.stdout.read(frame_size) if not data or len(data) < frame_size: break frame = np.frombuffer(data, dtype=np.uint8) hist, _ = np.histogram(frame, bins=64, range=(0, 255)) hist_signal.append(hist / (np.sum(hist) + 1e-6)) proc.wait() return np.array(hist_signal) print(f"Extracting signals (Native FPS: {FPS_NATIVE:.4f})...") a_raw = extract_hist_signal(video_a) b_raw = extract_hist_signal(video_b) # 1. In 1D-Signal umwandeln (Helligkeits-Schnitt) sig_a_raw = np.mean(a_raw, axis=1) sig_b_raw = np.mean(b_raw, axis=1) # 2. UPSAMPLING (Die "doppelte FPS" Logik) # Wir erstellen eine glatte Kurve durch die Frames und tasten sie feiner ab def upsample_signal(sig, factor): x = np.arange(len(sig)) f = interp1d(x, sig, kind='cubic') # Kubische Interpolation für weiche Übergänge x_new = np.linspace(0, len(sig) - 1, len(sig) * factor) return f(x_new) print(f"Upsampling signals by factor {UPSAMPLE_FACTOR}...") sig_a = upsample_signal(sig_a_raw, UPSAMPLE_FACTOR) sig_b = upsample_signal(sig_b_raw, UPSAMPLE_FACTOR) # 3. Zentrieren für bessere Korrelation sig_a -= np.mean(sig_a) sig_b -= np.mean(sig_b) print("Calculating high-precision cross correlation...") corr = np.correlate(sig_a, sig_b, mode='full') lags = np.arange(-len(sig_b) + 1, len(sig_a)) # Suchbereich einschränken (jetzt basierend auf virtueller FPS) max_lag_v = int(MAX_OFFSET_SECONDS * FPS_VIRTUAL) search_range = (lags >= -max_lag_v) & (lags <= max_lag_v) filtered_corr = corr[search_range] filtered_lags = lags[search_range] best_idx = np.argmax(filtered_corr) best_lag_v = filtered_lags[best_idx] # Der Offset berechnet sich nun aus dem virtuellen Lag und der virtuellen FPS best_offset_seconds = best_lag_v / FPS_VIRTUAL print("-" * 30) print(f"Best virtual lag: {best_lag_v} units") print(f"Resulting frames: {best_lag_v / UPSAMPLE_FACTOR:.3f}") print(f"Best offset seconds: {best_offset_seconds:.12f}") print("-" * 30) # Plot plt.figure(figsize=(12, 5)) plt.plot(filtered_lags / FPS_VIRTUAL, filtered_corr) plt.axvline(best_offset_seconds, color='r', linestyle='--', label=f"Peak: {best_offset_seconds:.6f}s") plt.title(f"High-Precision Sync (Upsampled x{UPSAMPLE_FACTOR})") plt.xlabel("Offset (seconds)") plt.ylabel("Correlation") plt.legend() plt.grid(True) plt.show()