class Tracksperanto::Pipeline::Base

The base pipeline is the whole process of track conversion from start to finish. The pipeline object organizes the import formats, scans them, applies the tools. Here's how a calling sequence for a pipeline looks like:

pipe = Tracksperanto::Pipeline::Base.new
pipe.tool_tuples = ["Shift", {:x => 10}]
pipe.progress_block = lambda{|percent, msg| puts("#{msg}..#{percent.to_i}%") }
pipe.run("/tmp/shakescript.shk", :width => 720, :height => 576)

The pipeline will also automatically allocate output files with the right extensions at the same place where the original file resides, and setup outputs for all supported export formats.

Constants

PERMITTED_OPTIONS

Attributes

converted_keyframes[R]

How many keyframes have been converted

converted_points[R]

How many points have been converted

exporters[RW]

Assign an array of exporter classes to use them instead of the default “All”

progress_block[RW]

A block acepting percent and message vars can be assigned here. When it's assigned, the pipeline will pass the status reports of all the importers and exporters to the block, together with percent complete

tool_tuples[RW]

Contains arrays of the form [“MiddewareName”, {:param => value}]

Public Class Methods

new(*any) click to toggle source
Calls superclass method Tracksperanto::BlockInit::new
# File lib/pipeline/base.rb, line 72
def initialize(*any)
  super
  @ios = []
end

Public Instance Methods

initialize_importer_with_path_and_options(from_input_file_path, options) click to toggle source
# File lib/pipeline/base.rb, line 125
def initialize_importer_with_path_and_options(from_input_file_path, options)
  
  d = Tracksperanto::FormatDetector.new(from_input_file_path)
  
  if options[:importer]
    imp = Tracksperanto.get_importer(options[:importer])
    require_dimensions_in!(options) unless imp.autodetects_size?
    imp.new(:width => options[:width], :height => options[:height])
  elsif d.match? && d.auto_size?
    d.importer_klass.new
  elsif d.match?
    require_dimensions_in!(options)
    d.importer_klass.new(:width => options[:width], :height => options[:height])
  else
    raise UnknownFormatError
  end
end
open_owned_export_file(path_to_file) click to toggle source

Open the file for writing and register it to be closed automatically

# File lib/pipeline/base.rb, line 232
def open_owned_export_file(path_to_file)
  @ios.push(File.open(path_to_file, "wb"))[-1]
end
report_progress(percent_complete, message) click to toggle source
# File lib/pipeline/base.rb, line 120
def report_progress(percent_complete, message)
  int_pct = percent_complete.to_f.floor # Prevent float overflow above 100 percent
  @progress_block.call(int_pct, message) if @progress_block
end
require_dimensions_in!(opts) click to toggle source
# File lib/pipeline/base.rb, line 143
def require_dimensions_in!(opts)
  raise DimensionsRequiredError unless (opts[:width] && opts[:height])
end
run(from_input_file_path, passed_options = {}) click to toggle source

Runs the whole pipeline. Accepts the following options

  • width - The comp width, for the case that the format does not support auto size

  • height - The comp height, for the case that the format does not support auto size

  • parser - The parser class, for the case that it can't be autodetected from the file name

Returns the number of trackers and the number of keyframes processed during the run

# File lib/pipeline/base.rb, line 92
def run(from_input_file_path, passed_options = {})
  
  # Prevent formats that we do not support
  Tracksperanto::Blacklist.raise_if_format_unsupported(from_input_file_path)
  
  # Check for empty files
  raise EmptySourceFileError if File.stat(from_input_file_path).size.zero?
  
  # Reset stats
  @converted_keyframes, @converted_points = 0, 0
  
  # Assign the parser
  importer = initialize_importer_with_path_and_options(from_input_file_path, passed_options)
  
  # Open the file
  read_data = File.open(from_input_file_path, "rb")
      
  # Setup a multiplexer
  mux = setup_outputs_for(from_input_file_path)
  
  # Wrap it into a module that will prevent us from exporting invalid trackers
  lint = Tracksperanto::Tool::Lint.new(mux)
  
  # Setup tools
  endpoint = wrap_output_with_tools(lint)
  @converted_points, @converted_keyframes = run_export(read_data, importer, endpoint)
