2010年12月19日日曜日

TAEC.py

またまた某所にて
(silverfilain) どうせならMaxMBPSとMaxFSも考慮して幅・高さ・fpsから
最適なレベルを表示するのも作って欲しいぉ
(Chikuzen) じゃあ、ちょっと調べてみる
てな感じで、また書くことになった。
#!/bin/env python
# coding: utf-8
#****************************************************************************
#  TAEC(Tiny Avc Encode Consultant).py 
#                                                     written by Chikuzen
#  Reference literature:
#    Rec. ITU-T H.264 (03/2010) – Prepublished version
#    インプレス標準教科書シリーズ改訂版 H.264/AVC教科書
#           著者:(監修)大久保 榮/(編者)角野 眞也、菊池 義浩、鈴木 輝彦
#    http://en.wikipedia.org/wiki/H.264/MPEG-4_AVC
#    猫科研究所( http://www.up-cat.net/ )
#         x264(vbv-maxrate,vbv-bufsize,profile,level),H.264(Profile/Level)
#
#****************************************************************************

__version__ = '0.3.3'
import sys
import getopt
import math

def set_default():
    width   = int(1280) 
    height  = int(720)
    fpsnum  = int(30000)
    fpsden  = int(1001)
    profile = 'high'
    mode    = 'progressive'
    return [[width, height], [fpsnum, fpsden], profile, mode]

def usage():
    param = set_default()
    print "\nUsage: taec.py [options]\n"
    print "  -r, --resolution <string> :set 'width x height' ('%ix%i')" % tuple(param[0])
    print "  -f, --fps <string>        :set 'fpsnum / fpsden' ('%i/%i')" % tuple(param[1])
    print "  -p, --profile <string>    :set 'profile' ('%s')" % param[2]
    print "  -i, --interlaced          :specify interlaced mode (not specified)"
    print "  -v, --version             :display version"
    print "  -h, --help                :display this help and exit\n"

def check_res_and_fps(arg, r_or_f):
    try:
        param = [abs(int(i)) for i in arg.split('x' * r_or_f or '/')]
        if len(param) != 2:
            raise SyntaxError
    except:
        print "\nERROR : invalid %s setting." % ('resolution' * r_or_f or 'fps')
        usage()
        sys.exit()
    else:
        return param

def check_profile(profile, ipflag):
    if profile in ('baseline', 'main', 'high'):
        if profile != 'baseline' or ipflag != 'interlaced':
            return 1
        else:
            print "\nERROR : baseline cannot accept interlaced."
    print "\nERROR : invalid profile setting."
    usage()
    sys.exit()

def calc_bs(resolution, fps, ipflag):
    fstmp = [int(math.ceil(i / 16.0)) for i in resolution]
    fs = dbp = fstmp[0] * fstmp[1]
    mbps  = fs * fps[0] // fps[1]
    if ipflag == 'interlaced':
        dbp += fstmp[0] * (fstmp[1] % 2)
    return [mbps, fs, dbp]

def calc_lv(bs, ipflag, spec):
    for i in spec:
        if bs[0] <= i[1] and bs[1] <= i[2] and bs[2] <= i[3]:
            return i[0]
    if ipflag == 'interlaced':
        print "ERROR : interlaced encoding cannot be done to this video."
    print "ERROR : there is no suitable setting."
    usage()
    sys.exit()

