n900-encode.py: Create N900-friendly MP4-Videos

Today I present a (relatively) simple script that I wrote to convert videos to a format suitable for viewing on the Nokia N900. I know that there are many solutions for doing this, but none could handle subtitles (especially styled ones like SSA) well. Also I wanted the script to be scriptable, e.g. for converting a whole directory. Finally its a follow-up from my old n800-encode.sh script. So here is my solution to creating N900-friendly MP4:

The most recent version can always be found at my GitHub.

The Features

  • scriptable
  • automatic output resolution calculation
  • thanks to mencoder it can handle a lot of input formats
  • adjustable default values
  • (hopefully) robust error handling

Version History

  • Version 0.1 (12. Feb 2010)
    • Initial Release
  • Version 0.2 (15. May 2010)
    • Switched encoder to mencoder due to sync problems with mplayer/ffmpeg combination
    • enabled CRF-Encoding as an alternative to Constant Bitrate
  • Version 1.1 (22.02.2012)
    • Script now runs on python 3 and python 2
    • ffmpeg is used as encoder again (tested with ffmpeg version 0.10)

Known Limitations and Bugs

  • only tested on Linux
  • h264 options might need some tuning, but seems to run fine on my N900 so far
  • videos with variable framerate might not work right

The Prequisites

  • mplayer for identifying video and audio
  • mencoder for creating the h264 and aac streams
  • MP4Box (from gpac) for muxing the final Video
  • python since its written in this language

The Usage

The simplest way is to just run this command:

n900-encode.py -i some_video.avi -o n900_video.mp4

for more available options see the script below or use the included help function (-h)

The Script

n900-encode.py
#!/usr/bin/env python
 
###########################################################################################
# n900-encode.py: Encode almost any Video to an Nokia N900-compatible format (h264,aac,mp4)
# Disclaimer: This program is provided without any warranty, USE AT YOUR OWN RISK!
#
# (C) 2010 Stefan Brand <seiichiro@seiichiro0185.org>
#
# Version 0.2
###########################################################################################
 
import sys, os, getopt, subprocess, re, atexit
from signal import signal, SIGTERM, SIGINT
from time import sleep
 
version = "0.2"
 
###########################################################################################
# Default values, feel free to adjust
###########################################################################################
 
_basewidth = 800         # Base width for widescreen Video
_basewidth43 = 640    # Base width for 4:3 Video
_maxheight = 480        # maximum height allowed
_abitrate = 112            # Audio Bitrate in kBit/s
_vbitrate = 22             # Video Bitrate, if set to value < 52 it is used as a CRF Value for Constant rate factor encoding
_threads = 0                # Use n Threads to encode (0 means use number of CPUs / Cores)
_mpbin = None                # mplayer binary, if set to None it is searched in your $PATH
_mcbin = None                # mencoder binary, if set to None it is searched in your $PATH
_m4bin = None             # MP4Box binary, if set to None it is searched in your $PATH
 
###########################################################################################
# Main Program, no changes needed below this line
###########################################################################################
 
