tracksperanto 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|