Deinterlacer Woes
Posted by at 21:03 on 25 Mar 2009. There are ? Comments

I've recently discovered that MPlayer's yadif deinterlacer seems to eat a frame from the start and end of your stream, so when handling interlaced content you need to assume two frames are lost. Normally this isn't an issue (who cares about two frames right?), but I recently had to deal with some customer videos where the first and/or last frame contained a logo that should be shown before and/or after the video... d'oh!

<p>This is how I'd look if I lived in the 1920s!
</p>

This is how I'd look if I lived in the 1920s!

Since I'm already processing video as YUV4MPEG2 data I figured a quick fix would be to pad the ends of the video by duplicating the first and last frames before deinterlacing. I looked up the format and hacked together a quick script in Python which does just that. It takes one optional argument (the number of duplicate frames to pad the video with), reads from stdin and writes to stdout.

#!/usr/bin/env python
 
"""
YUV4MPEG2 Pad Script
====================
This script will pad a YUV4MPEG2 stream by duplicating the first and last
frame X times. This is useful if operations later down the line eat a frame,
for example MPlayer's yadif deinterlacer.

Usage
-----
y4mpad.py [count] <in.y4m >out.y4m

Format
------
YUV4MPEG2 is a very simple format that consists of the following:

YUV4MPEG2 [tag] [tag] [...]\n
FRAME [tag] [tag] [...]\n
image data
FRAME [tag] [tag] [...]\n
image data
...

Tags are specified with a one-character name and then the value, e.g. W120
for a width of 120 pixels. Image data is is always (width * height) +
(width * height / 4) + (width * height / 4) bytes and represents the Y, U,
and V planes, respectively.

Important tags:

- W: width (int)
- H: height (int)
- F: framerate (int or int:int fraction)

License
-------
Copyright 2009 Daniel G. Taylor <dan@programmer-art.org>

This code is released under the GNU LGPL3. See http://www.gnu.org/
"""
 
import sys
 
infile = sys.stdin
outfile = sys.stdout
 
sys.stdout = sys.stderr
 
if len(sys.argv) == 1:
count = 1
elif len(sys.argv) == 2:
try:
count = int(sys.argv[1])
except ValueError:
print "Invalid count %s!" % sys.argv[1]
raise SystemExit(1)
else:
print "Usage: y4mpad.py [count] <infile >outfile"
raise SystemExit(1)
 
# Get main header
header = infile.read(9)
if not header.startswith("YUV4MPEG2"):
print "Not a YUV4MPEG2 stream?"
raise SystemExit(1)
 
width, height, framerate = (None,) * 3
 
parts = infile.readline().split()
for tag in parts:
if tag.startswith("W"):
try:
width = int(tag[1:])
except ValueError:
print "Invalid width value %s!" % tag
raise SystemExit(1)
elif tag.startswith("H"):
try:
height = int(tag[1:])
except ValueError:
print "Invalid height value %s!" % tag
raise SystemExit(1)
elif tag.startswith("F"):
framerate = tag[1:]
 
if width is None or height is None:
print "No width or height!"
raise SystemExit(1)
 
print "Processing data: %dx%d @ %s fps" % (width, height, framerate)
 
# Write main file header
outfile.write("YUV4MPEG2 " + " ".join(parts) + "\n")
 
last_header = None
last_data = None
frames = 0
while True:
try:
header = infile.read(5)
except:
break

if not header:
break

if header != "FRAME":
print "Not a valid frame? %s" % str(header)
raise SystemExit(1)
 
header = "FRAME" + infile.readline()

# YUV 4:2:0, so read:
# width * height bytes for Y,
# width * height / 4 bytes for U,
# width * height / 4 bytes for V
frame_size = (width * height) + (width * height / 2)

data = infile.read(frame_size)

# Write frame header and data
outfile.write(header)
outfile.write(data)
frames += 1

if frames == 1:
# We are on the first frame... pad!
for x in range(count):
outfile.write(header)
outfile.write(data)
frames += 1

last_header = header
last_data = data
 
# We are on the last frame... pad!
for x in range(count):
outfile.write(last_header)
outfile.write(last_data)
frames += 1
 
print "Wrote %d frames" % frames

Comments

blog comments powered by Disqus