lib/pipeline/base.rb in tracksperanto-1.1.1 vs lib/pipeline/base.rb in tracksperanto-1.2.0
- old
+ new
@@ -1,64 +1,115 @@
-# This is currently a bit of a mess.
-module Tracksperanto::Pipeline
-class Base
- attr_accessor :converted_points
- attr_accessor :converted_keyframes
+# 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
+class Tracksperanto::Pipeline::Base
+
+ # How many points have been converted. In general, the pipeline does not preserve the parsed tracker objects
+ # after they have been exported
+ attr_reader :converted_points
+
+ # How many keyframes have been converted
+ attr_reader :converted_keyframes
+
+ # 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
attr_accessor :progress_block
- def run(from_input_file_path, pix_w, pix_h, parser_class)
- # Read the input file
- read_data = File.read(from_input_file_path)
+ # Assign an array of exporters to use them instead of the standard ones
+ attr_accessor :exporters
+
+ DEFAULT_OPTIONS = {:pix_w => 720, :pix_h => 576, :parser => Tracksperanto::Import::ShakeScript }
+
+ # 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
+
+ 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)
+
# Assign the parser
- parser = create_parser(parser_class, pix_w, pix_h)
+ importer = parser_class.new(:width => pix_w, :height => pix_h)
# Setup a multiplexer
mux = setup_outputs_for(from_input_file_path)
- # Setup middlewares - skip for now
- scaler = Tracksperanto::Middleware::Scaler.new(mux)
- slipper = Tracksperanto::Middleware::Slipper.new(scaler)
- golden = Tracksperanto::Middleware::Golden.new(slipper)
- reformat = Tracksperanto::Middleware::Reformat.new(golden)
+ # Setup middlewares
+ middlewares = setup_middleware_chain_with(mux)
# Yield middlewares to the block
- yield(scaler, slipper, golden, reformat) if block_given?
+ yield(*middlewares) if block_given?
- @converted_points, @converted_keyframes = run_export(read_data, parser, reformat) do | p, m |
+ @converted_points, @converted_keyframes = run_export(read_data, importer, middlewares[-1]) do | p, m |
@progress_block.call(p, m) if @progress_block
end
end
+ 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]
+ raise "Width and height must be provided for this importer" unless (opts[:pix_w] && opts[:pix_h])
+ end
+
+ [opts[:pix_w], opts[:pix_h], opts[:parser]]
+ end
+
# 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
- # # :yields: percent_complete, status_message
- def run_export(tracker_data_blob, parser, processor)
+ 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 parse routine") if block_given?
+ 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_blob)
+ trackers = parser.parse(tracker_data_io)
- yield(percent_complete = 20.0, "Starting export for #{trackers.length} trackers") if block_given?
+ validate_trackers!(trackers)
- percent_per_tracker = 80.0 / trackers.length
+ yield(percent_complete = 50.0, "Parsing complete, starting export for #{trackers.length} trackers") if block_given?
- # Use the width and height provided by the parser itself
- processor.start_export(parser.width, parser.height)
+ 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.keyframes.each do | kf |
+ t.each do | kf |
keyframes += 1
processor.export_point(kf.frame, kf.abs_x, kf.abs_y, kf.residual)
yield(percent_complete += kf_weight, "Writing keyframe") if block_given?
end
processor.end_tracker_segment
@@ -66,26 +117,49 @@
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
- def create_parser(parser_class, w, h)
- p = parser_class.new
- p.width, p.height = w, h
- p
- end
-
+ # Setup output files and return a single output
+ # that replays to all of them
def setup_outputs_for(input_file_path)
file_name = File.basename(input_file_path).gsub(/\.([^\.]+)$/, '')
+ export_klasses = exporters || Tracksperanto.exporters
Tracksperanto::Export::Mux.new(
- Tracksperanto.exporters.map do | exporter_class |
- export_name = "%s_%s" % [file_name, exporter_class.desc_and_extension]
- export_path = File.dirname(input_file_path) + '/' + export_name
- exporter = exporter_class.new(File.open(export_path, 'w'))
- Tracksperanto::Middleware::Close.new(exporter)
+ 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
-end
+
+ # 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
+ 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)
+ [scaler, slipper, golden, reformat, shift]
+ end
+
+ # Open the file for writing and register it to be closed automatically
+ def open_owned_export_file(path_to_file)
+ @ios ||= []
+ handle = File.open(path_to_file, "w")
+ @ios << handle
+ handle
+ end
+
+ # Check that the trackers made by the parser are A-OK
+ def validate_trackers!(trackers)
+ raise "Could not recover any trackers from this file. Wrong import format maybe?" if trackers.empty?
+ trackers.reject!{|t| t.empty? }
+ end
end
\ No newline at end of file