Intermediate Form

POTW: dvdencode.py

Previous Entry | Home | Next Entry

This week's program of the week is dvdencode.py, which is the script that I use to turn various video files into a format appropriate for burning onto DVDs. This program is much more of a script than last week's program, as it mostly serves to manage calls to other programs, which do most of the work. Still, it's very useful if you're into making DVDs on Linux, which is why I decided to post it.

→ http://tom.idealog.info/potw/dvdencode.py
→ http://tom.idealog.info/potw/dvdencode.py.html

Usage

Like all python programs I write, dvdencode.py requires python 2.3 or higher. It also requires transcode 0.6.11, the newest release of transcode, and a reasonably recent version of mplayer. Transcode, in turn, depends on the mpeg2enc program distributed as part of mjpeg tools for mpeg2 encoding. If you're using Debians, many of these packages can be found in the unofficial apt repository at marillat.free.fr.

usage: dvdencode.py [options] <from> <to> [ -- <transcode options> ]

options:
  --version          show program's version number and exit
  -h, --help         show this help message and exit
  --full             encode full size frames (704x480)
  --half             encode half size frames (352x480)
  --quarter          encode quarter size frames (352x240)
  --border, --anime  add a border to the outside of the movie.

Ideally, the program can be run using a command like:

   dvdencode.py --half movie.avi movie.mpg

This will attempt to detect the framerate and aspect ratio of the movie, and then will encode movie.avi as movie.mpg, in the dvd format. It only works on source movies that are encoded in framerates that are convertable to the 29.970 fps rate used by the NTSC television standard. Practically, this means that the input must be either at 29.970 or 23.976 fps. These are the rates used by NTSC video and NTSC movies, respectively.

The three size options --full, --half, and --quarter pick the size of the encoded DVD movie. In general, only the first two work reliably. I believe --quarter will work only on non-interlaced source material running at 29.970 fps... but most movies run at the slower rate, and most video is interlaced. In general, I find that the --half option gives the best balance of encoding time, picture quality, and file size. If you want the highest picture quality, use --full.

The --border option puts a black border around the picture. I find this to be useful when I'm encoding video that has been digitally subtitled. Often, I find that the digital subtitles are right at the edge of the movie frame. Most TVs, however, only display the center portion of a TV picture. This can lead to subtitles being partially or fully obscured, making them hard to read. --border fixes this by shrinking the picture.

Finally, the user can give a pair of dashes (--), and then begin specifying transcode arguments on the command line. The most useful transcode argument is -c, followed by a range of frames. This encodes only a range of frames, which is useful for testing out options quickly. A more practical command line to create a test movie is:

   dvdencode.py --half movie.avi movie.mpg -- -c 1000-1500

With the -c option removed for the final encode.

Program Operation

The most important thing thing this program does is to detect the input format of the source movie. It uses that information to make decisions about the options to give to transcode. The big two parameters that need to be determined are aspect ratio and pulldown.

The aspect ratio determines the shape of the final picture. When encoding mpegs, the resolution of the encoded movie has little to do with the shape of the final image. Instead, an aspect ratio flag is included in the file, telling the system if it should be 4:3 or 16:9. The decoder then rescales the decoded image to fit. dvdencode.py uses the tcprobe program (a part of transcode) to get the picture size and aspect ratio flag (if it exists) of the source video, and uses them to guess the output aspect ratio.

Pulldown is needed if the input is encoded at the slower framerate. Pulldown works by encoding into the mpeg movie instructions to repeat a field every few frames. This slows down the movie from the 24 to 30 fps. We first try to guess if pulldown is needed by checking the framerate reported by tcprobe. If it's the slower rate, we know that we need pulldown. Unfortunately, this isn't enough. This is because an mpeg with pulldown causes tcprobe to lie about the framerate. We need to detect pulldown (also known as telecine) in the source movie, which we do by checking the output of mplayer, which reports it. (As far as I know, there's no good way to detect telecine only using transcode.)

Once the various options dependant on the input have been probed, dvdencode.py determines scaling and padding options based on the flags given to it. These are used to create command lines for transcode and tcmplex, which are then called to encode the movie.

Thoughts

This program was originally based on a pair of scripts, written in perl and bash. Some of the constructs, when written in python, are a bit more verbose then the perl versions. For example, the perl code to guess the aspect ratio looks like:

my ($w, $h) = $probe =~ /-g (\d+)x(\d+)/;

...

if ($w / $h > 1.7) {
    $aspect = 3;
} else {
    $aspect = 2;
}

Whereas in the python version, it's:

   73         m = re.search(r"-g (\d+)x(\d+)", l)
   74         if m:
   75             x = float(m.group(1))
   76             y = float(m.group(2))
   77 
   78             if x / y > 1.7:
   79                 aspect = "16:9"
   80             else:
   81                 aspect = "4:3"

It's not much of a difference, but python does require more code to handle a regex. On the other hand, in python, regular expressions and their results are both first-class values, which is really useful. (Also useful in python is the ability to name groups in regular expressions, which is hard to do in perl's syntax.) While I normally like the regularity of python, this part of this program would seem to favor the perl syntax. (Probably because it's a port of a perl program.) On the other hand, the if statement is more compact in python, and in general there are less special characters. Overall, I prefer python, but for this code it may be a wash.

Python Bug?

The single hardest part of writing this program was a nasty bug involving the way in which python handles SIGPIPE. For some purpose I have yet to divine, python sets SIGPIPE to SIG_IGN, ignoring it. This value is inherited by child processes, which then fail to terminate in some cases. The upshot of this is that programs can behave in different ways when run in python, failing to terminate when things stop reading their output. To fix it, one has to include code like:

  import signal
  signal.signal(signal.SIGPIPE, signal.SIG_DFL)

I thing I have to do this in any code that uses os.system, os.spawn, or os.exec calls. It's annoying, and probably a bug in either python or Linux. (I don't know if the SIG_IGN setting should be inherited for SIGPIPE.)

It took me too long to track this down, so hopefully posting this here will help the next guy to get bit by this one.

Anyway, that's it for this week's program. Next week, I'll post my dvd menu and mastering code, which is the rest of what you need to master DVDs under Linux.

- Tom | permalink | changelog | Last updated: 2003-12-11 20:19

Previous Entry | Home | Next Entry

Comments

Commenting has been suspended due to spam.