197 lines
6.3 KiB
Python
197 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
import cv2
|
|
import numpy as np
|
|
from scipy import signal
|
|
import sys
|
|
|
|
def run(cmd):
|
|
"""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):
|
|
"""Get video FPS, duration, codec, and stream start time."""
|
|
import json
|
|
cmd = [
|
|
"ffprobe",
|
|
"-v", "error",
|
|
"-print_format", "json",
|
|
"-show_streams",
|
|
"-show_format",
|
|
path
|
|
]
|
|
data = json.loads(subprocess.run(cmd, capture_output=True, text=True, check=True).stdout)
|
|
|
|
duration = float(data["format"]["duration"])
|
|
for s in data["streams"]:
|
|
if s["codec_type"] == "video":
|
|
num, den = s["r_frame_rate"].split("/")
|
|
fps = float(num) / float(den)
|
|
codec = s["codec_name"]
|
|
start_time = float(s.get("start_time", 0))
|
|
return fps, duration, codec, start_time
|
|
|
|
raise RuntimeError("No video stream found")
|
|
|
|
def trim_audio(German_Videout_mkv, start_time, end_time, out_audio):
|
|
"""Trim only the first (and only) audio track from German_Videout_mkv."""
|
|
run([
|
|
"ffmpeg",
|
|
"-y",
|
|
"-i", German_Videout_mkv,
|
|
"-ss", str(start_time),
|
|
"-to", str(end_time),
|
|
"-map", "0:a:0", # first audio track
|
|
"-c:a", "copy", # no re-encode
|
|
out_audio
|
|
])
|
|
|
|
def mux_audio_into_output(Quality_Video, audio_file, output_dir):
|
|
"""Mux trimmed audio into Quality_Video and place final file in output_dir."""
|
|
output_dir = Path(output_dir)
|
|
output_dir.mkdir(exist_ok=True)
|
|
|
|
final_output = output_dir / f"{Path(Quality_Video).stem}_ger.mkv"
|
|
|
|
run([
|
|
"mkvmerge",
|
|
"-o", str(final_output),
|
|
str(Quality_Video),
|
|
"--language", "0:deu", # assign German language
|
|
"--track-name", "0:German (2.0 aac)",
|
|
str(audio_file)
|
|
])
|
|
|
|
return final_output
|
|
|
|
def only_one(German_Video, Quality_Video):
|
|
n1 = find_offset(German_Video, Quality_Video) # first frame to keep
|
|
print(n1)
|
|
sys.exit()
|
|
output_dir = "./output_dir"
|
|
|
|
# --- Probe video ---
|
|
fps, duration, _, vstart = probe_video(German_Video)
|
|
_, duration2, _, _ = probe_video(Quality_Video)
|
|
|
|
# --- Compute timestamps ---
|
|
start = vstart + (n1 / fps)
|
|
end = start + duration2
|
|
|
|
if end <= start:
|
|
raise ValueError("n2 must be greater than n1")
|
|
|
|
|
|
|
|
# if duration < end:
|
|
# raise ValueError("German_Video must be longer than what it needs to be cut to")
|
|
|
|
print(f"fps: {fps}")
|
|
print(f"Cutting audio from frame {n1} (timestamp {start:.6f}s) to timestamp {end:.6f}s")
|
|
|
|
# --- Trim only audio ---
|
|
audio_file = Path(output_dir) / f"{Path(Quality_Video).stem}_german_audio.aac"
|
|
trim_audio(German_Video, start, end, audio_file)
|
|
|
|
# --- Mux trimmed audio ---
|
|
final_file = mux_audio_into_output(Quality_Video, audio_file, output_dir)
|
|
|
|
print(f"Final file with trimmed audio created at: {final_file}")
|
|
|
|
def main():
|
|
for i in range(1, 12):
|
|
# --- German_Videouts ---
|
|
German_Video = 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"
|
|
Quality_Video = f"/home/honney/projects/mkv/Anohana - The Flower We Saw That Day/S01/AnoHana The Flower We Saw That Day S01E0{i:02}.mkv"
|
|
n1 = find_offset(German_Video, Quality_Video) # first frame to keep
|
|
print(n1)
|
|
sys.exit()
|
|
output_dir = "./output_dir"
|
|
|
|
# --- Probe video ---
|
|
fps, duration, _, vstart = probe_video(German_Video)
|
|
_, duration2, _, _ = probe_video(Quality_Video)
|
|
|
|
# --- Compute timestamps ---
|
|
start = vstart + (n1 / fps)
|
|
end = start + duration2
|
|
|
|
if end <= start:
|
|
raise ValueError("n2 must be greater than n1")
|
|
|
|
if duration < end:
|
|
raise ValueError("German_Video must be longer than what it needs to be cut to")
|
|
|
|
print(f"fps: {fps}")
|
|
print(f"Cutting audio from frame {n1} (timestamp {start:.6f}s) to timestamp {end:.6f}s")
|
|
|
|
# --- Trim only audio ---
|
|
audio_file = Path(output_dir) / f"{Path(Quality_Video).stem}_german_audio.aac"
|
|
trim_audio(German_Video, start, end, audio_file)
|
|
|
|
# --- Mux trimmed audio ---
|
|
final_file = mux_audio_into_output(Quality_Video, audio_file, output_dir)
|
|
|
|
print(f"Final file with trimmed audio created at: {final_file}")
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) == 3:
|
|
print(sys.argv)
|
|
print(f"German_Video={sys.argv[1]}")
|
|
print(f"Quality_Video={sys.argv[2]}")
|
|
only_one(German_Video=sys.argv[1], Quality_Video=sys.argv[2])
|
|
else:
|
|
# print(len(sys.argv), sys.argv)
|
|
# sys.exit()
|
|
main()
|