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 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
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
thanks for the hint, fixed. That happens if you do copy & paste. :)
[…] n900-encode.py: Create N900-friendly MP4-Videos [Seiichiros HP] […]
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.
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!
Hmm, apparently there was a formatting screw-up: this is the output:
- $ ./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!
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
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
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.
@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.
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
@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.
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 :)
[…] original site […]