tracksperanto 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/Manifest.txt +37 -0
  2. data/README.txt +56 -0
  3. data/Rakefile +13 -0
  4. data/bin/tracksperanto +74 -0
  5. data/lib/export/base.rb +35 -0
  6. data/lib/export/flame_stabilizer.rb +50 -0
  7. data/lib/export/mux.rb +33 -0
  8. data/lib/export/pftrack.rb +30 -0
  9. data/lib/export/shake_text.rb +29 -0
  10. data/lib/export/syntheyes.rb +32 -0
  11. data/lib/import/base.rb +24 -0
  12. data/lib/import/flame_stabilizer.rb +34 -0
  13. data/lib/import/shake_script.rb +127 -0
  14. data/lib/import/shake_text.rb +29 -0
  15. data/lib/import/syntheyes.rb +32 -0
  16. data/lib/middleware/base.rb +28 -0
  17. data/lib/middleware/scaler.rb +24 -0
  18. data/lib/middleware/slipper.rb +12 -0
  19. data/lib/pipeline.rb +54 -0
  20. data/lib/tracksperanto.rb +131 -0
  21. data/test/.DS_Store +0 -0
  22. data/test/helper.rb +5 -0
  23. data/test/samples/.DS_Store +0 -0
  24. data/test/samples/flyover2DP_syntheyes.txt +12687 -0
  25. data/test/samples/megaTrack.action.3dtrack.stabilizer +29793 -0
  26. data/test/samples/one_shake_tracker.txt +49 -0
  27. data/test/samples/one_shake_tracker_from_first.txt +188 -0
  28. data/test/samples/shake_tracker_nodes.shk +921 -0
  29. data/test/samples/shake_tracker_nodes_to_syntheyes.txt +4091 -0
  30. data/test/samples/three_tracks_in_one_stabilizer.shk +320 -0
  31. data/test/samples/two_shake_trackers.txt +78 -0
  32. data/test/samples/two_tracks_in_one_tracker.shk +319 -0
  33. data/test/test_flame_block.rb +19 -0
  34. data/test/test_keyframe.rb +32 -0
  35. data/test/test_shake_export.rb +58 -0
  36. data/test/test_shake_script_import.rb +50 -0
  37. data/test/test_shake_text_import.rb +44 -0
  38. data/test/test_syntheyes_import.rb +25 -0
  39. metadata +111 -0
