#! /usr/bin/ruby # n800conv.rb -- a wrapper around mencode to convert video into something # usable for the Nokia N800 Internet Tablet. # Copyright (C) 2007 Michael T. Richter # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; Version 2, June 1991 (and no other). # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (in the file COPYING.txt); if not, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, # USA # The Nokia N800 Internet Tablet has good video-playback capabilities provided # the video fits within its restrictions. These restrictions include: # 1. Horizontal and vertical video dimensions are an even multiple of 16. # 2. A maximum of 800kbps throughput (audio and video combined). # 3. A maximum width of 400 or height of 240. # 4. Subtitles, if any, should be in the video, not in a separate file. # 5. The FOURCC setting should be "DIVX" and not "DX50". # 6. The audio should be 44.1kHz stereo or less. # # Using the MPlayer for playback relaxes some of these, but it's still best to # keep within them for maximum compatibility. # # This script will read the metadata of a passed-in AVI file and convert it so # that it adheres to the above guidelines, calculating all matters to provide # the best usable video for the N800. # # Prerequisites for using this script include: # 1. The RIO file I/O library. # 2. the RMovie gem (which itself requires the development kit for ffmpeg). begin require 'rubygems' ; rescue LoadError ; end require 'rio' require 'rmovie' module N800Conv class MenCoder def initialize &block # set up some sane default values self.N800Defaults # let the user fine-tune these instance_eval &block if block # check for sanity and consistency raise ArgumentError, "no maximum rate specified" unless @maximum_rate raise ArgumentError, "no audio rate specified" unless @audio_rate raise ArgumentError, "no maximum width specified" unless @maximum_width raise ArgumentError, "no maximum height specified" unless @maximum_height # calculate any parameters that haven't been over-ridden @video_rate ||= @maximum_rate - @audio_rate # protect ourself from changes self.freeze end def common_command_line [ "nice", "mencoder %s", # input file needs to be given in a % clause "-o %s", # as does output file "-oac lavc -lavcopts acodec=mp3:abitrate=%s" % @audio_rate, "-ovc lavc -lavcopts vcodec=mpeg4:autoaspect:vbitrate=%s:%s" % [@video_rate, "%s"], "-vf scale=%s:%s", "-ffourcc DIVX", "-idx" ].join(' ') end @@pass1 = "vpass=1:turbo" @@pass2 = "vpass=2" def convert infile, outfile=nil outfile ||= calculate_output_file infile width, height = calculate_output_dimensions infile Kernel.system common_command_line % [infile, outfile, @@pass1, width, height] Kernel.system common_command_line % [infile, outfile, @@pass2, width, height] end def calculate_output_file input_file (rio(input_file).dirname / rio(input_file).basename + "-n800" + rio(input_file).extname).to_s end def calculate_output_dimensions file m = RMovie::Movie.new file w = m.frame_width.to_f h = m.frame_height.to_f wr = w / @maximum_width hr = h / @maximum_height if wr > hr then width = @maximum_width height = (h / wr).to_i height -= height % 16 # lose a few pixels here to keep in spec else height = @maximum_height width = (w / hr).to_i width -= width % 16 # lose a few pixels here to keep in spec end return width, height end def N800Defaults @maximum_rate = 800 # kbps @audio_rate = 96 # default audio rate @maximum_width = 400 # pixels @maximum_height = 240 # pixels end end end # Mainline code if __FILE__ == $PROGRAM_NAME then require 'optparse' class Arguments < Hash def initialize(args) super() opts = OptionParser.new do |opts| opts.banner = "Usage: #$0 [options] " opts.on('-o', '--output_file [FILENAME]', 'write output to [FILENAME] instead of a default file', 'cannot be used with multiple input files') do |filename| self[:output_file] = filename end opts.on('-w', '--maximum_width [PIXELS]', 'set maximum video width to [PIXELS] instead of the device default') do |pixels| self[:maximum_width] = pixels.to_i end opts.on('-h', '--maximum_height [PIXELS]', 'set maximum video height to [PIXELS] instead of the device default') do |pixels| self[:maximum_height] = pixels.to_i end opts.on('-m', '--maximum_rate [KBPS]', 'set the maximum combined audio/video rate to [KBPS] instead of the default') do |kbps| self[:maximum_rate] = kbps.to_i end opts.on('-a', '--audio_rate [KBPS]', 'set the audio rate to [KBPS] instead of the default') do |kbps| self[:audio_rate] = kbps.to_i end opts.on('-v', '--video_rate [KBPS]', 'set the video rate to [KBPS] instead of the default', 'NOTE: only two of -m, -a and -v may be specified on the command line') do |kbps| self[:video_rate] = kbps.to_i end opts.on('-?', '--help', 'display this help and exit') do puts opts exit end begin opts.parse!(args) rescue OptionParser::InvalidOption puts opts end if ARGV.size < 1 then puts "Error: no input file specified." puts opts elsif ARGV.size > 1 and self[:output_file] then puts "Error: cannot specify output file and process more than one file at a time." puts opts elsif self[:audio_rate] and self[:video_rate] and self[:maximum_rate] then puts "Error: cannot set more than two of maximum rate, audio rate and video rate." puts opts end end end end arguments = Arguments.new(ARGV) transcoder = N800Conv::MenCoder.new do self.N800Defaults @maximum_width = arguments[:maximum_width] || @maximum_width @maximum_height = arguments[:maximum_height] || @maximum_height @maximum_rate = arguments[:maximum_rate] || @maximum_rate @audio_rate = arguments[:audio_rate] || @audio_rate @video_rate = arguments[:video_rate] || @video_rate end ARGV.each { |f| transcoder.convert f } end