Skip to content

Instantly share code, notes, and snippets.

@namazso
Last active December 24, 2025 21:35
Show Gist options
  • Select an option

  • Save namazso/6b4df92ffc4f19d62414034a843fa686 to your computer and use it in GitHub Desktop.

Select an option

Save namazso/6b4df92ffc4f19d62414034a843fa686 to your computer and use it in GitHub Desktop.
Your Archival Copy is Not Your Viewing Copy

Your Archival Copy is Not Your Viewing Copy

Technical explanation of display compatibility challenges when viewing analog-sourced video content on digital displays.

Introduction

Standard Definition video content is predominantly formatted for analog CRT display systems. This applies to analog storage formats (VHS, LaserDisc, etc.) as well as many digital media formats (DVD-Video). Individuals new to video archival often attempt to create a single copy that serves both as an authentic archival master and as a readily viewable file. This approach typically fails to achieve either objective effectively. This document explains the technical rationale for maintaining separate archival and viewing copies.

This document focuses primarily on 50/59.94 Hz native video content, including home video and other non-broadcast productions. If the steps are followed correctly, it is possible to create a "viewing copy" of the media that has viewing experience close to original, but consistent and compatible playback on contemporary consumer hardware.

Archival Copy

This section explains why archival copies cannot provide consistent, high-quality playback on modern equipment due to inherent format characteristics:

Interlacing

Interlaced video frames consist of two fields: one containing odd-numbered lines and another containing even-numbered lines. A 59.94 Hz interlaced signal effectively delivers 59.94 fields per second, with each field containing only half of the spatial information. This content format is designed exclusively for CRT displays. LCD displays require complete pixel data for every refresh cycle, necessitating interpolation of the missing field data to maintain the original temporal resolution on progressive displays.

Pixel Aspect Ratio

Contemporary LCD displays utilize square pixels, but this standard was not established when analog video sampling specifications were defined. Consequently, Standard Definition video typically employs non-square pixels. Displaying such content on modern equipment requires horizontal scaling, which may reduce horizontal resolution fidelity.

Chroma Subsampling

Modern widely-supported video codecs employ 4:2:0 chroma subsampling, storing chrominance information at half the vertical and horizontal resolution of luminance data. In contrast, analog NTSC provides independent color information for each scanline. To preserve complete source information in a highly compatible format, vertical upscaling by a factor of 2 would be necessary.

Overscan

CRT television displays did not render the entire broadcast picture area as visible content. Approximately 5% of the image on each edge could be lost to overscan. For non-broadcast content such as camcorder recordings, these regions may contain visual artifacts including edge distortions and signal anomalies.

Viewing Copy

This section outlines recommended processing steps to address the previously described technical limitations and create a viewing copy optimized for modern display systems and playback devices.

Deinterlacing

Deinterlacing converts interlaced video into progressive format. To preserve the temporal smoothness of native 50/59.94 Hz content, a "frame doubling" deinterlacer is recommended, which generates complete frames from individual fields through interpolation. The industry-standard solution for this purpose is QTGMC.

Scaling

This process addresses both pixel aspect ratio and chroma resolution limitations described previously.

Upscaling

Advanced neural network-based upscalers typically support integer scaling factors, commonly doubling resolution on one or both axes. Applying 2x scaling to both dimensions yields output heights of 960 or 1152 pixels for NTSC and PAL respectively. For content with "wide" pixels, 4x horizontal scaling may be needed to ensure "tall" pixel output.

Downscaling

Following upscaling, horizontal downscaling is applied to achieve square pixel aspect ratios. Note that some capture and export tools may include more than the active picture area, so the final output resolution may not conform to standard 4:3 aspect ratios when using square pixels.

Cropping and Padding

With square-pixel progressive video established, the content can be formatted to standard resolutions. 1080p is recommended as a target resolution due to its widespread support and proximity to the scaled source dimensions. For PAL content, this requires cropping 72 pixels vertically (6.25%, within the typical 10% overscan region) and adding pillarbox bars. NTSC content requires letterboxing. Edge artifacts from the source material should be cropped during this step as appropriate.

Export Format

Progressive square-pixel video at standard resolutions permits the use of mainstream codecs and encoding parameters. A Blu-ray-compatible format is recommended, as this profile ensures broad playback device support. Note that official Blu-ray specifications do not include 1080p50/1080p59.94 progressive video until the introduction of UHD Blu-ray, but in practice, modern playback devices handle these frame rates without issues.

Vapoursynth script

Here you can find a vapoursynth script attached which performs the earlier described steps with mostly default parameters and options. Please keep in mind it is meant to be a starting point to which you can add your own post-processing steps if needed, such as deringing or denoising.

import vapoursynth as vs
from vsdeinterlace.qtgmc import QTempGaussMC
from vsaa.deinterlacers import NNEDI3
import vskernels
import vstools
import math

clip = vs.core.bs.VideoSource('./decoded.mkv')

dar = vstools.Dar(4, 3)
# dar = vstools.Dar(16, 9)

if clip.height == 576:
    #  PAL
    interpreted = clip.std.SetFrameProps(
        _Primaries=vs.PRIMARIES_BT470_BG,
        _Matrix=vs.MATRIX_BT470_BG,
        _Transfer=vs.TRANSFER_BT709
    )
    # Top and bottom should add up to at least 72 to get to 1080p
    crop_top = 24
    crop_bottom = 48
    crop_left = 0
    crop_right = 0
elif clip.height == 480 or clip.height == 488:
    #  NTSC
    interpreted = clip.std.SetFrameProps(
        _Primaries=vs.PRIMARIES_ST170_M,
        _Matrix=vs.MATRIX_ST170_M,
        _Transfer=vs.TRANSFER_BT709
    )
    # No minimum cropping needed for NTSC sources
    crop_top = 0
    crop_bottom = 0
    crop_left = 0
    crop_right = 0
else:
    raise ValueError("Invalid input")

# Use a high depth working color space
workable = interpreted.resize.Point(
    format=interpreted.format.replace(bits_per_sample=16)
)

deinterlaced = QTempGaussMC(workable).deinterlace()

downscaler = vskernels.Lanczos(keep_ar=True, sar=dar.to_sar(deinterlaced.width, deinterlaced.height))
squarepx = NNEDI3(scaler=downscaler).scale(
    clip=deinterlaced,
    height=deinterlaced.height * 2,
    width=(math.ceil(deinterlaced.height * 2 * dar) + 1) // 2 * 2
)

cropped = squarepx.std.Crop(
    top=crop_top,
    bottom=crop_bottom,
    left=crop_left,
    right=crop_right
)

border_top = (1080 - cropped.height) // 4 * 2
border_left = (1920 - cropped.width) // 4 * 2

boxed = cropped.std.AddBorders(
    top = border_top,
    left = border_left,
    bottom = 1080 - cropped.height - border_top,
    right = 1920 - cropped.width - border_left
)

universal = boxed.resize.Point(
    format=vs.YUV420P8,
    resample_filter_uv="spline36",
    dither_type="error_diffusion",
    primaries_s='709',
    matrix_s='709',
    chromaloc=vs.CHROMA_LEFT
)
universal.set_output(0)

Use it like this:

vspipe modernize.vpy - --container y4m | ffmpeg -color_primaries bt709 -color_trc bt709 -colorspace bt709 -chroma_sample_location left -f yuv4mpegpipe -i - -codec:v libx264 -preset:v veryslow -level:v 4.1 -profile:v high -tune:v grain -crf:v 21 -pix_fmt yuv420p -x264opts bluray-compat=1 output.mkv

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment