Saturday, December 1, 2018

Tutorial - Using FFMPEG for DNxHD/DNxHR encoding, resizing, and batch encoding

Before I begin, I'd like to acknowledge Jon Hall, whose recently deceased web page taught me how to set up FFMPEG in my system path. Also, a shoutout to /u/kichigai on the Reddit /r/editors subreddit for his excellent FFMPEG for editors FAQ:

https://www.reddit.com/r/editors/wiki/ffmpeg

If you want a comprehensive look at FFMPEG for a bunch of editing tasks, the above link is the way to go. If you want a video tutorial for a comprehensive FFMPEG setup, check out my tutorial for setting up FFMPEG 32-bit (along with an alternate install method for AVISynth+).

Or, you could try setting up both the 32-bit and 64-bit versions of FFMPEG (along with AVISynth+) at the same time.


BIG DISCLAIMER: This process may not work, may crash, or do other things to your system. Virus scanning all files is strongly encouraged, but not a 100% guarantee of safety.

You have been warned. 

Also, this tutorial is for Windows 10. Most of the steps work for other OSes, but I won't cover the differences here.



Regardless, I'm going to assume for the rest of this post that you already have FFMPEG installed.

First, let's start with a quick review of an FFMPEG command. Note that the line below doesn't actually run as written; this is just to show you the basic structure of the command:

ffmpeg -i "inputvideo.mp4" -c:v videocodec -c:a audiocodec "outputvideofile.mp4"

Simple enough, right? -i for input, -c:v for video codec, -c:a for audio codec, and then the name of the output file. There's a bunch of other options you can put in amongst this, but basically, this is the way the command is structured.


DNxHD, DNxHR, ProRes and Cineform are all what are known as "intermediate codecs". This means that they're designed to be used to transcode footage from other sources into a form that's easy for video editing programs to work with while maintaining quality. So, unlike most h.264 implimentations, they focus on low CPU usage, retaining as much detail as possible,  and an ability to be re-compressed several times without significant loss in quality. They have larger bitrates than consumer video codecs, but they still represent a significant space savings over fully uncompressed video.

DNxHD and DNxHR are Avid's intermediate codecs, designed to work well with Avid Media Composer, and as competition for ProRes. They have roughly equivalent quality to ProRes, but, like Media Composer, are organized in a more complicated manner.


DNxHD


DNxHD is the first variant of Avid's intermediate codec, and focuses (as the name suggests) on high-definition resolutions. It works great, but has an incredibly unintuitive naming system for different quality levels and resolutions based on the overall bitrate of the video. If you really want to take a look at the full list, check out this Wikipedia page:

https://en.wikipedia.org/wiki/List_of_Avid_DNxHD_resolutions

In FFMPEG, you need to set the bitrate properly in order for the built-in DNxHD encoder to work. Here's an example command using a 1280x720 h.264 .mp4 file running at 29.97fps:

ffmpeg -i "inputvideo.mp4" -c:v dnxhd -b:v 110M -pix_fmt yuv422p -c:a pcm_s16le "outputvideo.mxf"

Using the list of resolutions, we find that is the correct bitrate to output an 8-bit 422 chroma subsampled 720p video at 29.97fps is 110 megabits, which is denoted by that "110M" setting in the command.

Unfortunately, the official list of bitrates isn't exact. For example, looking at the list, you might assume that if you wanted to do 10-bit instead of 8-bit, you would think that you just had to change the colorspace to yuv422p10le (like we do for ProRes) and keep the bitrate the same. However, that will give you an error. Luckily, DNxHD errors in FFMPEG will list the correct range of supported bitrates. Let's take a look (cropped to relevant section and cleaned up a bit for readability):

Frame size: 1280x720p; bitrate: 90Mbps; pixel format: yuv422p10
Frame size: 1280x720p; bitrate: 180Mbps; pixel format: yuv422p10
Frame size: 1280x720p; bitrate: 220Mbps; pixel format: yuv422p10
Frame size: 1280x720p; bitrate: 90Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 110Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 180Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 220Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 60Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 75Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 120Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 145Mbps; pixel format: yuv422p

