diff --git a/convert_to_dnx.py b/convert_to_dnx.py new file mode 100644 index 0000000..3e0f7f1 --- /dev/null +++ b/convert_to_dnx.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 + +# convert_to_dnx.py +# by Jamie Hardt +# (c) 2025 Squad 51, Inc. All rights reserved. + +# This script attempts to take an input file and attempts to generate +# the appropriate ffmpeg output arguments (or all possible appropriate +# ffmpeg output arguments) given the file's frame rate, dimesntions, +# and chroma subsampling. +# +# I'm not sure if I meant this seriously or if it's all a big satire of +# DNxHD's codecs. I never use this myself I just convert to prores. + +import sys +import subprocess +import json +import csv +import re +import io +import pprint + +# https://askubuntu.com/questions/907398/how-to-convert-a-video-with-ffmpeg-into-the-dnxhd-dnxhr-format +# https://en.wikipedia.org/wiki/List_of_Avid_DNxHD_resolutions + + +# Resolution,Frame Width, Frame Height,Chroma Subsampling,Bits,Frames Per Second,Megabits per second,Minutes per Gigabyte + +dnx_rates = """Avid DNxHD 440x,1920,1080,p,yuv422p10,60/1,440,0.325 +Avid DNxHD 440,1920,1080,p,yuv422p,60/1,440,0.325 +Avid DNxHD 290,1920,1080,p,yuv422p,60/1,291,0.492 +Avid DNxHD 90,1920,1080,p,yuv422p,60/1,90,1.585 +Avid DNxHD 440x,1920,1080,p,yuv422p10,60000/1001,440,0.325 +Avid DNxHD 440,1920,1080,p,yuv422p,60000/1001,440,0.325 +Avid DNxHD 290,1920,1080,p,yuv422p,60000/1001,291,0.493 +Avid DNxHD 90,1920,1080,p,yuv422p,60000/1001,90,1.585 +Avid DNxHD 365x,1920,1080,p,yuv422p10,50/1,367,0.39 +Avid DNxHD 365,1920,1080,p,yuv422p,50/1,367,0.39 +Avid DNxHD 240,1920,1080,p,yuv422p,50/1,242,0.59 +Avid DNxHD 75,1920,1080,p,yuv422p,50/1,75,1.9 +Avid DNxHD 220x,1920,1080,i,yuv422p10,30000/1001,220,0.651 +Avid DNxHD 220,1920,1080,i,yuv422p,30000/1001,220,0.651 +Avid DNxHD 145,1920,1080,i,yuv422p,30000/1001,145,0.985 +Avid DNxHD 100,1920,1080,i,yuv422p,30000/1001,100,1.429 +Avid DNxHD 440x,1920,1080,p,yuv444p10,30000/1001,440,0.325 +Avid DNxHD 220x,1920,1080,p,yuv422p10,30000/1001,220,0.651 +Avid DNxHD 220,1920,1080,p,yuv422p,30000/1001,220,0.651 +Avid DNxHD 145,1920,1080,p,yuv422p,30000/1001,145,0.985 +Avid DNxHD 100,1920,1080,p,yuv422p,30000/1001,100,1.429 +Avid DNxHD 45,1920,1080,p,yuv422p,30000/1001,45,3.18 +Avid DNxHD 185x,1920,1080,i,yuv422p10,25/1,184,0.78 +Avid DNxHD 185,1920,1080,i,yuv422p,25/1,184,0.78 +Avid DNxHD 120,1920,1080,i,yuv422p,25/1,121,1.713 +Avid DNxHD 85,1920,1080,i,yuv422p,25/1,84,1.181 +Avid DNxHD 365x,1920,1080,p,yuv444p10,25/1,367,0.39 +Avid DNxHD 185x,1920,1080,p,yuv422p10,25/1,184,0.78 +Avid DNxHD 185,1920,1080,p,yuv422p,25/1,184,0.78 +Avid DNxHD 120,1920,1080,p,yuv422p,25/1,121,1.181 +Avid DNxHD 85,1920,1080,p,yuv422p,25/1,84,1.713 +Avid DNxHD 36,1920,1080,p,yuv422p,25/1,36,3.98 +Avid DNxHD 350x,1920,1080,p,yuv444p10,24/1,352,0.406 +Avid DNxHD 175x,1920,1080,p,yuv422p10,24/1,176,0.814 +Avid DNxHD 175,1920,1080,p,yuv422p,24/1,176,0.814 +Avid DNxHD 115,1920,1080,p,yuv422p,24/1,116,1.231 +Avid DNxHD 80,1920,1080,p,yuv422p,24/1,80,1.785 +Avid DNxHD 36,1920,1080,p,yuv422p,24/1,36,3.98 +Avid DNxHD 350x,1920,1080,p,yuv444p10,24000/1001,352,0.407 +Avid DNxHD 175x,1920,1080,p,yuv422p10,24000/1001,176,0.814 +Avid DNxHD 175,1920,1080,p,yuv422p,24000/1001,176,0.814 +Avid DNxHD 115,1920,1080,p,yuv422p,24000/1001,116,1.231 +Avid DNxHD 80,1920,1080,p,yuv422p,24000/1001,80,1.787 +Avid DNxHD 36,1920,1080,p,yuv422p,24000/1001,36,3.98 +Avid DNxHD 220x,1280,720,p,yuv422p10,60000/1001,220,0.651 +Avid DNxHD 220,1280,720,p,yuv422p,60000/1001,220,0.651 +Avid DNxHD 145,1280,720,p,yuv422p,60000/1001,145,0.985 +Avid DNxHD 100,1280,720,p,yuv422p,60000/1001,100,1.402 +Avid DNxHD 175x,1280,720,p,yuv422p10,50/1,175,0.818 +Avid DNxHD 175,1280,720,p,yuv422p,50/1,175,0.818 +Avid DNxHD 115,1280,720,p,yuv422p,50/1,115,1.244 +Avid DNxHD 85,1280,720,p,yuv422p,50/1,85,1.68 +Avid DNxHD 110x,1280,720,p,yuv422p10,30000/1001,110,1.3 +Avid DNxHD 110,1280,720,p,yuv422p,30000/1001,110,1.3 +Avid DNxHD 75,1280,720,p,yuv422p,30000/1001,72,2.05 +Avid DNxHD 50,1280,720,p,yuv422p,30000/1001,51,2.8 +Avid DNxHD 90x,1280,720,p,yuv422p10,25/1,92,1.59 +Avid DNxHD 90,1280,720,p,yuv422p,25/1,92,1.59 +Avid DNxHD 60,1280,720,p,yuv422p,25/1,60,2.39 +Avid DNxHD 45,1280,720,p,yuv422p,25/1,43,3.361 +Avid DNxHD 90x,1280,720,p,yuv422p10,24000/1001,92,1.566 +Avid DNxHD 90,1280,720,p,yuv422p,24000/1001,92,1.566 +Avid DNxHD 60,1280,720,p,yuv422p,24000/1001,60,2.381 +Avid DNxHD 45,1280,720,p,yuv422p,24000/1001,43,3.504 +""" + + + +available_dnx_resolutions = [] +dnx_reader = csv.DictReader(io.StringIO(dnx_rates), + ["name","width","height","interlaced", + "pix_fmt","frame_rate","mb","mins_per_gig"]) + +for res in dnx_reader: + available_dnx_resolutions += [res] + +available_dnx_resolutions = sorted(available_dnx_resolutions, + key=lambda x: float(x["mins_per_gig"]), reverse=True) + +# ffmpeg -loglevel error -f lavfi -i testsrc2 -c:v dnxhd -f null - + +result = subprocess.run(["/usr/local/bin/ffmpeg", + "-hide_banner", + "-loglevel", + "error", + "-f", + "lavfi", + "-i", + "testsrc2", + "-c:v", + "dnxhd", + "-f", + "null", + "-"], capture_output=True) + +# result.check_returncode() + +ffmpeg_dnx_resolutions = [] +for line in result.stderr.splitlines(): + line = line.decode("utf-8") + matches = re.match(".*Frame size: (\d+)x(\d+)([pi]).*", line) + if matches is not None: + width, height = matches[1], matches[2] + interlace = matches[3] + + matches = re.match(".*format: (yuv\w+).*", line) + fmt = matches[1] + + matches = re.match(".*bitrate: (\d+)Mbps.*", line) + mbps = matches[1] + + ffmpeg_dnx_resolutions += [{ + "width": width, + "height": height, + "pix_fmt": fmt, + "mb": mbps, + "interlaced": interlace + }] + +def filter_pred(res): + def inner_pred(ff_res): + # print(res, ff_res) + # input("Press return...") + result = ff_res["interlaced"] == res["interlaced"] and \ + ff_res["width"] == res["width"] and \ + ff_res["height"] == res["height"] and \ + ff_res["pix_fmt"] == res["pix_fmt"] and \ + ff_res["mb"] == res["mb"] + + # print("Result", result) + return result + + found = filter(inner_pred,ffmpeg_dnx_resolutions) + return len(list(found)) > 0 + +available_dnx_resolutions = list(filter(filter_pred, available_dnx_resolutions)) + +# probe input file + +input_file = sys.argv[1] +result = subprocess.run(["/usr/local/bin/ffprobe","-hide_banner","-of","json", + "-show_streams","-select_streams","v", + input_file], capture_output=True) + +result.check_returncode() + +probe_output = json.loads(result.stdout) + +pix_fmt = probe_output["streams"][0]["pix_fmt"] +frame_size = (probe_output["streams"][0]["width"], probe_output["streams"][0]["height"]) +frame_rate = probe_output["streams"][0]["r_frame_rate"] +field_order_progressive = "p" if probe_output["streams"][0]["field_order"] == "progressive" else "i" + +def filter_for_frame_rate(res): + return res["frame_rate"] == frame_rate and res["interlaced"] == field_order_progressive + +def filter_for_scale(res): + return res["width"] == frame_size[0] and res["height"] == frame_size[1] and \ + res["interlaced"] == field_order_progressive + +# print("Available DNx resolutions:") +for n in filter(lambda r: filter_for_frame_rate(r) and filter_for_scale(r), available_dnx_resolutions): + print(f"-vcodec dnxhd -b:v {n['mb']}M -pix_fmt {n['pix_fmt']}") + +# print("Available with scaling:") +for n in filter(filter_for_frame_rate, available_dnx_resolutions): + # https://superuser.com/questions/991371/ffmpeg-scale-and-pad + print(f"""-vf "scale=w={n['width']}:h={n['height']}:force_original_aspect_ratio=1,""" + f"""pad={n['width']}:{n['height']}:(ow-iw)/2:(oh-ih)/2" -vcodec dnxhd -b:v {n['mb']}M -pix_fmt {n['pix_fmt']}""") + + +