Example Script using mythtranscode in fifodir mode

From MythTV Official Wiki
Jump to: navigation, search

The script pipes the output of mythtranscode into ffmpeg, using libx264 to compress the video. The intended use is to set it up as a myth user job.

#!/usr/bin/ruby -w

require 'time'
require 'fileutils'
require 'tmpdir'

$stdout.reopen("/var/log/mythtv/mythar#{Time.now.strftime '%F-%T'}.log", "w")
$stderr.reopen($stdout)

def usage
    print <<-END
Usage: #{$0} --chanid <channel id> --starttime <start time> --title <title> [--subtitle <sub title>] --outdir <directory> [options]
       #{$0} --chanid <channel id> --starttime <start time> -outfile <path> [options]

       --chanid <channel id>                   The channel id for the recording. REQUIRED.
       --starttime <start time>                The start time for the recording. REQUIRED.
       --title <title>                         The title of the recording.
       --subtitle <subtitle>                   The subtitle of the recording.
       --outdir <path>                         The path of the directory for the output file. If one of
                                               --title --subtitle and --outdir are specified, then both --title and --outdir must
                                               be, and an output file will be chosen on their basis, avoiding
                                               conflict with existing files.
       --outfile <path>                        The exact path of the output file. ALTERNATIVE to
                                               --title --subtitle and --outdir.
       --vsize <width>x<height>                Scale video to specified resolution (the aspect ratio will
                                               be maintained using non-square pixels). If not specified,
                                               the source resolution will be maintained.
       --interlaced                            Assume the source is interlaced and maintain the interlace,
                                               provided --vsize not specified. If --vsize is specified then
                                               recode at double the frame rate.
       --passthrough                           Pass through the audio unaltered if possible.
       --quality <quality>                     Quality for compression (default 20.0)
    END
end

# Read arguments
chanid      = nil
starttime   = nil
title       = nil
subtitle    = nil
outdir      = nil
outfile     = nil
vsize       = nil
interlaced  = false
passthrough = false
quality     = '20.0'

processOption = Proc.new do |args|
    raise "Misspecified option: #{args.join(', ')}" unless args.length == 2
    option = args[0].sub(/^--/, '')
    val = args[1]
    eval("#{option} = val")
end

processFlag = Proc.new do |args|
    raise "Misspecified option: #{args.join(', ')}" unless args.length == 1
    option = args[0].sub(/^--/, '')
    eval("#{option} = true")
end

handler = {'--chanid'      => processOption,
           '--starttime'   => processOption,
           '--title'       => processOption,
           '--subtitle'    => processOption,
           '--outdir'      => processOption,
           '--outfile'     => processOption,
           '--vsize'       => processOption,
           '--interlaced'  => processFlag,
           '--passthrough' => processFlag,
           '--quality'     => processOption}


ARGV.slice_before{|arg| arg =~ /^--/}.each do |par|
    raise "Unrecognised option: #{par.join(', ')}" unless handler.has_key? par[0]
    handler[par[0]].call(par)
end


if chanid.nil? || starttime.nil?
    raise '--chanid and --starttime required'
end

if outfile.nil? && outdir.nil?
    raise 'Use either --outfile, or both --title --outdir, optionally with --subtitle'
end

if outfile && (title || subtitle || outdir)
    raise 'Use either --outfile, or both --title --outdir, optionally with --subtitle'
end

if (title || outdir || subtitle) && ! (title && outdir)
    raise 'Both --title and --outdir required for this mode of oparation'
end

mythPassthrough = passthrough ? '--passthrough' : ''

#Detect source formats
info=`mythtranscode -v general --chanid #{chanid} --starttime #{starttime} --fifoinfo #{mythPassthrough}`

svWidth  = nil
svHeight = nil
svAspect = nil
svRate   = nil
saFmt    = nil
saChans  = nil
saRate   = nil