def main(argv):
    """Main Function, cli argument processing and checking"""
 
    # CLI Argument Processing
    try:
        opts, args = getopt.getopt(argv, "i:o:m:v:a:t:hf", ["input=", "output=", "mpopts=", "abitrate=", "vbitrate=", "threads=", "help", "force-overwrite"])
    except getopt.GetoptError, err:
        print str(err)
        usage()
 
    input = None
    output = "n900encode.mp4"
    mpopts = ""
    abitrate = _abitrate
    vbitrate = _vbitrate
    threads = _threads
    overwrite = False
    for opt, arg in opts:
        if opt in ("-i", "--input"):
            input = arg
        elif opt in ("-o" "--output"):
            output = arg
        elif opt in ("-m" "--mpopts"):
            mpopts = arg
        elif opt in ("-a", "--abitrate"):
            abitrate = int(arg)
        elif opt in ("-v", "--vbitrate"):
            vbitrate = int(arg)
        elif opt in ("-t", "--threads"):
            threads = arg
        elif opt in ("-f", "--force-overwrite"):
            overwrite = True
        elif opt in ("-h", "--help"):
            usage()
 
    # Check for needed Programs
    global mpbin
    mpbin = None
    if not _mpbin == None and os.path.exists(_mpbin) and not os.path.isdir(_mpbin):
        mpbin = _mpbin
    else:
        mpbin = progpath("mplayer")
    if mpbin == None:
        print "Error: mplayer not found in PATH and no binary given, Aborting!"
        sys.exit(1)
 
    global mcbin
    mcbin = None
    if not _mcbin == None and os.path.exists(_mcbin) and not os.path.isdir(_mcbin):
        mcbin = _mcbin
    else:
        mcbin = progpath("mencoder")
    if mcbin == None:
        print "Error: mencoder not found in PATH and no binary given, Aborting!"
        sys.exit(1)
 
    global m4bin
    m4bin = None
    if not _m4bin == None and os.path.exists(_m4bin) and not os.path.isdir(_m4bin):
        m4bin = _m4bin
    else:
        m4bin = progpath("MP4Box")
    if m4bin == None:
        print "Error: MP4Box not found in PATH and no binary given, Aborting!"
        sys.exit(1)
 
    # Check input and output files
    if not os.path.isfile(input):
        print "Error: input file is not a valid File or doesn't exist"
        sys.exit(2)
 
    if os.path.isfile(output):
        if overwrite:
            os.remove(output)
        else:
            print "Error: output file " + output + " already exists, force overwrite with -f"
            sys.exit(1)
 
    # Start Processing
    res = calculate(input)
    convert(input, output, res, _abitrate, vbitrate, threads, mpopts)
    sys.exit(0)
 
 
def calculate(input):
    """Get Characteristics from input video and calculate resolution for output"""
 
    # Get characteristics using mplayer
    cmd=[mpbin, "-ao", "null", "-vo", "null", "-frames", "0", "-identify", input]
    mp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
 
    try:
        s = re.compile("^ID_VIDEO_ASPECT=(.*)$", re.M)
        m = s.search(mp[0])
        orig_aspect = m.group(1)
        s = re.compile("^ID_VIDEO_WIDTH=(.*)$", re.M)
        m = s.search(mp[0])
        orig_width = m.group(1)
        s = re.compile("^ID_VIDEO_HEIGHT=(.*)$", re.M)
        m = s.search(mp[0])
        orig_height = m.group(1)
        s = re.compile("^ID_VIDEO_FPS=(.*)$", re.M)
        m = s.search(mp[0])
        fps = m.group(1)
 
    except:
        print "Error: unable to identify source video, exiting!"
        sys.exit(2)
 
    # Calculate output resolution
    if float(orig_aspect) == 0 or orig_aspect == "":
        orig_aspect = float(orig_width)/float(orig_height)
    width = _basewidth
    height = int(round(_basewidth / float(orig_aspect) / 16) * 16)
    if (height > _maxheight):
        width = _basewidth43
        height = int(round(_basewidth43 / float(orig_aspect) / 16) * 16)
 
    return (width, height, fps)
 
 
