tracksperanto 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.DS_Store +0 -0
- data/History.txt +11 -0
- data/Manifest.txt +64 -20
- data/README.txt +82 -30
- data/Rakefile +1 -0
- data/bin/tracksperanto +21 -14
- data/lib/export/base.rb +16 -6
- data/lib/export/match_mover.rb +40 -0
- data/lib/export/nuke_script.rb +78 -0
- data/lib/export/pftrack.rb +14 -14
- data/lib/export/shake_text.rb +7 -8
- data/lib/export/syntheyes.rb +9 -5
- data/lib/import/base.rb +45 -12
- data/lib/import/flame_stabilizer.rb +17 -32
- data/lib/import/match_mover.rb +65 -0
- data/lib/import/nuke_script.rb +96 -0
- data/lib/import/pftrack.rb +9 -2
- data/lib/import/shake_grammar/catcher.rb +56 -0
- data/lib/import/shake_grammar/lexer.rb +95 -0
- data/lib/import/shake_script.rb +189 -103
- data/lib/import/shake_text.rb +5 -3
- data/lib/import/syntheyes.rb +9 -2
- data/lib/middleware/base.rb +6 -1
- data/lib/middleware/golden.rb +7 -0
- data/lib/middleware/reformat.rb +10 -3
- data/lib/middleware/scaler.rb +14 -4
- data/lib/middleware/shift.rb +10 -0
- data/lib/middleware/slipper.rb +3 -0
- data/lib/pipeline/base.rb +111 -37
- data/lib/tracksperanto/block_init.rb +7 -0
- data/lib/tracksperanto/casts.rb +31 -0
- data/lib/tracksperanto/format_detector.rb +35 -0
- data/lib/tracksperanto/keyframe.rb +31 -0
- data/lib/tracksperanto/safety.rb +20 -0
- data/lib/tracksperanto/tracker.rb +38 -0
- data/lib/tracksperanto/zip_tuples.rb +20 -0
- data/lib/tracksperanto.rb +13 -100
- data/test/.DS_Store +0 -0
- data/test/export/.DS_Store +0 -0
- data/test/export/README_EXPORT_TESTS.txt +15 -0
- data/test/export/samples/ref_NukeScript.nk +25 -0
- data/test/export/samples/ref_NukeScript.nk.autosave +77 -0
- data/test/export/samples/ref_PFTrack.2dt +50 -0
- data/test/export/samples/ref_ShakeText.txt +48 -0
- data/test/export/samples/ref_Syntheyes.txt +42 -0
- data/test/export/samples/ref_matchmover.rz2 +52 -0
- data/test/export/test_match_mover_export.rb +16 -0
- data/test/export/test_mux.rb +23 -0
- data/test/export/test_nuke_export.rb +22 -0
- data/test/export/test_pftrack_export.rb +19 -0
- data/test/export/test_shake_export.rb +15 -0
- data/test/export/test_syntheyes_export.rb +15 -0
- data/test/helper.rb +85 -2
- data/test/import/.DS_Store +0 -0
- data/test/{samples → import/samples}/.DS_Store +0 -0
- data/test/{samples → import/samples}/flyover2DP_syntheyes.txt +0 -0
- data/test/import/samples/four_tracks_in_one_matchmove.shk +323 -0
- data/test/import/samples/four_tracks_in_one_stabilizer.shk +321 -0
- data/test/{samples → import/samples}/fromCombustion_fromMidClip_wSnap.stabilizer +0 -0
- data/test/{samples → import/samples}/hugeFlameSetup.stabilizer +0 -0
- data/test/import/samples/kipPointsMatchmover.rz2 +523 -0
- data/test/{samples → import/samples}/megaTrack.action.3dtrack.stabilizer +0 -0
- data/test/{samples → import/samples}/one_shake_tracker.txt +0 -0
- data/test/{samples → import/samples}/one_shake_tracker_from_first.txt +0 -0
- data/test/import/samples/one_tracker_with_break.nk +71 -0
- data/test/import/samples/one_tracker_with_break_in_grp.nk +91 -0
- data/test/{samples → import/samples}/shake_tracker_nodes.shk +0 -0
- data/test/{samples → import/samples}/shake_tracker_nodes_to_syntheyes.txt +0 -0
- data/test/{samples → import/samples}/sourcefile_pftrack.2dt +0 -0
- data/test/{samples → import/samples}/three_tracks_in_one_stabilizer.shk +0 -0
- data/test/{samples → import/samples}/two_shake_trackers.txt +0 -0
- data/test/{samples → import/samples}/two_tracks_in_one_tracker.shk +0 -0
- data/test/{test_flame_import.rb → import/test_flame_import.rb} +15 -9
- data/test/import/test_match_mover_import.rb +44 -0
- data/test/import/test_nuke_import.rb +63 -0
- data/test/{test_pftrack_import.rb → import/test_pftrack_import.rb} +10 -4
- data/test/import/test_shake_catcher.rb +72 -0
- data/test/import/test_shake_lexer.rb +95 -0
- data/test/import/test_shake_script_import.rb +75 -0
- data/test/{test_shake_text_import.rb → import/test_shake_text_import.rb} +10 -3
- data/test/{test_syntheyes_import.rb → import/test_syntheyes_import.rb} +8 -3
- data/test/middleware/test_golden_middleware.rb +32 -0
- data/test/middleware/test_reformat_middleware.rb +35 -0
- data/test/middleware/test_scaler_middleware.rb +51 -0
- data/test/middleware/test_shift_middleware.rb +26 -0
- data/test/middleware/test_slip_middleware.rb +31 -0
- data/test/pipeline/test_pipeline_base.rb +14 -0
- data/test/test_format_detector.rb +39 -0
- data/test/test_tracker.rb +48 -0
- data/tracksperanto.gemspec +9 -7
- metadata +101 -30
- data/lib/middleware/close.rb +0 -6
- data/test/test_shake_export.rb +0 -58
- data/test/test_shake_script_import.rb +0 -50
data/lib/import/shake_script.rb
CHANGED
@@ -1,128 +1,214 @@
|
|
1
|
-
require
|
1
|
+
require File.dirname(__FILE__) + "/shake_grammar/lexer"
|
2
|
+
require File.dirname(__FILE__) + "/shake_grammar/catcher"
|
2
3
|
|
3
4
|
class Tracksperanto::Import::ShakeScript < Tracksperanto::Import::Base
|
4
5
|
|
5
|
-
|
6
|
-
|
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
|
6
|
+
def self.human_name
|
7
|
+
"Shake .shk script file"
|
14
8
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
9
|
+
|
10
|
+
def self.distinct_file_ext
|
11
|
+
".shk"
|
24
12
|
end
|
25
|
-
|
26
|
-
class
|
27
|
-
|
13
|
+
|
14
|
+
class Traxtractor < Tracksperanto::ShakeGrammar::Catcher
|
15
|
+
include Tracksperanto::ZipTuples
|
28
16
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
break unless (value_at = scan_until(BLOCK_ENDS))
|
43
|
-
# Grab the value
|
44
|
-
val = ValueAt.from_string(value_at)
|
45
|
-
|
46
|
-
@values << val
|
17
|
+
class << self
|
18
|
+
attr_accessor :accumulator
|
19
|
+
attr_accessor :progress_block
|
20
|
+
end
|
21
|
+
|
22
|
+
self.accumulator = []
|
23
|
+
|
24
|
+
# For Linear() curve calls. If someone selected JSpline or Hermite it's his problem.
|
25
|
+
# We put the frame number at the beginning since it works witih oru tuple zipper
|
26
|
+
def linear(first_arg, *keyframes)
|
27
|
+
report_progress("Translating Linear animation")
|
28
|
+
keyframes.map do | kf |
|
29
|
+
[kf[1][1], kf[0][1]]
|
47
30
|
end
|
48
31
|
end
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
class TrackerParser < SloppyParser
|
53
|
-
attr_reader :x_curve, :y_curve, :c_curve, :name
|
54
32
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
33
|
+
# image Tracker(
|
34
|
+
# image In,
|
35
|
+
# const char * trackRange,
|
36
|
+
# const char * subPixelRes,
|
37
|
+
# const char * matchSpace,
|
38
|
+
# float referenceTolerance,
|
39
|
+
# const char * referenceBehavior,
|
40
|
+
# float failureTolerance,
|
41
|
+
# const char * failureBehavior,
|
42
|
+
# int limitProcessing,
|
43
|
+
# float referencFrame
|
44
|
+
# ...
|
45
|
+
# );
|
46
|
+
def tracker(input, trackRange, subPixelRes, matchSpace,
|
47
|
+
referenceTolerance, referenceBehavior, failureTolerance, failureBehavior, limitProcessing, referencFrame, s1, s2,
|
48
|
+
s3, s4, s5, s6, *trackers)
|
63
49
|
|
64
|
-
|
65
|
-
|
50
|
+
report_progress("Parsing Tracker node")
|
51
|
+
collect_trackers_from(get_variable_name, trackers)
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
# stabilize {
|
56
|
+
# image In,
|
57
|
+
# int applyTransform,
|
58
|
+
# int inverseTransform
|
59
|
+
# const char * trackType,
|
60
|
+
# float track1X,
|
61
|
+
# float track1Y,
|
62
|
+
# int stabilizeX,
|
63
|
+
# int stabilizeY,
|
64
|
+
# float track2X,
|
65
|
+
# float track2Y,
|
66
|
+
# int matchScale,
|
67
|
+
# int matchRotation,
|
68
|
+
# float track3X,
|
69
|
+
# float track3Y,
|
70
|
+
# float track4X,
|
71
|
+
# float track4Y,
|
72
|
+
# const char * xFilter,
|
73
|
+
# const char * yFilter,
|
74
|
+
# const char * transformationOrder,
|
75
|
+
# float motionBlur,
|
76
|
+
# float shutterTiming,
|
77
|
+
# float shutterOffset,
|
78
|
+
# float referenceFrame,
|
79
|
+
# float aspectRatio,
|
80
|
+
# ...
|
81
|
+
# };
|
82
|
+
def stabilize(imageIn, applyTransform, inverseTransform, trackType,
|
83
|
+
track1X, track1Y,
|
84
|
+
stabilizeX, stabilizeY,
|
85
|
+
track2X, track2Y,
|
86
|
+
matchScale,
|
87
|
+
matchRotation,
|
88
|
+
track3X, track3Y,
|
89
|
+
track4X, track4Y,
|
90
|
+
*useless_args)
|
66
91
|
|
67
|
-
|
92
|
+
report_progress("Parsing Stabilize node")
|
93
|
+
node_name = get_variable_name
|
94
|
+
collect_stabilizer_tracker("#{node_name}_track1", track1X, track1Y)
|
95
|
+
collect_stabilizer_tracker("#{node_name}_track2", track2X, track2Y)
|
96
|
+
collect_stabilizer_tracker("#{node_name}_track3", track3X, track3Y)
|
97
|
+
collect_stabilizer_tracker("#{node_name}_track4", track4X, track4Y)
|
68
98
|
end
|
69
99
|
|
70
|
-
|
71
|
-
|
100
|
+
# image = MatchMove(
|
101
|
+
# Background,
|
102
|
+
# Foreground,
|
103
|
+
# applyTransform,
|
104
|
+
# "trackType",
|
105
|
+
# track1X,
|
106
|
+
# track1Y,
|
107
|
+
# matchX,
|
108
|
+
# matchY,
|
109
|
+
# track2X,
|
110
|
+
# track2Y,
|
111
|
+
# scale,
|
112
|
+
# rotation,
|
113
|
+
# track3X,
|
114
|
+
# track3Y,
|
115
|
+
# track4X,
|
116
|
+
# track4Y,
|
117
|
+
# x1,
|
118
|
+
# y1,
|
119
|
+
# x2,
|
120
|
+
# y2,
|
121
|
+
# x3,
|
122
|
+
# y3,
|
123
|
+
# x4,
|
124
|
+
# y4,
|
125
|
+
# "xFilter",
|
126
|
+
# "yFilter",
|
127
|
+
# motionBlur,
|
128
|
+
# shutterTiming,
|
129
|
+
# shutterOffset,
|
130
|
+
# referenceFrame,
|
131
|
+
# "compositeType",
|
132
|
+
# clipMode,
|
133
|
+
# "trackRange",
|
134
|
+
# "subPixelRes",
|
135
|
+
# "matchSpace",
|
136
|
+
# float referenceTolerance,
|
137
|
+
# "referenceBehavior",
|
138
|
+
# float failureTolerance,
|
139
|
+
# "failureBehavior",
|
140
|
+
# int limitProcessing,
|
141
|
+
# ...
|
142
|
+
# );
|
143
|
+
def matchmove(bgImage, fgImage, applyTransform,
|
144
|
+
trackType,
|
145
|
+
track1X,
|
146
|
+
track1Y,
|
147
|
+
matchX,
|
148
|
+
matchY,
|
149
|
+
track2X,
|
150
|
+
track2Y,
|
151
|
+
scale,
|
152
|
+
rotation,
|
153
|
+
track3X,
|
154
|
+
track3Y,
|
155
|
+
track4X,
|
156
|
+
track4Y, *others)
|
157
|
+
|
158
|
+
report_progress("Parsing MatchMove node")
|
159
|
+
node_name = get_variable_name
|
160
|
+
collect_stabilizer_tracker("#{node_name}_track1", track1X, track1Y)
|
161
|
+
collect_stabilizer_tracker("#{node_name}_track2", track2X, track2Y)
|
162
|
+
collect_stabilizer_tracker("#{node_name}_track3", track3X, track3Y)
|
163
|
+
collect_stabilizer_tracker("#{node_name}_track4", track4X, track4Y)
|
164
|
+
|
72
165
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
def
|
77
|
-
|
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 ',' }
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def report_progress(with_message)
|
170
|
+
self.class.progress_block.call(with_message) if self.class.progress_block
|
84
171
|
end
|
85
|
-
end
|
86
|
-
|
87
|
-
TRACKER_PATTERN = /((\w+) = Tracker\(([^;]+))/m
|
88
|
-
|
89
|
-
def parse(sript_file_content)
|
90
172
|
|
91
|
-
|
173
|
+
def collect_trackers_from(name, array)
|
174
|
+
parameters_per_node = 16
|
175
|
+
nb_trackers = array.length / parameters_per_node
|
176
|
+
nb_trackers.times do | idx |
|
177
|
+
from_index, to_index = (idx * parameters_per_node), (idx+1) * parameters_per_node
|
178
|
+
tracker_args = array[from_index...to_index]
|
179
|
+
tracker_args[0] = "#{name}_#{tracker_args[0]}"
|
180
|
+
collect_tracker(*tracker_args)
|
181
|
+
end
|
182
|
+
end
|
92
183
|
|
93
|
-
|
94
|
-
|
95
|
-
parser = TrackerParser.new(tracker_text_block.to_s)
|
96
|
-
|
97
|
-
tracker = Tracksperanto::Tracker.new{|t| t.name = parser.name }
|
184
|
+
def collect_stabilizer_tracker(name, x_curve, y_curve)
|
185
|
+
return if (x_curve == :unknown || y_curve == :unknown)
|
98
186
|
|
99
|
-
|
187
|
+
keyframes = zip_curve_tuples(x_curve, y_curve).map do | (frame, x, y) |
|
188
|
+
Tracksperanto::Keyframe.new(:frame => frame - 1, :abs_x => x, :abs_y => y)
|
189
|
+
end
|
100
190
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
# Skip frame if only one keyframe is present
|
111
|
-
if !matching_y
|
112
|
-
STDERR.puts "Cannot find matching Y for frame #{value_at.frame} in tracker #{parser.name}, skipping keyframe"
|
113
|
-
next
|
114
|
-
end
|
115
|
-
|
116
|
-
tracker.keyframes << Tracksperanto::Keyframe.new do |k|
|
117
|
-
k.frame = (value_at.frame - 1)
|
118
|
-
k.abs_x = value_at.value
|
119
|
-
k.abs_y = matching_y.value
|
120
|
-
k.residual = 1 - (matching_residual.value rescue 1.0)
|
121
|
-
end
|
191
|
+
t = Tracksperanto::Tracker.new(:name => name, :keyframes => keyframes )
|
192
|
+
self.class.accumulator.push(t)
|
193
|
+
end
|
194
|
+
|
195
|
+
def collect_tracker(name, x_curve, y_curve, corr_curve, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12)
|
196
|
+
report_progress("Scavenging tracker #{name}")
|
197
|
+
keyframes = zip_curve_tuples(x_curve, y_curve, corr_curve).map do | (frame, x, y, corr) |
|
198
|
+
Tracksperanto::Keyframe.new(:frame => frame - 1, :abs_x => x, :abs_y => y, :residual => (1 - corr))
|
122
199
|
end
|
123
200
|
|
124
|
-
|
201
|
+
t = Tracksperanto::Tracker.new(:name => name, :keyframes => keyframes )
|
202
|
+
self.class.accumulator.push(t)
|
125
203
|
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def parse(script_io)
|
207
|
+
trackers = []
|
208
|
+
|
209
|
+
Traxtractor.accumulator = trackers
|
210
|
+
Traxtractor.progress_block = lambda{|msg| report_progress(msg) }
|
211
|
+
Traxtractor.new(script_io)
|
126
212
|
|
127
213
|
trackers
|
128
214
|
end
|
data/lib/import/shake_text.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
require 'stringio'
|
2
1
|
|
3
2
|
class Tracksperanto::Import::ShakeText < Tracksperanto::Import::Base
|
4
3
|
|
5
|
-
def
|
4
|
+
def self.human_name
|
5
|
+
"Shake .txt tracker file"
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse(io)
|
6
9
|
trackers = []
|
7
|
-
io = StringIO.new(track_file_content)
|
8
10
|
until io.eof?
|
9
11
|
line = io.gets
|
10
12
|
if line =~ /TrackName (.+)/
|
data/lib/import/syntheyes.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
class Tracksperanto::Import::Syntheyes < Tracksperanto::Import::Base
|
2
|
-
|
2
|
+
|
3
|
+
def self.human_name
|
4
|
+
"Syntheyes tracker export (UV) file"
|
5
|
+
end
|
6
|
+
|
7
|
+
def parse(io)
|
3
8
|
trackers = []
|
4
|
-
|
9
|
+
io.each_line do | line |
|
5
10
|
name, frame, x, y, corr = line.split
|
6
11
|
|
7
12
|
# Do we already have this tracker?
|
8
13
|
t = trackers.find {|e| e.name == name}
|
9
14
|
if !t
|
15
|
+
report_progress("Allocating tracker #{name}")
|
10
16
|
t = Tracksperanto::Tracker.new{|t| t.name = name }
|
11
17
|
trackers << t
|
12
18
|
end
|
@@ -18,6 +24,7 @@ class Tracksperanto::Import::Syntheyes < Tracksperanto::Import::Base
|
|
18
24
|
e.abs_y = height - convert_from_uv(height, y) # Convert TL to BL
|
19
25
|
e.residual = corr
|
20
26
|
end
|
27
|
+
report_progress("Adding keyframe #{frame} to #{name}")
|
21
28
|
end
|
22
29
|
|
23
30
|
trackers
|
data/lib/middleware/base.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
+
# The base middleware class works just like a Tracksperanto::Export::Base, but it only wraps another exporting object and does not get registered on it's own
|
2
|
+
# as an export format. Middleware can be used to massage the tracks being exported in various interesting ways - like moving the coordinates, clipping the keyframes,
|
3
|
+
# scaling the whole export or even reversing the trackers to go backwards
|
1
4
|
class Tracksperanto::Middleware::Base
|
2
5
|
include Tracksperanto::Casts
|
6
|
+
include Tracksperanto::BlockInit
|
3
7
|
|
4
|
-
def initialize(exporter)
|
8
|
+
def initialize(exporter, *args_for_block_init)
|
5
9
|
@exporter = exporter
|
10
|
+
super(*args_for_block_init)
|
6
11
|
end
|
7
12
|
|
8
13
|
# Called on export start
|
data/lib/middleware/golden.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
+
# This middleware marks all trackers as being 100% accurate
|
1
2
|
class Tracksperanto::Middleware::Golden < Tracksperanto::Middleware::Base
|
2
3
|
attr_accessor :enabled
|
4
|
+
|
5
|
+
def enabled
|
6
|
+
@enabled || false
|
7
|
+
end
|
8
|
+
|
3
9
|
def export_point(frame, float_x, float_y, float_residual)
|
4
10
|
super(frame, float_x, float_y, (enabled ? 0.0 : float_residual))
|
5
11
|
end
|
12
|
+
|
6
13
|
end
|
data/lib/middleware/reformat.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/scaler'
|
2
2
|
|
3
|
-
#
|
3
|
+
# This middleware reformats (scales) the track setup to a specific pixel resolution. Very useful for
|
4
|
+
# applying proxy tracks to full-res images
|
4
5
|
class Tracksperanto::Middleware::Reformat < Tracksperanto::Middleware::Scaler
|
6
|
+
|
7
|
+
# To which format we have to scale
|
5
8
|
attr_accessor :width, :height
|
9
|
+
cast_to_int :width, :height
|
10
|
+
|
11
|
+
private :x_factor=, :y_factor=
|
6
12
|
|
7
13
|
# Called on export start
|
8
14
|
def start_export( img_width, img_height)
|
@@ -10,7 +16,8 @@ class Tracksperanto::Middleware::Reformat < Tracksperanto::Middleware::Scaler
|
|
10
16
|
@height ||= img_height
|
11
17
|
|
12
18
|
self.x_factor, self.y_factor = (@width / img_width.to_f), (@height / img_height.to_f)
|
13
|
-
|
14
|
-
super
|
19
|
+
set_residual_factor
|
20
|
+
# Do not call super since it scales by itself :-)
|
21
|
+
@exporter.start_export(@width, @height)
|
15
22
|
end
|
16
23
|
end
|
data/lib/middleware/scaler.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
|
+
# Scales the comp being exported by a specific factor, together with the tracker keyframes
|
1
2
|
class Tracksperanto::Middleware::Scaler < Tracksperanto::Middleware::Base
|
2
3
|
DEFAULT_FACTOR = 1
|
3
4
|
|
4
5
|
attr_accessor :x_factor, :y_factor
|
6
|
+
cast_to_float :x_factor, :y_factor
|
5
7
|
|
6
8
|
# Called on export start
|
7
9
|
def start_export( img_width, img_height)
|
8
|
-
|
9
|
-
|
10
|
-
super( (img_width * x_factor), (img_height * y_factor))
|
10
|
+
set_residual_factor
|
11
|
+
super( (img_width * x_factor).to_i, (img_height * y_factor).to_i)
|
11
12
|
end
|
12
13
|
|
13
14
|
def y_factor
|
@@ -19,6 +20,15 @@ class Tracksperanto::Middleware::Scaler < Tracksperanto::Middleware::Base
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def export_point(frame, float_x, float_y, float_residual)
|
22
|
-
super(frame,
|
23
|
+
super(frame,
|
24
|
+
(float_x * x_factor),
|
25
|
+
(float_y * y_factor),
|
26
|
+
(float_residual * @residual_factor)
|
27
|
+
)
|
23
28
|
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def set_residual_factor
|
32
|
+
@residual_factor = Math.sqrt((x_factor ** 2) + (y_factor ** 2))
|
33
|
+
end
|
24
34
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# This middleware moves the keyframs by a preset number of pixels
|
2
|
+
class Tracksperanto::Middleware::Shift < Tracksperanto::Middleware::Base
|
3
|
+
attr_accessor :x_shift, :y_shift
|
4
|
+
cast_to_float :x_shift, :y_shift
|
5
|
+
|
6
|
+
def export_point(frame, float_x, float_y, float_residual)
|
7
|
+
super(frame, float_x + (@x_shift || 0), float_y + (@y_shift || 0), float_residual)
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
data/lib/middleware/slipper.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# Slips the keyframe positions by a specific integer amount of frames, positive values slip forward (later in time). Useful if you just edited some stuff onto
|
2
|
+
# the beginning if your sequence and need to extend your tracks.
|
1
3
|
class Tracksperanto::Middleware::Slipper < Tracksperanto::Middleware::Base
|
2
4
|
DEFAULT_SLIP = 0
|
3
5
|
attr_accessor :slip
|
6
|
+
cast_to_int :slip
|
4
7
|
|
5
8
|
def slip
|
6
9
|
@slip.to_i || DEFAULT_SLIP
|
data/lib/pipeline/base.rb
CHANGED
@@ -1,62 +1,113 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
# The base pipeline is the whole process of track conversion from start to finish. The pipeline object organizes the import formats, scans them,
|
2
|
+
# applies the default middlewares and yields them for processing. Here's how a calling sequence for a pipeline looks like:
|
3
|
+
#
|
4
|
+
# pipe = Tracksperanto::Pipeline::Base.new
|
5
|
+
# pipe.progress_block = lambda{|percent, msg| puts("#{msg}..#{percent.to_i}%") }
|
6
|
+
#
|
7
|
+
# pipe.run(input_file, width, height, reader_klass) do | scaler, slipper, golden, reformat |
|
8
|
+
# golden.enabled = false
|
9
|
+
# reformat.width = 1024
|
10
|
+
# reformat.width = 576
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# The pipeline will also automatically allocate output files with the right extensions at the same place where the original file resides,
|
14
|
+
# and setup outputs for all supported export formats. The pipeline will also report progress (with percent) using the passed progress block
|
15
|
+
class Tracksperanto::Pipeline::Base
|
16
|
+
|
17
|
+
# How many points have been converted. In general, the pipeline does not preserve the parsed tracker objects
|
18
|
+
# after they have been exported
|
19
|
+
attr_reader :converted_points
|
20
|
+
|
21
|
+
# How many keyframes have been converted
|
22
|
+
attr_reader :converted_keyframes
|
23
|
+
|
24
|
+
# A block acepting percent and message vars can be assigned here.
|
25
|
+
# When it's assigned, the pipeline will pass the status reports
|
26
|
+
# of all the importers and exporters to the block, together with
|
27
|
+
# percent complete
|
6
28
|
attr_accessor :progress_block
|
7
29
|
|
8
|
-
|
9
|
-
|
10
|
-
|
30
|
+
# Assign an array of exporters to use them instead of the standard ones
|
31
|
+
attr_accessor :exporters
|
32
|
+
|
33
|
+
DEFAULT_OPTIONS = {:pix_w => 720, :pix_h => 576, :parser => Tracksperanto::Import::ShakeScript }
|
34
|
+
|
35
|
+
# Runs the whole pipeline. Accepts the following options
|
36
|
+
# * pix_w - The comp width, for the case that the format does not support auto size
|
37
|
+
# * pix_h - The comp height, for the case that the format does not support auto size
|
38
|
+
# * parser - The parser class, for the case that the format does not support auto size
|
39
|
+
|
40
|
+
def run(from_input_file_path, passed_options = {})
|
41
|
+
pix_w, pix_h, parser_class = detect_importer_or_use_options(from_input_file_path, DEFAULT_OPTIONS.merge(passed_options))
|
42
|
+
|
43
|
+
# Reset stats
|
44
|
+
@converted_keyframes, @converted_points = 0, 0
|
45
|
+
|
46
|
+
# Grab the input
|
47
|
+
read_data = File.open(from_input_file_path)
|
11
48
|
|
12
49
|
# Assign the parser
|
13
|
-
|
50
|
+
importer = parser_class.new(:width => pix_w, :height => pix_h)
|
14
51
|
|
15
52
|
# Setup a multiplexer
|
16
53
|
mux = setup_outputs_for(from_input_file_path)
|
17
54
|
|
18
|
-
# Setup middlewares
|
19
|
-
|
20
|
-
slipper = Tracksperanto::Middleware::Slipper.new(scaler)
|
21
|
-
golden = Tracksperanto::Middleware::Golden.new(slipper)
|
22
|
-
reformat = Tracksperanto::Middleware::Reformat.new(golden)
|
55
|
+
# Setup middlewares
|
56
|
+
middlewares = setup_middleware_chain_with(mux)
|
23
57
|
|
24
58
|
# Yield middlewares to the block
|
25
|
-
yield(
|
59
|
+
yield(*middlewares) if block_given?
|
26
60
|
|
27
|
-
@converted_points, @converted_keyframes = run_export(read_data,
|
61
|
+
@converted_points, @converted_keyframes = run_export(read_data, importer, middlewares[-1]) do | p, m |
|
28
62
|
@progress_block.call(p, m) if @progress_block
|
29
63
|
end
|
30
64
|
end
|
31
65
|
|
66
|
+
def detect_importer_or_use_options(path, opts)
|
67
|
+
d = Tracksperanto::FormatDetector.new(path)
|
68
|
+
if d.match? && d.auto_size?
|
69
|
+
return [1, 1, d.importer_klass]
|
70
|
+
elsif d.match?
|
71
|
+
raise "Width and height must be provided for a #{d.importer_klass}" unless (opts[:pix_w] && opts[:pix_h])
|
72
|
+
opts[:parser] = d.importer_klass
|
73
|
+
else
|
74
|
+
raise "Cannot autodetect the file format - please specify the importer explicitly" unless opts[:parser]
|
75
|
+
raise "Width and height must be provided for this importer" unless (opts[:pix_w] && opts[:pix_h])
|
76
|
+
end
|
77
|
+
|
78
|
+
[opts[:pix_w], opts[:pix_h], opts[:parser]]
|
79
|
+
end
|
80
|
+
|
32
81
|
# Runs the export and returns the number of points and keyframes processed.
|
33
82
|
# If a block is passed, the block will receive the percent complete and the last
|
34
83
|
# status message that you can pass back to the UI
|
35
|
-
|
36
|
-
|
84
|
+
def run_export(tracker_data_io, parser, processor)
|
85
|
+
@ios << tracker_data_io
|
86
|
+
|
37
87
|
points, keyframes, percent_complete = 0, 0, 0.0
|
38
88
|
|
39
|
-
yield(percent_complete, "Starting the
|
89
|
+
yield(percent_complete, "Starting the parser") if block_given?
|
40
90
|
parser.progress_block = lambda do | message |
|
41
91
|
yield(percent_complete, message) if block_given?
|
42
92
|
end
|
43
93
|
|
44
|
-
trackers = parser.parse(
|
94
|
+
trackers = parser.parse(tracker_data_io)
|
45
95
|
|
46
|
-
|
96
|
+
validate_trackers!(trackers)
|
47
97
|
|
48
|
-
|
98
|
+
yield(percent_complete = 50.0, "Parsing complete, starting export for #{trackers.length} trackers") if block_given?
|
49
99
|
|
50
|
-
|
51
|
-
processor.start_export(parser.width, parser.height)
|
100
|
+
percent_per_tracker = (100.0 - percent_complete) / trackers.length
|
52
101
|
|
53
102
|
yield(percent_complete, "Starting export") if block_given?
|
54
103
|
|
104
|
+
# Use the width and height provided by the parser itself
|
105
|
+
processor.start_export(parser.width, parser.height)
|
55
106
|
trackers.each do | t |
|
56
107
|
kf_weight = percent_per_tracker / t.keyframes.length
|
57
108
|
points += 1
|
58
109
|
processor.start_tracker_segment(t.name)
|
59
|
-
t.
|
110
|
+
t.each do | kf |
|
60
111
|
keyframes += 1
|
61
112
|
processor.export_point(kf.frame, kf.abs_x, kf.abs_y, kf.residual)
|
62
113
|
yield(percent_complete += kf_weight, "Writing keyframe") if block_given?
|
@@ -68,24 +119,47 @@ class Base
|
|
68
119
|
yield(100.0, "Wrote #{points} points and #{keyframes} keyframes") if block_given?
|
69
120
|
|
70
121
|
[points, keyframes]
|
122
|
+
ensure
|
123
|
+
@ios.reject!{|e| e.close unless (!e.respond_to?(:closed?) || e.closed?) } if @ios
|
71
124
|
end
|
72
125
|
|
73
|
-
|
74
|
-
|
75
|
-
p.width, p.height = w, h
|
76
|
-
p
|
77
|
-
end
|
78
|
-
|
126
|
+
# Setup output files and return a single output
|
127
|
+
# that replays to all of them
|
79
128
|
def setup_outputs_for(input_file_path)
|
80
129
|
file_name = File.basename(input_file_path).gsub(/\.([^\.]+)$/, '')
|
130
|
+
export_klasses = exporters || Tracksperanto.exporters
|
81
131
|
Tracksperanto::Export::Mux.new(
|
82
|
-
|
83
|
-
export_name =
|
84
|
-
export_path = File.dirname(input_file_path)
|
85
|
-
exporter = exporter_class.new(
|
86
|
-
Tracksperanto::Middleware::Close.new(exporter)
|
132
|
+
export_klasses.map do | exporter_class |
|
133
|
+
export_name = [file_name, exporter_class.desc_and_extension].join("_")
|
134
|
+
export_path = File.join(File.dirname(input_file_path), export_name)
|
135
|
+
exporter = exporter_class.new(open_owned_export_file(export_path))
|
87
136
|
end
|
88
137
|
)
|
89
138
|
end
|
90
|
-
|
139
|
+
|
140
|
+
# Setup and return the output multiplexor wrapped in all necessary middlewares.
|
141
|
+
# Middlewares must be returned in an array to be passed to the configuration block
|
142
|
+
# afterwards
|
143
|
+
def setup_middleware_chain_with(output)
|
144
|
+
scaler = Tracksperanto::Middleware::Scaler.new(output)
|
145
|
+
slipper = Tracksperanto::Middleware::Slipper.new(scaler)
|
146
|
+
golden = Tracksperanto::Middleware::Golden.new(slipper)
|
147
|
+
reformat = Tracksperanto::Middleware::Reformat.new(golden)
|
148
|
+
shift = Tracksperanto::Middleware::Shift.new(reformat)
|
149
|
+
[scaler, slipper, golden, reformat, shift]
|
150
|
+
end
|
151
|
+
|
152
|
+
# Open the file for writing and register it to be closed automatically
|
153
|
+
def open_owned_export_file(path_to_file)
|
154
|
+
@ios ||= []
|
155
|
+
handle = File.open(path_to_file, "w")
|
156
|
+
@ios << handle
|
157
|
+
handle
|
158
|
+
end
|
159
|
+
|
160
|
+
# Check that the trackers made by the parser are A-OK
|
161
|
+
def validate_trackers!(trackers)
|
162
|
+
raise "Could not recover any trackers from this file. Wrong import format maybe?" if trackers.empty?
|
163
|
+
trackers.reject!{|t| t.empty? }
|
164
|
+
end
|
91
165
|
end
|