module MovieMasher::ShellHelper
executes commands and optionally checks created files for duration
Public Class Methods
audio_command(path)
click to toggle source
# File lib/util/shell_helper.rb, line 9 def audio_command(path) if path.to_s.empty? || !File.exist?(path) raise(Error::Parameter, "__audio_from_file with invalid path #{path}") end file_name = File.basename(path, File.extname(path)) file_name = "#{file_name}-intermediate.#{Intermediate::AUDIO_EXTENSION}" out_file = Path.concat(File.dirname(path), file_name) switches = [] switches << switch(path, 'i') switches << switch(2, 'ac') switches << switch(44_100, 'ar') exec_opts = {} exec_opts[:command] = switches.join exec_opts[:file] = out_file exec_opts end
capture(cmd)
click to toggle source
# File lib/util/shell_helper.rb, line 27 def capture(cmd) result = Open3.capture3(cmd) result = result.reject { |s| s.to_s.empty? } result = result.join("\n") # puts result # make sure result is utf-8 encoded enc_options = {} enc_options[:invalid] = :replace enc_options[:undef] = :replace enc_options[:replace] = '?' # enc_options[:universal_newline] = true result.encode(Encoding::UTF_8, **enc_options) end
command(options)
click to toggle source
# File lib/util/shell_helper.rb, line 41 def command(options) out_file = options[:file].to_s app = options[:app] || 'ffmpeg' app_path = MovieMasher.configuration["#{app}_path".to_sym] app_path = app if app_path.to_s.empty? cmd = "#{app_path} #{options[:command]}" cmd += " #{out_file}" unless out_file.empty? cmd end
escape(string)
click to toggle source
# File lib/util/shell_helper.rb, line 51 def escape(string) Shellwords.escape(string) end
execute(options)
click to toggle source
# File lib/util/shell_helper.rb, line 55 def execute(options) capture(command(options)) end
output_command(output, av_type, duration = nil)
click to toggle source
# File lib/util/shell_helper.rb, line 59 def output_command(output, av_type, duration = nil) switches = [] switches << switch(FloatUtil.string(duration), 't') if duration if AV::VIDEO_ONLY == av_type switches << switch('', 'an') else # we have audio output switches << switch(output[:audio_bitrate], 'b:a', 'k') switches << switch(output[:audio_rate], 'r:a') switches << switch(output[:audio_codec], 'c:a') end if AV::AUDIO_ONLY == av_type switches << switch('', 'vn') else # we have visuals case output[:type] when Type::VIDEO switches << switch(output[:dimensions], 's') switches << switch(output[:video_format], 'f:v') switches << switch(output[:video_codec], 'c:v') switches << switch(output[:video_bitrate], 'b:v', 'k') switches << switch(output[:video_rate], 'r:v') when Type::IMAGE switches << switch(output[:quality], 'q:v') switches << switch('1', 'vframes') unless output[:offset].to_s.empty? output_time = TimeRange.input_time(output, :offset) switches << switch(output_time, 'ss') end when Type::SEQUENCE switches << switch(output[:quality], 'q:v') switches << switch(output[:video_rate], 'r:v') end end switches << switch(output[:metadata], 'metadata') switches.join end
raise_unless_rendered(result, cmd, options)
click to toggle source
# File lib/util/shell_helper.rb, line 95 def raise_unless_rendered(result, cmd, options) logs = [] out_file = options[:file].to_s outputs = !['', '/dev/null'].include?(out_file) dur = options[:duration] precision = options[:precision] || 1 logs += __raise_if_no_file(out_file, cmd, result) if outputs if dur logs += __raise_unless_duration(result, dur, precision, out_file, cmd) end logs end
set_output_commands(job, output)
click to toggle source
# File lib/util/shell_helper.rb, line 108 def set_output_commands(job, output) output[:commands] = [] switches = end_switches = '' audio_dur = video_dur = FloatUtil::ZERO rend_path, out_path = __job_paths(job, output) avb, v_graphs, a_graphs = __output_graphs(output, job) output_type = output[:type] a_or_v = Type::RAW_AVS.include?(output_type) unless AV::AUDIO_ONLY == avb if v_graphs.length == 1 graph = v_graphs.first video_dur = graph.duration switches += __graph_command(graph, output) else ffconcat = 'ffconcat version 1.0' v_graphs.length.times do |index| graph = v_graphs[index] duration = graph.duration video_dur += duration out_file_name = "concat-#{index}.#{output[:extension]}" out_file = "#{out_path}#{out_file_name}" ffconcat += "\nfile '#{out_file_name}'\nduration #{duration}" out_command = output_command(output, AV::VIDEO_ONLY, duration) grph_command = __graph_command(graph, output) output[:commands] << { duration: duration, file: out_file, precision: output[:precision], command: "-y#{grph_command}#{out_command}#{switch('0', 'qp')}" } end file_path = "#{out_path}concat.txt" output[:commands] << { content: ffconcat, file: file_path } switches += switch("'#{file_path}'", 'i') end_switches += switch('copy', 'c:v') end end unless AV::VIDEO_ONLY == avb if __audio_raw(a_graphs) # just one non-looping graph, starting at zero with no gain change graph = a_graphs.first audio_dur = graph[:length] cmd_hash = audio_command(graph[:cached_file]) output[:commands] << cmd_hash graph[:waved_file] = cmd_hash[:file] else # merge audio and feed resulting file to ffmpeg audio_cmd = '' a_len = a_graphs.length a_len.times do |index| graph = a_graphs[index] __raise_if_negative(graph[:start], "negative start time #{graph}") __raise_if_zero(graph[:length], "zero length #{graph}") audio_dur = FloatUtil.max( audio_dur, graph[:start] + graph[:length] ) cmd_hash = audio_command(graph[:cached_file]) output[:commands] << cmd_hash graph[:waved_file] = cmd_hash[:file] audio_cmd += __audio_graph(graph, index) end max_dur = FloatUtil.max(audio_dur, video_dur) audio_cmd += __audio_silence(a_len, max_dur) path = __audio_path(out_path, audio_cmd) output[:commands] << { app: 'ecasound', command: audio_cmd, precision: output[:precision], file: path, duration: max_dur } graph = { type: Type::AUDIO, offset: FloatUtil::ZERO, length: audio_dur, waved_file: path } end # audio graph now represents just one file if Type::WAVEFORM == output_type output[:commands] << { app: 'audiowaveform', file: rend_path, command: __waveform_switches(graph, output) } else switches += switch(graph[:waved_file], 'i') end_switches += __audio_switches(graph, audio_dur) end end return if switches.empty? # we've got audio and/or video max_dur = FloatUtil.max(audio_dur, video_dur) duration = __output_duration(a_or_v, max_dur) out_command = output_command(output, avb, duration) output[:commands] << { file: rend_path, precision: output[:precision], pass: __is_two_pass(a_or_v, v_graphs), duration: __type_duration(output_type, max_dur), command: "-y #{switches + end_switches}#{out_command}" } end
switch(value, prefix = '', suffix = '', dont_escape: false)
click to toggle source
# File lib/util/shell_helper.rb, line 206 def switch(value, prefix = '', suffix = '', dont_escape: false) cmd = '' if value value = value.to_s.strip unless dont_escape splits = Shellwords.split(value) splits = splits.map { |word| escape(word) } # puts "SPLITS: #{splits}" if 1 < splits.length value = splits.join(' ') end cmd += ' ' # always add a leading space if value.start_with?('-') # it's a switch, include and ignore rest cmd += value else # prepend value with prefix and space cmd += '-' unless prefix.start_with?('-') cmd += prefix cmd += " #{value}" unless value.empty? cmd += suffix unless cmd.end_with?(suffix) # NOTE: lack of space! end end cmd end