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



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

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

Instance Attribute Details

#converted_keyframesObject (readonly)

How many keyframes have been converted



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

def converted_keyframes
  @converted_keyframes
end

#converted_pointsObject (readonly)

How many points have been converted



54
55
56
# File 'lib/pipeline/base.rb', line 54

def converted_points
  @converted_points
end

#exportersObject

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



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

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



63
64
65
# File 'lib/pipeline/base.rb', line 63

def progress_block
  @progress_block
end

#tool_tuplesObject

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



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

def tool_tuples
  @tool_tuples
end

Instance Method Details

#initialize_importer_with_path_and_options(from_input_file_path, options) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# 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) ⇒ Object

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



232
233
234
# 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) ⇒ Object



120
121
122
123
# 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) ⇒ Object



143
144
145
# 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 = {}) ⇒ 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



92
93
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
# 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) ⇒ 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



150
151
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
# 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) ⇒ Object

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



220
221
222
223
224
225
226
227
228
229
# 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) ⇒ 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



79
80
81
82
83
84
85
# 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