tracksperanto 2.1.1 → 2.2.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/.gemtest +0 -0
- data/DEVELOPER_DOCS.rdoc +41 -39
- data/History.txt +7 -0
- data/Manifest.txt +3 -1
- data/{README.txt → README.rdoc} +11 -13
- data/Rakefile +3 -1
- data/lib/export/nuke_script.rb +3 -2
- data/lib/import/base.rb +20 -31
- data/lib/import/boujou.rb +4 -4
- data/lib/import/equalizer3.rb +5 -5
- data/lib/import/equalizer4.rb +3 -4
- data/lib/import/flame_stabilizer.rb +8 -20
- data/lib/import/match_mover.rb +3 -4
- data/lib/import/maya_live.rb +4 -4
- data/lib/import/nuke_grammar/utils.rb +32 -0
- data/lib/import/nuke_script.rb +7 -37
- data/lib/import/pftrack.rb +5 -5
- data/lib/import/shake_grammar/lexer.rb +3 -0
- data/lib/import/shake_script.rb +7 -4
- data/lib/import/shake_text.rb +4 -4
- data/lib/import/syntheyes.rb +4 -4
- data/lib/pipeline/base.rb +19 -11
- data/lib/tracksperanto.rb +1 -1
- data/lib/tracksperanto/accumulator.rb +45 -19
- data/lib/tracksperanto/buffer_io.rb +3 -2
- data/lib/tracksperanto/const_name.rb +2 -0
- data/lib/tracksperanto/progressive_io.rb +3 -1
- data/lib/tracksperanto/tracker.rb +2 -1
- data/lib/tracksperanto/uv_coordinates.rb +3 -1
- data/lib/tracksperanto/zip_tuples.rb +2 -1
- data/test/helper.rb +0 -5
- data/test/import/samples/shake_script/shake_tracker_nodes.shk +11 -299
- data/test/import/test_3de_import.rb +2 -5
- data/test/import/test_3de_import3.rb +2 -2
- data/test/import/test_boujou_import.rb +2 -2
- data/test/import/test_flame_import.rb +9 -8
- data/test/import/test_match_mover_import.rb +2 -4
- data/test/import/test_maya_live_import.rb +5 -4
- data/test/import/test_nuke_import.rb +20 -16
- data/test/import/test_pftrack_import.rb +10 -8
- data/test/import/test_shake_script_import.rb +12 -22
- data/test/import/test_shake_text_import.rb +2 -2
- data/test/import/test_syntheyes_import.rb +7 -8
- data/test/test_accumulator.rb +31 -0
- data/test/test_cli.rb +2 -2
- metadata +18 -13
data/lib/import/maya_live.rb
CHANGED
@@ -15,13 +15,13 @@ class Tracksperanto::Import::MayaLive < Tracksperanto::Import::Base
|
|
15
15
|
|
16
16
|
COMMENT = /^# /
|
17
17
|
|
18
|
-
def
|
19
|
-
io = Tracksperanto::ExtIO.new(
|
18
|
+
def each
|
19
|
+
io = Tracksperanto::ExtIO.new(@io)
|
20
20
|
extract_width_height_and_aspect(io.gets_non_empty)
|
21
21
|
|
22
22
|
while line = io.gets_and_strip
|
23
23
|
if line =~ COMMENT
|
24
|
-
|
24
|
+
yield(@last_tracker) if @last_tracker
|
25
25
|
@last_tracker = Tracksperanto::Tracker.new(:name => line.gsub(/#{COMMENT} Name(\s+)/, ''))
|
26
26
|
next
|
27
27
|
end
|
@@ -32,7 +32,7 @@ class Tracksperanto::Import::MayaLive < Tracksperanto::Import::Base
|
|
32
32
|
@last_tracker.keyframe! :frame => frame, :abs_x => abs_x, :abs_y => abs_y, :residual => set_residual(residual)
|
33
33
|
end
|
34
34
|
|
35
|
-
|
35
|
+
yield(@last_tracker) if @last_tracker
|
36
36
|
end
|
37
37
|
|
38
38
|
private
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Tracksperanto::NukeGrammarUtils
|
2
|
+
SECTION_START = /^x(\d+)$/
|
3
|
+
KEYFRAME = /^([-\d\.]+)$/
|
4
|
+
|
5
|
+
# Scan a TCL curve expression into a number of tuples of [frame, value]
|
6
|
+
def parse_curve(curve_text)
|
7
|
+
# Replace the closing curly brace with a curly brace with space so that it gets caught by split
|
8
|
+
atoms, tuples = curve_text.gsub(/\}/m, ' }').split, []
|
9
|
+
# Nuke saves curves very efficiently. x(keyframe_number) means that an uninterrupted sequence of values will start,
|
10
|
+
# after which values follow. When the curve is interrupted in some way a new x(keyframe_number) will signifu that we
|
11
|
+
# skip to that specified keyframe and the curve continues from there, in gap size defined by the last fragment.
|
12
|
+
# That is, x1 1 x3 2 3 4 will place 2, 3 and 4 at 2-frame increments
|
13
|
+
|
14
|
+
last_processed_keyframe = 1
|
15
|
+
intraframe_gap_size = 1
|
16
|
+
while atom = atoms.shift
|
17
|
+
if atom =~ SECTION_START
|
18
|
+
last_processed_keyframe = $1.to_i
|
19
|
+
if tuples.any?
|
20
|
+
last_captured_frame = tuples[-1][0]
|
21
|
+
intraframe_gap_size = last_processed_keyframe - last_captured_frame
|
22
|
+
end
|
23
|
+
elsif atom =~ KEYFRAME
|
24
|
+
tuples << [last_processed_keyframe, $1.to_f]
|
25
|
+
last_processed_keyframe += intraframe_gap_size
|
26
|
+
elsif atom == '}'
|
27
|
+
return tuples
|
28
|
+
end
|
29
|
+
end
|
30
|
+
tuples
|
31
|
+
end
|
32
|
+
end
|
data/lib/import/nuke_script.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'delegate'
|
2
|
+
require File.expand_path(File.dirname(__FILE__)) + "/nuke_grammar/utils"
|
2
3
|
|
3
4
|
class Tracksperanto::Import::NukeScript < Tracksperanto::Import::Base
|
4
5
|
|
@@ -10,14 +11,13 @@ class Tracksperanto::Import::NukeScript < Tracksperanto::Import::Base
|
|
10
11
|
".nk"
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
-
io = Tracksperanto::ExtIO.new(
|
14
|
+
def each
|
15
|
+
io = Tracksperanto::ExtIO.new(@io)
|
15
16
|
while line = io.gets_and_strip
|
16
17
|
if line =~ TRACKER_3_PATTERN
|
17
|
-
scan_tracker_node(io).each { |t|
|
18
|
+
scan_tracker_node(io).each { |t| yield(t) }
|
18
19
|
end
|
19
20
|
end
|
20
|
-
|
21
21
|
end
|
22
22
|
|
23
23
|
private
|
@@ -45,43 +45,13 @@ class Tracksperanto::Import::NukeScript < Tracksperanto::Import::Base
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def scan_track(line_with_curve)
|
48
|
-
x_curve, y_curve = line_with_curve.split(/\}/).map
|
48
|
+
x_curve, y_curve = line_with_curve.split(/\}/).map do | one_curve|
|
49
|
+
Tracksperanto::NukeGrammarUtils.new.parse_curve(one_curve)
|
50
|
+
end
|
49
51
|
return nil unless (x_curve && y_curve)
|
50
52
|
zip_curve_tuples(x_curve, y_curve)
|
51
53
|
end
|
52
54
|
|
53
|
-
SECTION_START = /^x(\d+)$/
|
54
|
-
KEYFRAME = /^([-\d\.]+)$/
|
55
|
-
|
56
|
-
# Scan a curve to a number of tuples of [frame, value]
|
57
|
-
def parse_curve(curve_text)
|
58
|
-
# Replace the closing curly brace with a curly brace with space so that it gets caught by split
|
59
|
-
atoms, tuples = curve_text.gsub(/\}/m, ' }').split, []
|
60
|
-
# Nuke saves curves very efficiently. x(keyframe_number) means that an uninterrupted sequence of values will start,
|
61
|
-
# after which values follow. When the curve is interrupted in some way a new x(keyframe_number) will signifu that we
|
62
|
-
# skip to that specified keyframe and the curve continues from there, in gap size defined by the last fragment.
|
63
|
-
# That is, x1 1 x3 2 3 4 will place 2, 3 and 4 at 2-frame increments
|
64
|
-
|
65
|
-
last_processed_keyframe = 1
|
66
|
-
intraframe_gap_size = 1
|
67
|
-
while atom = atoms.shift
|
68
|
-
if atom =~ SECTION_START
|
69
|
-
last_processed_keyframe = $1.to_i
|
70
|
-
if tuples.any?
|
71
|
-
last_captured_frame = tuples[-1][0]
|
72
|
-
intraframe_gap_size = last_processed_keyframe - last_captured_frame
|
73
|
-
end
|
74
|
-
elsif atom =~ KEYFRAME
|
75
|
-
report_progress("Reading curve at frame #{last_processed_keyframe}")
|
76
|
-
tuples << [last_processed_keyframe, $1.to_f]
|
77
|
-
last_processed_keyframe += intraframe_gap_size
|
78
|
-
elsif atom == '}'
|
79
|
-
return tuples
|
80
|
-
end
|
81
|
-
end
|
82
|
-
tuples
|
83
|
-
end
|
84
|
-
|
85
55
|
def extract_tracker(line)
|
86
56
|
tuples = scan_track(line)
|
87
57
|
return nil unless (tuples && tuples.any?)
|
data/lib/import/pftrack.rb
CHANGED
@@ -10,16 +10,16 @@ class Tracksperanto::Import::PFTrack < Tracksperanto::Import::Base
|
|
10
10
|
CHARACTERS_OR_QUOTES = /[AZaz"]/
|
11
11
|
INTS = /^\d+$/
|
12
12
|
|
13
|
-
def
|
14
|
-
until io.eof?
|
15
|
-
line = io.gets
|
13
|
+
def each
|
14
|
+
until @io.eof?
|
15
|
+
line = @io.gets
|
16
16
|
next if (!line || line =~ /^#/)
|
17
17
|
|
18
18
|
if line =~ CHARACTERS_OR_QUOTES # Tracker with a name
|
19
19
|
t = Tracksperanto::Tracker.new(:name => unquote(line.strip))
|
20
20
|
report_progress("Reading tracker #{t.name}")
|
21
|
-
parse_tracker(t, io)
|
22
|
-
|
21
|
+
parse_tracker(t, @io)
|
22
|
+
yield(t)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -112,6 +112,9 @@ module Tracksperanto::ShakeGrammar
|
|
112
112
|
string.strip.gsub(/^\"/, '').gsub(/\"$/, '').gsub(/\\\"/, '"')
|
113
113
|
end
|
114
114
|
|
115
|
+
# In the default impl. this just puts things on the stack. However,
|
116
|
+
# if you want to unwrap structures as they come along (whych you do for big files)
|
117
|
+
# you have to override this
|
115
118
|
def push(atom_array)
|
116
119
|
@stack << atom_array
|
117
120
|
end
|
data/lib/import/shake_script.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require File.dirname(__FILE__) + "/shake_grammar/lexer"
|
2
|
-
require File.dirname(__FILE__) + "/shake_grammar/catcher"
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + "/shake_grammar/lexer"
|
2
|
+
require File.expand_path(File.dirname(__FILE__)) + "/shake_grammar/catcher"
|
3
|
+
|
3
4
|
class Tracksperanto::Import::ShakeScript < Tracksperanto::Import::Base
|
4
5
|
|
5
6
|
def self.human_name
|
@@ -10,9 +11,9 @@ class Tracksperanto::Import::ShakeScript < Tracksperanto::Import::Base
|
|
10
11
|
".shk"
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
+
def each
|
14
15
|
progress_proc = lambda{|msg| report_progress(msg) }
|
15
|
-
Traxtractor.new(
|
16
|
+
Traxtractor.new(@io, [Proc.new, progress_proc])
|
16
17
|
end
|
17
18
|
|
18
19
|
private
|
@@ -216,6 +217,8 @@ class Tracksperanto::Import::ShakeScript < Tracksperanto::Import::Base
|
|
216
217
|
def collect_stabilizer_tracker(name, x_curve, y_curve)
|
217
218
|
return unless valid_curves?(x_curve, y_curve)
|
218
219
|
|
220
|
+
report_progress("Scavenging tracker #{name}")
|
221
|
+
|
219
222
|
keyframes = zip_curve_tuples(x_curve, y_curve).map do | (frame, x, y) |
|
220
223
|
Tracksperanto::Keyframe.new(:frame => frame - 1, :abs_x => x, :abs_y => y)
|
221
224
|
end
|
data/lib/import/shake_text.rb
CHANGED
@@ -5,10 +5,10 @@ class Tracksperanto::Import::ShakeText < Tracksperanto::Import::Base
|
|
5
5
|
"Shake .txt tracker file"
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
9
|
-
io.each do | line |
|
8
|
+
def each
|
9
|
+
@io.each do | line |
|
10
10
|
if line =~ /TrackName (.+)/
|
11
|
-
|
11
|
+
yield(@last_tracker) if @last_tracker && @last_tracker.any?
|
12
12
|
@last_tracker = Tracksperanto::Tracker.new(:name => $1)
|
13
13
|
# Toss the next following string - header
|
14
14
|
io.gets
|
@@ -25,7 +25,7 @@ class Tracksperanto::Import::ShakeText < Tracksperanto::Import::Base
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
yield(@last_tracker) if @last_tracker && @last_tracker.any?
|
29
29
|
end
|
30
30
|
|
31
31
|
end
|
data/lib/import/syntheyes.rb
CHANGED
@@ -5,13 +5,13 @@ class Tracksperanto::Import::Syntheyes < Tracksperanto::Import::Base
|
|
5
5
|
"Syntheyes 2D tracker paths file"
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
9
|
-
io.each_line do | line |
|
8
|
+
def each
|
9
|
+
@io.each_line do | line |
|
10
10
|
name, frame, x, y, frame_status = line.split
|
11
11
|
|
12
12
|
# Do we already have this tracker?
|
13
13
|
unless @last_tracker && @last_tracker.name == name
|
14
|
-
|
14
|
+
yield(@last_tracker) if @last_tracker
|
15
15
|
report_progress("Allocating tracker #{name}")
|
16
16
|
@last_tracker = Tracksperanto::Tracker.new{|t| t.name = name }
|
17
17
|
end
|
@@ -26,7 +26,7 @@ class Tracksperanto::Import::Syntheyes < Tracksperanto::Import::Base
|
|
26
26
|
@last_tracker.push(k)
|
27
27
|
report_progress("Adding keyframe #{frame} to #{name}")
|
28
28
|
end
|
29
|
-
|
29
|
+
yield(@last_tracker) if @last_tracker && @last_tracker.any?
|
30
30
|
|
31
31
|
end
|
32
32
|
end
|
data/lib/pipeline/base.rb
CHANGED
@@ -116,24 +116,30 @@ class Tracksperanto::Pipeline::Base
|
|
116
116
|
end
|
117
117
|
@ios << io_with_progress
|
118
118
|
|
119
|
-
accumulator = Tracksperanto::Accumulator.new
|
120
|
-
importer.
|
121
|
-
|
122
|
-
|
119
|
+
@accumulator = Tracksperanto::Accumulator.new
|
120
|
+
importer.io = io_with_progress
|
121
|
+
importer.each {|t| @accumulator.push(t) }
|
122
|
+
|
123
|
+
# OBSOLETE - for this version we are going to permit it
|
124
|
+
if importer.respond_to?(:stream_parse)
|
125
|
+
importer.receiver = @accumulator
|
126
|
+
importer.stream_parse(io_with_progress)
|
127
|
+
end
|
123
128
|
|
124
|
-
report_progress(percent_complete = 50.0, "Validating #{accumulator.
|
125
|
-
if accumulator.
|
129
|
+
report_progress(percent_complete = 50.0, "Validating #{@accumulator.size} imported trackers")
|
130
|
+
if @accumulator.size.zero?
|
126
131
|
raise "Could not recover any non-empty trackers from this file. Wrong import format maybe?"
|
127
132
|
end
|
128
133
|
|
129
134
|
report_progress(percent_complete, "Starting export")
|
130
135
|
|
131
|
-
percent_per_tracker = (100.0 - percent_complete) / accumulator.
|
136
|
+
percent_per_tracker = (100.0 - percent_complete) / @accumulator.size
|
132
137
|
|
133
138
|
# Use the width and height provided by the parser itself
|
134
139
|
exporter.start_export(importer.width, importer.height)
|
135
|
-
|
136
|
-
|
140
|
+
|
141
|
+
# Now send each tracker through the middleware chain
|
142
|
+
@accumulator.each_with_index do | t, tracker_idx |
|
137
143
|
|
138
144
|
kf_weight = percent_per_tracker / t.keyframes.length
|
139
145
|
points += 1
|
@@ -143,7 +149,7 @@ class Tracksperanto::Pipeline::Base
|
|
143
149
|
exporter.export_point(kf.frame, kf.abs_x, kf.abs_y, kf.residual)
|
144
150
|
report_progress(
|
145
151
|
percent_complete += kf_weight,
|
146
|
-
"Writing keyframe #{idx+1} of #{t.name.inspect}, #{accumulator.
|
152
|
+
"Writing keyframe #{idx+1} of #{t.name.inspect}, #{@accumulator.size - tracker_idx} trackers to go"
|
147
153
|
)
|
148
154
|
end
|
149
155
|
exporter.end_tracker_segment
|
@@ -154,8 +160,10 @@ class Tracksperanto::Pipeline::Base
|
|
154
160
|
|
155
161
|
[points, keyframes]
|
156
162
|
ensure
|
163
|
+
@accumulator.clear
|
157
164
|
@ios.map!{|e| e.close! rescue e.close }
|
158
165
|
@ios = []
|
166
|
+
@accumulator = nil
|
159
167
|
end
|
160
168
|
|
161
169
|
# Setup output files and return a single output
|
@@ -176,4 +184,4 @@ class Tracksperanto::Pipeline::Base
|
|
176
184
|
@ios ||= []
|
177
185
|
@ios.push(File.open(path_to_file, "wb"))[-1]
|
178
186
|
end
|
179
|
-
end
|
187
|
+
end
|
data/lib/tracksperanto.rb
CHANGED
@@ -1,42 +1,68 @@
|
|
1
1
|
# An accumulator buffer for Ruby objects. Use it to sequentially store a shitload
|
2
|
-
# of objects on disk and then retreive them one by one. Make sure to call
|
3
|
-
# discard the stored blob.
|
2
|
+
# of objects on disk and then retreive them one by one. Make sure to call clear when done
|
3
|
+
# with it to discard the stored blob.
|
4
|
+
#
|
5
|
+
# This object is intended to be used as a Tracksperanto::Import::Base#receiver, but can be used
|
6
|
+
# in general like a disk-based object buffer.
|
7
|
+
#
|
8
|
+
# a = Tracksperanto::Accumulator.new
|
9
|
+
# parse_big_file do | one_node |
|
10
|
+
# a.push(one_node)
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# a.size #=> 30932
|
14
|
+
# a.each do | node_read_from_disk |
|
15
|
+
# # do something with node
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# a.clear # ensure that the file is deleted
|
4
19
|
class Tracksperanto::Accumulator
|
20
|
+
include Enumerable
|
5
21
|
|
6
|
-
#
|
7
|
-
attr_reader :
|
8
|
-
alias_method :length, :num_objects
|
22
|
+
# Returns the number of objects stored so far
|
23
|
+
attr_reader :size
|
9
24
|
|
10
25
|
def initialize
|
11
26
|
@store = Tracksperanto::BufferIO.new
|
12
|
-
@
|
27
|
+
@size = 0
|
28
|
+
@byte_size = 0
|
29
|
+
|
13
30
|
super
|
14
31
|
end
|
15
32
|
|
16
33
|
# Store an object
|
17
34
|
def push(object_to_store)
|
18
|
-
@
|
19
|
-
|
20
|
-
|
35
|
+
@store.seek(@byte_size)
|
36
|
+
blob = marshal_object(object_to_store)
|
37
|
+
@store.write(blob)
|
38
|
+
@size += 1
|
39
|
+
@byte_size = @byte_size + blob.size
|
40
|
+
object_to_store
|
41
|
+
end
|
42
|
+
|
43
|
+
# Retreive each stored object in succession. All other Enumerable
|
44
|
+
# methods are also available (but be careful with Enumerable#map)
|
45
|
+
def each
|
46
|
+
@store.rewind
|
47
|
+
@size.times { yield(recover_object) }
|
21
48
|
end
|
22
49
|
|
23
|
-
#
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
@num_objects.times { |i| yield(recover_object, i - 1) }
|
28
|
-
ensure
|
29
|
-
@store.close!
|
30
|
-
end
|
50
|
+
# Calls close! on the datastore and deletes the objects in it
|
51
|
+
def clear
|
52
|
+
@store.close!
|
53
|
+
@size = 0
|
31
54
|
end
|
32
|
-
|
33
55
|
|
34
56
|
private
|
35
57
|
|
58
|
+
def marshal_object(object_to_store)
|
59
|
+
d = Marshal.dump(object_to_store)
|
60
|
+
blob = [d.size, "\t", d, "\n"].join
|
61
|
+
end
|
62
|
+
|
36
63
|
def recover_object
|
37
64
|
# Up to the tab is the amount of bytes to read
|
38
65
|
demarshal_bytes = @store.gets("\t").strip.to_i
|
39
|
-
# Then read the bytes and unmarshal it
|
40
66
|
Marshal.load(@store.read(demarshal_bytes))
|
41
67
|
end
|
42
68
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
require "tempfile"
|
2
2
|
|
3
|
-
# BufferIO is used for writing big segments of text.
|
4
|
-
# the underlying
|
3
|
+
# BufferIO is used for writing big segments of text. It works like a StringIO, but when the size
|
4
|
+
# of the underlying string buffer exceeds MAX_IN_MEM_BYTES the string will be flushed to disk
|
5
|
+
# and it automagically becomes a Tempfile
|
5
6
|
class Tracksperanto::BufferIO < DelegateClass(IO)
|
6
7
|
include Tracksperanto::Returning
|
7
8
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# Used for IO objects that need to report the current offset at each operation that changes the said offset
|
2
|
-
# (useful for building progress bars that report on a file read operation)
|
2
|
+
# (useful for building progress bars that report on a file read operation). Pass the actual IO and a block to
|
3
|
+
# report progress to the constructor.
|
3
4
|
class Tracksperanto::ProgressiveIO < DelegateClass(IO)
|
4
5
|
include Tracksperanto::Returning
|
5
6
|
|
@@ -14,6 +15,7 @@ class Tracksperanto::ProgressiveIO < DelegateClass(IO)
|
|
14
15
|
@progress_block = blk.to_proc if blk
|
15
16
|
end
|
16
17
|
|
18
|
+
# Report offset at each line
|
17
19
|
def each(sep_string = $/, &blk)
|
18
20
|
# Report offset at each call of the iterator
|
19
21
|
result = super(sep_string) do | line |
|
@@ -1,4 +1,5 @@
|
|
1
|
-
# Internal representation of a tracker point with keyframes. A Tracker is an array of Keyframe objects
|
1
|
+
# Internal representation of a tracker point with keyframes. A Tracker is an array of Keyframe objects
|
2
|
+
# with a few methods added for convenience
|
2
3
|
class Tracksperanto::Tracker < DelegateClass(Array)
|
3
4
|
include Tracksperanto::Casts
|
4
5
|
include Tracksperanto::BlockInit
|