@@ -0,0 +1,127 @@
1
+ require 'strscan'
2
+
3
+ class Tracksperanto::Import::ShakeScript < Tracksperanto::Import::Base
4
+
5
+ # Allow literal strings instead of Regexps
6
+ class SloppyParser < StringScanner
7
+ def skip_until(pattern)
8
+ pattern.is_a?(String) ? super(/#{Regexp.escape(pattern)}/m) : super(pattern)
9
+ end
10
+
11
+ def scan_until(pattern)
12
+ pattern.is_a?(String) ? super(/#{Regexp.escape(pattern)}/m) : super(pattern)
13
+ end
14
+ end
15
+
16
+ class ValueAt
17
+ attr_accessor :value, :frame
18
+ def self.from_string(str)
19
+ v = new
20
+ val_s, frame_s = str.scan(/(.+)@(\d+)/).to_a.flatten
21
+ v.value, v.frame = val_s.to_f, frame_s.to_i
22
+ v
23
+ end
24
+ end
25
+
26
+ class CurveParser < SloppyParser
27
+ BLOCK_ENDS = /\d,|\)/
28
+
29
+ attr_reader :values
30
+ def initialize(with_curve_arg)
31
+ @values = []
32
+
33
+ super(with_curve_arg.to_s)
34
+
35
+ # Skip the interpolation name, we assume it to be linear anyway
36
+ skip_until '('
37
+
38
+ # Skip the first defining parameter whatever that might be
39
+ skip_until ','
40
+
41
+ loop do
42
+ break unless (value_at = scan_until(BLOCK_ENDS))
43
+ # Grab the value
44
+ val = ValueAt.from_string(value_at)
45
+
46
+ @values << val
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ class TrackerParser < SloppyParser
53
+ attr_reader :x_curve, :y_curve, :c_curve, :name
54
+
55
+ def initialize(with_tracker_args)
56
+ super(with_tracker_args)
57
+
58
+ # Name me!
59
+ @name = scan_until(/(\w+) /).strip
60
+
61
+ # All the tracker arguments
62
+ 17.times { skip_until ',' } # input data
63
+
64
+ # Grab the curves
65
+ @x_curve, @y_curve, @c_curve = (0..2).map{ CurveParser.new(scan_until('),')).values }
66
+
67
+ # if the next argument is an integer, we reached the end of the tracks. If not - make a nested one.
68
+ end
69
+
70
+ def curves
71
+ [@x_curve, @y_curve, @c_curve]
72
+ end
73
+ end
74
+
75
+ class TrackParser < SloppyParser
76
+ def initialize(with_tracker_block)
77
+ # scan until the first " - name of the track
78
+ skip_until ','
79
+ # scan values
80
+ # discard the 8 box determinators
81
+ 7.times { skip_until ',' }
82
+ # discard the box animation
83
+ 2.times { skip_until ',' }
84
+ end
85
+ end
86
+
87
+ TRACKER_PATTERN = /((\w+) = Tracker\(([^;]+))/m
88
+
89
+ def parse(sript_file_content)
90
+
91
+ trackers = []
92
+
93
+ sript_file_content.scan(TRACKER_PATTERN).each_with_index do | tracker_text_block, idx |
94
+
95
+ parser = TrackerParser.new(tracker_text_block.to_s)
96
+
97
+ tracker = Tracksperanto::Tracker.new{|t| t.name = parser.name }
98
+
99
+ x_keyframes, y_keyframes, residual_keyframes = TrackerParser.new(tracker_text_block.to_s).curves
100
+ x_keyframes.each_with_index do | value_at, kf_index |
101
+
102
+ # Find the Y keyframe with the same frame
103
+ matching_y = y_keyframes.find{|f| f.frame == value_at.frame }
104
+
105
+ # Find the correlation keyframe with the same frame
106
+ matching_residual = residual_keyframes.find{|f| f.frame == value_at.frame }
107
+
108
+ # Skip frame if only one keyframe is present
109
+ if !matching_y
110
+ STDERR.puts "Cannot find matching Y for frame #{value_at.frame} in tracker #{parser.name}, skipping keyframe"
111
+ next
112
+ end
113
+
114
+ tracker.keyframes << Tracksperanto::Keyframe.new do |k|
115
+ k.frame = (value_at.frame - 1)
116
+ k.abs_x = value_at.value
117
+ k.abs_y = matching_y.value
118
+ k.residual = 1 - (matching_residual.value rescue 1.0)
119
+ end
120
+ end
121
+
122
+ trackers << tracker
123
+ end
124
+
125
+ trackers
126
+ end
127
+ end
@@ -0,0 +1,29 @@
1
+ require 'stringio'
2
+
3
+ class Tracksperanto::Import::ShakeText < Tracksperanto::Import::Base
4
+
5
+ def parse(track_file_content)
6
+ trackers = []
7
+ io = StringIO.new(track_file_content)
8
+ until io.eof?
9
+ line = io.gets
10
+ if line =~ /TrackName (.+)/
11
+ trackers << Tracksperanto::Tracker.new{|t| t.name = $1 }
12
+ # Toss the next following string - header
13
+ io.gets
14
+ else
15
+ keyframe_values = line.split
16
+ next if keyframe_values.length < 4
17
+
18
+ trackers[-1].keyframes << Tracksperanto::Keyframe.new do | kf |
19
+ kf.frame = (keyframe_values[0].to_i - 1)
20
+ kf.abs_x = keyframe_values[1]
21
+ kf.abs_y = keyframe_values[2]
22
+ kf.residual = (1 - keyframe_values[3].to_f)
23
+ end
24
+ end
25
+ end
26
+
27
+ trackers
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ class Tracksperanto::Import::Syntheyes < Tracksperanto::Import::Base
2
+ def parse(file_content)
3
+ trackers = []
4
+ file_content.split("\n").each do | line |
5
+ name, frame, x, y, corr = line.split
6
+
7
+ # Do we already have this tracker?
8
+ t = trackers.find {|e| e.name == name}
9
+ if !t
10
+ t = Tracksperanto::Tracker.new{|t| t.name = name }
11
+ trackers << t
12
+ end
13
+
14
+ # Add the keyframe
15
+ t.keyframes << Tracksperanto::Keyframe.new do |e|
16
+ e.frame = frame
17
+ e.abs_x = convert_from_uv(width, x)
18
+ e.abs_y = height - convert_from_uv(height, y) # Convert TL to BL
19
+ e.residual = corr
20
+ end
21
+ end
22
+
23
+ trackers
24
+ end
25
+
26
+ private
27
+ def convert_from_uv(absolute_side, uv_value)
28
+ # First, start from zero (-.1 becomes .4)
29
+ value_off_corner = (uv_value.to_f / 2) + 0.5
30
+ absolute_side * value_off_corner
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ class Tracksperanto::Middleware::Base
2
+ include Tracksperanto::Casts
3
+
4
+ def initialize(exporter)
5
+ @exporter = exporter
6
+ end
7
+
8
+ # Called on export start
9
+ def start_export( img_width, img_height)
10
+ @exporter.start_export(img_width, img_height)
11
+ end
12
+
13
+ # Called on export end
14
+ def end_export
15
+ @exporter.end_export
16
+ end
17
+
18
+ # Called on tracker start, one for each tracker. Start of the next tracker
19
+ # signifies that the previous tracker has passed by
20
+ def start_tracker_segment(tracker_name)
21
+ @exporter.start_tracker_segment(tracker_name)
22
+ end
23
+
24
+ # Called for each tracker keyframe
25
+ def export_point(at_frame_i, abs_float_x, abs_float_y, float_residual)
26
+ @exporter.export_point(at_frame_i, abs_float_x, abs_float_y, float_residual)
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ class Tracksperanto::Middleware::Scaler < Tracksperanto::Middleware::Base
2
+ DEFAULT_FACTOR = 1
3
+
4
+ attr_accessor :x_factor, :y_factor
5
+
6
+ # Called on export start
7
+ def start_export( img_width, img_height)
8
+ # Compute the average factor
9
+ @residual_factor = (x_factor + y_factor) / 2
10
+ super( (img_width * x_factor), (img_height * y_factor))
11
+ end
12
+
13
+ def y_factor
14
+ @y_factor || DEFAULT_FACTOR
15
+ end
16
+
17
+ def x_factor
18
+ @x_factor || DEFAULT_FACTOR
19
+ end
20
+
21
+ def export_point(frame, float_x, float_y, float_residual)
22
+ super(frame, (float_x * x_factor), (float_y * y_factor), (float_residual * @residual_factor))
23
+ end
24
+ end
@@ -0,0 +1,12 @@
1
+ class Tracksperanto::Middleware::Slipper < Tracksperanto::Middleware::Base
2
+ DEFAULT_SLIP = 0
3
+ attr_accessor :slip
4
+
5
+ def slip
6
+ @slip.to_i || DEFAULT_SLIP
7
+ end
8
+
9
+ def export_point(frame, float_x, float_y, float_residual)
10
+ super(frame + slip, float_x, float_y, float_residual)
11
+ end
12
+ end
@@ -0,0 +1,54 @@
1
+ class Tracksperanto::Pipeline
2
+ attr_accessor :converted_points
3
+ attr_accessor :converted_keyframes
4
+ def run(from_input_file, pix_w, pix_h, parser_class)
5
+ # Read the input file
6
+ read_data = File.read(from_input_file)
7
+ file_name = File.basename(from_input_file).gsub(/\.([^\.]+)$/, '')
8
+
9
+ # Assign the parser
10
+ parser = parser_class.new
11
+ parser.width = pix_w
12
+ parser.height = pix_h
13
+
14
+ # Setup a multiplexer
15
+ mux = Tracksperanto::Export::Mux.new(
16
+ Tracksperanto.exporters.map do | exporter_class |
17
+ export_name = "%s_%s" % [file_name, exporter_class.desc_and_extension]
18
+ outfile = File.dirname(from_input_file) + '/' + export_name
19
+ io = File.open(outfile, 'w')
20
+ exporter_class.new(io)
21
+ end
22
+ )
23
+
24
+ # Setup middlewares - skip for now
25
+ scaler = Tracksperanto::Middleware::Scaler.new(mux)
26
+ slipper = Tracksperanto::Middleware::Slipper.new(scaler)
27
+ golden = Tracksperanto::Middleware::Golden.new(slipper)
28
+
29
+ yield(scaler, slipper, golden) if block_given?
30
+
31
+ processor = golden
32
+
33
+ # Run the export
34
+ trackers = parser.parse(read_data)
35
+ processor.start_export(parser.width, parser.height)
36
+ trackers.each do | t |
37
+
38
+ @converted_points ||= 0
39
+ @converted_points += 1
40
+
41
+ processor.start_tracker_segment(t.name)
42
+ t.keyframes.each do | kf |
43
+
44
+ @converted_keyframes ||= 0
45
+ @converted_keyframes += 1
46
+
47
+ processor.export_point(kf.frame, kf.abs_x, kf.abs_y, kf.residual)
48
+ end
49
+ end
50
+
51
+ processor.end_export
52
+ end
53
+ end
54
+
@@ -0,0 +1,131 @@
1
+ module Tracksperanto
2
+ VERSION = '1.0.0'
3
+
4
+ module Import; end
5
+ module Export; end
6
+ module Middleware; end
7
+
8
+ class << self
9
+ # Returns the array of all exporter classes defined
10
+ attr_accessor :exporters
11
+
12
+ # Returns the array of all importer classes defined
13
+ attr_accessor :importers
14
+
15
+ # Returns the array of all available middlewares
16
+ attr_accessor :middlewares
17
+ end
18
+ self.exporters, self.importers, self.middlewares = [], [], []
19
+
20
+ # Helps to define things that will forcibly become floats, integers or strings
21
+ module Casts
22
+ def self.included(into)
23
+ into.extend(self)
24
+ super
25
+ end
26
+
27
+ # Same as attr_accessor but will always convert to Float internally
28
+ def cast_to_float(*attributes)
29
+ attributes.each do | an_attr |
30
+ define_method(an_attr) { instance_variable_get("@#{an_attr}").to_f }
31
+ define_method("#{an_attr}=") { |to| instance_variable_set("@#{an_attr}", to.to_f) }
32
+ end
33
+ end
34
+
35
+ # Same as attr_accessor but will always convert to Integer/Bignum internally
36
+ def cast_to_int(*attributes)
37
+ attributes.each do | an_attr |
38
+ define_method(an_attr) { instance_variable_get("@#{an_attr}").to_i }
39
+ define_method("#{an_attr}=") { |to| instance_variable_set("@#{an_attr}", to.to_i) }
40
+ end
41
+ end
42
+
43
+ # Same as attr_accessor but will always convert to String internally
44
+ def cast_to_string(*attributes)
45
+ attributes.each do | an_attr |
46
+ define_method(an_attr) { instance_variable_get("@#{an_attr}").to_s }
47
+ define_method("#{an_attr}=") { |to| instance_variable_set("@#{an_attr}", to.to_s) }
48
+ end
49
+ end
50
+ end
51
+
52
+ module Safety
53
+ def self.included(into)
54
+ into.extend(self)
55
+ super
56
+ end
57
+
58
+ # Inject a reader that checks for nil
59
+ def safe_reader(*attributes)
60
+ attributes.each do | an_attr |
61
+ alias_method "#{an_attr}_without_nil_protection", an_attr
62
+ define_method(an_attr) do
63
+ val = send("#{an_attr}_without_nil_protection")
64
+ raise "Expected #{an_attr} on #{self} not to be nil" if val.nil?
65
+ val
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ module BlockInit
72
+ def initialize
73
+ yield(self) if block_given?
74
+ end
75
+ end
76
+
77
+ # Internal representation of a tracker
78
+ class Tracker
79
+ include Casts
80
+ include BlockInit
81
+
82
+ # Contains the array of all Keyframe objects for this tracker
83
+ attr_accessor :name
84
+
85
+ # Contains the array of all Keyframe objects for this tracker
86
+ attr_accessor :keyframes
87
+
88
+ cast_to_string :name
89
+
90
+ def initialize
91
+ @name, @keyframes = 'Tracker', []
92
+ super if block_given?
93
+ end
94
+ end
95
+
96
+ # Internal representation of a keyframe
97
+ class Keyframe
98
+ include Casts
99
+ include BlockInit
100
+
101
+ # Absolute integer frame where this keyframe is placed, 0 based
102
+ attr_accessor :frame
103
+
104
+ # Absolute float X value of the point, zero is lower left
105
+ attr_accessor :abs_x
106
+
107
+ # Absolute float Y value of the point, zero is lower left
108
+ attr_accessor :abs_y
109
+
110
+ # Absolute float residual (0 is "spot on")
111
+ attr_accessor :residual
112
+
113
+ cast_to_float :abs_x, :abs_y, :residual
114
+ cast_to_int :frame
115
+ end
116
+ end
117
+
118
+ # Load importers
119
+ Dir.glob(File.dirname(__FILE__) + '/import/*.rb').each do | i |
120
+ require i
121
+ end
122
+
123
+ # Load exporters
124
+ Dir.glob(File.dirname(__FILE__) + '/export/*.rb').each do | i |
125
+ require i
126
+ end
127
+
128
+ # Load middleware
129
+ Dir.glob(File.dirname(__FILE__) + '/middleware/*.rb').each do | i |
130
+ require i
131
+ end
Binary file