2018/03/09

tc2mp4.plってしばらく更新されていないですよね


なので勝手にmodしました
・exe版と同じように-Lオプション対応させた
・日本語のダメ文字に・・・自分が困らない程度に対応
・mp4boxが新しい場合に対応(nhmlの出力が最近はなんか違う)

そういえば、vfrにする時って多分プロファイル(最大ビットレート)が不正になるんですよね。
なんせ普通にエンコードしたのに2.5(60/24)倍のフレームを詰め込むことになるので。
x264で直接tcfileinを指定していたときはどうだったんだろう・・・

#!/usr/bin/perl
# author: zmi
# version: 01/24/2007 mod 03/09/2018
# info: http://d.hatena.ne.jp/zmi/ (japanese)
# info: http://forum.doom9.org/showthread.php?t=112199 (english)

#使い方:
# mp4box -nhml 1 cfr.mp4
# (VFR変換スクリプト) -i mkv-timecodesfile.txt -n cfr_track1.nhml -o vfr_track1.nhml
# mp4box -add vfr_track1.nhml -new vfr_track1.mp4
# mp4box -add vfr_track1.mp4 -add cfr.aac -new vfr.mp4

use Getopt::Std;
use Math::BigInt;
use File::Basename;
use Encode;

$usage = <<USAGE
tc2mp4 (01/24/2007 mod 03/09/2018)
usage: tc2mp4 -i InputFile -o OutputFile -t TimecodeFile [-k] [-n TrackNumber] [-L mp4boxpath]
USAGE
;

getopts('i:t:o:n:kL:h');

die $usage if $opt_h;
$srcMP4 = $opt_i or die $usage;
$dstMP4 = $opt_o or die $usage;
$srcTC = $opt_t or die $usage;
$number = ($opt_n or 1);
$keep = $opt_k;
$mp4box = ($opt_L or 'mp4box');
@extlist = ('\....$');
($basename, $dir) = fileparse(Encode::decode('cp932',"$opt_i"), @extlist);
$basename=Encode::encode('cp932',$basename);
$dir=Encode::encode('cp932',$dir);

$srcNHML = "${dir}${basename}_track$number.nhml";
# $tcNHML = "${dir}${basename}_track$number"."_tc.nhml";mp4boxには0.72rev359現在バグがあるようで、wide characterのファイルをnhmlとしてインポートできない(file errorになる) 出力ファイルにはできるが・・・
$tcNHML = "${dir}_track$number\_tc.nhml";    #ファイル名に2byte文字を含まないように なのでテンポラリファイルがカレントフォルダに作られる


doCmd("\"$mp4box\" -nhml $number \"$srcMP4\"");

$tcfv = detectTCFVersion($srcTC);

print "[Timecode info]\n";
print "  Timecode format(v$tcfv)\n";

if($tcfv == 1){
    @timescales = parseTimecodeV1($srcTC);

    $ntsc = detectNTSC($srcTC);
   
    for($frame = 0; $frame < @timescales; $frame++){
    $factors{$timescales[$frame]}++;
    }
   
    $lcmTimescale = Math::BigInt::blcm(keys %factors);
   
    # generate CTS from timecode
    @cts = toCTS(\@timescales, $lcmTimescale, $ntsc);

    print "  Total frames(".@cts."), LCM Timescale($lcmTimescale)\n";
    print "  " . ($ntsc ? 'NTSC, ' : '') ."Factors(" . join('/',sort keys %factors) . ")\n";
}elsif($tcfv == 2){
    $lcmTimescale = 1000000;
    @cts = parseTimecodeV2($srcTC);

    print "  Total frames(".@cts."), Timescale($lcmTimescale)\n";
}
   
# read Timebase from source nhml
$timebase = readTimebase($srcNHML);

print "[NHML info]\n";
print "  Timebase($timebase)\n";

applyTimecode($srcNHML, $tcNHML, $timebase, \@cts);

doCmd("\"$mp4box\" -add \"$tcNHML\" -new \"$dstMP4\"");

unlink("$srcNHML");
unlink("$tcNHML") unless $keep;
unlink("${dir}${basename}_track$number.info","${dir}${basename}_track$number.media");

doCmd("\"$mp4box\" -info \"$dstMP4\"");

print "Completed.\n";
exit 0;

sub doCmd{
    my $cmd = shift @_;
    print "$cmd\n";
    system($cmd) and die $!;
}

