tracksperanto 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +37 -0
- data/README.txt +56 -0
- data/Rakefile +13 -0
- data/bin/tracksperanto +74 -0
- data/lib/export/base.rb +35 -0
- data/lib/export/flame_stabilizer.rb +50 -0
- data/lib/export/mux.rb +33 -0
- data/lib/export/pftrack.rb +30 -0
- data/lib/export/shake_text.rb +29 -0
- data/lib/export/syntheyes.rb +32 -0
- data/lib/import/base.rb +24 -0
- data/lib/import/flame_stabilizer.rb +34 -0
- data/lib/import/shake_script.rb +127 -0
- data/lib/import/shake_text.rb +29 -0
- data/lib/import/syntheyes.rb +32 -0
- data/lib/middleware/base.rb +28 -0
- data/lib/middleware/scaler.rb +24 -0
- data/lib/middleware/slipper.rb +12 -0
- data/lib/pipeline.rb +54 -0
- data/lib/tracksperanto.rb +131 -0
- data/test/.DS_Store +0 -0
- data/test/helper.rb +5 -0
- data/test/samples/.DS_Store +0 -0
- data/test/samples/flyover2DP_syntheyes.txt +12687 -0
- data/test/samples/megaTrack.action.3dtrack.stabilizer +29793 -0
- data/test/samples/one_shake_tracker.txt +49 -0
- data/test/samples/one_shake_tracker_from_first.txt +188 -0
- data/test/samples/shake_tracker_nodes.shk +921 -0
- data/test/samples/shake_tracker_nodes_to_syntheyes.txt +4091 -0
- data/test/samples/three_tracks_in_one_stabilizer.shk +320 -0
- data/test/samples/two_shake_trackers.txt +78 -0
- data/test/samples/two_tracks_in_one_tracker.shk +319 -0
- data/test/test_flame_block.rb +19 -0
- data/test/test_keyframe.rb +32 -0
- data/test/test_shake_export.rb +58 -0
- data/test/test_shake_script_import.rb +50 -0
- data/test/test_shake_text_import.rb +44 -0
- data/test/test_syntheyes_import.rb +25 -0
- 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
|
data/lib/pipeline.rb
ADDED
@@ -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
|
data/test/.DS_Store
ADDED
Binary file
|