Class: Tracksperanto::Pipeline::Base

Inherits:
Object
  • Object
show all
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 default middlewares and yields them for processing. Here’s how a calling sequence for a pipeline looks like:

pipe = Tracksperanto::Pipeline::Base.new
pipe.progress_block = lambda{|percent, msg| puts("#{msg}..#{percent.to_i}%") }

pipe.run(input_file, width, height, reader_klass) do | scaler, slipper, golden, reformat |
  golden.enabled = false
  reformat.width = 1024
  reformat.width = 576
end

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. The pipeline will also report progress (with percent) using the passed progress block

Constant Summary collapse

DEFAULT_OPTIONS =
{:pix_w => 720, :pix_h => 576, :parser => Tracksperanto::Import::ShakeScript }

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#converted_keyframesObject (readonly)

How many keyframes have been converted



22
23
24
# File 'lib/pipeline/base.rb', line 22

def converted_keyframes
  @converted_keyframes
end

#converted_pointsObject (readonly)

How many points have been converted. In general, the pipeline does not preserve the parsed tracker objects after they have been exported



19
20
21
# File 'lib/pipeline/base.rb', line 19

def converted_points
  @converted_points
end

#exportersObject

Assign an array of exporters to use them instead of the standard ones



31
32
33
# File 'lib/pipeline/base.rb', line 31

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



28
29
30
# File 'lib/pipeline/base.rb', line 28

def progress_block
  @progress_block
end

Instance Method Details

#detect_importer_or_use_options(path, opts) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/pipeline/base.rb', line 66

def detect_importer_or_use_options(path, opts)
  d = Tracksperanto::FormatDetector.new(path)
  if d.match? && d.auto_size?
    return [1, 1, d.importer_klass]
  elsif d.match?
    raise "Width and height must be provided for a #{d.importer_klass}" unless (opts[:pix_w] && opts[:pix_h])
    opts[:parser] = d.importer_klass
  else
    raise "Cannot autodetect the file format - please specify the importer explicitly" unless opts[:parser]
    unless opts[:parser].autodetects_size?
      raise "Width and height must be provided for this importer" unless (opts[:pix_w] && opts[:pix_h])
    end
  end
  
  [opts[:pix_w], opts[:pix_h], opts[:parser]]    
end

#open_owned_export_file(path_to_file) ⇒ Object

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



156
157
158
159
160
161
# File 'lib/pipeline/base.rb', line 156

def open_owned_export_file(path_to_file)
  @ios ||= []
  handle = File.open(path_to_file, "wb")
  @ios << handle
  handle
end

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

Runs the whole pipeline. Accepts the following options

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

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

  • parser - The parser class, for the case that the format does not support auto size

Yields:

  • (middlewares)


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/pipeline/base.rb', line 40

def run(from_input_file_path, passed_options = {})
  pix_w, pix_h, parser_class = detect_importer_or_use_options(from_input_file_path, DEFAULT_OPTIONS.merge(passed_options))
  
  # Reset stats
  @converted_keyframes, @converted_points = 0, 0
  
  # Grab the input
  read_data = File.open(from_input_file_path, "rb")
  
  # Assign the parser
  importer = parser_class.new(:width => pix_w, :height => pix_h)
  
  # Setup a multiplexer
  mux = setup_outputs_for(from_input_file_path)
  
  # Setup middlewares
  middlewares = setup_middleware_chain_with(mux)
  
  # Yield middlewares to the block
  yield(*middlewares) if block_given?
  
  @converted_points, @converted_keyframes = run_export(read_data, importer, middlewares[-1]) do | p, m |
    @progress_block.call(p, m) if @progress_block
  end
end

#run_export(tracker_data_io, parser, processor) ⇒ 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



86
87
88
89
90
91
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
119
120
121
122
123
124
125
126
# File 'lib/pipeline/base.rb', line 86

def run_export(tracker_data_io, parser, processor)
  @ios << tracker_data_io
  
  points, keyframes, percent_complete = 0, 0, 0.0
  
  yield(percent_complete, "Starting the parser") if block_given?
  parser.progress_block = lambda do | message |
    yield(percent_complete, message) if block_given?
  end
  
  trackers = parser.parse(tracker_data_io)
  
  validate_trackers!(trackers)
  
  yield(percent_complete = 50.0, "Parsing complete, starting export for #{trackers.length} trackers") if block_given?
  
  percent_per_tracker = (100.0 - percent_complete) / trackers.length
  
  yield(percent_complete, "Starting export") if block_given?
  
  # Use the width and height provided by the parser itself
  processor.start_export(parser.width, parser.height)
  trackers.each do | t |
    kf_weight = percent_per_tracker / t.keyframes.length
    points += 1
    processor.start_tracker_segment(t.name)
    t.each_with_index do | kf, idx |
      keyframes += 1
      processor.export_point(kf.frame, kf.abs_x, kf.abs_y, kf.residual)
      yield(percent_complete += kf_weight, "Writing keyframe #{idx+1} of #{t.length}") if block_given?
    end
    processor.end_tracker_segment
  end
  processor.end_export
  
  yield(100.0, "Wrote #{points} points and #{keyframes} keyframes") if block_given?
  
  [points, keyframes]
ensure
  @ios.reject!{|e| e.close unless (!e.respond_to?(:closed?) || e.closed?) } if @ios
end

#setup_middleware_chain_with(output) ⇒ Object

Setup and return the output multiplexor wrapped in all necessary middlewares. Middlewares must be returned in an array to be passed to the configuration block afterwards



145
146
147
148
149
150
151
152
153
# File 'lib/pipeline/base.rb', line 145

def setup_middleware_chain_with(output)
  scaler = Tracksperanto::Middleware::Scaler.new(output)
  slipper = Tracksperanto::Middleware::Slipper.new(scaler)
  golden = Tracksperanto::Middleware::Golden.new(slipper)
  reformat = Tracksperanto::Middleware::Reformat.new(golden)
  shift = Tracksperanto::Middleware::Shift.new(reformat)
  prefix = Tracksperanto::Middleware::Prefix.new(shift)
  [scaler, slipper, golden, reformat, shift, prefix]
end

#setup_outputs_for(input_file_path) ⇒ Object

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



130
131
132
133
134
135
136
137
138
139
140
# File 'lib/pipeline/base.rb', line 130

def setup_outputs_for(input_file_path)
  file_name = File.basename(input_file_path).gsub(/\.([^\.]+)$/, '')
  export_klasses = exporters || Tracksperanto.exporters    
  Tracksperanto::Export::Mux.new(
    export_klasses.map do | exporter_class |
      export_name = [file_name, exporter_class.desc_and_extension].join("_")
      export_path = File.join(File.dirname(input_file_path), export_name)
      exporter = exporter_class.new(open_owned_export_file(export_path))
    end
  )
end

#validate_trackers!(trackers) ⇒ Object

Check that the trackers made by the parser are A-OK



164
165
166
167
# File 'lib/pipeline/base.rb', line 164

def validate_trackers!(trackers)
  trackers.reject!{|t| t.length < 2 }
  raise "Could not recover any non-empty trackers from this file. Wrong import format maybe?" if trackers.empty?
end