Files
ffmpeg-recipes/convert_to_dnx.py
2025-09-02 17:57:07 +00:00

211 lines
8.2 KiB
Python

#!/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, dimensions,
# 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)
print(f"Loaded {len(available_dnx_resolutions)} available DNxHD resolutions...")
print(f"Detecting available DNxHD codecs...")
result = subprocess.run(["ffmpeg",
"-hide_banner",
"-loglevel",
"error",
"-f",
"lavfi",
"-i",
"testsrc2",
"-c:v",
"dnxhd",
"-f",
"null",
"-"], capture_output=True)
ffmpeg_dnx_resolutions = []
for line in result.stderr.splitlines():
line = line.decode("utf-8")
matches = re.match(r".*Frame size: (\d+)x(\d+)([pi]).*", line)
if matches is not None:
width, height = matches[1], matches[2]
interlace = matches[3]
matches = re.match(r".*format: (yuv\w+).*", line)
assert matches
fmt = matches[1]
matches = re.match(r".*bitrate: (\d+)Mbps.*", line)
assert matches
mbps = matches[1]
ffmpeg_dnx_resolutions += [{
"width": width,
"height": height,
"pix_fmt": fmt,
"mb": mbps,
"interlaced": interlace
}]
print(f"Detected {len(ffmpeg_dnx_resolutions)} codec resolutions...")
def filter_pred(res):
def inner_pred(ff_res):
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"]
return result
found = filter(inner_pred,ffmpeg_dnx_resolutions)
return len(list(found)) > 0
available_dnx_resolutions = list(filter(filter_pred, available_dnx_resolutions))
print(f"{len(available_dnx_resolutions)} resolutions are available and detected...")
input_file = sys.argv[1]
print(f"Probing input file {input_file}")
result = subprocess.run(["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"
print("Input file characteristics...")
print(f" - pix fmt = {pix_fmt}")
print(f" - frame size = {frame_size[0]}x{frame_size[1]}")
print(f" - frame rate = {frame_rate}")
print(f" - field order = {field_order_progressive}")
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']}""")