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_orderreads the file’s label:progressive, orttandbbfor interlaced. FFmpeg’sidetfilter checks the pixels. - Shotstack’s Probe endpoint returns the same
field_orderfor a file at a URL, with no FFmpeg install on your machine. ffmpeg -vf bwdif=mode=send_framedeinterlaced 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).
| Label | Scan | Lines in each frame | Moments in each frame |
|---|---|---|---|
| 1080p | Progressive | 1,080 | One |
| 1080i | Interlaced | 1,080, as two fields of 540 lines | Two |
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:

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:
ffprobe -v error -select_streams v:0 -show_entries stream=field_order -of csv=p=0 bmx-1080i.mp4For our test clip it prints tt. These are the values you are most likely to see:
field_order | Meaning |
|---|---|
progressive | Progressive frames |
tt | Interlaced, top field first |
bb | Interlaced, bottom field first |
tb or bt | Interlaced, with the fields stored in one order and shown in the other |
missing or unknown | The 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.”
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:
Multi frame detection: TFF: 140 BFF: 0 Progressive: 0 Undetermined: 0All 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.
ffmpeg -i bmx-1080i.mp4 -vf bwdif=mode=send_frame -c:a copy bmx-1080p.mp4The mode option decides how many frames come out:
| Mode | Output | Our 1080i clip became |
|---|---|---|
send_frame | One frame for each interlaced frame | 140 frames at 25 fps |
send_field | One frame for each field | 280 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:
ffmpeg -i bmx-1080i.mp4 -vf "bwdif=mode=send_frame,scale=1280:720" bmx-720p.mp4FFmpeg’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.
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:
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 renderHow the script works
probe()sends the file’s URL, encoded, to the Probe endpoint. It returns the first stream whosecodec_typeisvideo.SCANturns thefield_ordervalue into words. A file with nofield_ordergets a note to check its pixels withidet.- 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_orderto sort files quickly, and runideton 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_framekeeps 25 or 30 fps.send_fieldgives 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.