DVCheck/dv_check.py
2024-07-21 23:02:28 -04:00

235 lines
6.4 KiB
Python

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)