def convert(input, output, res, abitrate, vbitrate, threads, mpopts):
    """Convert the Video"""
 
    # Needed for cleanup function
    global h264, aac
 
    # define some localvariables
    pid = os.getpid()
    h264 = output + ".h264"
    aac = output + ".aac"
    width = str(res[0])
    height = str(res[1])
    fps = str(res[2])
 
    if (vbitrate < 52):
        vbr = "crf=" + str(vbitrate)
    else:
        vbr = "bitrate=" + str(vbitrate)
 
    # Define mencoder command for video encoding
    mencvideo = [ mcbin,
            "-ovc", "x264",
            "-x264encopts", vbr + ":bframes=0:trellis=0:nocabac:no8x8dct:level_idc=30:frameref=4:me=umh:weightp=0:vbv_bufsize=2000:vbv_maxrate=1800:threads=" + str(threads),
            "-sws", "9",
            "-vf", "scale=" + width + ":" + height + ",dsize=" + width + ":" + height + ",fixpts=fps=" + fps + ",ass,fixpts,harddup",
            "-of", "rawvideo", 
            "-o", h264,
            "-nosound",
            "-noskip",
            "-ass",
            input ]
    if (mpopts != ""):
        for mpopt in mpopts.split(" "):
            mencvideo.append(mpopt)
 
    # Define mencoder command for audio encoding
    mencaudio = [ mcbin,
            "-oac", "faac",
            "-faacopts", "br=" + str(abitrate) + ":mpeg=4:object=2",
            "-ovc", "copy",
            "-mc", "0",
            "-vf", "softskip",
            "-of", "rawaudio",
            "-o", aac,
            "-noskip",
            input ]
    if (mpopts != ""):
        for mpopt in mpopts.split(" "):
            mencaudio.append(mpopt)
 
 
    # Define MB4Box Muxing Command
    mp4mux = [ m4bin,
            "-fps", fps,
            "-new", "-add", h264,
            "-add", aac,
            output ]
 
    # Encode Video
    print "### Starting Video Encode ###"
    try:
        subprocess.check_call(mencvideo)
        print "### Video Encoding Finished. ###"
    except subprocess.CalledProcessError:
        print "Error: Video Encoding Failed!"
        sys.exit(3)
 
    print "### Starting Audio Encode ###"
    try:
        subprocess.check_call(mencaudio)
        print "### Audio Encode Finished. ###"
    except subprocess.CalledProcessError:
        print "Error: Audio Encoding Failed!"
        sys.exit(3)
 
    # Mux Video and Audio to MP4
    print "### Starting MP4 Muxing ###"
    try:
        subprocess.check_call(mp4mux)
        print "### MP4 Muxing Finished. Video is now ready! ###"
    except subprocess.CalledProcessError:
        print "Error: Encoding thread failed!"
        sys.exit(4)
 
 
def progpath(program):
    """Get Full path for given Program"""
 
    for path in os.environ.get('PATH', '').split(':'):
        if os.path.exists(os.path.join(path, program)) and not os.path.isdir(os.path.join(path, program)):
            return os.path.join(path, program)
    return None
 
def cleanup():
    """Clean up when killed"""
 
    # Cleanup
    try:
        os.remove(h264)
        os.remove(aac)
    finally:
        sys.exit(0)
 
 
def usage():
    """Print avaiable commandline arguments"""
 
    print "This is n900-encode.py Version" + version + "(C) 2010 Stefan Brand <seiichiro0185 AT tol DOT ch>"
    print "Usage:"
    print "  n900-encode.py --input <file> [opts]\n"
    print "Options:"
    print "  --input <file>    [-i]: Video to Convert"
    print "  --output <file>   [-o]: Name of the converted Video"
    print "  --mpopts \"<opts>\" [-m]: Additional options for mplayer (eg -sid 1 or -aid 1) Must be enclosed in \"\""
    print "  --abitrate <br>   [-a]: Audio Bitrate in KBit/s"
    print "  --vbitrate <br>   [-v]: Video Bitrate in kBit/s, values < 52 activate h264 CRF-Encoding, given value is used as CRF Factor"
    print "  --threads <num>   [-t]: Use <num> Threads to encode, giving 0 will autodetect number of CPUs"
    print "  --force-overwrite [-f]: Overwrite output-file if existing"
    print "  --help            [-h]: Print this Help"
    sys.exit(0)
 
 
# Start the Main Function
if __name__ == "__main__":
    # Catch kill and clean up
    atexit.register(cleanup)
 
    signal(SIGTERM, lambda signum, stack_frame: exit(1))
    signal(SIGINT, lambda signum, stack_frame: exit(1))
 
    # Check min params and start if sufficient
    if len(sys.argv) > 1:
        main(sys.argv[1:])
    else:
        print "Error: You have to give an input file at least!"
        usage()

Simply use the download link above the code to get the script and put it into your PATH.

If you have questions or suggestions, or if you find bugs feel free to use the comment section below or drop me an email.

Comments

1
15.05.2010 09:37

[…] original site […]

2
Alexei
15.05.2010 23:27

Please fix this error message:

if m4bin == None:
	print "Error: mencoder not found in PATH and no binary given, Aborting!"
	sys.exit(1)

I've just spent ten minutes to figure out why it can't find a freshly installed mencoder. Now I'm going to investigate how to install MP4Box on debian :-)

3
Seiichiro
16.05.2010 06:34

thanks for the hint, fixed. That happens if you do copy & paste. :)

4
17.05.2010 14:11

[…] n900-encode.py: Create N900-friendly MP4-Videos [Seiichiros HP] […]

5
Cj
19.07.2010 04:30