info.each_line do |line|
    case line.chomp
    when / FifoVideoWidth (.*)$/       then svWidth  = $1
    when / FifoVideoHeight (.*)$/      then svHeight = $1
    when / FifoVideoAspectRatio (.*)$/ then svAspect = $1
    when / FifoVideoFrameRate (.*)$/   then svRate   = $1
    when / FifoAudioFormat (.*)$/      then saFmt    = $1
    when / FifoAudioChannels (.*)$/    then saChans  = $1
    when / FifoAudioHz (.*)$/          then saRate   = $1
    when / FifoAudioSampleRate (.*)$/  then saRate   = $1
    end
end

if !svWidth || !svHeight || !svAspect || !svRate || !saFmt || !saChans || !saRate
    print info
    raise 'Failed to derive fifo formats'
end

svSize="#{svWidth}x#{svHeight}"
svAspect = '16:9' if svAspect == '1.77778'
svAspect = '4:3'  if svAspect == '1.33333'
	


saFmt = 'latm' if saFmt == 'acc_latm'


if saFmt == 'latm'
    ext  = '.mkv'
    fFmt = 'matroska'
else
    ext  = '.mp4'
    fFmt = 'mp4'
end

# If --outfile not specified, use --outdir --title and --subtitle
if !outfile
    obName = File.join(outdir, title)
    obName += " - #{subtitle}" if (subtitle && subtitle.length > 0)

    (0..Float::INFINITY).each do |i|
        outfile = obName + (i == 0 ? "" : "(#{i})") + ext
        break if !File.exist? outfile
    end
end

# Can do pass through only with formats that ffmpeg will take as input
passthrough = false unless %w{ac3 dts mp3 latm}.include? saFmt

# Tell ffmpeg what audio to expect and what to do with it
if passthrough
    mythPassthrough = '--passthrough'
    audInDesc       = "-f #{saFmt}"
    audCodec        = 'copy'
else
    mythPassthrough = ''
    audInDesc       = "-f s16le -ac 2 -ar #{saRate}"
    audCodec        = 'ac3 -ab 160k'
end


filters = []
vFilter = nil
vRate   = nil
vFlags  = nil

filters.push("crop=#{svWidth}:1080:0:0") if svHeight == '1088'

if interlaced
    if vsize
        # If scaling interlaced video, we must deinterlace first
        filters.push('yadif=3')
        vRate   = "-r #{2 * svRate.to_i}"
    else
        # Otherwise maintain the interlace
        vFlags  = '-flags +ilme+ildct'
    end
end

if vsize
    filters.push("scale=#{vsize.sub('x', ':')}")
end

vFilter = '-vf ' + filters.join(',') if filters.length > 0

# Create fifo dir
fifoDir = Dir.mktmpdir('mythar')
fifoAudio = File.join(fifoDir, 'audout')
fifoVideo = File.join(fifoDir, 'vidout')

transcodeCmd = "/usr/bin/mythtranscode --chanid #{chanid} --starttime #{starttime} --honorcutlist #{mythPassthrough} --fifodir #{fifoDir} --cleancut"
ffmpegCmd = "/usr/bin/ffmpeg #{audInDesc} -i #{fifoAudio} -f rawvideo -top 1 -pix_fmt yuv420p -s #{svSize} -r #{svRate} -i #{fifoVideo} -threads 2 #{vFilter} -vcodec libx264 -preset medium -profile high #{vFlags} -crf #{quality} #{vRate} -aspect #{svAspect} -acodec #{audCodec} -ar #{saRate} -f #{fFmt} \"#{outfile}\""

puts transcodeCmd
puts
puts ffmpegCmd
puts

pid = 0
begin
    pid = Process.spawn(transcodeCmd)
    while !File.exist?(fifoAudio) || !File.exist?(fifoVideo)
        raise 'Transcode died before creating pipes' unless Process.wait(pid, Process::WNOHANG).nil?
        sleep(1)
    end
    system(ffmpegCmd)
ensure
    if pid != 0
        Process.kill('KILL', pid)
        Process.wait(pid)
    end
    FileUtils.rm_rf fifoDir
end