Unfortunately, this list doesn't display framerates, but you'll find that these bitrates work for any framerate within the same frame size and pixel format. In this case, the answer for 10-bit 720p/29.97fps is 180M. So, the command for a 10-bit output would look like this:

ffmpeg -i "inputvideo.mp4" -c:v dnxhd -b:v 180M -pix_fmt yuv422p10le -c:a pcm_s16le "outputvideo.mxf"

Just for reference, here's the complete list that FFMPEG spits out:

Frame size: 1920x1080p; bitrate: 175Mbps; pixel format: yuv422p10
Frame size: 1920x1080p; bitrate: 185Mbps; pixel format: yuv422p10
Frame size: 1920x1080p; bitrate: 365Mbps; pixel format: yuv422p10
Frame size: 1920x1080p; bitrate: 440Mbps; pixel format: yuv422p10
Frame size: 1920x1080p; bitrate: 115Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 120Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 145Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 240Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 290Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 175Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 185Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 220Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 365Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 440Mbps; pixel format: yuv422p
Frame size: 1920x1080i; bitrate: 185Mbps; pixel format: yuv422p10
Frame size: 1920x1080i; bitrate: 220Mbps; pixel format: yuv422p10
Frame size: 1920x1080i; bitrate: 120Mbps; pixel format: yuv422p
Frame size: 1920x1080i; bitrate: 145Mbps; pixel format: yuv422p
Frame size: 1920x1080i; bitrate: 185Mbps; pixel format: yuv422p
Frame size: 1920x1080i; bitrate: 220Mbps; pixel format: yuv422p
Frame size: 1440x1080i; bitrate: 120Mbps; pixel format: yuv422p
Frame size: 1440x1080i; bitrate: 145Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 90Mbps; pixel format: yuv422p10
Frame size: 1280x720p; bitrate: 180Mbps; pixel format: yuv422p10
Frame size: 1280x720p; bitrate: 220Mbps; pixel format: yuv422p10
Frame size: 1280x720p; bitrate: 90Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 110Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 180Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 220Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 60Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 75Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 120Mbps; pixel format: yuv422p
Frame size: 1280x720p; bitrate: 145Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 36Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 45Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 75Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 90Mbps; pixel format: yuv422p
Frame size: 1920x1080p; bitrate: 350Mbps; pixel format: yuv444p10, gbrp10
Frame size: 1920x1080p; bitrate: 390Mbps; pixel format: yuv444p10, gbrp10
Frame size: 1920x1080p; bitrate: 440Mbps; pixel format: yuv444p10, gbrp10
Frame size: 1920x1080p; bitrate: 730Mbps; pixel format: yuv444p10, gbrp10
Frame size: 1920x1080p; bitrate: 880Mbps; pixel format: yuv444p10, gbrp10
Frame size: 960x720p; bitrate: 42Mbps; pixel format: yuv422p
Frame size: 960x720p; bitrate: 60Mbps; pixel format: yuv422p
Frame size: 960x720p; bitrate: 75Mbps; pixel format: yuv422p
Frame size: 960x720p; bitrate: 115Mbps; pixel format: yuv422p
Frame size: 1440x1080p; bitrate: 63Mbps; pixel format: yuv422p
Frame size: 1440x1080p; bitrate: 84Mbps; pixel format: yuv422p
Frame size: 1440x1080p; bitrate: 100Mbps; pixel format: yuv422p
Frame size: 1440x1080p; bitrate: 110Mbps; pixel format: yuv422p
Frame size: 1440x1080i; bitrate: 80Mbps; pixel format: yuv422p
Frame size: 1440x1080i; bitrate: 90Mbps; pixel format: yuv422p
Frame size: 1440x1080i; bitrate: 100Mbps; pixel format: yuv422p
Frame size: 1440x1080i; bitrate: 110Mbps; pixel format: yuv422p
You'll notice some 10-bit 444 color profiles in the 1920x1080p section. Don't use those unless you're coming from a 444 source, or you're wasting hard drive space.

