module AviGlitch # Base is the object that provides interfaces mainly used. # To glitch, and save file. The instance is returned through AviGlitch#open. # class Base # AviGlitch::Frames object generated from the +file+. attr_reader :frames # The input file (copied tempfile). attr_reader :file ## # Creates a new instance of AviGlitch::Base, open the file and # make it ready to manipulate. # It requires +path+ as Pathname. def initialize path File.open(path, 'rb') do |f| # copy as tempfile @file = Tempfile.open 'aviglitch' f.rewind while d = f.read(BUFFER_SIZE) do @file.print d end end unless AviGlitch::Base.surely_formatted? @file raise 'Unsupported file passed.' end @frames = Frames.new @file # I believe Ruby's GC to close and remove the Tempfile.. end ## # Outputs the glitched file to +path+, and close the file. def output path, do_file_close = true FileUtils.cp @file.path, path close if do_file_close self end ## # An explicit file close. def close @file.close! end ## # Glitches each frame data. # It is a convenient method to iterate each frame. # # The argument +target+ takes symbols listed below: # [:keyframe or :iframe] select video key frames (aka I-frame) # [:deltaframe or :pframe] select video delta frames (difference frames) # [:videoframe] select both of keyframe and deltaframe # [:audioframe] select audio frames # [:all] select all frames # # It also requires a block. In the block, you take the frame data # as a String parameter. # To modify the data, simply return a modified data. # With a block it returns Enumerator, without a block it returns +self+. def glitch target = :all, &block # :yield: data if block_given? @frames.each do |frame| if valid_target? target, frame frame.data = yield frame.data end end self else self.enum_for :glitch, target end end ## # Do glitch with index. def glitch_with_index target = :all, &block # :yield: data, index if block_given? self.glitch(target).with_index do |x, i| yield x, i end self else self.glitch target end end ## # Mutates all (or in +range+) keyframes into deltaframes. # It's an alias for Frames#mutate_keyframes_into_deltaframes! def mutate_keyframes_into_deltaframes! range = nil self.frames.mutate_keyframes_into_deltaframes! range self end ## # Check if it has keyframes. def has_keyframe? result = false self.frames.each do |f| if f.is_keyframe? result = true break end end result end ## # Removes all keyframes. # It is same as +glitch(:keyframes){|f| nil }+ def remove_all_keyframes! self.glitch :keyframe do |f| nil end end ## # Swaps the frames with other Frames data. def frames= other raise TypeError unless other.kind_of?(Frames) @frames.clear @frames.concat other end alias_method :write, :output alias_method :has_keyframes?, :has_keyframe? def valid_target? target, frame #:nodoc: return true if target == :all begin frame.send "is_#{target.to_s.sub(/frames$/, 'frame')}?" rescue false end end private :valid_target? class << self ## # Checks if the +file+ is a correctly formetted AVI file. # +file+ can be String or Pathname or IO. def surely_formatted? file, debug = false answer = true is_io = file.respond_to?(:seek) # Probably IO. file = File.open(file, 'rb') unless is_io begin file.seek 0, IO::SEEK_END eof = file.pos file.rewind unless file.read(4) == 'RIFF' answer = false warn 'RIFF sign is not found' if debug end len = file.read(4).unpack('V').first unless file.read(4) == 'AVI ' answer = false warn 'AVI sign is not found' if debug end while file.read(4) =~ /^(?:LIST|JUNK)$/ do s = file.read(4).unpack('V').first file.pos += s end file.pos -= 4 # we require idx1 unless file.read(4) == 'idx1' answer = false warn 'idx1 is not found' if debug end s = file.read(4).unpack('V').first file.pos += s rescue => err warn err.message if debug answer = false ensure file.close unless is_io end answer end end end end