n900-encode/n900-encode.py

305 lines
8.6 KiB
Python
Raw Normal View History

2010-02-12 10:23:29 +00:00
#!/usr/bin/env python
2010-02-12 11:29:11 +00:00
###########################################################################################
# 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!
#
2012-02-22 19:55:40 +00:00
# Version 1.1 (22.02.2012)
#
# (C) 2010-2012 Stefan Brand <seiichiro@seiichiro0185.org>
2011-07-17 08:47:20 +00:00
#
# This programm is licensed under the Terms of the GPL Version 3
# See COPYING for more info.
#
2010-02-12 11:29:11 +00:00
###########################################################################################
import sys, os, getopt, subprocess, re, atexit
from signal import signal, SIGTERM, SIGINT
from time import sleep
2010-02-12 10:23:29 +00:00
2010-02-12 11:29:11 +00:00
###########################################################################################
# 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 = 96 # Audio Bitrate in kBit/s
_vbitrate = 500 # Video Bitrate in kBit/s
_threads = 2 # Use n Threads to encode
_mpbin = None # mplayer binary, if set to None it is searched in your $PATH
_ffbin = None # ffmpeg binary, if set to None it is searched in your $PATH
###########################################################################################
2010-02-12 11:39:39 +00:00
# Main Program, no changes needed below this line
2010-02-12 11:29:11 +00:00
###########################################################################################
2010-02-12 10:23:29 +00:00
2012-02-22 18:31:45 +00:00
# Global Variables
mda = None
mdv = None
afifo = None
vfifo = None
# Main Function
2010-02-12 10:23:29 +00:00
def main(argv):
"""Main Function, cli argument processing and checking"""
2010-02-12 11:29:11 +00:00
# CLI Argument Processing
2010-02-12 10:23:29 +00:00
try:
2010-02-12 11:29:11 +00:00
opts, args = getopt.getopt(argv, "i:o:m:v:a:t:hf", ["input=", "output=", "mpopts=", "abitrate=", "vbitrate=", "threads=", "help", "force-overwrite"])
2012-02-22 18:22:47 +00:00
except getopt.GetoptError as err:
printi(str(err))
2010-02-12 10:23:29 +00:00
usage()
2010-02-12 11:29:11 +00:00
2010-02-12 10:23:29 +00:00
input = None
2010-02-12 11:29:11 +00:00
output = "n900encode.mp4"
2010-02-12 10:23:29 +00:00
mpopts = ""
abitrate = _abitrate * 1000
vbitrate = _vbitrate * 1000
threads = _threads
2010-02-12 11:29:11 +00:00
overwrite = False
2010-02-12 10:23:29 +00:00
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"):
2010-02-12 16:18:01 +00:00
abitrate = int(arg) * 1000
2010-02-12 10:23:29 +00:00
elif opt in ("-v", "--vbitrate"):
vbitrate = int(arg)
2010-02-12 10:23:29 +00:00
elif opt in ("-t", "--threads"):
threads = arg
2010-02-12 11:29:11 +00:00
elif opt in ("-f", "--force-overwrite"):
overwrite = True
2010-02-12 10:23:29 +00:00
elif opt in ("-h", "--help"):
usage()
2010-02-12 11:29:11 +00:00
# Check for needed Programs
global mpbin
mpbin = None
2010-02-12 12:08:54 +00:00
if not _mpbin == None and os.path.exists(_mpbin) and not os.path.isdir(_mpbin):
mpbin = _mpbin
2010-02-12 11:29:11 +00:00
else:
2010-02-12 12:08:54 +00:00
mpbin = progpath("mplayer")
2010-02-12 11:29:11 +00:00
if mpbin == None:
2012-02-22 18:22:47 +00:00
print("Error: mplayer not found in PATH and no binary given, Aborting!")
2010-02-12 11:29:11 +00:00
sys.exit(1)
global ffbin
ffbin = None
2010-02-12 12:08:54 +00:00
if not _ffbin == None and os.path.exists(_ffbin) and not os.path.isdir(_ffbin):
ffbin = _ffbin
2010-02-12 11:29:11 +00:00
else:
2010-02-12 12:08:54 +00:00
ffbin = progpath("ffmpeg")
2010-02-12 11:29:11 +00:00
if ffbin == None:
2012-02-22 18:22:47 +00:00
print( "Error: ffmpeg not found in PATH and no binary given, Aborting!")
2010-02-12 11:29:11 +00:00
sys.exit(1)
# Check input and output files
2010-02-12 10:23:29 +00:00
if not os.path.isfile(input):
2012-02-22 18:22:47 +00:00
print("Error: input file is not a valid File or doesn't exist")
2010-02-12 10:23:29 +00:00
sys.exit(2)
2010-02-12 11:29:11 +00:00
if os.path.isfile(output):
if overwrite:
os.remove(output)
else:
2012-02-22 18:22:47 +00:00
print("Error: output file " + output + " already exists, force overwrite with -f")
2010-02-12 11:29:11 +00:00
sys.exit(1)
2010-02-12 11:29:11 +00:00
# Start Processing
2010-02-12 10:23:29 +00:00
res = calculate(input)
convert(input, output, res, abitrate, vbitrate, threads, mpopts)
sys.exit(0)
2010-02-12 10:23:29 +00:00
def calculate(input):
2010-02-12 11:29:11 +00:00
"""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()
2012-02-22 19:48:43 +00:00
2010-02-12 11:29:11 +00:00
try:
s = re.compile("^ID_VIDEO_ASPECT=(.*)$", re.M)
2012-02-22 19:48:43 +00:00
m = s.search(bytes.decode(mp[0]))
2010-02-12 11:29:11 +00:00
orig_aspect = m.group(1)
s = re.compile("^ID_VIDEO_WIDTH=(.*)$", re.M)
2012-02-22 19:48:43 +00:00
m = s.search(bytes.decode(mp[0]))
2010-02-12 11:29:11 +00:00
orig_width = m.group(1)
s = re.compile("^ID_VIDEO_HEIGHT=(.*)$", re.M)
2012-02-22 19:48:43 +00:00
m = s.search(bytes.decode(mp[0]))
2010-02-12 11:29:11 +00:00
orig_height = m.group(1)
except:
2012-02-22 18:22:47 +00:00
print("Error: unable to identify source video, exiting!")
2010-02-12 11:29:11 +00:00
sys.exit(2)
2010-02-12 10:23:29 +00:00
2010-02-12 11:29:11 +00:00
# Calculate output resolution
2010-02-12 16:18:01 +00:00
if float(orig_aspect) == 0 or orig_aspect == "":
2010-02-12 20:09:03 +00:00
orig_aspect = float(orig_width)/float(orig_height)
2010-02-12 10:23:29 +00:00
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)
def convert(input, output, res, abitrate, vbitrate, threads, mpopts):
"""Convert the Video"""
# Needed for cleanup function
global afifo, vfifo, mda, mdv
2010-02-12 11:29:11 +00:00
# Create FIFOs for passing audio/video from mplayer to ffmpeg
2010-02-12 10:23:29 +00:00
pid = os.getpid()
afifo = "/tmp/stream" + str(pid) + ".wav"
vfifo = "/tmp/stream" + str(pid) + ".yuv"
os.mkfifo(afifo)
os.mkfifo(vfifo)
2010-02-12 11:29:11 +00:00
# Define mplayer command for video decoding
mpvideodec = [ mpbin,
"-sws", "9",
"-vf", "scale=" + str(res[0]) + ":" + str(res[1]) + ",unsharp=c4x4:0.3:l5x5:0.5",
"-vo", "yuv4mpeg:file=" + vfifo,
"-ao", "null",
"-nosound",
"-noframedrop",
"-benchmark",
"-quiet",
2010-02-12 13:36:31 +00:00
"-nolirc",
2010-02-12 11:29:11 +00:00
"-msglevel", "all=-1",
input ]
2010-02-12 11:39:39 +00:00
for mpopt in mpopts.split(" "):
mpvideodec.append(mpopt)
2010-02-12 11:29:11 +00:00
# Define mplayer command for audio decoding
mpaudiodec = [ mpbin,
"-ao", "pcm:file=" + afifo,
"-vo", "null",
"-vc", "null",
"-noframedrop",
"-quiet",
2010-02-12 13:36:31 +00:00
"-nolirc",
2010-02-12 11:29:11 +00:00
"-msglevel", "all=-1",
input ]
2010-02-12 11:39:39 +00:00
for mpopt in mpopts.split(" "):
mpaudiodec.append(mpopt)
2010-02-12 11:29:11 +00:00
# Define ffmpeg command for a/v encoding
if (vbitrate > 51):
rmode = "-b:v"
vbitr = str(vbitrate*1000)
else
rmode = "-crf"
vbitr = vbitrate
2010-02-12 11:29:11 +00:00
ffmenc = [ ffbin,
"-f", "yuv4mpegpipe",
"-i", vfifo,
"-i", afifo,
2012-02-22 19:48:43 +00:00
"-acodec", "aac",
"-strict", "experimental",
2010-02-12 11:29:11 +00:00
"-ac", "2",
"-ab", str(abitrate),
2012-02-22 19:48:43 +00:00
"-ar", "44100",
2010-02-12 11:29:11 +00:00
"-vcodec", "libx264",
"-threads", str(threads),
2012-02-22 19:48:43 +00:00
"-vprofile", "baseline",
"-tune", "animation",
rmode, vbitr,
2010-02-12 11:29:11 +00:00
"-flags", "+loop", "-cmp", "+chroma",
"-partitions", "+parti4x4+partp8x8+partb8x8",
"-subq", "5", "-trellis", "1", "-refs", "1",
"-coder", "0", "-me_range", "16",
"-g", "300", "-keyint_min", "25",
"-sc_threshold", "40", "-i_qfactor", "0.71",
2010-02-12 16:18:01 +00:00
"-bt", "640", "-bufsize", "10M", "-maxrate", "1000000",
2010-02-12 11:29:11 +00:00
"-rc_eq", "'blurCplx^(1-qComp)'",
"-qcomp", "0.62", "-qmin", "10", "-qmax", "51",
2012-02-22 19:48:43 +00:00
"-x264opts", "level=3.0", "-f", "mp4",
2010-02-12 11:29:11 +00:00
output ]
# Start mplayer decoding processes in background
try:
mdv = subprocess.Popen(mpvideodec, stdout=None, stderr=None)
mda = subprocess.Popen(mpaudiodec, stdout=None, stderr=None)
2010-02-12 11:29:11 +00:00
except:
2012-02-22 18:22:47 +00:00
print("Error: Starting decoding threads failed!")
2010-02-12 11:29:11 +00:00
sys.exit(3)
# Start ffmpeg encoding process in foreground
try:
subprocess.check_call(ffmenc)
except subprocess.CalledProcessError:
2012-02-22 18:22:47 +00:00
print("Error: Encoding thread failed!")
sys .exit(4)
2010-02-12 10:23:29 +00:00
2010-02-12 11:29:11 +00:00
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"""
# give ffmpeg time to stop
sleep(2)
# Cleanup
try:
2012-02-22 18:31:45 +00:00
if (mda != None):
os.kill(mda.pid())
if (mdv != None):
os.kill(mdv.pid())
finally:
2012-02-22 18:31:45 +00:00
if (afifo != None):
2010-02-12 16:18:01 +00:00
os.remove(afifo)
2012-02-22 18:31:45 +00:00
if (vfifo != None):
2010-02-12 16:18:01 +00:00
os.remove(vfifo)
2012-02-22 18:31:45 +00:00
os._exit(0)
2010-02-12 10:23:29 +00:00
def usage():
"""Print avaiable commandline arguments"""
2012-02-22 18:22:47 +00:00
print("This is n900-encode.py (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")
print(" --threads <num> [-t]: Use <num> Threads to encode")
print(" --force-overwrite [-f]: Overwrite output-file if existing")
print(" --help [-h]: Print this Help")
2012-02-22 18:31:45 +00:00
os._exit(0)
2010-02-12 10:23:29 +00:00
# Start the Main Function
if __name__ == "__main__":
2010-02-12 13:36:31 +00:00
# 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:
2012-02-22 18:22:47 +00:00
print("Error: You have to give an input file at least!")
usage()