I recently got my Nokia N900 and i want to converter videos to the N900, but to really i have no idea of how to use the The Script You have given.. because Im not much of a computer person.. so please can you instruct me in converting the video? PLEASE.:-D

6
a
21.07.2010 15:32

Hi, just wanted to let you know I'm getting the following error on Ubuntu Lucid, do you know what to do? $ ./n900-encode.py -i f.avi -o f.mp4 ### Starting Video Encode ### MEncoder SVN-r1.0~rc3+svn20090426-4.4.3 (C) 2000-2009 MPlayer Team Option vf: fixpts doesn't exist. Error parsing option on the command line: -vf Exiting… (error parsing command line) Error: Video Encoding Failed!

7
a
21.07.2010 15:33

Hmm, apparently there was a formatting screw-up: this is the output:

  1. $ ./n900-encode.py -i f.avi -o f.mp4
  2. ### Starting Video Encode ###
  3. MEncoder SVN-r1.0~rc3+svn20090426-4.4.3 (C) 2000-2009 MPlayer Team
  4. Option vf: fixpts doesn't exist.
  5. Error parsing option on the command line: -vf
  6. Exiting… (error parsing command line)
  7. Error: Video Encoding Failed!
8
Seiichiro
25.07.2010 08:23

sorry for the delay, but I'm moving at the moment :) The script is meant to be used on the linux commandline. The basic usage is like this:

/path/to/n900-encode.sh -i inputvideo.avi -o outputvideo.mp4

all other options are optional, the standard settings should create a working video fpr the N900

9
Seiichiro
25.07.2010 08:25

looks like your mencoder-version doesn't recognize the fixpts filter. according to the version string your version is from Apil 2009 which is quite old. maybe you could try a newer mencoder/mplayer version

10
isbaran
09.10.2010 14:37

hi,

this script is huge .. i think you're trying to do other programs jobs in your script. this is a bad way. it'd be better if you just wrote a mencoder profile for n900 and let mencoder do its job.

11
Seiichiro
10.10.2010 11:24

@isbaran: Well, I tried to do that at first, but at least the last time I tried mencoder's MP4 support it was pretty broken and didn't create any useable MP4 files (at least not useable on my N900). So I would at least have to re-create the MP4 with MP4Box. And during my experiments I got to the conclusion that this worked the most reliable with having separate streams for audio/video like my script creates. Therefor I automated the process in the script. It may be that in the meantime mencoder is capable of creating proper MP4-files, but since my script works nicely for me I didn't test it again.

12
sam
13.10.2010 00:03

Hi im having a big issue with this script when i try adn run it through terminal i get command not found. can you please help or do a step by step tutorial on how to set this up ^^'

im trying this in Ubuntu 10.10 wubi setup because i dont want to install it just yet until i get this working

13
Seiichiro
13.10.2010 07:58

@sam: how exactly do you try to run it? I guess you used the link on top of the script and saved it to a file?

Make sure you make the file executable with the command chmod 755 n900-encode.py (assuimng you are in the directory where you put the script) after that you should be able to run it with ./n900-encode.py <options> (again being in the directory you put the script in)

if you want to run it from arbitary directories you will have to put it in you PATH (have a look here for a short explanation: http://www.linuxheadquarters.com/howto/basic/path.shtml)

If this is still not working please describe what you did exactly so I can see what is going wrong.

14
Matt
10.11.2010 08:05

Hey, thanks very much! This makes scripting the conversions much easier. Plus it's the first settings I've found that have actually output n900-playable files - the handbrake preset in the maemo wiki wasn't working for me, for some reason. This is perfect.

A quick note in case anyone else has the same problem as me - the current svn version of mplayer/mencoder will sometimes incorrectly detect the fps rate of mkv files. Adding '-demuxer mkv' to the command line fixes this. I wasn't able to add it using the -m option to n900-encode.py though, because it also needs to be used for the mplayer command in the calculate function. A couple of extra parameters to the command list if the input files ends with '.mkv' works though :)




C W I D S
blog/n900-encode.py_create_n900-friendly_mp4-videos.txt · Last modified: 22.02.2012 20:16 by Seiichiro
CC Attribution-Share Alike 3.0 Unported
Driven by DokuWiki Hosted by Linode Recent changes RSS feed Valid XHTML 1.0