Actually Just Void

Upscaling very low resolution videos for YouTube, using ffmpeg and interleaved gradient noise

Today, I wanted to help someone with upscaling videos for YouTube. In the days of AI and upscaling models, this seemed to be an easy task at first. Yet, it quickly became a bit more complicated.

The original videos are from a homemade DVD, with a ridiculously small resolution of 252x214 pixels. Upscaling this via AI will look like absolute garbage. So, I came come up with a different solution... that includes upscaling the video to 1440p and adding interleaved gradient noise.

Comparison
Consider this a vague comparison since both versions are scaled to 480p. The following explanation will provide closure on what's happening and why.

Here's what I've done:

Why Upscale to 1440p?

Upscaling a 352x214 video to 1440p does not add new physical detail, but it fundamentally changes how YouTube handles the file.
If you upload a video below 1080p, YouTube processes it using the older AVC (H.264) codec at a very low bitrate. This aggressively compresses the file, destroying edges and turning low-resolution footage into a blurry, blocky mess.
Uploading at 1440p or 4K forces YouTube to assign the superior VP9 (or AV1) codec and allocates a significantly higher bitrate. This protects the mathematical upscaling and preserves the artificial noise layer, ensuring the video reaches the viewer looking as close as possible to what it did look like on your machine.

Why Use Interleaved Gradient Noise (IGN)?

Mathematical scalers like Lanczos do an excellent job of preserving edges, but they leave low-resolution surfaces looking perfectly smooth, flat, and sterile.

Perceptual Sharpness: The human eye interprets fine texture as "sharpness." By adding a subtle layer of noise, we trick the brain into perceiving detail that does not actually exist.

Encoder Forcing: Digital video encoders look for flat areas of color to compress. By introducing a constant, high-frequency noise floor (IGN), we force the encoder to work harder and allocate more data to the entire frame, preventing color banding and macro-blocking.

Why IGN specifically? Unlike pure white noise, IGN is a structured noise pattern specifically designed to be visually pleasing and less susceptible to causing severe compression artifacts.

How to generate interleaved gradient noise for this usecase

I use pre-calculated interleaved gradient noise via: ign-gen

The Script

:: Define directories and noise file here
SET "INPUT_DIR=PATH\TO\CDDASH"
SET "OUTPUT_DIR=PATH\TO\\CDDASH_UP"
SET "IGN=PATH\TO\ign_2560x1440.png"

:: Create the output directory if it doesn't exist
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"

:: Loop through every .mp4 file in the input directory
FOR %%F IN ("%INPUT_DIR%\*.mp4") DO (

    echo Processing: %%~nxF
    echo =======================================================

    ffmpeg -i "%%F" -loop 1 -i "%IGN%" -filter_complex "[0:v]fps=60,scale=2560:1440:force_original_aspect_ratio=decrease:flags=lanczos,pad=2560:1440:(ow-iw)/2:(oh-ih)/2,format=yuv420p[upscaled];[1:v]format=yuv420p[ign_fmt];[upscaled][ign_fmt]lut2=c0='clip(x+(y-128)*0.15,0,255)':c1='x':c2='x':shortest=1[outv]" -map "[outv]" -map 0:a? -c:v hevc_nvenc -preset p6 -tune hq -rc vbr -cq 24 -b:v 0 -c:a aac -b:a 192k -shortest "%OUTPUT_DIR%\%%~nF_upscaled.mp4" -y

)
pause

Since the ffmpeg command is pretty long, let's take a closer look:

Inputs and Outputs

Stream Mapping

The Video Filtergraph

The filtergraph is the CPU-driven pipeline where the actual image processing happens. It is divided into two distinct stream preparations and a final merge.

Stream A ([0:v]): Preparing the Source Video

[0:v]scale=2560:1440:force_original_aspect_ratio=decrease:flags=lanczos,pad=2560:1440:(ow-iw)/2:(oh-ih)/2,format=yuv420p[upscaled];

Stream B ([1:v]): Preparing the Noise

[1:v]format=yuv420p[ign_fmt];

The Merge ([lut2]): 2D Lookup Table

[upscaled]ign_fmt]lut2=c0='clip(x+(y-128)*0.15,0,255)':c1='x':c2='x':shortest=1[outv]

This filter merges the video (x) and the noise (y) using explicit pixel math.

Hardware Encoding Once the CPU finishes filtering a frame, it is sent to the NVIDIA GPU (NVENC) for highly efficient hardware encoding.

-c:v hevc_nvenc -preset p6 -tune hq -rc vbr -cq 24 -b:v 0 -c:a aac -b:a 192k

This is standard ffmpeg stuff and I don't want to explain the parameters here. This post is long enough...

Hope this will help someone in the future.
Enjoy!