lib/import/flame_stabilizer.rb in tracksperanto-1.0.4 vs lib/import/flame_stabilizer.rb in tracksperanto-1.0.6
- old
+ new
@@ -1,34 +1,205 @@
require 'stringio'
class Tracksperanto::Import::FlameStabilizer < Tracksperanto::Import::Base
- # Flame records channels in it's .stabilizer file. Per tracker, we use tracker1/track/x and tracker1/track/y as the base
- # tracking value. Then, the tracker1/shift/x and tracker1/shift/y are added to it. The problem is that when we track backwards
- # the main reference keyframe for the x and y coordinates that sets the base for the animation might be at the last frame, at
- # a frame in the middle or in the end of the setup. We have to pluck it out and then we compute the shift relative to the values
- # in this main frame. Obviously we discard the track width and search width information as we cannot use it in any meaningful way.
- #
- # Here is how the relevant portions look like this (note the tab indents, not spaces, indentation starts at hashmark)
- #Channel tracker1/shift/x
- # Extrapolation linear
- # Value 0
- # Size 185
- # KeyVersion 1
- # Key 0
- # Frame 1
- # Value 0
- # Interpolation linear
- # RightSlope 0.15802
- # LeftSlope 0.15802
- # End
- # Key 1
- #
- # This is in big lines:
- # (tabs)Key(space)Value
+
+ class Kf
+ include ::Tracksperanto::Casts
+ include ::Tracksperanto::BlockInit
+ cast_to_int :frame
+ cast_to_float :value
+ end
+
+ T = ::Tracksperanto::Tracker
+ K = ::Tracksperanto::Keyframe
+
+ class ChannelBlock < Array
+
+ attr_accessor :name
+ def <=>(o)
+ @name <=> o.name
+ end
+
+ def initialize(io, channel_name)
+ @name = channel_name.strip
+
+ base_value_matcher = /Value ([\-\d\.]+)/
+ keyframe_count_matcher = /Size (\d+)/
+ indent = nil
+
+ keyframes = []
+
+ while line = io.gets
+ indent ||= line.scan(/^(\s+)/)[1]
+
+ if line =~ keyframe_count_matcher
+ # Remove the keyframes which are already there
+ clear
+ $1.to_i.times { push(extract_key_from(io)) }
+ elsif line =~ base_value_matcher && empty?
+ push(Kf.new(:frame => 1, :value => $1))
+ elsif line.strip == "#{indent}End"
+ break
+ end
+ end
+
+ raise "Parsed a channel #{@name} with no keyframes" if length.zero?
+ end
+
+ def extract_key_from(io)
+ frame = nil
+ frame_matcher = /Frame ([\-\d\.]+)/
+ value_matcher = /Value ([\-\d\.]+)/
+
+ until io.eof?
+ line = io.gets
+ if line =~ frame_matcher
+ frame = $1.to_i
+ elsif line =~ value_matcher
+ value = $1.to_f
+ return Kf.new(:frame => frame, :value => value)
+ end
+ end
+
+ raise "Did not detect any keyframes!"
+ end
+ end
+
def parse(stabilizer_setup_content)
- trackers = []
io = StringIO.new(stabilizer_setup_content)
- trackers
+ self.width, self.height = extract_width_and_height_from_stream(io)
+ channels = extract_channels_from_stream(io)
+
+ raise "The setup contained no channels that we could process" if channels.empty?
+ raise "A channel was nil" if channels.find{|e| e.nil? }
+
+ trackers = scavenge_trackers_from_channels(channels)
end
+
+ private
+ def extract_width_and_height_from_stream(io)
+ w, h = nil, nil
+
+ w_matcher = /FrameWidth (\d+)/
+ h_matcher = /FrameHeight (\d+)/
+
+ until io.eof?
+ line = io.gets
+ if line =~ w_matcher
+ w = $1
+ elsif line =~ h_matcher
+ h = $1
+ end
+
+ return [w, h] if (w && h)
+ end
+
+ end
+=begin
+ Here's how a Flame channel looks like
+The Size will not be present if there are no keyframes
+
+Channel tracker1/ref/x
+ Extrapolation constant
+ Value 770.41
+ Size 4
+ KeyVersion 1
+ Key 0
+ Frame 1
+ Value 770.41
+ Interpolation constant
+ End
+ Key 1
+ Frame 44
+ Value 858.177
+ Interpolation constant
+ RightSlope 2.31503
+ LeftSlope 2.31503
+ End
+ Key 2
+ Frame 74
+ Value 939.407
+ Interpolation constant
+ RightSlope 2.24201
+ LeftSlope 2.24201
+ End
+ Key 3
+ Frame 115
+ Value 1017.36
+ Interpolation constant
+ End
+ Colour 50 50 50
+ End
+=end
+
+ def extract_channels_from_stream(io)
+ channels = []
+ channel_matcher = /Channel (.+)\n/
+ until io.eof?
+ line = io.gets
+ if line =~ channel_matcher
+ channels << extract_channel_from(io, $1)
+ end
+ end
+ channels
+ end
+
+ def extract_channel_from(io, channel_name)
+ ChannelBlock.new(io, channel_name)
+ end
+
+ def report_progress(msg)
+ end
+
+ def scavenge_trackers_from_channels(channels)
+ trackers = []
+ channels.select{|e| e.name =~ /\/track\/x/}.each do | track_x |
+ trackers << grab_tracker(channels, track_x)
+ end
+
+ trackers
+ end
+
+ def grab_tracker(channels, track_x)
+ t = T.new(:name => track_x.name.split('/').shift)
+
+ track_y = channels.find{|e| e.name == "#{t.name}/track/y" }
+ shift_x = channels.find{|e| e.name == "#{t.name}/shift/x" }
+ shift_y = channels.find{|e| e.name == "#{t.name}/shift/x" }
+
+ shift_tuples = zip_channels(shift_x, shift_y)
+ track_tuples = zip_channels(track_x, track_y)
+
+ base_x, base_y = find_base_x_and_y(track_tuples, shift_tuples)
+
+ t.keyframes = shift_tuples.map do | (at, x, y) |
+ K.new(:frame => at, :abs_x => (base_x + x.to_f), :abs_y => (base_y + y.to_f))
+ end
+
+ return t
+ end
+
+ def find_base_x_and_y(track_tuples, shift_tuples)
+ base_track_tuple = track_tuples.find do | track_tuple |
+ shift_tuples.find { |shift_tuple| shift_tuple[0] == track_tuple [0] }
+ end
+ base_track_tuple ? base_track_tuple[1..2] : track_tuples[0][1..2]
+ end
+
+ # Zip two channel objects to tuples of [frame, valuex, valuey]
+ # skipping keyframes that do not match in the two
+ def zip_channels(a, b)
+ tuples = []
+
+ a.each do | keyframe |
+ tuples[keyframe.frame] = [keyframe.frame, keyframe.value]
+ end
+
+ b.each do | keyframe |
+ tuples[keyframe.frame] = (tuples[keyframe.frame] << keyframe.value) if tuples[keyframe.frame]
+ end
+
+ tuples.compact
+ end
end
\ No newline at end of file