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