DVCheck/dv_check.py

235 lines
6.4 KiB
Python
Raw Normal View History

2024-07-21 23:01:21 -04:00
import argparse
import json
import subprocess
import sys
from pathlib import Path
from pymediainfo import MediaInfo
from tkinter import Tk, messagebox
from typing import Optional, Tuple
__version__ = "0.1.0"
def exit_print(message: Optional[str], exit_code: Optional[int]) -> None:
print(message if message else "")
sys.exit(exit_code if exit_code else 0)
def cli() -> Tuple[Path, Path, Path, bool, bool]:
parser = argparse.ArgumentParser(prog="DV Check")
parser.add_argument("-f", "--ffmpeg", type=str, help="Path to FFMPEG executable.")
parser.add_argument(
"-d", "--dovi-tool", type=str, help="Path to dovi_tool executable."
)
parser.add_argument(
"-p",
"--prompt",
action="store_true",
help="Prompt user with a GUI prompt if FEL.",
)
parser.add_argument(
"-c",
"--create-fel-mkv",
action="store_true",
help="Create FEL output mkv if FEL layer exists (input name_el.ext).",
)
parser.add_argument("input", type=str, help="Path to input file.")
args = parser.parse_args()
if not args.input:
exit_print("Program requires an input file", 1)
if not args.ffmpeg:
exit_print("Program requires FFEMPG", 1)
if not args.dovi_tool:
exit_print("Program requires dovi_tool", 1)
return (
Path(args.input),
Path(args.ffmpeg),
Path(args.dovi_tool),
args.prompt,
args.create_fel_mkv,
)
def detect_el_layer(file_input: Path) -> bool:
if not file_input.exists() or not file_input.is_file():
exit_print("Input file not found", 1)
if file_input.suffix not in (".mp4", ".mkv"):
exit_print("Only MP4/MKV input's are supported", 1)
media_info = MediaInfo.parse(file_input)
try:
video_track = media_info.video_tracks[0]
hdr_format = video_track.hdr_format
if not hdr_format:
exit_print("No HDR detected", 0)
if "Dolby Vision" in hdr_format:
hdr_format_settings = video_track.hdr_format_settings
if not hdr_format_settings:
exit_print("No HDR detected", 0)
if "EL" in hdr_format_settings:
return True
else:
exit_print("EL layer not detected", 0)
else:
exit_print("No dolby vision metadata detected", 0)
except IndexError:
exit_print("Input has no video track", 1)
return False
def read_rpu(
file_input: Path, ffmpeg_path: Path, dovi_tool_path: Path
) -> Optional[str]:
# output_el_hevc = file_input.with_suffix(".hevc").with_name(f"{file_input.stem}_el")
output_bin = file_input.with_suffix(".bin")
# Change the working directory to the directory containing the input file
file_input_parent = file_input.parent
# Define the ffmpeg command
ffmpeg_command = [
str(ffmpeg_path),
"-ss",
"00:00:10",
"-i",
str(file_input),
"-t",
"00:00:01",
"-map",
"0:v:0",
"-c:v:0",
"copy",
"-bsf:v",
"hevc_mp4toannexb",
"-f",
"hevc",
"-",
"-hide_banner",
"-v",
"panic",
]
# Define the dovi_tool command
dovi_tool_command = [dovi_tool_path, "extract-rpu", "-", "-o", str(output_bin)]
# Run the ffmpeg command and pipe its output to the dovi_tool command
with subprocess.Popen(
ffmpeg_command, cwd=file_input_parent, stdout=subprocess.PIPE
) as ffmpeg_proc:
with subprocess.Popen(
dovi_tool_command,
cwd=file_input_parent,
stdin=ffmpeg_proc.stdout,
stdout=subprocess.DEVNULL,
) as dovi_proc:
ffmpeg_proc.stdout.close() # Close the ffmpeg stdout to allow it to receive a SIGPIPE if dovi_proc exits
dovi_proc.communicate() # Wait for the dovi_tool process to complete
if output_bin.exists():
rpu_data_cmd = [dovi_tool_path, "info", "-f", "0", "-i", str(output_bin)]
rpu_data_job = subprocess.run(rpu_data_cmd, check=True, capture_output=True)
rpu_stdout_data = json.loads(
rpu_data_job.stdout.decode().split("Parsing RPU file...")[1]
)
el_type = rpu_stdout_data.get("el_type")
# clean up files
output_bin.unlink()
return el_type
def prompt_user() -> None:
root = Tk()
root.withdraw()
root.attributes('-topmost', True)
messagebox.showinfo(
"Alert",
"FEL source detected, please switch to a FEL enabled template to process this encode.",
parent=root
)
root.destroy()
root.mainloop()
def create_fel(file_input: Path, ffmpeg_path: Path, dovi_tool_path: Path) -> None:
ffmpeg_command = [
str(ffmpeg_path),
"-i",
str(file_input),
"-map",
"0:v:0",
"-c:v:0",
"copy",
"-bsf:v",
"hevc_mp4toannexb",
"-f",
"hevc",
"-",
"-hide_banner",
"-v",
"panic",
"-stats",
]
el_output_path = file_input.with_name(f"{file_input.stem}_el.hevc")
dovi_tool_command = [
dovi_tool_path,
"demux",
"--el-only",
"-",
"-e",
str(el_output_path),
]
with subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE) as ffmpeg_proc:
with subprocess.Popen(
dovi_tool_command,
stdin=ffmpeg_proc.stdout,
) as dovi_proc:
ffmpeg_proc.stdout.close() # Close the ffmpeg stdout to allow it to receive a SIGPIPE if dovi_proc exits
dovi_proc.communicate() # Wait for the dovi_tool process to complete
ffmpeg_mux_command = [
str(ffmpeg_path),
"-y",
"-i",
str(el_output_path),
"-c",
"copy",
"-f",
"hevc",
str(el_output_path.with_suffix(".mkv")),
]
subprocess.run(ffmpeg_mux_command)
if __name__ == "__main__":
file_input, ffmpeg, dovi_tool, prompt, create_fel_mkv = cli()
detect_el = detect_el_layer(file_input)
if detect_el:
rpu = read_rpu(file_input, ffmpeg, dovi_tool)
if rpu:
rpu = rpu.strip()
print(rpu)
if prompt and rpu == "FEL":
prompt_user()
if create_fel_mkv:
create_fel(file_input, ffmpeg, dovi_tool)
else:
print("No detected EL layer")
exit_print("", 0)