Learn · Developers

Interlaced vs progressive video: detect and deinterlace

Contents

Broadcast cameras, TV archives and older camcorders often record interlaced video. Phones, web players and editing tools work in progressive frames. When an interlaced file reaches a progressive workflow without being converted, every moving edge shows thin horizontal lines.

This guide explains the difference between interlaced and progressive video and what labels like 1080i mean. It shows three ways to tell whether a file is interlaced, and how to deinterlace it with FFmpeg. We tested each step on a 1080i clip made from Shotstack’s stock footage.

TL;DR

  • A progressive frame holds every line of the picture from one moment. An interlaced frame holds two half-pictures, called fields, from two moments.
  • On a progressive screen, movement between the two fields shows up as combing: jagged, comb-like lines on moving edges.
  • ffprobe’s field_order reads the file’s label: progressive, or tt and bb for interlaced. FFmpeg’s idet filter checks the pixels.
  • Shotstack’s Probe endpoint returns the same field_order for a file at a URL, with no FFmpeg install on your machine.
  • ffmpeg -vf bwdif=mode=send_frame deinterlaced our 1080i clip into 140 progressive frames at the same 25 fps.

What is the difference between interlaced and progressive video?

Wikipedia’s article on progressive scan describes it as a format “in which all the lines of each frame are drawn in sequence”. Every line of a progressive frame comes from the same moment.

Interlaced video splits each frame in two. Wikipedia’s article on interlaced video puts it this way: “One field contains all odd-numbered lines in the image; the other contains all even-numbered lines.” The two fields are captured one after the other, so each frame holds two moments.

Television used interlacing to save bandwidth. The same article notes that interlacing “provides full vertical detail with the same bandwidth that would be required for a full progressive scan”.

What 1080i and 1080p mean

The number is the picture height in lines, and the letter is the scan type. Wikipedia’s 1080i article says “1080 in 1080i refers to the horizontal lines that make up the resolution of the display”. 1080p is the same 1920x1080 picture, sent as full frames.

The field rate depends on the region. The same article says it “is typically 50 or 60 fields per second, depending on the area.” 50 fields a second gives 25 full frames, and 60 fields a second gives about 30 (29.97 in NTSC regions).

LabelScanLines in each frameMoments in each frame
1080pProgressive1,080One
1080iInterlaced1,080, as two fields of 540 linesTwo

Why interlaced video shows comb lines

A progressive screen draws all 1,080 lines at once. When it shows an interlaced frame as it is, the odd lines show one moment and the even lines show the next. Anything that moved between the two fields appears twice, sliced into alternating lines.

The image below is one frame of our 1080i test clip, zoomed in on the rider. On the left is the frame as stored. On the right is the same frame after deinterlacing with FFmpeg:

Left: a 1080i frame of a BMX rider with comb lines along his shirt and arms. Right: the same frame after deinterlacing, with clean edges.

The background barely moves, so it looks clean in both. The comb lines appear only where something moved in the 1/50 of a second between the two fields.

How to tell if a video is interlaced

Read the field order with ffprobe

ffprobe comes with FFmpeg. This command prints the field order of the first video stream:

Bash
ffprobe -v error -select_streams v:0 -show_entries stream=field_order -of csv=p=0 bmx-1080i.mp4

For our test clip it prints tt. These are the values you are most likely to see:

field_orderMeaning
progressiveProgressive frames
ttInterlaced, top field first
bbInterlaced, bottom field first
tb or btInterlaced, with the fields stored in one order and shown in the other
missing or unknownThe file doesn’t say

The field order matters when you deinterlace. Top field first means the odd lines were captured first. A deinterlacer that assumes the wrong order shows each pair of fields in the wrong order, and motion judders. Our guide on how to use ffprobe covers the other fields ffprobe can read.

Check the pixels with FFmpeg’s idet filter

field_order is a label, and labels can be wrong. A file that was re-encoded without deinterlacing can carry the wrong label while the comb lines stay in the picture.

