Class: Tracksperanto::Pipeline::Base

Inherits:
Object
  • Object
show all
Includes:
BlockInit
Defined in:
lib/pipeline/base.rb

Overview

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.

Constant Summary collapse

EXTENSION =

:nodoc:

/\.([^\.]+)$/
PERMITTED_OPTIONS =
[:importer, :width, :height]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*any) ⇒ Base

Returns a new instance of Base.



74
75
76
77
# File 'lib/pipeline/base.rb', line 74

def initialize(*any)
  super
  @ios = []
end

Instance Attribute Details

#converted_keyframesObject (readonly)

How many keyframes have been converted



59
60
61
# File 'lib/pipeline/base.rb', line 59

def converted_keyframes
  @converted_keyframes
end

#converted_pointsObject (readonly)

How many points have been converted



56
57
58
# File 'lib/pipeline/base.rb', line 56

def converted_points
  @converted_points
end

#exportersObject

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



68
69
70
# File 'lib/pipeline/base.rb', line 68

def exporters
  @exporters
end

#progress_blockObject

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



65
66
67
# File 'lib/pipeline/base.rb', line 65

def progress_block
  @progress_block
end

#tool_tuplesObject

Contains arrays of the form [“MiddewareName”, => value]



71
72
73
# File 'lib/pipeline/base.rb', line 71

def tool_tuples
  @tool_tuples
end

Instance Method Details

#initialize_importer_with_path_and_options(from_input_file_path, options) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/pipeline/base.rb', line 127

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) ⇒ Object

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



234
235
236
# File 'lib/pipeline/base.rb', line 234

def open_owned_export_file(path_to_file)
  @ios.push(File.open(path_to_file, "wb"))[-1]
end

#report_progress(percent_complete, message) ⇒ Object



122
123
124
125
# File 'lib/pipeline/base.rb', line 122

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) ⇒ Object



145
146
147
# File 'lib/pipeline/base.rb', line 145

def require_dimensions_in!(opts)
  raise DimensionsRequiredError unless (opts[:width] && opts[:height])
end

#run(from_input_file_path, passed_options = {}) ⇒ Object

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



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/pipeline/base.rb', line 94

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) ⇒ Object

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



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/pipeline/base.rb', line 152

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) ⇒ Object

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



222
223
224
225
226
227
228
229
230
231
# File 'lib/pipeline/base.rb', line 222

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) ⇒ Object

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



81
82
83
84
85
86
87
# File 'lib/pipeline/base.rb', line 81

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