VFRなmp4の作り方(3)
24fpsと30fpsが混合したmp4を作る自分的手順(自動化編)
timecode v1対応のnhml→VFR変換スクリプトを作ってみた。
これの場合、timecode v2対応のものに比べて、30000/1001とか24000/1001など、より正確なフレームレートが再現できる。
使い方: 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
ここにexe版も用意してみました。
#!/usr/bin/perl # url - http://d.hatena.ne.jp/zmi/ use Getopt::Std; use Math::BigInt; getopts('i:n:o:'); @timescales = parseTimecode($opt_i); for($frame = 0; $frame < @timescales; $frame++){ $factors{$timescales[$frame]}++; } $lcmTimescale = Math::BigInt::blcm(keys %factors); print "[Timecode info]\n"; print " Total frames($frame), LCM Timescale($lcmTimescale)"; print ", Factors(" . join('/',sort keys %factors) . ")\n"; # generate CTS from timecode @cts = toCTS(\@timescales, $lcmTimescale); # parse nhml open SRC_NHML, "<$opt_n" or die $!; open TC_NHML, ">$opt_o" or die $!; while(<SRC_NHML>){ next unless /<NHNTSample.*DTS="(\d+)".*\/>/; $dts[$count] = $1; break if $count++ < 2; } seek(SRC_NHML, 0, 0); $timeBase = $dts[1] - $dts[0]; print "[NHML info]\n"; print " TimeBase($timeBase)\n"; $dtsFrame = 0; 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)\n"; } if($dtsFrame < $delayFrame){ $dts = $cts[$dtsFrame]; $ctsIndex = $ctsFrame; $cts = $cts[$ctsIndex]; }else{ $dts = $cts[$dtsFrame - $delayFrame] + $delayTC; $ctsIndex = $ctsFrame - $delayFrame; $cts = $cts[$ctsIndex] + $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; print "Completed.\n"; exit 0; sub toCTS{ my $ref = shift @_; my @timescales = @$ref; my $lcmTimescale = shift @_; my @cts, $frame, $duration; $cts[0] = 0; for($frame = 1; $frame < @timescales; $frame++){ $duration = ($lcmTimescale / $timescales[$frame -1]) * 1001; $cts[$frame] = $cts[$frame -1] + $duration; } return @cts; } sub toTimescale{ my $fps = shift @_; my $timescale = int($fps * 1.001 + 0.5) * 1000; return $timescale; } sub parseTimecode{ my $timecodeFile = shift @_; my @timescale; my $tcfv; my $assume; my $begin,$end,$last,$fps; my $frame = 0; open TC, "<$timecodeFile" or die $!; while(<TC>){ ($tcfv = $1, next) if /^\# timecode format v([1-2])/; ($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; defined $tcfv or die "Only timecode format v1 is supported."; return @timescale; }