end
run_export(tracker_data_io, importer, exporter) click to toggle source

Runs the export and returns the number of points and keyframes processed. If a block is passed, the block will receive the percent complete and the last status message that you can pass back to the UI

# File lib/pipeline/base.rb, line 150
def run_export(tracker_data_io, importer, exporter)
  points, keyframes, percent_complete = 0, 0, 0.0
  last_reported_percentage = 0.0
  
  report_progress(percent_complete, "Starting the parser")
  progress_lambda = lambda do | m | 
    last_reported_percentage = percent_complete
    report_progress(percent_complete, m)
  end
  
  # Report progress from the parser
  importer.progress_block = progress_lambda
  
  # Wrap the input in a progressive IO, setup a lambda that will spy on the reader and
  # update the percentage. We will only broadcast messages that come from the parser
  # though (complementing it with a percentage)
  io_with_progress = ProgressiveIO.new(tracker_data_io) do | offset, of_total |
    percent_complete = (50.0 / of_total) * offset
    
    # Some importers do not signal where they are and do not send nice reports. The way we can help that in the interim
    # would be just to indicate where we are in the input, but outside of the exporter. We do not want to flood
    # the logs though so what we WILL do instead is report some progress going on every 2-3 percent
    progress_lambda.call("Parsing the file") if (percent_complete - last_reported_percentage) > 3
  end
  
  @ios.push(io_with_progress)
  
  importer.io = io_with_progress
  obuf = Obuf.new(Tracksperanto::YieldNonEmpty.new(importer))
  
  report_progress(percent_complete = 50.0, "Validating #{obuf.size} imported trackers")
  raise NoTrackersRecoveredError.new(importer) if obuf.size.zero?

  report_progress(percent_complete, "Starting export")

  percent_per_tracker = (100.0 - percent_complete) / obuf.size

  # Use the width and height provided by the parser itself
  exporter.start_export(importer.width, importer.height)

  # Now send each tracker through the tool chain
  obuf.each_with_index do | t, tracker_idx |
  
    kf_weight = percent_per_tracker / t.keyframes.length
    points += 1
    exporter.start_tracker_segment(t.name)
    t.each_with_index do | kf, idx |
      keyframes += 1
      exporter.export_point(kf.frame, kf.abs_x, kf.abs_y, kf.residual)
      report_progress(
          percent_complete += kf_weight,
          "Writing keyframe #{idx+1} of #{t.name.inspect}, #{obuf.size - tracker_idx} trackers to go"
      )
    end
    exporter.end_tracker_segment
  end
  exporter.end_export

  report_progress(100.0, "Wrote #{points} points and #{keyframes} keyframes")
  
  obuf.clear
  
  @ios.map!{|e| e.close! rescue e.close }
  @ios.clear

  return [points, keyframes]
end
setup_outputs_for(input_file_path) click to toggle source

Setup output files and return a single output that replays to all of them

# File lib/pipeline/base.rb, line 220
def setup_outputs_for(input_file_path)
  file_name_without_extension = File.basename(input_file_path, '.*')
  outputs = (exporters || Tracksperanto.exporters).map do | exporter_class |
    export_name = [file_name_without_extension, exporter_class.desc_and_extension].join("_")
    export_path = File.join(File.dirname(input_file_path), export_name)
    exporter_class.new(open_owned_export_file(export_path))
  end
  
  Tracksperanto::Export::Mux.new(outputs)
end
wrap_output_with_tools(output) click to toggle source

Will scan the tool_tuples attribute and create a processing chain. Tools will be instantiated and wrap each other, starting with the first one

# File lib/pipeline/base.rb, line 79
def wrap_output_with_tools(output)
  return output unless (tool_tuples && tool_tuples.any?)
  
  tool_tuples.reverse.inject(output) do | wrapped, (tool_name, options) |
    Tracksperanto.get_tool(tool_name).new(wrapped, options || {})
  end
end