Files
anime_dub_sync/main.py
T
2026-03-20 00:46:36 +01:00

207 lines
8.0 KiB
Python

import sys
from pathlib import Path
from typing import Optional
import os
import glob
import numpy as np
import subprocess
from utils import run
from audio import extract_audio_segment_file, get_audio_duration, stick_together_audio, replace_with_shifted_mka
from video import probe_video, find_temporal_offset, corelate_videos
from mkv import check_intro_outro_sync
from tqdm import tqdm
DEBUG = False
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"
final_output = output_dir / f"{Path(Quality_Video).stem}.mkv"
run([
"mkvmerge",
"-o", str(final_output),
str(Quality_Video),
"--language", "0:deu", # assign German language
"--track-name", "0:German (Opus Stereo)",
str(audio_file)
])
return final_output
def process_one(German_Video: Path, Quality_Video: Path, output_dir: Path, offset: Optional[int] = None) -> bool:
print("probe_video")
# --- Probe video ---
fps, duration, _, vstart = probe_video(German_Video)
fps2, duration2, _, vstart2 = probe_video(Quality_Video)
if duration <= 0:
raise ValueError("Duration of German_Video must be greater than 0")
if duration2 <= 0:
raise ValueError("Duration of Quality_Video must be greater than 0")
print("Compute offset")
# --- Offset ---
if offset == None:
offset0 = corelate_videos(Quality_Video, German_Video, fps2)
else:
offset0 = offset
if offset0 > duration or offset0 > duration2:
raise ValueError("Offset needs to be smaller than the video durations")
print(f"Offset: {offset0}s")
print("Extract pre beginning")
german_audio_file = output_dir / f"{Path(Quality_Video).stem}_german_audio.mka"
quality_audio_file1 = output_dir / f"{Path(Quality_Video).stem}_original_audio1.mka"
quality_audio_file2 = output_dir / f"{Path(Quality_Video).stem}_original_audio2.mka"
# Clear old faulty files, ger is gearantied to be replaced
if os.path.isfile(quality_audio_file1):
os.remove(quality_audio_file1)
if os.path.isfile(quality_audio_file2):
os.remove(quality_audio_file2)
### Get the first part till german starts (if german does not start before file)
start2 = vstart2
end2 = start2 + offset0
if end2 - start2 > 0.05:
if DEBUG: print(f"Cutting audio ({quality_audio_file1}) (from timestamp {start2:.6f}s) to timestamp {end2:.6f}s")
try:
extract_audio_segment_file(Quality_Video, start2, end2, quality_audio_file1)
except subprocess.CalledProcessError as e:
raise BrokenPipeError(
f"{e}\n"
f"Trying to extract Audio from {Quality_Video} {start2}-{end2} "
f"to {quality_audio_file2} to have a duration of {end2-start2}, "
f"while the audio has a length of {get_audio_duration(Quality_Video)}"
)
print("Extract german audio")
### Get the German Audio
if offset0 < 0:
start = vstart - offset0
else:
start = vstart
end = min(vstart + duration, start + duration2)
if DEBUG: print(f"Cutting audio ({german_audio_file}) (from timestamp {start:.6f}s) to timestamp {end:.6f}s")
try:
extract_audio_segment_file(German_Video, start, end, german_audio_file)
except subprocess.CalledProcessError as e:
raise BrokenPipeError(
f"{e}\n"
f"Trying to extract Audio from {German_Video} {start}-{end} "
f"to {german_audio_file} to have a duration of {end-start}, "
f"while the audio has a length of {get_audio_duration(German_Video)}"
)
print("Extract post end")
### Get the end from the first original audio if it is longer than the german part
start2 = vstart2 + end
# end2 = start2 + (duration2 - duration - (n1 / fps))
end2 = vstart2 + get_audio_duration(Quality_Video)
if end2 - start2 > 0.5:
if DEBUG: print(f"Cutting audio ({quality_audio_file2}) (from timestamp {start2:.6f}s) to timestamp {end2:.6f}s")
try:
extract_audio_segment_file(Quality_Video, start2, end2, quality_audio_file2)
except subprocess.CalledProcessError as e:
raise BrokenPipeError(
f"{e}\n"
f"Trying to extract Audio from {Quality_Video} {start2}-{end2} "
f"to {quality_audio_file2} to have a duration of {end2-start2}, "
f"while the audio has a length of {get_audio_duration(Quality_Video)}"
)
print("Combine audio")
# Combine the audio files
if os.path.isfile(quality_audio_file1):
stick_together_audio(quality_audio_file1, german_audio_file, german_audio_file)
if os.path.isfile(quality_audio_file2):
stick_together_audio(german_audio_file, quality_audio_file2, german_audio_file)
print("Mux")
# --- Mux trimmed audio ---
final_file = mux_audio_into_output(Quality_Video, german_audio_file, output_dir)
print("Cleanup")
if os.path.isfile(quality_audio_file1):
os.remove(quality_audio_file1)
if os.path.isfile(quality_audio_file2):
os.remove(quality_audio_file2)
print("Final check")
try:
files_acceptable, op_ed_offset, stream_num = check_intro_outro_sync(final_file)
except:
new_name = final_file.with_name(f"[NO CHAPTER]_{final_file.name}")
final_file.rename(new_name)
if os.path.isfile(german_audio_file):
os.remove(path=german_audio_file)
return False
if files_acceptable and isinstance(op_ed_offset, float) and isinstance(stream_num, int):
print(f"✔ {final_file.name} | offset={op_ed_offset:.4f}")
if np.abs(op_ed_offset) > (1/fps2) and os.path.isfile(german_audio_file):
replace_with_shifted_mka(final_file, german_audio_file, stream_num, op_ed_offset)
if os.path.isfile(german_audio_file):
os.remove(path=german_audio_file)
return True
else:
print(f"✖ FAILED: {final_file.name}")
if os.path.isfile(german_audio_file):
os.remove(path=german_audio_file)
new_name = final_file.with_name(f"[FAILED]_{final_file.name}")
final_file.rename(new_name)
return False
def main():
output_dir = "./output_dir"
start_num = 1
episode_num = 11
fault_catch = []
episodes = range(start_num, episode_num + 1)
for i in episodes:
german_pattern = f"Folder0/S01E{i:02}*.mkv"
if os.path.isfile(german_pattern):
German_Video = [german_pattern]
else:
German_Video = glob.glob(german_pattern)
quality_pattern = f"Folder1/S01E{i:02}*.mkv"
if os.path.isfile(quality_pattern):
Quality_Video = [quality_pattern]
else:
Quality_Video = glob.glob(quality_pattern)
# print(f"German_Video={German_Video}")
# print(f"Quality_Video={Quality_Video}")
if len(German_Video) != 1:
raise ValueError(f"Error with German wildcard:\n{german_pattern}\n{German_Video}")
if len(Quality_Video) != 1:
raise ValueError(f"Error with Quality wildcard:\n{quality_pattern}\n{Quality_Video}")
if not process_one(Path(German_Video[0]), Path(Quality_Video[0]), Path(output_dir)):
fault_catch.append([Path(German_Video[0]), Path(Quality_Video[0])])
if fault_catch:
print("\nFailed Videos:")
for ger, orig in fault_catch:
print(f"{ger} + {orig}")
if __name__ == '__main__':
if len(sys.argv) == 3:
if not process_one(German_Video=Path(sys.argv[1]), Quality_Video=Path(sys.argv[2]), output_dir=Path("../output_dir")):
print(f"Video could not be correctly combined")
elif len(sys.argv) == 4:
if not process_one(German_Video=Path(sys.argv[1]), Quality_Video=Path(sys.argv[2]), output_dir=Path(sys.argv[3])):
print(f"Video could not be correctly combined")
else:
main()