DNxHR


So what about DNxHR? This is the more modern variant of DNxHD that supports a wider range of resolutions and color options. More importantly, it's way simpler to work with.

There's no need to specify bitrate, it will automatically use the correct one based on your input file. Unfortunately, most HR codec variants are only 8-bit, and will give an error if you try to convert to a different bit depth. The options for DNxHR are:

LB (roughly equivalent to ProRes Proxy)
SQ (roughly equivalent to ProRes LT)
HQ (roughly equivalent to ProRes 422)
HQX (roughly equivalent to ProRes 422HQ)
444 (you guessed it, roughly equivalent to ProRes 444)

Both the 444 and HQX variants use 10-bit color in HD or less, and either 10 or 12-bit color at 2K/UHD/4k resolutions. LB through HQX will always use 422 chroma subsampling, 444 will always use, well, 444 chroma subsampling. Still Confusing? You bet.

What's worse is that FFMPEG won't actually encode to 12-bit DNxHR HQX/444 or ProRes 4444XQ, it only supports 10-bit color depth.

That all said, here's a command that will give the same video quality as the first DNxHD command above, but using DNxHR:

ffmpeg -i "inputvideo.mp4" -c:v dnxhd  -profile:v dnxhr_hq -pix_fmt yuv422p -c:a pcm_s16le "outputvideo.mxf"

Notice how the codec variant is specified at the end of the profile option with an underscore before it.

If you wanted to transcode to HQX 10bit from a file with a different bitdepth/colorspace, you would do something like:

ffmpeg -i "inputvideo.mp4" -c:v dnxhd -profile:v dnxhr_hqx -pix_fmt yuv422p10le -c:a pcm_s16le "outputvideo.mxf"

To go to 10-bit 444, you would need to give it a 2K or larger resolution file and do:

ffmpeg -i "inputvideo.mp4" -c:v dnxhd -profile:v dnxhr_444 -pix_fmt rgb24 -c:a pcm_s16le "outputvideo.mxf"

As before, you generally don't want to use 444 unless you're coming from a 444 colorspace file, or a raw capture format that FFMPEG can understand. Otherwise, it's a waste of storage space, and is much slower to encode.

Oh, and if you're wondering how the FFMPEG DNxHD/HR encoder compares to the equivalent presets in Adobe Media Encoder, know that it's very close, but just slightly softer.

22 comments:

FilmFix said...

Thank you Andrew Swan. It is thanks to your most helpful how-to videos I finally got AVISYNTH to work.

Now I was wondering... is there a way to program a bat file with a counter variable that passes on that value to the .avs file? I have 30+ files to process, and some kind of loop would really help. ;)

I am up-scaling from NTSC SD to full-HD.

This is my 1.avs file
----------------------------
SetFilterMTMode("QTGMC", 2)
FFmpegSource2("test.mov", atrack=1)
ConvertToYV12()
AssumeBFF()
QTGMC(Preset="Slower", FPSDivisor=2, Edithreads=3)
# VHS tape NTSC
Crop(10,0,-6,-10)
Spline64Resize(1448,1080)
LimitedSharpenFaster(ss_x=1.9, ss_y=1.9, Smode=1, strength=60, radius=4, Lmode=1, wide=false, overshoot=1, soft=-1, edgemode=0, special=false, exborder=1)
AddBorders(236, 0, 236, 0, color_black)
Prefetch(threads=10)
----------------------------

And this is my bat file for now (export to mp4):
----------------------------
ffmpeg -i "1.avs" -c:v libx264 -b:v 45M -c:a pcm_s16le "1.mp4"
----------------------------

Andrew Swan said...