FFmpeg’s idet filter looks at the frames themselves. The FFmpeg filter documentation says it “tries to detect if the input frames are interlaced, progressive, top or bottom field first.”

Bash
ffmpeg -i bmx-1080i.mp4 -vf idet -an -f null -

At the end of the run, idet prints counts. Read the multi-frame line, which uses the frames around each one for a steadier result. For our test clip it was:

Text
Multi frame detection: TFF:   140 BFF:     0 Progressive:     0 Undetermined:     0

All 140 frames were top field first (TFF), which matches the tt label. On the deinterlaced copy, the same line read Progressive: 140.

Check files at a URL with the Shotstack Probe endpoint

ffprobe and idet need the file on your machine. When your media is already online, Shotstack’s Probe endpoint reads it at its URL and returns the ffprobe metadata as JSON, field_order included.

The script further down uses it to check a list of files. Our article on inspecting media metadata walks through the full response.

How to deinterlace video with FFmpeg

FFmpeg has several deinterlacing filters. This example uses bwdif, which the FFmpeg documentation describes as “motion adaptive deinterlacing based on yadif with the use of w3fdif and cubic interpolation algorithms.” It reads the field order from the file.

Bash
ffmpeg -i bmx-1080i.mp4 -vf bwdif=mode=send_frame -c:a copy bmx-1080p.mp4

The mode option decides how many frames come out:

ModeOutputOur 1080i clip became
send_frameOne frame for each interlaced frame140 frames at 25 fps
send_fieldOne frame for each field280 frames at 50 fps

send_field is the default, so bwdif with no options doubles the frame rate. Use send_frame to keep the frame rate of the rest of your project. Use send_field to keep the smoother motion of the 50 fields, for sport or fast action. Our guide to video frame rates helps you pick.

After bwdif, ffprobe read progressive and idet counted 140 progressive frames out of 140.

Deinterlace before you resize or crop

A resize blends neighboring lines. In an interlaced frame, neighboring lines come from different moments, so a resize before deinterlacing mixes the two fields into one blurred picture. Put the deinterlacer first in the filter chain:

Bash
ffmpeg -i bmx-1080i.mp4 -vf "bwdif=mode=send_frame,scale=1280:720" bmx-720p.mp4

FFmpeg’s own documentation shows the same order in its first filtergraph example: “deinterlacing (using the yadif deinterlacer) followed by resizing (using the scale filter)”.

Check your video files with Python before you render

The script below checks a list of video files with the Probe endpoint and reports the scan type of each. It ends with exit code 1 when any file is interlaced, so you can run it as a check before a render job. It uses only the Python standard library.

The first file is a progressive stock clip. The second is our 1080i test clip: Shotstack’s BMX stock footage, interpolated to 50 frames a second and woven into top-first fields with FFmpeg. Set SHOTSTACK_API_KEY to the API key from your Shotstack dashboard, then run it with Python 3.

Python
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
from fractions import Fraction

API = "https://api.shotstack.io/edit/v1/probe"
FILES = [
    "https://shotstack-assets.s3-ap-southeast-2.amazonaws.com/footage/road.mp4",
    "https://d2jn8jtjz02j0j.cloudfront.net/bmx_1080i_7479e2fa77.mp4",
]
SCAN = {
    "progressive": "progressive",
    "tt": "interlaced, top field first",
    "bb": "interlaced, bottom field first",
    "tb": "interlaced",
    "bt": "interlaced",
}


def probe(url):
    """Return the first video stream of a file, as the Probe endpoint reports it."""
    encoded = urllib.parse.quote(url, safe="")
    request = urllib.request.Request(
        f"{API}/{encoded}", headers={"x-api-key": os.environ["SHOTSTACK_API_KEY"]}
    )
    try:
        with urllib.request.urlopen(request) as response:
            metadata = json.load(response)["response"]["metadata"]
    except urllib.error.HTTPError as error:
        sys.exit(f"HTTP {error.code}: {' '.join(error.read().decode().split())[:300]}")
    return next((s for s in metadata["streams"] if s["codec_type"] == "video"), None)


