Example Script using mythtranscode in fifodir mode
From MythTV Official Wiki
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