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.
Files changed (46) hide show
  1. data/.gemtest +0 -0
  2. data/DEVELOPER_DOCS.rdoc +41 -39
  3. data/History.txt +7 -0
  4. data/Manifest.txt +3 -1
  5. data/{README.txt → README.rdoc} +11 -13
  6. data/Rakefile +3 -1
  7. data/lib/export/nuke_script.rb +3 -2
  8. data/lib/import/base.rb +20 -31
  9. data/lib/import/boujou.rb +4 -4
  10. data/lib/import/equalizer3.rb +5 -5
  11. data/lib/import/equalizer4.rb +3 -4
  12. data/lib/import/flame_stabilizer.rb +8 -20
  13. data/lib/import/match_mover.rb +3 -4
  14. data/lib/import/maya_live.rb +4 -4
  15. data/lib/import/nuke_grammar/utils.rb +32 -0
  16. data/lib/import/nuke_script.rb +7 -37
  17. data/lib/import/pftrack.rb +5 -5
  18. data/lib/import/shake_grammar/lexer.rb +3 -0
  19. data/lib/import/shake_script.rb +7 -4
  20. data/lib/import/shake_text.rb +4 -4
  21. data/lib/import/syntheyes.rb +4 -4
  22. data/lib/pipeline/base.rb +19 -11
  23. data/lib/tracksperanto.rb +1 -1
  24. data/lib/tracksperanto/accumulator.rb +45 -19
  25. data/lib/tracksperanto/buffer_io.rb +3 -2
  26. data/lib/tracksperanto/const_name.rb +2 -0
  27. data/lib/tracksperanto/progressive_io.rb +3 -1
  28. data/lib/tracksperanto/tracker.rb +2 -1
  29. data/lib/tracksperanto/uv_coordinates.rb +3 -1
  30. data/lib/tracksperanto/zip_tuples.rb +2 -1
  31. data/test/helper.rb +0 -5
  32. data/test/import/samples/shake_script/shake_tracker_nodes.shk +11 -299
  33. data/test/import/test_3de_import.rb +2 -5
  34. data/test/import/test_3de_import3.rb +2 -2
  35. data/test/import/test_boujou_import.rb +2 -2
  36. data/test/import/test_flame_import.rb +9 -8
  37. data/test/import/test_match_mover_import.rb +2 -4
  38. data/test/import/test_maya_live_import.rb +5 -4
  39. data/test/import/test_nuke_import.rb +20 -16
  40. data/test/import/test_pftrack_import.rb +10 -8
  41. data/test/import/test_shake_script_import.rb +12 -22
  42. data/test/import/test_shake_text_import.rb +2 -2
  43. data/test/import/test_syntheyes_import.rb +7 -8
  44. data/test/test_accumulator.rb +31 -0
  45. data/test/test_cli.rb +2 -2
  46. metadata +18 -13
@@ -15,13 +15,13 @@ class Tracksperanto::Import::MayaLive < Tracksperanto::Import::Base
15
15
 
16
16
  COMMENT = /^# /
17
17
 
18
- def stream_parse(original_io)
19
- io = Tracksperanto::ExtIO.new(original_io)
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
- send_tracker(@last_tracker) if @last_tracker
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
- send_tracker(@last_tracker) if @last_tracker
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
@@ -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 stream_parse(in_io)
14
- io = Tracksperanto::ExtIO.new(in_io)
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| send_tracker(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{ | one_curve| parse_curve(one_curve) }
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?)
@@ -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 stream_parse(io)
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
- send_tracker(t)
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
@@ -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 stream_parse(script_io)
14
+ def each
14
15
  progress_proc = lambda{|msg| report_progress(msg) }
15
- Traxtractor.new(script_io, [method(:send_tracker), progress_proc])
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
@@ -5,10 +5,10 @@ class Tracksperanto::Import::ShakeText < Tracksperanto::Import::Base
5
5
  "Shake .txt tracker file"
6
6
  end
7
7
 
8
- def stream_parse(io)
9
- io.each do | line |
8
+ def each
9
+ @io.each do | line |
10
10
  if line =~ /TrackName (.+)/
11
- send_tracker(@last_tracker) if @last_tracker && @last_tracker.any?
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
- send_tracker(@last_tracker) if @last_tracker && @last_tracker.any?
28
+ yield(@last_tracker) if @last_tracker && @last_tracker.any?
29
29
  end
30
30
 
31
31
  end
@@ -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 stream_parse(io)
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
- send_tracker(@last_tracker) if @last_tracker
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
- send_tracker(@last_tracker) if @last_tracker && @last_tracker.any?
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.receiver = accumulator
121
-
122
- importer.stream_parse(io_with_progress)
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.length} imported trackers")
125
- if accumulator.num_objects.zero?
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.num_objects
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
- accumulator.each_object_with_index do | t, tracker_idx |
136
- raise "Not a Tracker" unless t.is_a?(Tracksperanto::Tracker)
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.num_objects - tracker_idx} trackers to go"
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
@@ -5,7 +5,7 @@ require 'tempfile'
5
5
 
6
6
  module Tracksperanto
7
7
  PATH = File.expand_path(File.dirname(__FILE__))
8
- VERSION = '2.1.1'
8
+ VERSION = '2.2.0'
9
9
 
10
10
  module Import; end
11
11
  module Export; end
@@ -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 #close! when done with it to
3
- # discard the stored blob. This object is intended to be used as a Tracksperanto::Import::Base#receiver
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
- # Stores the number of objects stored so far
7
- attr_reader :num_objects
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
- @num_objects = 0
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
- @num_objects += 1
19
- d = Marshal.dump(object_to_store)
20
- [d.size, "\t", d, "\n"].map(&@store.method(:write))
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
- # Retreive each stored object in succession and unlink the buffer
24
- def each_object_with_index
25
- begin
26
- @store.rewind
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. When the segment is bigger than a certain number of bytes,
4
- # the underlying memory buffer will be swapped with a tempfile
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,3 +1,5 @@
1
+ # Provides const_name that returns the name of the class or module (or the name of the class
2
+ # an instance belongs to) without it's parent namespace. Useful for building module tables
1
3
  module Tracksperanto::ConstName
2
4
  module C
3
5
  def const_name
@@ -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 and should act and work like one
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