Hmmm... it's not really my area of expertise, but if you really want to make a counter, I'd say look into creating a Python script that launches FFMPEG, detects when a certain block of text goes by and then increments a variable.

Andrew Swan said...

Oh, additionally, are you sure of your EdiThreads/Prefetch settings? If you don't have a 6-core processor with hyperthreading, you might benefit from lower settings. Prefetch should be set to 1-2 less than the number of "logical processors" in your sysytem, and EdiThreads should be set to half your physical cores or less.

FilmFix said...

Thank you for checking in...
Yes, I have 6-cores -- just like you. :)
AND Thank you for pointing me to the direction of Python.

This is my first draft

#!/usr/bin/env python
import subprocess
import tempfile

#sourcedir = input("enter directory of files to process (do not indluce '\' at end): ")
sourcedir = "H:\FilmFix Film Transfer\VHS-C tapes\SD"
#diroutput = input("enter output location (do not indluce '\' at end): ")
diroutput = "H:\FilmFix Film Transfer\VHS-C tapes\HD"
#sourceExt = input("avi or mov?: ")
sourceExt = "mov"

def write_AVS_file(sourcedir,x,sourceExt):
# create a temporary file and write some data to it
f = open('temp_AVS.avs', 'w')
f.write('SetFilterMTMode("QTGMC", 2)\n')
f.write('FFmpegSource2("' + sourcedir + '\\' + x + '.' + sourceExt + '", atrack=1)\n')
f.write('ConvertToYV12()\n')
f.write('AssumeBFF()\n')
f.write('QTGMC(Preset="Slower", FPSDivisor=2, Edithreads=3)\n')
f.write('Crop(10,0,-6,-10)\n')
f.write('Spline64Resize(1448,1080)\n')
f.write('LimitedSharpenFaster(ss_x=1.9, ss_y=1.9, Smode=1, strength=60, radius=4, Lmode=1, wide=false, overshoot=1, soft=-1, edgemode=0, special=false, exborder=1)\n')
f.write('AddBorders(236, 0, 236, 0, color_black)\n')
f.write('Prefetch(threads=10)\n')

def getFileNames():
global filenamefirst
filenamefirst = int(input("enter NUMBER of starting file name to process: "))
global filenamelast
filenamelast = int(input("enter NUMBER of last file to process: "))

def main():
import os
import os.path
getFileNames()
for x in range(filenamefirst, filenamelast+1):
write_AVS_file(sourcedir,str(x),sourceExt)
if os.path.exists(diroutput + "\\" + str(x) + ".mp4"):
os.remove(diroutput + "\\" + str(x) + ".mp4")
from subprocess import check_output
check_output("ffmpeg -i \"temp_AVS.avs\" -c:v libx264 -b:v 50M -c:a aac \"" + diroutput + "\\" + str(x) + ".mp4\"", shell=True)
print("^======== finished file " + str(x) + "." + sourceExt)
print("end of program")

main()


processing at speed=0.353x (this will take forever!?!)

It would be nice to have a better indication of total frame count in the file to get an idea of how long this will take.

FilmFix said...

link to python file

Andrew Swan said...

Cool! The only quibble I have is that I don't think you need the color_black entry; black is already the default color of AddBorders. It actually gave me an error when I tried to preview the script in AvsPmod until I removed it.

One suggestion to improve speed: you might try running the 64-bit versions of AVISynth+/FFMPEG. In a quick test, it provided a couple fps difference (16 vs 13).

Also, if you want to test the speed of just the AVISynth script, I recommend trying out AVSMeter. It's been a huge help for me in hunting down bottlenecks.

Andrew Swan said...

And actually one more thing: If you want to detect the total number of frames, you might try using FFPROBE as in this post:

https://stackoverflow.com/questions/2017843/fetch-frame-count-with-ffmpeg

FilmFix said...

