commit 6c18827b932f6497d63e48b3311e323a930b3e5c Author: Hannes Date: Sun Mar 8 00:44:40 2026 +0100 after gemini diff --git a/sync_by_time.py b/sync_by_time.py new file mode 100644 index 0000000..7fb18f8 --- /dev/null +++ b/sync_by_time.py @@ -0,0 +1,46 @@ +import subprocess + +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" + +offset_seconds = -0.1918081918081918 +offset_ms = offset_seconds * 1000 +preview_length = 10 +# 0.1 entspricht 1/10 der Geschwindigkeit +speed_factor = 0.1 + +filter_complex = ( + f"[1:v]scale=640:-1[left_vid];" + f"[0:v]scale=640:-1,hflip,drawtext=" + f"fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:" + f"text='Offset\\: {offset_ms:.2f}ms':" + f"x=10:y=10:fontsize=24:fontcolor=white:box=1:boxcolor=black@0.5[right_vid];" + f"[left_vid][right_vid]hstack=inputs=2" +) + +# Schritt 1: Video rendern +cmd_ffmpeg = [ + "ffmpeg", "-y", + "-itsoffset", f"{offset_seconds:.6f}", "-i", video_b, + "-i", video_a, + "-filter_complex", filter_complex, + "-t", str(preview_length), + "-c:v", "libx264", + "-crf", "18", + "-preset", "fast", + "sync_preview.mp4" +] + +# Schritt 2: MPV starten +cmd_mpv = [ + "mpv", + f"--speed={speed_factor}", + "--title=Sync-Check (1/10 Speed)", + "sync_preview.mp4" +] + +print("Rendere Vorschau...") +subprocess.run(cmd_ffmpeg) + +print("Starte MPV...") +subprocess.run(cmd_mpv) \ No newline at end of file diff --git a/temporal_correlation.py b/temporal_correlation.py new file mode 100644 index 0000000..1a6fc3f --- /dev/null +++ b/temporal_correlation.py @@ -0,0 +1,95 @@ +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() \ No newline at end of file