if __name__ == "__main__":
    if not os.environ.get("SHOTSTACK_API_KEY"):
        sys.exit("Set SHOTSTACK_API_KEY first.")
    interlaced = 0
    for url in FILES:
        stream = probe(url)
        name = url.rsplit("/", 1)[-1]
        if stream is None:
            print(f"{name}: no video stream")
            continue
        order = stream.get("field_order", "unknown")
        scan = SCAN.get(order, "not flagged: check the pixels with FFmpeg's idet")
        size = f"{stream['width']}x{stream['height']}"
        fps = round(float(Fraction(stream["r_frame_rate"])), 3)
        print(f"{name}: {size}, {fps:g} fps, {scan}")
        interlaced += order in ("tt", "bb", "tb", "bt")
    print(f"{interlaced} of {len(FILES)} files need deinterlacing before you render")
    sys.exit(1 if interlaced else 0)

Run python check_fields.py:

Text
road.mp4: 1920x1080, 23.976 fps, progressive
bmx_1080i_7479e2fa77.mp4: 1920x1080, 25 fps, interlaced, top field first
1 of 2 files need deinterlacing before you render

How the script works

  • probe() sends the file’s URL, encoded, to the Probe endpoint. It returns the first stream whose codec_type is video.
  • SCAN turns the field_order value into words. A file with no field_order gets a note to check its pixels with idet.
  • The loop prints the size, frame rate and scan type of each file, and counts the interlaced ones.
  • The exit code is 0 when every file is progressive. It is 1 when any file needs deinterlacing or a request fails, so a CI job or a queue worker can stop on it.

When a file is interlaced, deinterlace it with the bwdif command above. You can then upload the progressive copy with the Ingest API and use it in your edit.

Tips for working with interlaced footage

  • Check both the label and the pixels. Use field_order to sort files quickly, and run idet on anything unlabeled or from an unknown source.
  • Deinterlace once, at the start. Convert the source file before any resize, crop or edit, and keep the progressive copy as your working file.
  • Match the frame rate to the project. send_frame keeps 25 or 30 fps. send_field gives 50 or 60 fps.
  • Keep the original. Deinterlacing rebuilds the missing lines of each field, so keep the interlaced source if you might need to convert it again.
  • Look at moving edges. Comb lines show up on movement, so check a fast shot rather than a still one.

Check your footage before every render with Shotstack

You now know how interlaced and progressive video differ, how to spot an interlaced file, and how to deinterlace it with FFmpeg. The script checks each source at its URL with the Probe endpoint, so interlaced footage is caught before it reaches a render.

Create a free Shotstack account to get your API key and check your first files.

Frequently asked questions (FAQs)

Which is better, interlaced or progressive?

For video that plays on phones, computers and the web, progressive. Those screens draw full frames, so interlaced video needs converting before it looks right on them.

Does deinterlacing lose quality?

A little, in moving areas. bwdif is motion adaptive: where the picture is still, it keeps both fields. Where something moves, it rebuilds the missing lines from the field it keeps, so fine vertical detail on moving parts is softer than in footage shot progressive.

What is the difference between bwdif and yadif?

bwdif builds on yadif and adds more interpolation. The defaults also differ. yadif outputs one frame per frame by default (send_frame), and bwdif outputs one frame per field (send_field), so the same command without a mode gives 25 fps from one filter and 50 fps from the other.

How do I deinterlace 1080i at 29.97 fps?

The same way. 60-field footage from NTSC regions runs at 59.94 fields a second. With bwdif=mode=send_frame it becomes 29.97 progressive frames a second, and with send_field it becomes 59.94.

Keep reading

Start free

  • Unlimited developer sandbox
  • No credit card required