Thank you for the frame count link and the border color idea. My speed is at 11 fps. I am not pushing my hardware as much as I could ... I have a i7-5820K @ 3.30GHz I am holding it at x38.0 for the Core Multiplier.

Have you ever tried to add some GPU support alongside for processing?

Andrew Swan said...

I did some basic tests tonight. If you don't set a quality option, h264_nvenc gives noticeably worse quality. However, it can be as much as 3 times as fast. Wow. My GPU isn't even that powerful, either.

It also doesn't appear to support -crf, so I tried using the less efficient -qp mode. Setting -qp to 18 results in similar visual quality for both software only and nvenc h264 encodes. nvenc is once again blazingly fast compared to software only, although not as efficient in terms of filesize. Personally, I haven't run into a project where I needed faster encoding, but it's good to know it's an option in the future.

Unknown said...

been a while on this post but useful info. thanks! have you ever gotten actual 12 bit results from your posted 12 bit command:

ffmpeg -i "inputvideo.mp4" -c:v dnxhd -profile:v dnxhr_444 -pix_fmt rgb24 -c:a pcm_s16le "outputvideo.mxf"

whenever i try to get a greater than 10 bit conversion, ffmpeg always changes the format to a 10 bit format with an error like:

Incompatible pixel format 'rgb24' for codec 'dnxhd', auto-selecting format 'gbrp10le'

thanks again,
BabaG

Andrew Swan said...

So, here's the thing (and something I'll amend in the post to make clear). DNxHD/HR doesn't let you choose your bit depth independently. It automatically changes bit depth depending on resolution and quality settings.

Basically:

HD or below = LB, SQ, and HQ will all use 8-bit color; HQX and 444 will use 10-bit
2K/4K/UHD = LB, SQ, and HQ (I believe, will test later) will still use 8-bit; HQX and 444 will use 12-bit

So, DNxHR 444 will always require 444 chroma subsampling, but in the case of sub-2K resolutions, will always use 10-bit color depth. That means you would need to either use the colorspace FFMPEG recommended, or something equivalent like yuv444p10le.

Unknown said...

thanks, andrew. interesting. just tried:

ffmpeg -i file_2880x2160p_GV-HQX_23976fps.mov -c:v dnxhd -profile:v dnxhr_444 -pix_fmt rgb24 -c:a copy swantest.mxf

got same result. got error message:

Incompatible pixel format 'rgb24' for codec 'dnxhd', auto-selecting format 'gbrp10le'

original file should be large enought, i'd think, at 2880x2160. it is 4x3, which might cause an issue?

anyway, thanks again and interested to see what you find,
BabaG

Unknown said...

tried another test with some 16x9 material at 3840x2160 and got same error:

Incompatible pixel format 'rgb24' for codec 'dnxhd', auto-selecting format 'gbrp10le'

thanks,
BabaG

Andrew Swan said...

I stand corrected. FFMPEG appears to only encode to 10-bit color depth for all variants of both DNxHR and ProRes. It's an oft-lamented "bug", apparently.

If you want to convert a file to DNxHR 444 12-bit, then you should use DaVinci Resolve. It also appears that Resolve can do 422 (HQX) 12-bit as well, if that's more your thing. The downside is that the free version will only go up to UHD, so no DCI 4K resolutions.

Also, the official codec specifications for DNxHD/DNxHR really need to be clarified by Avid.

Bobbo said...

Hey Andrew, I hope you are still on here and if so can help me out. So I am trying to export an image sequence to a quicktime with a DNXHD codec. Specifically with these parameters:

Format: 1080p/24f DNXHD 175 10-bit
Color Levels: 709
Quality: Medium

I am really new to encoding and wanted to see if this is viable at all.

Here is what someone on reddit suggest I try out:

ffmpeg -r 24 -i /Volumes/BOBBO_T5/Freelance/Starburns/Freak_BROS/01HARMONY/00DOWNLOADS/112520/FB102_15_02_TK4/frames/IMG_SEQ2/final-0001.bmp -c:v dnxhd -b:v 176M -pix_fmt yuv422p10le -vf scale=1920:1080:flags=lanczos ./TESTBK.mov