def calc_result(profile, bitstream, line):
    vbv = [int(i * ((profile == 'high') * 1.25 or 1)) for i in line[4]]
    ref = [16 * (i > 16) or i for i in [line[3] // bitstream[2]]]
    return tuple([line[0]] + vbv + ref)

def display_result(level, profile, bitstream, spec):
    index = [i[0] for i in spec]
    try:
        for i in xrange(len(index)):
            if index[i] == level:
                line = spec[i]
                print "%5s%12i%13i%8i" % calc_result(profile, bitstream, line)
                level = index[i + 1]
    except:
        return 0

def get_spec():
#H.264/AVC spec [(level, MaxMBPs, MaxFS, MaxDbpMBs, [MaxBR, MaxCPB], ipflag]}
    return [('1.0',   1485,    99,    396, [    64,    175], 'p'),
            ('1b ',   1485,    99,    396, [   128,    350], 'p'),
            ('1.1',   3000,   396,    900, [   192,    500], 'p'),
            ('1.2',   6000,   396,   2376, [   384,   1000], 'p'),
            ('1.3',  11880,   396,   2376, [   768,   2000], 'p'),
            ('2.0',  11880,   396,   2376, [  2000,   2000], 'p'),
            ('2.1',  19800,   792,   4752, [  4000,   4000], 'i'),
            ('2.2',  20250,  1620,   8100, [  4000,   4000], 'i'),
            ('3.0',  40500,  1620,   8100, [ 10000,  10000], 'i'),
            ('3.1', 108000,  3600,  18000, [ 14000,  14000], 'i'),
            ('3.2', 216000,  5120,  20480, [ 20000,  20000], 'i'),
            ('4.0', 245760,  8192,  32768, [ 20000,  25000], 'i'),
            ('4.1', 245760,  8192,  32768, [ 50000,  62500], 'i'),
            ('4.2', 491520,  8192,  34816, [ 50000,  62500], 'p'),
            ('5.0', 589824, 22080, 110400, [135000, 135000], 'p'),
            ('5.1', 983040, 36864, 184320, [240000, 240000], 'p')]

def set_param(opts, param):
    for opt, arg in opts:
        if opt in ("-r", "--resolution"):
            param[0] = check_res_and_fps(arg, 1)
        elif opt in ("-f", "--fps"):
            param[1] = check_res_and_fps(arg, 0)
        elif opt in ("-p", "--profile"):
            param[2] = arg
        elif opt in ("-i", "--interlaced"):
            param[3] = 'interlaced'
        elif opt in ("-h", "--help"):
            usage()
            sys.exit()
        elif opt in ("-v", "--version"):
            print "tiny avc encode consultant %s" % __version__
            sys.exit()
    return param

if __name__ == '__main__':
    try:
        opts, args = getopt.getopt(sys.argv[1:], "r:f:p:ihv",
            ["resolution=","fps=","profile=","interlaced","help","version"])
    except:
        usage()
        sys.exit()

    param = set_default()

    if len(opts) > 0:
        param = set_param(opts, param)
    else:
        usage()

    check_profile(param[2], param[3])

    print
    print " resolution       : %i x %i" % tuple(param[0])
    print " fps              : %i / %i" % tuple(param[1])
    print " profile          : %s"      % param[2]
    print " encoding mode    : %s\n"    % param[3]

    bitstream = calc_bs(param[0], param[1], param[3])
    print " MBPS ... %6iMB/s" % bitstream[0]
    print " FS   ... %6iMBs"  % bitstream[1]
    print " DPB  ... %6iMBs\n"  % bitstream[2]

    if param[3] == 'interlaced':
        avcspec = [i for i in get_spec() if i[5] == 'i']
    else:
        avcspec = get_spec()

    minlv = calc_lv(bitstream, param[3], avcspec)

    print " suitable settings are ...\n"
    print " level  vbv-maxrate  vbv-bufsize  max-ref"
    print " ----------------------------------------"
    display_result(minlv, param[2], bitstream, avcspec)

#changelog
# 2010/12/19     0.1.0 公開
# 2010/12/19     0.1.1 いろいろ計算がおかしかったのを修正
# 2010/12/20     0.2.0 lambda式面白い
# 2010/12/21     0.3.0 リスト内包とgetoptの存在を知る
#    〃          0.3.1  getoptの長文形式における要引数要素に=を付けていなかったのを修正
# 2010/01/02     0.3.2  処理が重複する関数(check_resolution, check_fps)をひとつにまとめた
# 2011/03/17     0.3.3  cosmetics
# 2011/10/12     0.3.4  Fix 10L
なにせ「コンサルタント」ですから、まず役にたたないと思われます。

追記:
06_taro氏がメンテナンスを引き継いでくれたようです。
https://gist.github.com/2325004

2010年12月18日土曜日

avc_refcalc.py

某所にて
(boiled_sugar) level計算機とかないのかなー
(Chikuzen) どんな計算したいの?
(boiled_sugar) 解像度とlevelで最大ref計算
(Chikuzen) この前D_Sがそんなの書いてx264に組み込んでなかったっけ?
(boiled_sugar) 独立したプログラムが欲しい
というわけで、書いてみた。
#!/bin/env python
# coding: utf-8

#****************************************************************************
#  avc_refcalc.py 0.20
#                                   written by Chikezun
#  Reference literature:
#    インプレス標準教科書シリーズ改訂版 H.264/AVC教科書
#           著者:(監修)大久保 榮/(編者)角野 眞也、菊池 義浩、鈴木 輝彦
#    http://en.wikipedia.org/wiki/H.264/MPEG-4_AVC
#    猫科研究所(http://www.up-cat.net/FrontPage.html)
#         x264(vbv-maxrate,vbv-bufsize,profile,level),H.264(Profile/Level)
#
#****************************************************************************

import sys
import math

def usage():
    print "Usage: avc_refcalc.py [options]\n"
    print "  -r, --resolution <string> :set 'width x height' ('1280x720')"
    print "  -l, --level <string>      :set 'level' ('4.1')"
    print "  -p, --profile <string>    :set 'profile' ('high')"
    print "  -i, --interlaced          :specify interlaced mode (not specified)"
    print "  -h, --help                :display this help and exit\n"

def check_prof(pr, ip):
    for i in ['baseline', 'main', 'high']:
        if i == pr:
            if i != 'baseline' or ip != 'interlaced':
                return i
            else:
                print "ERROR : baseline cannot accept interlaced."
    print "ERROR : invalid profile setting.\n"
    usage()
    sys.exit()

def check_level(lv, ip, dic):
    lvl = lv.replace('0','').replace('.','')
    if dic.has_key(lvl):
        if ip[0] != 'i' or dic.get(lvl)[0] == 'i':
            return lvl
        else:
            print "ERROR : specified level cannot accept interlaced."
    print "ERROR : invalid level value.\n"
    usage()
    sys.exit()

def calc_mbs(w, h, ip):
    mbh = int(math.ceil(float(w) / 16))
    mbv = int(math.ceil(float(h) / 16))
    if mbv % 2 == 1 and ip == 'interlaced':
        mbv += 1
    mbs = mbh * mbv
    if mbs > 0:
        return mbs
    else:
        print "ERROR : invalid resolution setting.\n"
        usage()
        sys.exit()

def calc_vbv(lv, pr, dic):
    vbvmax = dic.get(lv)[1]
    vbvbuf = dic.get(lv)[2]
    if pr == 'high':
        return [int(vbvmax * 1.25), int(vbvbuf * 1.25)]
    else:
        return [vbvmax, vbvbuf]

def calc_maxref(lv, mbs, dic):
    ref = int(dic.get(lv)[3] / mbs)
    if ref > 16:
        ref = 16
    if ref > 0:
        return ref
    else:
        print "ERROR : resolution is too large to level.\n"
        usage()
        sys.exit()

options = sys.argv
len_opt = len(options)

#set default values
width  = 1280
height = 720
level  = '4.1'
prof   = 'high'
mode   = 'progressive'
help   = 0

#H.264/AVC level dictionary {level: [interlaced flag, MaxBR, MaxCPB, MaxDbpMbs]}
avcdic = {'1' :['p',     64,    175,    396], '1b':['p',    128,    350,    396],
          '11':['p',    192,    500,    900], '12':['p',    384,   1000,   2376],
          '13':['p',    768,   2000,   2376], '2' :['p',   2000,   2000,   2376],
          '21':['i',   4000,   4000,   4752], '22':['i',   4000,   4000,   8100],
          '3' :['i',  10000,  10000,   8100], '31':['i',  14000,  14000,  18000],
          '32':['i',  20000,  20000,  20480], '4' :['i',  20000,  25000,  32768],
          '41':['i',  50000,  62500,  32768], '42':['p',  50000,  62500,  34816],
          '5' :['p', 135000, 135000, 110400], '51':['p', 240000, 240000, 184320]}

if len_opt > 1:
    for i in range(len_opt):
        try:
            if options[i] == '-r' or options[i] == '--resolution':
                res    = options[i + 1].split('x')
                width  = int(res[0])
                height = int(res[1])
            if options[i] == '-l' or options[i] == '--level':
                level = options[i + 1]
            if options[i] == '-p' or options[i] == '--profile':
                prof = options[i + 1]
            if options[i] == '-i' or options[i] == '--interlaced':
                mode = 'interlaced'
            if options[i] == '-h' or options[i] == '--help':
                help = 1

        except:
            print "ERROR : invalid arguments\n"
            help = 1

        else:
            pass

    if help == 1:
        usage()
        sys.exit()

else:
    usage()

profile = check_prof(prof, mode)
lv_tmp  = check_level(level, mode, avcdic)
mbs     = calc_mbs(width, height, mode)
vbv     = calc_vbv(lv_tmp, profile, avcdic)
maxref  = calc_maxref(lv_tmp, mbs, avcdic)

print " resolution       : %i x %i" % (width, height)
print " level            : %s" % level
print " profile          : %s" % profile
print " mode             : %s" % mode
print " vbv-maxrate(vlc) : %i" % vbv[0]
print " vbv-bufsize(vlc) : %i" % vbv[1]
print " max ref number   : %i" % maxref