sub applyTimecode{
    my $srcNHML = shift @_;
    my $tcNHML = shift @_;
    my $timebase = shift @_;
    my @cts = @{shift @_};

    my $dtsFrame = 0,$dts;
    my $ctsFrame,$ctsOffset,$cts;
    my $delayFrame,$delayTC;

    open SRC_NHML, "<$srcNHML" or die $!;
    open TC_NHML, ">$tcNHML" or die $!;

    while(<SRC_NHML>){
    s/timeScale="\d+"/timeScale="$lcmTimescale"/ if(/<NHNTStream.*?>/);
   
    ((print TC_NHML), next) unless /<NHNTSample.*>/;
   
    next unless $dtsFrame < @cts;
   
    /CTSOffset="(\d+)"/;
    $ctsOffset = ($1 or 0);
   
    $ctsFrame = $dtsFrame + int($ctsOffset / $timebase + 0.5);
   
    if($dtsFrame == 0){
        $delayFrame = $ctsFrame;
        $delayTC =  $cts[$delayFrame];
        print "[Delay]\n";
        print "  Frames($delayFrame), Timecode($delayTC) Milliseconds(" . ($delayTC * 1000 / $lcmTimescale) . ")\n";
    }
   
    if($dtsFrame < $delayFrame){
        $dts = $cts[$dtsFrame];
        $cts = $cts[$ctsFrame];
    }else{
        $dts = $cts[$dtsFrame - $delayFrame] + $delayTC;
        $cts = $cts[$ctsFrame - $delayFrame] + $delayTC;
    }
    $ctsOffset = $cts - $dts;

    s/DTS="\d+"/DTS="$dts"/;
    s/CTSOffset="\d+"/CTSOffset="$ctsOffset"/;
    #s/(dataLength="\d+")/$1 CTSOffset="$ctsOffset"/ if $ctsOffset == 0; # hack for mp4box's bug
    print TC_NHML;
   
    $dtsFrame++;
    }

    close SRC_NHML;
    close TC_NHML;
}

sub toCTS{
    my @timescales = @{shift @_};
    my $lcmTimescale = shift @_;
    my $ntsc = shift @_;
    my @cts, $frame, $duration;
    my $step = $ntsc ? 1001 : 1000;
       
    $cts[0] = 0;
    for($frame = 1; $frame < @timescales; $frame++){
    $duration = ($lcmTimescale / $timescales[$frame -1]) * $step;
    $cts[$frame] = $cts[$frame -1] + $duration;
    }
    return @cts;
}

sub readTimebase{
    my $nhmlFile = shift @_;
    my @dts;
    my $timebase;
    my $count = 0;

    open NHML, "<$nhmlFile" or die $!;
    while(<NHML>){
    next unless /<NHNTSample.*DTS="(\d+)".*>/;
    $dts[$count] = $1;
    last if (++$count >= 10);
    }
    close NHML;
   
    $timebase = $dts[1] - $dts[0];
    $avgTimebase = ($dts[$count - 1] - $dts[0]) / ($count - 1);

    die "Input file is not CFR." if ($timebase != $avgTimebase);
    return $timebase;
}

sub toTimescale{
    my $fps = shift @_;
    my $timescale = int($fps * 1.001 + 0.5) * 1000;
    return $timescale;
}

sub detectTCFVersion{
    my $timecodeFile = shift @_;
    my $tcfv;

    open TC, "<$timecodeFile" or die $!;
    while(<TC>){
    next unless /^\#\s+timecode format v([1-2])/;
    $tcfv = $1;
    }
    close TC;

    defined $tcfv or die "Only timecode format v1/v2 is supported.";
    return $tcfv;
}

sub detectNTSC{
    my $timecodeFile = shift @_;
    my $assume;

    open TC, "<$timecodeFile" or die $!;
    while(<TC>){
    next if /^\#/;
    next unless /^Assume ([0-9.]+)/;
    $assume = $1;
    }
    close TC;

    if(toTimescale($assume) == $assume * 1000){
    return 0;
    }else{
    return 1;
    }
}

sub parseTimecodeV1{
    my $timecodeFile = shift @_;
    my $ntsc = shift @_;

    my @timescale;
    my $assume;
    my $begin,$end,$last,$fps;
    my $frame = 0;

    open TC, "<$timecodeFile" or die $!;
   
    while(<TC>){
    ($last = $1, next)
        if /^\# TDecimate Mode.*Last Frame = (\d+)/;
   
    next if /^\#/;
   
    ($assume = $1, next)
        if /^Assume ([0-9.]+)/;
   
    next unless /(\d+),(\d+),([\d.]+)/;
   
    ($begin, $end, $fps) = ($1, $2, $3);
   
    for($frame; $frame < $begin; $frame++){
        $timescale[$frame] = toTimescale($assume);
    }
   
    for($frame = $begin; $frame <= $end; $frame++){
        $timescale[$frame] = toTimescale($fps);
    }
    }
    for(;$frame <= $last; $frame++){
    $timescale[$frame] = toTimescale($assume);
    }
    close TC;

    $ntsc = 1111;
   
    return @timescale;
}

sub parseTimecodeV2{
    my $timecodeFile = shift @_;

    my @cts;
    my $frame = 0;

    open TC, "<$timecodeFile" or die $!;
    while(<TC>){
    next if /^\#/;
    next unless /([\d.]+)/;
   
    $cts[$frame++] = int($1 * 1000 + 0.5);
    }
    close TC;
   
    return @cts;
}

0 件のコメント:

コメントを投稿