That gave me an error stating "error while opening encoder stream #0:0 - maybe incorrect parameters such as bit_rate, rate, width or height.

Andrew Swan said...

Bobbo,

Try changing your bitrate to 175M or 180M rather than 176M. Alternatively, you could try using Shutter Encoder as a slightly easier-to-use wrapper around FFMPEG, although I'm not as experienced in using it for this purpose.

larkalarhki said...

Hi, I'm trying to convert my SD video to HD 1920.

Can someone please share a avs script which I can use to do SD to HD.

I have installed and setup my system as per Andrew instructions.

I have 6 core CPU machine

Andrew Swan said...

Have you checked out the main blog post? It has some sample .avs scripting in it:

https://macilatthefront.blogspot.com/2021/01/deinterlacing-with-avisynth-and-qtgmc.html

That being said, here's how you might convert to HD by cropping out the top and bottom of the frame and resize (assuming you're using NTSC video):

SetFilterMTMode ("QTGMC", 2)
FFMPEGSource2("videofile.avi", atrack=1)
ConvertToYV12()
AssumeBFF()
QTGMC(preset="Slower", EdiThreads=3)
BilinearResize(720,540)
Crop(0,68,0,-68)
Spline64Resize(1920,1080)
Prefetch(10)

I'm doing an extra resize step to correct the Pixel Aspect Ratio before cropping, but you can try removing the first resize and adjusting the crop as you like to get a little more quality back. I might be off, but I think you would only need something like this:

Crop(0,56,0,-56)

If you're using PAL video, then you'd probably want to change the PAR correction resize and crop to:

BilinearResize(768,576)
Crop(0,72,0,-72)

Again, this is just to be as close to preserving the original aspect ratio as possible.


Or if you wanted to keep whole original frame and pillarbox the image:

SetFilterMTMode ("QTGMC", 2)
FFMPEGSource2("videofile.avi", atrack=1)
ConvertToYV12()
AssumeBFF()
QTGMC(preset="Slower", EdiThreads=3)
Spline64Resize(1440,1080)
AddBorders(0, 240, 0, 240, color_black)
Prefetch(10)

Adjust parameters based on your system settings and preferences.

larkalarhki said...

Thank you for your reply.

I try using a different way to improve the quality of the video but still when I upscale the video from SD to HD its blur.


I try following AVS file

SetFilterMTMode("QTGMC", 2)
FFmpegSource2("3.avi", atrack=1)
ConvertToYV12()
AssumeBFF()
QTGMC(preset="Slower", EdiThreads=3)
Spline64Resize(1920,1080)
LimitedSharpenFaster()
Prefetch(10)

my bat file
ffmpeg -i "3.avs" -c:v libx264 -b:v 10M -c:a aac "3.mp4"

it's it possible to add more colours and sharpens to the video without making it too smooth


Andrew Swan said...

If you're doing both a sharpening resize method *and* LimitedSharpen, then you're pushing what AVISynth can do.

You might look into AI upscaling - a number of folks have been recommending Topaz AI to me as an option - it'll essentially add detail that wasn't originally there, but it's pretty much the only way to make your video actually more detailed, rather than just enhancing contrast.

larkalarhki said...

Do you have any link for tutorials for AI up-scaling.

What is AI up-scaling?

Andrew Swan said...

It's not something I've really messed around with yet, but here's a reasonably good overview:

https://blogs.nvidia.com/blog/2020/02/03/what-is-ai-upscaling/

And here a tutorial about Topaz's software:

https://www.youtube.com/watch?v=vC01CNmhusU

Which deinterlacing algorithm is the best? Part 1 - HD interlaced footage

Before I began, I'd like to give a special thanks to Aleksander Kozak for his help in testing and providing the HD footage used in this ...