Added all functionality to cut_to_length
This commit is contained in:
1 parent
6c1687d6bc
commit
cb56886a4b
5 files changed
+229
-143
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
*/
|
||||||
+86
-66
@@ -1,16 +1,66 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
import shutil
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
from scipy import signal
|
||||||
|
|
||||||
def run(cmd):
|
def run(cmd):
|
||||||
return subprocess.run(cmd, capture_output=True, text=True, check=True)
|
"""Run a subprocess command and raise on error."""
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
def get_video_signal(path):
|
||||||
|
cap = cv2.VideoCapture(path)
|
||||||
|
motion_signal = []
|
||||||
|
prev_gray = None
|
||||||
|
|
||||||
|
while True:
|
||||||
|
ret, frame = cap.read()
|
||||||
|
if not ret:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 1. Shrink the frame (makes it faster and removes noise)
|
||||||
|
small_frame = cv2.resize(frame, (160, 90))
|
||||||
|
gray = cv2.cvtColor(small_frame, cv2.COLOR_BGR2GRAY)
|
||||||
|
|
||||||
|
if prev_gray is not None:
|
||||||
|
# 2. Calculate the difference between this frame and the last
|
||||||
|
# This detects the "movement" or "cuts"
|
||||||
|
diff = cv2.absdiff(gray, prev_gray)
|
||||||
|
motion_signal.append(np.sum(diff))
|
||||||
|
else:
|
||||||
|
motion_signal.append(0)
|
||||||
|
|
||||||
|
prev_gray = gray
|
||||||
|
|
||||||
|
cap.release()
|
||||||
|
return np.array(motion_signal)
|
||||||
|
|
||||||
|
def find_offset(video_a, video_b):
|
||||||
|
# print("Extracting signals (this may take a moment)...")
|
||||||
|
sig_a = get_video_signal(video_a)
|
||||||
|
# print("sig_a finished")
|
||||||
|
sig_b = get_video_signal(video_b)
|
||||||
|
# print("sig_b finished")
|
||||||
|
|
||||||
|
# Normalize signals (subtract mean, divide by std dev) to improve correlation
|
||||||
|
sig_a = (sig_a - np.mean(sig_a)) / (np.std(sig_a) + 1e-5)
|
||||||
|
sig_b = (sig_b - np.mean(sig_b)) / (np.std(sig_b) + 1e-5)
|
||||||
|
|
||||||
|
# Perform Cross-Correlation
|
||||||
|
correlation = signal.correlate(sig_a, sig_b, mode='full')
|
||||||
|
lags = signal.correlation_lags(len(sig_a), len(sig_b), mode='full')
|
||||||
|
# plt.plot(lags)
|
||||||
|
|
||||||
|
# The peak of the correlation is our offset in frames
|
||||||
|
best_lag = lags[np.argmax(correlation)]
|
||||||
|
|
||||||
|
return best_lag
|
||||||
|
|
||||||
def probe_video(path):
|
def probe_video(path):
|
||||||
|
"""Get video FPS, duration, codec, and stream start time."""
|
||||||
|
import json
|
||||||
cmd = [
|
cmd = [
|
||||||
"ffprobe",
|
"ffprobe",
|
||||||
"-v", "error",
|
"-v", "error",
|
||||||
@@ -19,11 +69,9 @@ def probe_video(path):
|
|||||||
"-show_format",
|
"-show_format",
|
||||||
path
|
path
|
||||||
]
|
]
|
||||||
|
data = json.loads(subprocess.run(cmd, capture_output=True, text=True, check=True).stdout)
|
||||||
data = json.loads(run(cmd).stdout)
|
|
||||||
|
|
||||||
duration = float(data["format"]["duration"])
|
duration = float(data["format"]["duration"])
|
||||||
|
|
||||||
for s in data["streams"]:
|
for s in data["streams"]:
|
||||||
if s["codec_type"] == "video":
|
if s["codec_type"] == "video":
|
||||||
num, den = s["r_frame_rate"].split("/")
|
num, den = s["r_frame_rate"].split("/")
|
||||||
@@ -34,95 +82,67 @@ def probe_video(path):
|
|||||||
|
|
||||||
raise RuntimeError("No video stream found")
|
raise RuntimeError("No video stream found")
|
||||||
|
|
||||||
def vaapi_cut(inp, outp, start, end, codec="h264_vaapi"):
|
def trim_audio(input_mkv, start_time, end_time, out_audio):
|
||||||
encoder = codec
|
"""Trim only the first (and only) audio track from input_mkv."""
|
||||||
|
run([
|
||||||
cmd = [
|
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"-y",
|
"-y",
|
||||||
"-vaapi_device", "/dev/dri/renderD128",
|
"-i", input_mkv,
|
||||||
|
"-ss", str(start_time),
|
||||||
"-ss", str(start),
|
"-to", str(end_time),
|
||||||
"-to", str(end),
|
"-map", "0:a:0", # first audio track
|
||||||
|
"-c:a", "copy", # no re-encode
|
||||||
"-i", inp,
|
out_audio
|
||||||
"-map", "0",
|
|
||||||
|
|
||||||
"-vf", "format=nv12,hwupload",
|
|
||||||
|
|
||||||
"-c:v", encoder,
|
|
||||||
"-qp", "20",
|
|
||||||
|
|
||||||
"-c:a", "copy",
|
|
||||||
"-c:s", "copy",
|
|
||||||
"-c:d", "copy",
|
|
||||||
"-c:t", "copy",
|
|
||||||
|
|
||||||
outp
|
|
||||||
]
|
|
||||||
|
|
||||||
subprocess.run(cmd, check=True)
|
|
||||||
|
|
||||||
def extract_audio(output_mkv, out_audio):
|
|
||||||
# Extract the first (and only) audio track
|
|
||||||
run([
|
|
||||||
"mkvextract",
|
|
||||||
"tracks",
|
|
||||||
output_mkv,
|
|
||||||
f"1:{out_audio}" # track IDs are usually 0 for video, 1 for audio
|
|
||||||
])
|
])
|
||||||
|
|
||||||
def mux_audio_into_copy(inp2, audio_file, output_dir):
|
def mux_audio_into_output(inp2, audio_file, output_dir):
|
||||||
|
"""Mux trimmed audio into inp2 and place final file in output_dir."""
|
||||||
output_dir = Path(output_dir)
|
output_dir = Path(output_dir)
|
||||||
output_dir.mkdir(exist_ok=True)
|
output_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
copied_video = output_dir / Path(inp2).name
|
final_output = output_dir / f"{Path(inp2).stem}_ger.mkv"
|
||||||
shutil.copy2(inp2, copied_video)
|
|
||||||
|
|
||||||
final_output = output_dir / "final_with_audio.mkv"
|
|
||||||
|
|
||||||
run([
|
run([
|
||||||
"mkvmerge",
|
"mkvmerge",
|
||||||
"-o", str(final_output),
|
"-o", str(final_output),
|
||||||
str(copied_video),
|
str(inp2),
|
||||||
"--language", "0:deu", # assign German language
|
"--language", "0:deu", # assign German language
|
||||||
|
"--track-name", "0:German (2.0 aac)",
|
||||||
str(audio_file)
|
str(audio_file)
|
||||||
])
|
])
|
||||||
|
|
||||||
return final_output
|
return final_output
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
inp = f"/home/honney/projects/mkv/AnoHana The Flower We Saw That Day (2011-2011) [imdbid-tt1913273]/Season 01/AnoHana The Flower We Saw That Day S01E001.mkv"
|
for i in range(1, 12):
|
||||||
inp2 = f"/home/honney/projects/mkv/Anohana - The Flower We Saw That Day - S01 (BD 1080p HEVC Opus) [derpie]/[derpie] Anohana - The Flower We Saw That Day - S01E01 (BD 1080p HEVC Opus) [Dual Audio].mkv"
|
# --- Inputs ---
|
||||||
|
inp = f"/home/honney/projects/mkv/AnoHana The Flower We Saw That Day (2011-2011) [imdbid-tt1913273]/Season 01/AnoHana The Flower We Saw That Day S01E0{i:02}.mkv"
|
||||||
|
inp2 = f"/home/honney/projects/mkv/Anohana - The Flower We Saw That Day/S01/AnoHana The Flower We Saw That Day S01E0{i:02}.mkv"
|
||||||
outp = "./output.mkv"
|
n1 = find_offset(inp, inp2) # first frame to keep
|
||||||
n1 = 24
|
output_dir = "./output_dir"
|
||||||
|
|
||||||
|
# --- Probe video ---
|
||||||
fps, _, _, vstart = probe_video(inp)
|
fps, _, _, vstart = probe_video(inp)
|
||||||
_, duration, _, _ = probe_video(inp2)
|
_, duration2, _, _ = probe_video(inp2)
|
||||||
|
|
||||||
|
# --- Compute timestamps ---
|
||||||
start = vstart + (n1 / fps)
|
start = vstart + (n1 / fps)
|
||||||
end = duration + start
|
end = start + duration2
|
||||||
|
|
||||||
if end <= start:
|
if end <= start:
|
||||||
raise ValueError("n2 must be greater than n1")
|
raise ValueError("n2 must be greater than n1")
|
||||||
|
|
||||||
print(f"fps: {fps}")
|
print(f"fps: {fps}")
|
||||||
print(f"cutting from frame {n1} to {end*fps}")
|
print(f"Cutting audio from frame {n1} (timestamp {start:.6f}s) to timestamp {end:.6f}s")
|
||||||
print(f"time range: {start:.6f}s -> {end:.6f}s")
|
|
||||||
|
|
||||||
print("Trying stream copy...")
|
# --- Trim only audio ---
|
||||||
vaapi_cut(inp, outp, start, end)
|
audio_file = Path(output_dir) / f"{Path(inp2).stem}_german_audio.aac"
|
||||||
|
trim_audio(inp, start, end, audio_file)
|
||||||
|
|
||||||
output_dir = "./output_dir"
|
# --- Mux trimmed audio ---
|
||||||
audio_file = Path(output_dir) / "german_audio.aac"
|
final_file = mux_audio_into_output(inp2, audio_file, output_dir)
|
||||||
|
|
||||||
extract_audio(outp, audio_file)
|
print(f"Final file with trimmed audio created at: {final_file}")
|
||||||
|
|
||||||
final_file = mux_audio_into_copy(inp2, audio_file, output_dir)
|
|
||||||
|
|
||||||
print(f"Final file created at: {final_file}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def create_comparison_collage(video_a_path, video_b_path, time_a, offset_b, radius=1, output_name="collage.jpg"):
|
||||||
|
caps = {
|
||||||
|
"A": cv2.VideoCapture(video_a_path),
|
||||||
|
"B": cv2.VideoCapture(video_b_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
results = {"A": [], "B": []}
|
||||||
|
|
||||||
|
for label, cap in caps.items():
|
||||||
|
if not cap.isOpened():
|
||||||
|
print(f"Error: Could not open Video {label}")
|
||||||
|
return
|
||||||
|
|
||||||
|
fps = cap.get(cv2.CAP_PROP_FPS)
|
||||||
|
# Determine base time for this specific video
|
||||||
|
base_time = time_a if label == "A" else (time_a + offset_b)
|
||||||
|
center_frame = int(round(base_time * fps))
|
||||||
|
|
||||||
|
# Extract range: from (center - radius) to (center + radius)
|
||||||
|
for i in range(-radius, radius + 1):
|
||||||
|
target_idx = center_frame + i
|
||||||
|
cap.set(cv2.CAP_PROP_POS_FRAMES, target_idx)
|
||||||
|
success, frame = cap.read()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Add overlay text (Timestamp and Frame Index)
|
||||||
|
current_time = target_idx / fps
|
||||||
|
text = f"Vid {label} | F: {target_idx} | {current_time:.4f}s"
|
||||||
|
|
||||||
|
# Draw black background for text for readability
|
||||||
|
cv2.putText(frame, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,0,0), 4, cv2.LINE_AA)
|
||||||
|
cv2.putText(frame, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255,255,255), 2, cv2.LINE_AA)
|
||||||
|
|
||||||
|
results[label].append(frame)
|
||||||
|
else:
|
||||||
|
# Placeholder if frame is missing
|
||||||
|
blank = np.zeros((720, 1280, 3), dtype=np.uint8)
|
||||||
|
cv2.putText(blank, "FRAME MISSING", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
|
||||||
|
results[label].append(blank)
|
||||||
|
|
||||||
|
cap.release()
|
||||||
|
|
||||||
|
# Ensure all frames are the same size for the collage (using Video A's dimensions)
|
||||||
|
h, w, _ = results["A"][0].shape
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for i in range(len(results["A"])):
|
||||||
|
img_a = cv2.resize(results["A"][i], (w, h))
|
||||||
|
img_b = cv2.resize(results["B"][i], (w, h))
|
||||||
|
# Combine Left (A) and Right (B)
|
||||||
|
combined_row = np.hstack((img_a, img_b))
|
||||||
|
rows.append(combined_row)
|
||||||
|
|
||||||
|
# Combine all rows Top to Bottom
|
||||||
|
final_collage = np.vstack(rows)
|
||||||
|
|
||||||
|
# Save and show
|
||||||
|
cv2.imwrite(output_name, final_collage)
|
||||||
|
print(f"Collage saved as {output_name}")
|
||||||
|
|
||||||
|
# --- SETTINGS ---
|
||||||
|
video_a = "/home/honney/projects/mkv/Anohana - The Flower We Saw That Day - S01 (BD 1080p HEVC Opus) [derpie]/[derpie] Anohana - The Flower We Saw That Day - S01E02 (BD 1080p HEVC Opus) [Dual Audio].mkv"
|
||||||
|
video_b = "/home/honney/projects/mkv/AnoHana The Flower We Saw That Day (2011-2011) [imdbid-tt1913273]/Season 01/AnoHana The Flower We Saw That Day S01E002.mkv"
|
||||||
|
|
||||||
|
timestamp_a = 60 * 20#1/(24000/1001) * 250 # Center point for Video A 75
|
||||||
|
offset_for_b = 1/(24000/1001) * 24
|
||||||
|
frames_around = 10 # 2 before, 2 after (5 frames total per video)
|
||||||
|
|
||||||
|
create_comparison_collage(video_a, video_b, timestamp_a, offset_for_b, frames_around)
|
||||||
+13
-11
@@ -2,6 +2,7 @@ import cv2
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy import signal
|
from scipy import signal
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
import sys
|
||||||
|
|
||||||
def get_video_signal(path):
|
def get_video_signal(path):
|
||||||
cap = cv2.VideoCapture(path)
|
cap = cv2.VideoCapture(path)
|
||||||
@@ -52,17 +53,18 @@ def find_offset(video_a, video_b):
|
|||||||
return best_lag
|
return best_lag
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
# for i in range(1, 12):
|
for i in range(1, 12):
|
||||||
# # print(f"{i:02}")
|
|
||||||
# video_a = f"/home/honney/projects/mkv/AnoHana The Flower We Saw That Day (2011-2011) [imdbid-tt1913273]/Season 01/AnoHana The Flower We Saw That Day S01E0{i:02}.mkv"
|
|
||||||
# video_b = f"/home/honney/projects/mkv/Anohana - The Flower We Saw That Day - S01 (BD 1080p HEVC Opus) [derpie]/[derpie] Anohana - The Flower We Saw That Day - S01E{i:02} (BD 1080p HEVC Opus) [Dual Audio].mkv"
|
|
||||||
|
|
||||||
# offset_frames = find_offset(video_a, video_b)
|
|
||||||
# print(f"{i}: Video B is offset by {offset_frames} frames relative to Video A.")
|
|
||||||
|
|
||||||
# print(f"{i:02}")
|
# print(f"{i:02}")
|
||||||
video_a = f"/home/honney/projects/mkv/output.mkv"
|
video_a = f"/home/honney/projects/mkv/AnoHana The Flower We Saw That Day (2011-2011) [imdbid-tt1913273]/Season 01/AnoHana The Flower We Saw That Day S01E0{i:02}.mkv"
|
||||||
video_b = f"/home/honney/projects/mkv/Anohana - The Flower We Saw That Day - S01 (BD 1080p HEVC Opus) [derpie]/[derpie] Anohana - The Flower We Saw That Day - S01E01 (BD 1080p HEVC Opus) [Dual Audio].mkv"
|
video_b = f"/home/honney/projects/mkv/Anohana - The Flower We Saw That Day/S01/AnoHana The Flower We Saw That Day S01E0{i:02}.mkv"
|
||||||
|
|
||||||
offset_frames = find_offset(video_a, video_b)
|
offset_frames = find_offset(video_a, video_b)
|
||||||
print(f"Video B is offset by {offset_frames} frames relative to Video A.")
|
print(f"{i}: Video B is offset by {offset_frames} frames relative to Video A.")
|
||||||
|
# sys.exit()
|
||||||
|
|
||||||
|
# video_a = f"/home/honney/projects/mkv/output_dir/AnoHana The Flower We Saw That Day S01E001_ger.mkv"
|
||||||
|
# video_b = f"/home/honney/projects/mkv/Anohana - The Flower We Saw That Day/S01/AnoHana The Flower We Saw That Day S01E001.mkv"
|
||||||
|
|
||||||
|
# offset_frames = find_offset(video_a, video_b)
|
||||||
|
# print(f"Video B is offset by {offset_frames} frames relative to Video A.")
|
||||||
|
# sys.exit()
|
||||||
+57
-66
@@ -1,104 +1,95 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import sys
|
from scipy.interpolate import interp1d
|
||||||
|
|
||||||
# --- Setup ---
|
# --- 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_a = "/home/honney/projects/mkv/Anohana - The Flower We Saw That Day - S01 (BD 1080p HEVC Opus) [derpie]/[derpie] Anohana - The Flower We Saw That Day - S01E01 (BD 1080p HEVC Opus) [Dual Audio].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"
|
video_b = "/home/honney/projects/mkv/AnoHana The Flower We Saw That Day (2011-2011) [imdbid-tt1913273]/Season 01/AnoHana The Flower We Saw That Day S01E001.mkv"
|
||||||
|
|
||||||
|
FPS_NATIVE = 24000/1001
|
||||||
WIDTH = 160
|
WIDTH = 160
|
||||||
HEIGHT = int((WIDTH/16)*9)
|
HEIGHT = int((WIDTH/16)*9)
|
||||||
FPS = 24000/1001
|
MAX_OFFSET_SECONDS = 1
|
||||||
MAX_OFFSET_SECONDS = 2 # ±2 Sekunden Suche
|
|
||||||
|
|
||||||
|
# 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):
|
def extract_hist_signal(video):
|
||||||
"""Extrahiert pro Frame einen Histogrammvektor unter Nutzung von VA-API (AMD GPU)."""
|
|
||||||
# Wir nutzen vaapi für Hardware-Dekodierung und Scaling
|
|
||||||
proc = subprocess.Popen([
|
proc = subprocess.Popen([
|
||||||
"ffmpeg",
|
"ffmpeg", "-loglevel", "error", "-i", video,
|
||||||
"-loglevel", "error",
|
"-vf", f"scale={WIDTH}:{HEIGHT},format=gray",
|
||||||
"-hwaccel", "vaapi", # Nutze VA-API Hardware-Beschleunigung
|
"-f", "rawvideo", "-"
|
||||||
"-hwaccel_device", "/dev/dri/renderD128", # Pfad zur RX 7900 XTX
|
|
||||||
"-hwaccel_output_format", "vaapi",
|
|
||||||
"-i", video,
|
|
||||||
"-vf", (
|
|
||||||
f"fps={FPS}," # Framerate Anpassung
|
|
||||||
f"scale_vaapi=w={WIDTH}:h={HEIGHT}:format=gray8," # GPU-Scaling + Grayscale
|
|
||||||
"hwdownload,format=gray8" # Zurück in den RAM schieben
|
|
||||||
),
|
|
||||||
"-f", "rawvideo",
|
|
||||||
"-"
|
|
||||||
], stdout=subprocess.PIPE)
|
], stdout=subprocess.PIPE)
|
||||||
|
|
||||||
frame_size = WIDTH * HEIGHT
|
frame_size = WIDTH * HEIGHT
|
||||||
hist_signal = []
|
hist_signal = []
|
||||||
|
|
||||||
# Der restliche Loop bleibt gleich, da die Histogramm-Berechnung
|
|
||||||
# (np.histogram) weiterhin auf der CPU via NumPy erfolgt.
|
|
||||||
while True:
|
while True:
|
||||||
data = proc.stdout.read(frame_size)
|
data = proc.stdout.read(frame_size)
|
||||||
if not data or len(data) < frame_size:
|
if not data or len(data) < frame_size: break
|
||||||
break
|
|
||||||
frame = np.frombuffer(data, dtype=np.uint8)
|
frame = np.frombuffer(data, dtype=np.uint8)
|
||||||
hist, _ = np.histogram(frame, bins=32, range=(0, 255))
|
hist, _ = np.histogram(frame, bins=64, range=(0, 255))
|
||||||
hist_signal.append(hist / np.sum(hist))
|
hist_signal.append(hist / (np.sum(hist) + 1e-6))
|
||||||
|
|
||||||
proc.wait()
|
proc.wait()
|
||||||
return np.array(hist_signal)
|
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)
|
||||||
|
|
||||||
print("Extracting signal of video A")
|
# 1. In 1D-Signal umwandeln (Helligkeits-Schnitt)
|
||||||
a = extract_hist_signal(video_a)
|
sig_a_raw = np.mean(a_raw, axis=1)
|
||||||
|
sig_b_raw = np.mean(b_raw, axis=1)
|
||||||
|
|
||||||
print("Extracting signal of video B")
|
# 2. UPSAMPLING (Die "doppelte FPS" Logik)
|
||||||
b = extract_hist_signal(video_b)
|
# 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("Calculating cross correlation")
|
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)
|
||||||
|
|
||||||
max_lag_frames = int(MAX_OFFSET_SECONDS * FPS)
|
# 3. Zentrieren für bessere Korrelation
|
||||||
n_a, n_bins = a.shape
|
sig_a -= np.mean(sig_a)
|
||||||
n_b, _ = b.shape
|
sig_b -= np.mean(sig_b)
|
||||||
|
|
||||||
lags = np.arange(-max_lag_frames, max_lag_frames + 1)
|
print("Calculating high-precision cross correlation...")
|
||||||
corr = []
|
corr = np.correlate(sig_a, sig_b, mode='full')
|
||||||
|
lags = np.arange(-len(sig_b) + 1, len(sig_a))
|
||||||
|
|
||||||
for lag in lags:
|
# Suchbereich einschränken (jetzt basierend auf virtueller FPS)
|
||||||
if lag < 0:
|
max_lag_v = int(MAX_OFFSET_SECONDS * FPS_VIRTUAL)
|
||||||
# Offset negativ → Video B vor Video A
|
search_range = (lags >= -max_lag_v) & (lags <= max_lag_v)
|
||||||
sub_a = a[:lag] # bis lag Frames von Video A
|
|
||||||
sub_b = b[-lag:] # von -lag bis Ende von Video B
|
|
||||||
else:
|
|
||||||
sub_a = a[lag:] # ab lag bis Ende von Video A
|
|
||||||
sub_b = b[:n_b - lag] # bis n_b - lag von Video B
|
|
||||||
|
|
||||||
# Frames angleichen
|
filtered_corr = corr[search_range]
|
||||||
min_len = min(len(sub_a), len(sub_b))
|
filtered_lags = lags[search_range]
|
||||||
if min_len == 0:
|
|
||||||
corr.append(0)
|
|
||||||
continue
|
|
||||||
sub_a = sub_a[:min_len]
|
|
||||||
sub_b = sub_b[:min_len]
|
|
||||||
|
|
||||||
corr.append(np.sum(sub_a * sub_b))
|
best_index = np.argmax(filtered_corr)
|
||||||
|
best_lag_v = filtered_lags[best_index]
|
||||||
|
|
||||||
corr = np.array(corr)
|
# Der Offset berechnet sich nun aus dem virtuellen Lag und der virtuellen FPS
|
||||||
best_index = np.argmax(corr)
|
best_offset_seconds = best_lag_v / FPS_VIRTUAL
|
||||||
best_lag = lags[best_index]
|
|
||||||
|
|
||||||
print("Best offset frames:", best_lag)
|
print("-" * 30)
|
||||||
print("Best offset seconds:", best_lag / FPS)
|
print(f"Best virtual lag: {best_lag_v} units")
|
||||||
|
print(f"Resulting frames: {best_lag_v / UPSAMPLE_FACTOR:.3f}")
|
||||||
sys.exit()
|
print(f"Best offset seconds: {best_offset_seconds:.12f}")
|
||||||
|
print("-" * 30)
|
||||||
|
|
||||||
# Plot
|
# Plot
|
||||||
plt.figure(figsize=(10, 5))
|
plt.figure(figsize=(12, 5))
|
||||||
plt.plot(lags / FPS, corr, marker='o')
|
plt.plot(filtered_lags / FPS_VIRTUAL, filtered_corr)
|
||||||
plt.axvline(best_lag / FPS, color='r', linestyle='--', label=f"Best offset = {best_lag / FPS:.3f}s")
|
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.xlabel("Offset (seconds)")
|
||||||
plt.ylabel("Cross correlation")
|
plt.ylabel("Correlation")
|
||||||
plt.title("Video cross correlation (Histogram-based)")
|
|
||||||
plt.legend()
|
plt.legend()
|
||||||
plt.grid(True)
|
plt.grid(True)
|
||||||
plt.show()
|
plt.show()
|
||||||
Reference in new issue
Block a user