A lot of bug fixes and edge cases
This commit is contained in:
1 parent
58e66996cc
commit
004556c4a7
5 files changed
+237
-106
No files matched your search
@@ -8,6 +8,7 @@ from .audio_utils import (
|
|||||||
get_audio_streams,
|
get_audio_streams,
|
||||||
get_track_info,
|
get_track_info,
|
||||||
shift_audio,
|
shift_audio,
|
||||||
|
add_silence,
|
||||||
replace_with_shifted_mka
|
replace_with_shifted_mka
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+148
-65
@@ -35,8 +35,9 @@ def extract_audio_segment_file(video: Path, start_time: float, end_time: float,
|
|||||||
"-i", video,
|
"-i", video,
|
||||||
"-ss", str(start_time),
|
"-ss", str(start_time),
|
||||||
"-to", str(end_time),
|
"-to", str(end_time),
|
||||||
"-map", "0:a:0", # first audio track
|
"-map", "0:a:0",
|
||||||
"-c:a", "copy", # no re-encode
|
"-ac", "2", # force stereo
|
||||||
|
"-c:a", "aac", # re-encode
|
||||||
out_audio
|
out_audio
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -152,32 +153,55 @@ def get_audio_duration(file_path: Path):
|
|||||||
res += float(result[i])*(60**i)
|
res += float(result[i])*(60**i)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def stick_together_audio(left_audio: Path, right_audio: Path, output_audio: Path) -> None:
|
### V1
|
||||||
"""
|
# def stick_together_audio(left_audio: Path, right_audio: Path, output_audio: Path) -> None:
|
||||||
Concatenate two audio files so that left_audio plays directly
|
# """
|
||||||
before right_audio in the output file.
|
# Concatenate two audio files so that left_audio plays directly
|
||||||
"""
|
# before right_audio in the output file.
|
||||||
|
# """
|
||||||
|
|
||||||
|
# left_audio = Path(left_audio)
|
||||||
|
# right_audio = Path(right_audio)
|
||||||
|
# output_audio = Path(output_audio)
|
||||||
|
|
||||||
|
# # Use stem instead of prefix to create a temporary file
|
||||||
|
# output_audio_tmp = output_audio.parent / (output_audio.stem + "_temp" + output_audio.suffix)
|
||||||
|
|
||||||
|
# run([
|
||||||
|
# "ffmpeg",
|
||||||
|
# "-y",
|
||||||
|
# "-i", str(left_audio),
|
||||||
|
# "-i", str(right_audio),
|
||||||
|
# "-filter_complex", "[0:a][1:a]concat=n=2:v=0:a=1[a]",
|
||||||
|
# "-map", "[a]",
|
||||||
|
# "-c:a", "opus", # or "aac" if you prefer
|
||||||
|
# "-strict", "-2",
|
||||||
|
# str(output_audio_tmp)
|
||||||
|
# ])
|
||||||
|
|
||||||
|
# shutil.move(output_audio_tmp, output_audio)
|
||||||
|
|
||||||
|
def stick_together_audio(left_audio: Path, right_audio: Path, output_audio: Path) -> None:
|
||||||
left_audio = Path(left_audio)
|
left_audio = Path(left_audio)
|
||||||
right_audio = Path(right_audio)
|
right_audio = Path(right_audio)
|
||||||
output_audio = Path(output_audio)
|
output_audio = Path(output_audio)
|
||||||
|
|
||||||
# Use stem instead of prefix to create a temporary file
|
with tempfile.NamedTemporaryFile("w", delete=False, suffix=".txt") as f:
|
||||||
output_audio_tmp = output_audio.parent / (output_audio.stem + "_temp" + output_audio.suffix)
|
f.write(f"file '{left_audio.resolve()}'\n")
|
||||||
|
f.write(f"file '{right_audio.resolve()}'\n")
|
||||||
run([
|
list_file = f.name
|
||||||
"ffmpeg",
|
try:
|
||||||
"-y",
|
run([
|
||||||
"-i", str(left_audio),
|
"ffmpeg",
|
||||||
"-i", str(right_audio),
|
"-y",
|
||||||
"-filter_complex", "[0:a][1:a]concat=n=2:v=0:a=1[a]",
|
"-f", "concat",
|
||||||
"-map", "[a]",
|
"-safe", "0",
|
||||||
"-c:a", "opus", # or "aac" if you prefer
|
"-i", list_file,
|
||||||
"-strict", "-2",
|
"-c", "copy", # no re-encode
|
||||||
str(output_audio_tmp)
|
str(output_audio)
|
||||||
])
|
])
|
||||||
|
except Exception as e:
|
||||||
shutil.move(output_audio_tmp, output_audio)
|
raise BrokenPipeError(f"{e}\n{f.readlines(-1)}")
|
||||||
|
|
||||||
def get_audio_streams(file: Path) -> List[int]:
|
def get_audio_streams(file: Path) -> List[int]:
|
||||||
"""
|
"""
|
||||||
@@ -224,6 +248,9 @@ def get_track_info(file: Path, audioline: int):
|
|||||||
return {"codec": "aac", "bitrate": "192k", "lang": "eng", "title": "Audio"}
|
return {"codec": "aac", "bitrate": "192k", "lang": "eng", "title": "Audio"}
|
||||||
|
|
||||||
def shift_audio(file: Path, audioline: int, offset: float):
|
def shift_audio(file: Path, audioline: int, offset: float):
|
||||||
|
"""
|
||||||
|
Shifts audio by offset sekonds to the right.
|
||||||
|
"""
|
||||||
if not file.is_file(): return
|
if not file.is_file(): return
|
||||||
|
|
||||||
info = get_track_info(file, audioline)
|
info = get_track_info(file, audioline)
|
||||||
@@ -258,7 +285,7 @@ def shift_audio(file: Path, audioline: int, offset: float):
|
|||||||
"-b:a", str(info['bitrate']),
|
"-b:a", str(info['bitrate']),
|
||||||
str(temp_mka)
|
str(temp_mka)
|
||||||
]
|
]
|
||||||
subprocess.run(process_cmd, check=True)
|
run(process_cmd)
|
||||||
|
|
||||||
# --- STEP 2: Replace in MKV via mkvmerge ---
|
# --- STEP 2: Replace in MKV via mkvmerge ---
|
||||||
# --audio-tracks !ID excludes the old track
|
# --audio-tracks !ID excludes the old track
|
||||||
@@ -271,13 +298,71 @@ def shift_audio(file: Path, audioline: int, offset: float):
|
|||||||
str(temp_mka)
|
str(temp_mka)
|
||||||
]
|
]
|
||||||
|
|
||||||
subprocess.run(merge_cmd, check=True)
|
run(merge_cmd)
|
||||||
|
|
||||||
# Cleanup and Replace
|
# Cleanup and Replace
|
||||||
shutil.move(str(output_mkv), str(file))
|
shutil.move(str(output_mkv), str(file))
|
||||||
temp_mka.unlink()
|
temp_mka.unlink()
|
||||||
print(f"Done: {file.name} updated physically.")
|
print(f"Done: {file.name} updated physically.")
|
||||||
|
|
||||||
|
def add_silence(file: Path, offset: float) -> None:
|
||||||
|
"""
|
||||||
|
Hard-edits an MKA file by adding silence.
|
||||||
|
Positive offset: Adds silence at the beginning.
|
||||||
|
Negative offset: Adds silence at the end.
|
||||||
|
"""
|
||||||
|
if not file.is_file():
|
||||||
|
print(f"File not found: {file}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if offset == 0:
|
||||||
|
print("Offset is 0, no silence to add.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create a normal file path right next to the original file
|
||||||
|
new_file = file.parent / f"silenced_{file.name}"
|
||||||
|
|
||||||
|
# 1. Determine the filter
|
||||||
|
if offset > 0:
|
||||||
|
# Silence at the beginning: delay all channels
|
||||||
|
ms = int(offset * 1000)
|
||||||
|
audio_filter = f"adelay={ms}:all=1"
|
||||||
|
action_text = "beginning"
|
||||||
|
else:
|
||||||
|
# Silence at the end: pad with specific duration in seconds
|
||||||
|
sec = abs(offset)
|
||||||
|
audio_filter = f"apad=pad_dur={sec}"
|
||||||
|
action_text = "end"
|
||||||
|
|
||||||
|
# 2. Execute FFmpeg
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg", "-y",
|
||||||
|
"-i", str(file),
|
||||||
|
"-af", audio_filter,
|
||||||
|
"-c:a", "libopus",
|
||||||
|
str(new_file)
|
||||||
|
]
|
||||||
|
|
||||||
|
# print(f"Adding {abs(offset)}s of silence to the {action_text} of {file.name}...")
|
||||||
|
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
|
# 3. Verify and replace
|
||||||
|
if result.returncode == 0:
|
||||||
|
# Quick safety check to ensure FFmpeg didn't output an empty file
|
||||||
|
if new_file.exists() and new_file.stat().st_size > 0:
|
||||||
|
shutil.move(str(new_file), str(file))
|
||||||
|
# print(f"Successfully updated: {file.name}")
|
||||||
|
else:
|
||||||
|
print("Error: FFmpeg succeeded, but the output file is empty.")
|
||||||
|
if new_file.exists():
|
||||||
|
new_file.unlink()
|
||||||
|
else:
|
||||||
|
print(f"FFmpeg Error:\n{result.stderr}")
|
||||||
|
# Clean up the leftover file if the process failed
|
||||||
|
if new_file.exists():
|
||||||
|
new_file.unlink()
|
||||||
|
|
||||||
def replace_with_shifted_mka(mkv_file: Path, mka_source: Path, audioline: int, offset: float):
|
def replace_with_shifted_mka(mkv_file: Path, mka_source: Path, audioline: int, offset: float):
|
||||||
"""
|
"""
|
||||||
Physically shifts an existing MKA and replaces a track in the MKV.
|
Physically shifts an existing MKA and replaces a track in the MKV.
|
||||||
@@ -289,45 +374,43 @@ def replace_with_shifted_mka(mkv_file: Path, mka_source: Path, audioline: int, o
|
|||||||
return
|
return
|
||||||
|
|
||||||
# mkvmerge IDs: 0 is usually video, so audioline 0 is track 1, etc.
|
# mkvmerge IDs: 0 is usually video, so audioline 0 is track 1, etc.
|
||||||
mkv_track_id = audioline + 1
|
mkv_track_id = audioline + 1
|
||||||
|
|
||||||
|
shifted_mka = mkv_file.parent / "shifted.mka"
|
||||||
|
output_mkv = mkv_file.parent / "final_output.mkv"
|
||||||
|
|
||||||
|
# --- STEP 1: Physical Shift ---
|
||||||
|
# Positive offset: adelay (adds real silence packets)
|
||||||
|
# Negative offset: atrim (removes packets) + asetpts (resets timestamps)
|
||||||
|
ms = int(abs(offset) * 1000)
|
||||||
|
if offset > 0:
|
||||||
|
filter_chain = f"adelay={ms}:all=1"
|
||||||
|
else:
|
||||||
|
filter_chain = f"atrim=start={abs(offset)},asetpts=PTS-STARTPTS"
|
||||||
|
|
||||||
|
print(f"Physically shifting {mka_source.name}...")
|
||||||
|
process_cmd = [
|
||||||
|
"ffmpeg", "-y", "-i", str(mka_source),
|
||||||
|
"-af", filter_chain,
|
||||||
|
"-c:a", "libopus", # Set to Opus as per your track name
|
||||||
|
"-b:a", "160k", # Standard high-quality bitrate for stereo Opus
|
||||||
|
str(shifted_mka)
|
||||||
|
]
|
||||||
|
subprocess.run(process_cmd, check=True, capture_output=True)
|
||||||
|
|
||||||
|
# --- STEP 2: Replace track in MKV ---
|
||||||
|
# --audio-tracks !ID excludes the old line from the original MKV
|
||||||
|
print(f"Replacing track {audioline} in {mkv_file.name}...")
|
||||||
|
merge_cmd = [
|
||||||
|
"mkvmerge", "-o", str(output_mkv),
|
||||||
|
"--audio-tracks", f"!{mkv_track_id}", str(mkv_file),
|
||||||
|
"--language", "0:ger",
|
||||||
|
"--track-name", "0:German (Opus Stereo)",
|
||||||
|
str(shifted_mka)
|
||||||
|
]
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
subprocess.run(merge_cmd, check=True, capture_output=True)
|
||||||
temp_path = Path(temp_dir)
|
|
||||||
shifted_mka = temp_path / "shifted.mka"
|
|
||||||
output_mkv = temp_path / "final_output.mkv"
|
|
||||||
|
|
||||||
# --- STEP 1: Physical Shift ---
|
# --- STEP 3: Cleanup and Overwrite ---
|
||||||
# Positive offset: adelay (adds real silence packets)
|
shutil.move(str(output_mkv), str(mkv_file))
|
||||||
# Negative offset: atrim (removes packets) + asetpts (resets timestamps)
|
# print(f"Success: Updated {mkv_file.name}")
|
||||||
ms = int(abs(offset) * 1000)
|
|
||||||
if offset > 0:
|
|
||||||
filter_chain = f"adelay={ms}:all=1"
|
|
||||||
else:
|
|
||||||
filter_chain = f"atrim=start={abs(offset)},asetpts=PTS-STARTPTS"
|
|
||||||
|
|
||||||
print(f"Physically shifting {mka_source.name}...")
|
|
||||||
process_cmd = [
|
|
||||||
"ffmpeg", "-y", "-i", str(mka_source),
|
|
||||||
"-af", filter_chain,
|
|
||||||
"-c:a", "libopus", # Set to Opus as per your track name
|
|
||||||
"-b:a", "160k", # Standard high-quality bitrate for stereo Opus
|
|
||||||
str(shifted_mka)
|
|
||||||
]
|
|
||||||
subprocess.run(process_cmd, check=True, capture_output=True)
|
|
||||||
|
|
||||||
# --- STEP 2: Replace track in MKV ---
|
|
||||||
# --audio-tracks !ID excludes the old line from the original MKV
|
|
||||||
print(f"Replacing track {audioline} in {mkv_file.name}...")
|
|
||||||
merge_cmd = [
|
|
||||||
"mkvmerge", "-o", str(output_mkv),
|
|
||||||
"--audio-tracks", f"!{mkv_track_id}", str(mkv_file),
|
|
||||||
"--language", "0:ger",
|
|
||||||
"--track-name", "0:German (Opus Stereo)",
|
|
||||||
str(shifted_mka)
|
|
||||||
]
|
|
||||||
|
|
||||||
subprocess.run(merge_cmd, check=True, capture_output=True)
|
|
||||||
|
|
||||||
# --- STEP 3: Cleanup and Overwrite ---
|
|
||||||
shutil.move(str(output_mkv), str(mkv_file))
|
|
||||||
print(f"Success: Updated {mkv_file.name}")
|
|
||||||
@@ -6,13 +6,15 @@ import glob
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import subprocess
|
import subprocess
|
||||||
from utils import run
|
from utils import run
|
||||||
from audio import extract_audio_segment_file, get_audio_duration, stick_together_audio, replace_with_shifted_mka
|
from audio import extract_audio_segment_file, get_audio_duration, stick_together_audio, replace_with_shifted_mka, add_silence
|
||||||
from video import probe_video, find_temporal_offset, corelate_videos
|
from video import probe_video, find_temporal_offset, corelate_videos
|
||||||
from mkv import check_intro_outro_sync
|
from mkv import check_intro_outro_sync
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
|
MIN_END_DUR = 2.5
|
||||||
|
|
||||||
def mux_audio_into_output(Quality_Video, audio_file, output_dir):
|
def mux_audio_into_output(Quality_Video, audio_file, output_dir):
|
||||||
"""Mux trimmed audio into Quality_Video and place final file in output_dir."""
|
"""Mux trimmed audio into Quality_Video and place final file in output_dir."""
|
||||||
output_dir = Path(output_dir)
|
output_dir = Path(output_dir)
|
||||||
@@ -82,6 +84,8 @@ def process_one(German_Video: Path, Quality_Video: Path, output_dir: Path, offse
|
|||||||
|
|
||||||
if step == "Extract pre beginning":
|
if step == "Extract pre beginning":
|
||||||
german_audio_file = output_dir / f"{Path(Quality_Video).stem}_german_audio.mka"
|
german_audio_file = output_dir / f"{Path(Quality_Video).stem}_german_audio.mka"
|
||||||
|
# german_audio_file1 = output_dir / f"{Path(Quality_Video).stem}_german_audio1.mka"
|
||||||
|
# german_audio_file2 = output_dir / f"{Path(Quality_Video).stem}_german_audio2.mka"
|
||||||
quality_audio_file1 = output_dir / f"{Path(Quality_Video).stem}_original_audio1.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"
|
quality_audio_file2 = output_dir / f"{Path(Quality_Video).stem}_original_audio2.mka"
|
||||||
|
|
||||||
@@ -94,6 +98,7 @@ def process_one(German_Video: Path, Quality_Video: Path, output_dir: Path, offse
|
|||||||
### Get the first part till german starts (if german does not start before file)
|
### Get the first part till german starts (if german does not start before file)
|
||||||
start2 = vstart2
|
start2 = vstart2
|
||||||
end2 = start2 + offset0
|
end2 = start2 + offset0
|
||||||
|
first_part = [start2, end2]
|
||||||
if end2 - start2 > 0.05:
|
if end2 - start2 > 0.05:
|
||||||
if DEBUG: print(f"Cutting audio ({quality_audio_file1}) (from timestamp {start2:.6f}s) to timestamp {end2:.6f}s")
|
if DEBUG: print(f"Cutting audio ({quality_audio_file1}) (from timestamp {start2:.6f}s) to timestamp {end2:.6f}s")
|
||||||
try:
|
try:
|
||||||
@@ -114,6 +119,7 @@ def process_one(German_Video: Path, Quality_Video: Path, output_dir: Path, offse
|
|||||||
else:
|
else:
|
||||||
start = vstart
|
start = vstart
|
||||||
end = min(vstart + duration, start + duration2)
|
end = min(vstart + duration, start + duration2)
|
||||||
|
main_part = [start, end]
|
||||||
if DEBUG: print(f"Cutting audio ({german_audio_file}) (from timestamp {start:.6f}s) to timestamp {end:.6f}s")
|
if DEBUG: print(f"Cutting audio ({german_audio_file}) (from timestamp {start:.6f}s) to timestamp {end:.6f}s")
|
||||||
try:
|
try:
|
||||||
extract_audio_segment_file(German_Video, start, end, german_audio_file)
|
extract_audio_segment_file(German_Video, start, end, german_audio_file)
|
||||||
@@ -131,11 +137,12 @@ def process_one(German_Video: Path, Quality_Video: Path, output_dir: Path, offse
|
|||||||
start2 = vstart2 + end
|
start2 = vstart2 + end
|
||||||
# end2 = start2 + (duration2 - duration - (n1 / fps))
|
# end2 = start2 + (duration2 - duration - (n1 / fps))
|
||||||
end2 = vstart2 + get_audio_duration(Quality_Video)
|
end2 = vstart2 + get_audio_duration(Quality_Video)
|
||||||
|
last_part = [start2, end2]
|
||||||
if end2 - start2 > 0.5:
|
if end2 - start2 > 0.5:
|
||||||
if DEBUG: print(f"Cutting audio ({quality_audio_file2}) (from timestamp {start2:.6f}s) to timestamp {end2:.6f}s")
|
if DEBUG: print(f"Cutting audio ({quality_audio_file2}) (from timestamp {start2:.6f}s) to timestamp {end2:.6f}s")
|
||||||
try:
|
try:
|
||||||
extract_audio_segment_file(Quality_Video, start2, end2, quality_audio_file2)
|
extract_audio_segment_file(Quality_Video, start2, end2, quality_audio_file2)
|
||||||
except subprocess.CalledProcessError as e:
|
except Exception as e:
|
||||||
raise BrokenPipeError(
|
raise BrokenPipeError(
|
||||||
f"{e}\n"
|
f"{e}\n"
|
||||||
f"Trying to extract Audio from {Quality_Video} {start2}-{end2} "
|
f"Trying to extract Audio from {Quality_Video} {start2}-{end2} "
|
||||||
@@ -147,9 +154,32 @@ def process_one(German_Video: Path, Quality_Video: Path, output_dir: Path, offse
|
|||||||
|
|
||||||
# Combine the audio files
|
# Combine the audio files
|
||||||
if os.path.isfile(quality_audio_file1):
|
if os.path.isfile(quality_audio_file1):
|
||||||
stick_together_audio(quality_audio_file1, german_audio_file, german_audio_file)
|
try:
|
||||||
|
dur_q1 = get_audio_duration(quality_audio_file1)
|
||||||
|
if dur_q1:
|
||||||
|
stick_together_audio(quality_audio_file1, german_audio_file, german_audio_file)
|
||||||
|
else: raise
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
add_silence(german_audio_file, offset0)
|
||||||
|
except Exception as e:
|
||||||
|
raise BrokenPipeError(
|
||||||
|
f"{e}\n"
|
||||||
|
f"Trying to put together {quality_audio_file1} and {german_audio_file}"
|
||||||
|
f"into {german_audio_file}. First file start, end is {first_part} and second is {main_part}"
|
||||||
|
)
|
||||||
|
|
||||||
if os.path.isfile(quality_audio_file2):
|
if os.path.isfile(quality_audio_file2):
|
||||||
stick_together_audio(german_audio_file, quality_audio_file2, german_audio_file)
|
try:
|
||||||
|
dur_q2 = get_audio_duration(quality_audio_file2)
|
||||||
|
if dur_q2 > MIN_END_DUR:
|
||||||
|
stick_together_audio(german_audio_file, quality_audio_file2, german_audio_file)
|
||||||
|
except:
|
||||||
|
raise BrokenPipeError(
|
||||||
|
f"{e}\n"
|
||||||
|
f"Trying to put together {german_audio_file} and {quality_audio_file2}"
|
||||||
|
f"into {german_audio_file}. First file start, end is {main_part} and second is {last_part}"
|
||||||
|
)
|
||||||
|
|
||||||
if step == "Mux":
|
if step == "Mux":
|
||||||
|
|
||||||
@@ -162,6 +192,7 @@ def process_one(German_Video: Path, Quality_Video: Path, output_dir: Path, offse
|
|||||||
os.remove(quality_audio_file1)
|
os.remove(quality_audio_file1)
|
||||||
if os.path.isfile(quality_audio_file2):
|
if os.path.isfile(quality_audio_file2):
|
||||||
os.remove(quality_audio_file2)
|
os.remove(quality_audio_file2)
|
||||||
|
# pass
|
||||||
|
|
||||||
if step == "Final check":
|
if step == "Final check":
|
||||||
try:
|
try:
|
||||||
@@ -192,20 +223,25 @@ def process_one(German_Video: Path, Quality_Video: Path, output_dir: Path, offse
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
output_dir = "./output_dir"
|
output_dir = "/home/honney/mount/Media/tmp/serien/ready/Classroom of the Elite/Season 01"
|
||||||
start_num = 1
|
if not os.path.isdir(output_dir):
|
||||||
episode_num = 11
|
raise NotADirectoryError(f"Output directory is not a valid Directory: {output_dir}")
|
||||||
|
start_num = 2
|
||||||
|
episode_num = 24
|
||||||
fault_catch = []
|
fault_catch = []
|
||||||
|
|
||||||
episodes = range(start_num, episode_num + 1)
|
episodes = range(start_num, episode_num + 1)
|
||||||
|
# episodes = [1]
|
||||||
|
|
||||||
for i in tqdm(episodes, desc="Episodes"):
|
for i in tqdm(episodes, desc="Episodes"):
|
||||||
german_pattern = f"Folder0/S01E{i:02}*.mkv"
|
# i =1
|
||||||
|
# if i:
|
||||||
|
german_pattern = f"/home/honney/mount/Media/tmp/serien/ger_low_quality/Classroom of the Elite (2017-2024) [imdbid-tt7263328]/Season 01/Classroom of the Elite S01E0{i:02}.mkv"
|
||||||
if os.path.isfile(german_pattern):
|
if os.path.isfile(german_pattern):
|
||||||
German_Video = [german_pattern]
|
German_Video = [german_pattern]
|
||||||
else:
|
else:
|
||||||
German_Video = glob.glob(german_pattern)
|
German_Video = glob.glob(german_pattern)
|
||||||
quality_pattern = f"Folder1/S01E{i:02}*.mkv"
|
quality_pattern = f"/home/honney/mount/Media/tmp/serien/high_quality/Classroom of the Elite/S01/E{i:02}*"
|
||||||
if os.path.isfile(quality_pattern):
|
if os.path.isfile(quality_pattern):
|
||||||
Quality_Video = [quality_pattern]
|
Quality_Video = [quality_pattern]
|
||||||
else:
|
else:
|
||||||
|
|||||||
+23
-20
@@ -35,32 +35,35 @@ from .intro_outro importme, duration) for each chapter."""
|
|||||||
def get_intro_outro(file: Path, length: float = 90.0) -> List[Tuple[float, float]]:
|
def get_intro_outro(file: Path, length: float = 90.0) -> List[Tuple[float, float]]:
|
||||||
try:
|
try:
|
||||||
chapters = get_chapters(Path(file))
|
chapters = get_chapters(Path(file))
|
||||||
except:
|
multiplier = 2
|
||||||
raise AttributeError(f"{file} has no chapter Marks")
|
|
||||||
multiplier = 2
|
|
||||||
min_len = length / multiplier
|
|
||||||
max_len = length * multiplier
|
|
||||||
filtered = [
|
|
||||||
(start, dur) for start, dur in chapters
|
|
||||||
if min_len <= dur <= max_len
|
|
||||||
]
|
|
||||||
while len(filtered) > 2 and multiplier <= 1.1:
|
|
||||||
multiplier -= 0.1
|
|
||||||
min_len = length / multiplier
|
min_len = length / multiplier
|
||||||
max_len = length * multiplier
|
max_len = length * multiplier
|
||||||
filtered = [
|
filtered = [
|
||||||
(start, dur) for start, dur in chapters
|
(start, dur) for start, dur in chapters
|
||||||
if min_len <= dur <= max_len
|
if min_len <= dur <= max_len
|
||||||
]
|
]
|
||||||
if len(filtered) == 2:
|
while len(filtered) > 2 and multiplier <= 1.1:
|
||||||
return filtered
|
multiplier -= 0.1
|
||||||
elif len(filtered) == 1:
|
min_len = length / multiplier
|
||||||
### Found only a Intro or Outro
|
max_len = length * multiplier
|
||||||
raise ValueError(f"Could only find one intro or outro:\n{filtered}")
|
filtered = [
|
||||||
elif len(filtered) == 0:
|
(start, dur) for start, dur in chapters
|
||||||
raise ValueError(f"Found no outro or intro:\n{filtered}")
|
if min_len <= dur <= max_len
|
||||||
else:
|
]
|
||||||
raise ValueError(f"Found too many intros/outros:\n{filtered}")
|
if len(filtered) == 2:
|
||||||
|
return filtered
|
||||||
|
elif len(filtered) == 1:
|
||||||
|
### Found only a Intro or Outro
|
||||||
|
raise ValueError(f"Could only find one intro or outro:\n{filtered}")
|
||||||
|
elif len(filtered) == 0:
|
||||||
|
raise ValueError(f"Found no outro or intro:\n{filtered}")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Found too many intros/outros:\n{filtered}")
|
||||||
|
except:
|
||||||
|
raise AttributeError(f"{file} has no chapter Marks")
|
||||||
|
|
||||||
|
### TODO: Find more alternative ways to find intro and outro, possible: https://github.com/aniskip/aniskip-cli
|
||||||
|
### https://github.com/jonbarrow/open-anime-timestamps?utm_source=chatgpt.com
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
file = "/home/honney/projects/mkv/output_dir/08.mkv"
|
file = "/home/honney/projects/mkv/output_dir/08.mkv"
|
||||||
|
|||||||
+20
-12
@@ -7,28 +7,36 @@ sys.path.append(str(Path(__file__).resolve().parent.parent))
|
|||||||
from utils import run
|
from utils import run
|
||||||
### Normal Imports:
|
### Normal Imports:
|
||||||
|
|
||||||
|
from subprocess import run, PIPE
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
|
||||||
def probe_video(path: Path) -> tuple[float, float, str, float]:
|
def probe_video(path: Path) -> tuple[float, float, str, float]:
|
||||||
"""
|
|
||||||
Get video FPS, duration, codec, and stream start time.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path: To Video File
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
fps, duration, codec, start_time
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
cmd = [
|
cmd = [
|
||||||
"ffprobe",
|
"ffprobe",
|
||||||
"-v", "error",
|
"-v", "error",
|
||||||
"-print_format", "json",
|
"-print_format", "json",
|
||||||
"-show_streams",
|
"-show_streams",
|
||||||
"-show_format",
|
"-show_format",
|
||||||
path
|
str(path), # IMPORTANT
|
||||||
]
|
]
|
||||||
data = json.loads(run(cmd).stdout)
|
|
||||||
|
result = run(cmd, stdout=PIPE, stderr=PIPE, text=True)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(f"ffprobe failed:\n{result.stderr}")
|
||||||
|
|
||||||
|
if not result.stdout.strip():
|
||||||
|
raise RuntimeError("ffprobe returned empty output")
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(result.stdout)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print("RAW OUTPUT:\n", result.stdout) # debug
|
||||||
|
raise
|
||||||
|
|
||||||
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("/")
|
||||||
|
|||||||
Reference in new issue
Block a user