lib/aviglitch/frames.rb in aviglitch-0.0.2 vs lib/aviglitch/frames.rb in aviglitch-0.0.3

- old
+ new

@@ -1,5 +1,7 @@ +require 'stringio' + module AviGlitch # Frames provides the interface to access each frame # in the AVI file. # It is implemented as Enumerable. You can access this object @@ -10,16 +12,23 @@ # frames.each do |frame| # ## frame is a reference of a AviGlitch::Frame object # frame.data = frame.data.gsub(/\d/, '0') # end # - # In the block passed into iteration method, the parameter is the reference + # In the block passed into iteration method, the parameter is a reference # of AviGlitch::Frame object. # class Frames include Enumerable + SAFE_FRAMES_COUNT = 150000 # :nodoc: + @@warn_if_frames_are_too_large = true # :nodoc: + + attr_reader :meta + + ## + # Creates a new AviGlitch::Frames object. def initialize io io.rewind io.pos = 12 # /^RIFF[\s\S]{4}AVI $/ while io.read(4) =~ /^(?:LIST|JUNK)$/ do s = io.read(4).unpack('V').first @@ -36,42 +45,69 @@ :flag => io.read(4).unpack('V').first, :offset => io.read(4).unpack('V').first, :size => io.read(4).unpack('V').first, } end + unless safe_frames_count? @meta.size + @io.close! + exit + end io.rewind @io = io end + ## + # Enumerates the frames. def each temp = Tempfile.new 'frames' - temp.print 'movi' + frames_data_as_io(temp, Proc.new) + overwrite temp + temp.close! + end + + ## + # Returns the number of frames. + def size + @meta.size + end + + def frames_data_as_io(io = nil, block = nil) #:nodoc: + io = Tempfile.new('tmep') if io.nil? @meta = @meta.select do |m| - @io.pos = @pos_of_movi + m[:offset] + 8 + @io.pos = @pos_of_movi + m[:offset] + 8 # 8 for id and size frame = Frame.new(@io.read(m[:size]), m[:id], m[:flag]) - yield frame - unless frame.data.nil? || frame.data.to_s.empty? - m[:offset] = temp.pos + block.call(frame) if block # accept the variable block as Proc + yield frame if block_given? # or a given block (or do nothing) + unless frame.data.nil? + m[:offset] = io.pos + 4 # 4 for 'movi' m[:size] = frame.data.size - temp.print m[:id] - temp.print [frame.data.size].pack('V') - temp.print frame.data - temp.print "\000" if frame.data.size % 2 == 1 + io.print m[:id] + io.print [frame.data.size].pack('V') + io.print frame.data + io.print "\000" if frame.data.size % 2 == 1 true else false end end + io + end + def overwrite data #:nodoc: + unless safe_frames_count? @meta.size + @io.close! + exit + end # Overwrite the file - @io.pos = @pos_of_movi - 4 - @io.print [temp.pos].pack('V') - temp.rewind - while d = temp.read(1024) do + data.seek 0, IO::SEEK_END + @io.pos = @pos_of_movi - 4 # 4 for size + @io.print [data.pos + 4].pack('V') # 4 for 'movi' + @io.print 'movi' + data.rewind + while d = data.read(1024) do @io.print d end - temp.close true @io.print 'idx1' @io.print [@meta.size * 16].pack('V') @meta.each do |m| @io.print m[:id] @io.print [m[:flag], m[:offset], m[:size]].pack('V3') @@ -83,15 +119,251 @@ ## file size @io.pos = 4 @io.print [eof - 8].pack('V') ## frame count @io.pos = 48 - @io.print [@meta.size].pack('V') + vid_frames = @meta.select do |m| + id = m[:id] + id[2, 2] == 'db' || id[2, 2] == 'dc' + end + @io.print [vid_frames.size].pack('V') + @io.pos end - def size - @meta.size + ## + # Removes all frames and returns self. + def clear + @meta = [] + overwrite StringIO.new + self end + ## + # Appends the frames in the other Frames into the tail of self. + # It is destructive like Array does. + def concat other_frames + raise TypeError unless other_frames.kind_of?(Frames) + # data + this_data = Tempfile.new 'this' + self.frames_data_as_io this_data + other_data = Tempfile.new 'other' + other_frames.frames_data_as_io other_data + this_data.seek 0, IO::SEEK_END + this_size = this_data.pos + other_data.rewind + while d = other_data.read(1024) do + this_data.print d + end + other_data.close! + # meta + other_meta = other_frames.meta.collect do |m| + x = m.dup + x[:offset] += this_size + x + end + @meta.concat other_meta + # close + overwrite this_data + this_data.close! + end + + ## + # Returns a concatenation of the two Frames as a new Frames instance. + def + other_frames + r = self.to_avi + r.frames.concat other_frames + r.frames + end + + ## + # Returns the new Frames as a +times+ times repeated concatenation + # of the original Frames. + def * times + result = self.slice 0, 0 + frames = self.slice 0..-1 + times.times do + result.concat frames + end + result + end + + ## + # Returns the Frame object at the given index or + # returns new Frames object that sliced with the given index and length + # or with the Range. + # Just like Array. + def slice *args + b, l = get_beginning_and_length *args + if l.nil? + self.at b + else + e = b + l - 1 + r = self.to_avi + r.frames.each_with_index do |f, i| + unless i >= b && i <= e + f.data = nil + end + end + r.frames + end + end + + ## + # Alias for slice + alias_method :[], :slice + + ## + # Removes frame(s) at the given index or the range (same as slice). + # Returns the new Frames contains removed frames. + def slice! *args + b, l = get_beginning_and_length *args + head, sliced, tail = () + sliced = l.nil? ? self.slice(b) : self.slice(b, l) + head = self.slice(0, b) + l = 1 if l.nil? + tail = self.slice((b + l)..-1) + self.clear + self.concat head + tail + sliced + end + + ## + # Removes frame(s) at the given index or the range (same as []). + # Inserts the given Frame or Frames's contents into the removed index. + def []= *args, value + b, l = get_beginning_and_length *args + ll = l.nil? ? 1 : l + head = self.slice(0, b) + rest = self.slice((b + ll)..-1) + if l.nil? || value.kind_of?(Frame) + head.push value + elsif value.kind_of?(Frames) + head.concat value + else + raise TypeError + end + new_frames = head + rest + + self.clear + self.concat new_frames + end + + ## + # Returns one Frame object at the given index. + def at n + m = @meta[n] + return nil if m.nil? + @io.pos = @pos_of_movi + m[:offset] + 8 + frame = Frame.new(@io.read(m[:size]), m[:id], m[:flag]) + @io.rewind + frame + end + + ## + # Returns the first Frame object. + def first + self.slice(0) + end + + ## + # Returns the last Frame object. + def last + self.slice(self.size - 1) + end + + ## + # Appends the given Frame into the tail of self. + def push frame + raise TypeError unless frame.kind_of? Frame + # data + this_data = Tempfile.new 'this' + self.frames_data_as_io this_data + this_data.seek 0, IO::SEEK_END + this_size = this_data.pos + this_data.print frame.id + this_data.print [frame.data.size].pack('V') + this_data.print frame.data + this_data.print "\000" if frame.data.size % 2 == 1 + # meta + @meta << { + :id => frame.id, + :flag => frame.flag, + :offset => this_size + 4, # 4 for 'movi' + :size => frame.data.size, + } + # close + overwrite this_data + this_data.close! + self + end + + ## + # Alias for push + alias_method :<<, :push + + ## + # Insert the given Frame objects into the given index. + def insert n, *args + new_frames = self.slice(0, n) + args.each do |f| + new_frames.push f + end + new_frames.concat self.slice(n..-1) + + self.clear + self.concat new_frames + self + end + + ## + # Deletes one Frame at the given index. + def delete_at n + self.slice! n + end + + ## + # Returns true if +other+'s frames are same as self's frames. + def == other + @meta == other.meta + end + + ## + # Generate new AviGlitch::Base instance using self. + def to_avi + AviGlitch.open @io.path + end + + def get_beginning_and_length *args #:nodoc: + b, l = args + if args.first.kind_of? Range + r = args.first + b = r.begin + e = r.end >= 0 ? r.end : @meta.size + r.end + l = e - b + 1 + end + b = b >= 0 ? b : @meta.size + b + [b, l] + end + + def safe_frames_count? count # :nodoc: + r = true + if @@warn_if_frames_are_too_large && count >= SAFE_FRAMES_COUNT + trap(:INT) do + @io.close! + exit + end + m = ["WARNING: The avi data has too many frames (#{count}).\n", + "It may use a large memory to process. ", + "We recommend to chop the movie to smaller chunks before you glitch.\n", + "Do you want to continue anyway? [yN] "].join('') + a = Readline.readline m + r = a == 'y' + @@warn_if_frames_are_too_large = !r + end + r + end + + protected :frames_data_as_io, :meta + private :overwrite, :get_beginning_and_length end end