class ActiveEncode::EngineAdapters::PassThroughAdapter

Constants

MEDIAINFO_PATH
WORK_DIR

Public Instance Methods

cancel(id) click to toggle source

Cancel ongoing encode using pid file

# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 117
def cancel(id)
  # Check for errors and if not then create cancelled file else raise CancelError?
  if running?(id)
    File.write(working_path("cancelled", id), "")
    find id
  else
    raise CancelError
  end
end
create(input_url, options = {}) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 19
def create(input_url, options = {})
  # Decode file uris for ffmpeg (mediainfo works either way)
  input_url = URI.decode(input_url) if input_url.starts_with? "file:///"

  new_encode = ActiveEncode::Base.new(input_url, options)
  new_encode.id = SecureRandom.uuid
  new_encode.current_operations = []
  new_encode.output = []

  # Create a working directory that holds all output files related to the encode
  FileUtils.mkdir_p working_path("", new_encode.id)
  FileUtils.mkdir_p working_path("outputs", new_encode.id)

  # Extract technical metadata from input file
  `#{MEDIAINFO_PATH} --Output=XML --LogFile=#{working_path("input_metadata", new_encode.id)} #{input_url.shellescape}`
  new_encode.input = build_input new_encode
  new_encode.input.id = new_encode.id
  new_encode.created_at, new_encode.updated_at = get_times new_encode.id

  if new_encode.input.duration.blank?
    new_encode.state = :failed
    new_encode.percent_complete = 1

    new_encode.errors = if new_encode.input.file_size.blank?
                          ["#{input_url} does not exist or is not accessible"]
                        else
                          ["Error inspecting input: #{input_url}"]
                        end

    write_errors new_encode
    return new_encode
  end

  # For saving filename to label map used to find the label when building outputs
  filename_label_hash = {}

  # Copy derivatives to work directory
  options[:outputs].each do |opt|
    url = opt[:url]
    output_path = working_path("outputs/#{sanitize_base opt[:url]}#{File.extname opt[:url]}", new_encode.id)
    FileUtils.cp FileLocator.new(url).location, output_path
    filename_label_hash[output_path] = opt[:label]
  end

  # Write filename-to-label map so we can retrieve them on build_output
  File.write working_path("filename_label.yml", new_encode.id), filename_label_hash.to_yaml

  new_encode.percent_complete = 1
  new_encode.state = :running
  new_encode.errors = []

  new_encode
rescue StandardError => e
  new_encode.state = :failed
  new_encode.percent_complete = 1
  new_encode.errors = [e.full_message]
  write_errors new_encode
  return new_encode
end
find(id, opts = {}) click to toggle source

Return encode object from file system

# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 80
def find(id, opts = {})
  encode_class = opts[:cast]
  encode_class ||= ActiveEncode::Base
  encode = encode_class.new(nil, opts)
  encode.id = id
  encode.created_at, encode.updated_at = get_times encode.id
  encode.input = build_input encode
  encode.input.id = encode.id
  encode.output = []
  encode.current_operations = []

  encode.errors = read_errors(id)
  if encode.errors.present?
    encode.state = :failed
    encode.percent_complete = 1
  elsif cancelled?(id)
    encode.state = :cancelled
    encode.percent_complete = 1
  elsif completed?(id)
    encode.state = :completed
    encode.percent_complete = 100
  else
    encode.output = build_outputs encode
    encode.state = :completed
    encode.percent_complete = 100
  end

  encode
rescue StandardError => e
  encode.state = :failed
  encode.percent_complete = 1
  encode.errors = [e.full_message]
  write_errors encode
  return encode
end

Private Instance Methods

build_input(encode) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 173
def build_input(encode)
  input = ActiveEncode::Input.new
  metadata = get_tech_metadata(working_path("input_metadata", encode.id))
  input.url = metadata[:url]
  input.assign_tech_metadata(metadata)
  created_at = File.mtime(working_path("input_metadata", encode.id))
  input.created_at = created_at
  input.updated_at = created_at

  input
end
build_outputs(encode) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 185
def build_outputs(encode)
  id = encode.id
  outputs = []
  filename_label_hash = YAML.safe_load(File.read(working_path("filename_label.yml", id))) if File.exist?(working_path("filename_label.yml", id))
  Dir["#{File.absolute_path(working_path('outputs', id))}/*"].each do |file_path|
    output = ActiveEncode::Output.new
    output.url = "file://#{file_path}"
    output.label = filename_label_hash[file_path] if filename_label_hash
    output.id = "#{encode.input.id}-#{output.label}"
    output.created_at = encode.created_at
    output.updated_at = File.mtime file_path

    # Extract technical metadata from output file
    metadata_path = working_path("output_metadata-#{output.label}", id)
    `#{MEDIAINFO_PATH} --Output=XML --LogFile=#{metadata_path} #{output.url}` unless File.file? metadata_path
    output.assign_tech_metadata(get_tech_metadata(metadata_path))

    outputs << output
  end
  File.write(working_path("completed", id), "")

  outputs
end
cancelled?(id) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 133
def cancelled?(id)
  File.exist? working_path("cancelled", id)
end
completed?(id) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 141
def completed?(id)
  File.exist? working_path("completed", id)
end
failed?(id) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 137
def failed?(id)
  read_errors(id).present?
end
get_tech_metadata(file_path) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 217
def get_tech_metadata(file_path)
  doc = Nokogiri::XML File.read(file_path)
  doc.remove_namespaces!
  duration = get_xpath_text(doc, '//Duration/text()', :to_f)
  duration *= 1000 unless duration.nil? # Convert to milliseconds
  { url: get_xpath_text(doc, '//media/@ref', :to_s),
    width: get_xpath_text(doc, '//Width/text()', :to_f),
    height: get_xpath_text(doc, '//Height/text()', :to_f),
    frame_rate: get_xpath_text(doc, '//FrameRate/text()', :to_f),
    duration: duration,
    file_size: get_xpath_text(doc, '//FileSize/text()', :to_i),
    audio_codec: get_xpath_text(doc, '//track[@type="Audio"]/CodecID/text()', :to_s),
    audio_bitrate: get_xpath_text(doc, '//track[@type="Audio"]/BitRate/text()', :to_i),
    video_codec: get_xpath_text(doc, '//track[@type="Video"]/CodecID/text()', :to_s),
    video_bitrate: get_xpath_text(doc, '//track[@type="Video"]/BitRate/text()', :to_i) }
end
get_times(id) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 145
def get_times(id)
  updated_at = if File.file? working_path("completed", id)
                 File.mtime(working_path("completed", id))
               elsif File.file? working_path("cancelled", id)
                 File.mtime(working_path("cancelled", id))
               elsif File.file? working_path("error.log", id)
                 File.mtime(working_path("error.log", id))
               else
                 File.mtime(working_path("input_metadata", id))
               end

  [File.mtime(working_path("input_metadata", id)), updated_at]
end
get_xpath_text(doc, xpath, cast_method) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 234
def get_xpath_text(doc, xpath, cast_method)
  doc.xpath(xpath).first&.text&.send(cast_method)
end
read_errors(id) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 163
def read_errors(id)
  err_path = working_path("error.log", id)
  error = File.read(err_path) if File.file? err_path
  if error.present?
    [error]
  else
    []
  end
end
running?(id) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 129
def running?(id)
  !cancelled?(id) || !failed?(id) || !completed?(id)
end
sanitize_base(input_url) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 209
def sanitize_base(input_url)
  File.basename(input_url, File.extname(input_url)).gsub(/[^0-9A-Za-z.\-]/, '_')
end
working_path(path, id) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 213
def working_path(path, id)
  File.join(WORK_DIR, id, path)
end
write_errors(encode) click to toggle source
# File lib/active_encode/engine_adapters/pass_through_adapter.rb, line 159
def write_errors(encode)
  File.write(working_path("error.log", encode.id), encode.errors.join("\n"))
end