class AviGlitch::Avi
Avi
parses the passed RIFF-AVI file and maintains binary data as a structured object. It contains headers, frame’s raw data, and indices of frames. The AviGlitch
library accesses the data through this class internally.
Constants
- MAX_RIFF_SIZE
Attributes
List of indices for ‘movi’ data.
Object which represents RIFF structure.
Public Class Methods
Generates an instance.
# File lib/aviglitch/avi.rb, line 87 def initialize path = nil return unless @movi.nil? # don't reconfigure the path when cloning self.path = path unless path.nil? end
Parses the file
and prints the RIFF structure to stdout.
# File lib/aviglitch/avi.rb, line 550 def print_rifftree file Avi.rifftree file, $stdout end
Parses the file
and returns the RIFF structure.
# File lib/aviglitch/avi.rb, line 504 def rifftree file, out = nil returnable = out.nil? out = StringIO.new if returnable parse = ->(io, depth = 0, len = 0) do offset = io.pos while id = io.read(4) do if len > 0 && io.pos >= offset + len io.pos -= 4 break end size = io.read(4).unpack('V').first str = depth > 0 ? ' ' * depth + id : id if id =~ /^(?:RIFF|LIST)$/ lid = io.read(4) str << (' (%d)' % size) + " ’#{lid}’\n" out.print str parse.call io, depth + 1, size else str << (' (%d)' % size ) + "\n" out.print str io.pos += size io.pos += 1 if size % 2 == 1 end end end io = file is_io = file.respond_to?(:seek) # Probably IO. io = File.open(file, 'rb') unless is_io begin io.rewind parse.call io io.rewind ensure io.close unless is_io end if returnable out.rewind out.read end end
Public Instance Methods
Returns true if other
‘s indices are same as self’s indices.
# File lib/aviglitch/avi.rb, line 390 def == other self.indices == other.indices end
Closes the file.
# File lib/aviglitch/avi.rb, line 168 def close @movi.close! end
Detects the current data will be an AVI2.0 file.
# File lib/aviglitch/avi.rb, line 180 def is_avi2? @movi.size >= MAX_RIFF_SIZE end
Saves data to AVI formatted file.
# File lib/aviglitch/avi.rb, line 186 def output path @index_pos = 0 # prepare headers by reusing existing ones strl = search 'hdrl', 'strl' if is_avi2? # indx vid_frames_size = 0 @indexinfo = @indices.collect { |ix| vid_frames_size += 1 if ix[:id] =~ /d[bc]$/ ix[:id] }.uniq.sort.collect { |d| [d, {}] }.to_h # should be like: {"00dc"=>{}, "01wb"=>{}} strl.each_with_index do |sl, i| indx = sl.child 'indx' if indx.nil? indx = RiffChunk.new('indx', 4120, "\0" * 4120) indx.value[0, 8] = [4, 0, 0, 0].pack('vccV') sl.value.push indx else indx.value[4, 4] = [0].pack('V') indx.value[24..-1] = "\0" * (indx.value.size - 24) end preid = indx.value[8, 4] info = @indexinfo.find do |key, val| # more strict way must exist though.. if preid == "\0\0\0\0" key.start_with? "%02d" % i else key == preid end end indx.value[8, 4] = info.first if preid == "\0\0\0\0" info.last[:indx] = indx info.last[:fcc] = 'ix' + info.first[0, 2] info.last[:cur] = [] end # odml odml = search('hdrl', 'odml').first if odml.nil? odml = RiffChunk.new( 'LIST', 260, 'odml', [RiffChunk.new('dmlh', 248, "\0" * 248)] ) @riff.first.child('hdrl').value.push odml end odml.child('dmlh').value[0, 4] = [@indices.size].pack('V') else strl.each do |sl| indx = sl.child 'indx' unless indx.nil? sl.value.delete indx end end end # movi write_movi = ->(io) do vid_frames_size = 0 io.print 'LIST' io.print "\0\0\0\0" data_offset = io.pos io.print 'movi' while io.pos - data_offset <= MAX_RIFF_SIZE ix = @indices[@index_pos] @indexinfo[ix[:id]][:cur] << { pos: io.pos, size: ix[:size], flag: ix[:flag] } if is_avi2? io.print ix[:id] vid_frames_size += 1 if ix[:id] =~ /d[bc]$/ io.print [ix[:size]].pack('V') @movi.pos += 8 io.print @movi.read(ix[:size]) if ix[:size] % 2 == 1 io.print "\0" @movi.pos += 1 end @index_pos += 1 break if @index_pos > @indices.size - 1 end # standard index if is_avi2? @indexinfo.each do |key, info| ix_offset = io.pos io.print info[:fcc] io.print [24 + 8 * info[:cur].size].pack('V') io.print [2, 0, 1, info[:cur].size].pack('vccV') io.print key io.print [data_offset, 0].pack('qV') info[:cur].each.with_index do |cur, i| io.print [cur[:pos] - data_offset + 8].pack('V') # 8 for LIST#### sz = cur[:size] if cur[:flag] & Frame::AVIIF_KEYFRAME == 0 # is not keyframe sz = sz | 0b1000_0000_0000_0000_0000_0000_0000_0000 end io.print [sz].pack('V') end # rewrite indx indx = info[:indx] nent = indx.value[4, 4].unpack('V').first + 1 indx.value[4, 4] = [nent].pack('V') indx.value[24 + 16 * (nent - 1), 16] = [ ix_offset, io.pos - ix_offset, info[:cur].size ].pack('qVV') io.pos = expected_position_of(indx) + 8 io.print indx.value # clean up info[:cur] = [] io.seek 0, IO::SEEK_END end end # size of movi size = io.pos - data_offset io.pos = data_offset - 4 io.print [size].pack('V') io.seek 0, IO::SEEK_END io.print "\0" if size % 2 == 1 vid_frames_size end File.open(path, 'w+') do |io| io.binmode @movi.rewind # normal AVI # header io.print 'RIFF' io.print "\0\0\0\0" io.print 'AVI ' @riff.first.value.each do |chunk| break if chunk.id == 'movi' print_chunk io, chunk end # movi vid_size = write_movi.call io # rewrite frame count in avih header io.pos = 48 io.print [vid_size].pack('V') io.seek 0, IO::SEEK_END # idx1 io.print 'idx1' io.print [@index_pos * 16].pack('V') @indices[0..(@index_pos - 1)].each do |ix| io.print ix[:id] + [ix[:flag], ix[:offset] + 4, ix[:size]].pack('V3') end # rewrite riff chunk size avisize = io.pos - 8 io.pos = 4 io.print [avisize].pack('V') io.seek 0, IO::SEEK_END # AVI2.0 while @index_pos < @indices.size io.print 'RIFF' io.print "\0\0\0\0" riff_offset = io.pos io.print 'AVIX' # movi write_movi.call io # rewrite total chunk size avisize = io.pos - riff_offset io.pos = riff_offset - 4 io.print [avisize].pack('V') io.seek 0, IO::SEEK_END end end end
Parses the passed RIFF formated file recursively.
# File lib/aviglitch/avi.rb, line 112 def parse_riff io, target, len = 0, is_movi = false offset = io.pos binoffset = @movi.pos while id = io.read(4) do if len > 0 && io.pos >= offset + len io.pos -= 4 break end size = io.read(4).unpack('V').first if id == 'RIFF' || id == 'LIST' lid = io.read(4) newarr = [] chunk = RiffChunk.new id, size, lid, newarr target << chunk parse_riff io, newarr, size, lid == 'movi' else value = nil if is_movi if id =~ /^ix/ v = io.read size # confirm the super index surely has information @superidx.each do |sidx| nent = sidx[4, 4].unpack('v').first cid = sidx[8, 4] nent.times do |i| ent = sidx[24 + 16 * i, 16] # we can check other informations thuogh valid = ent[0, 8].unpack('q').first == io.pos - v.size - 8 parse_avi2_indices(v, binoffset) if valid end end else io.pos -= 8 v = io.read(size + 8) @movi.print v @movi.print "\0" if size % 2 == 1 end elsif id == 'idx1' v = io.read size parse_avi1_indices v unless was_avi2? else value = io.read size if id == 'indx' @superidx << value @was_avi2 = true end end chunk = RiffChunk.new id, size, value target << chunk io.pos += 1 if size % 2 == 1 end end end
Provides internal accesses to movi binary data. It requires the yield block to return an array of pair values which consists of new indices array and new movi binary data.
# File lib/aviglitch/avi.rb, line 356 def process_movi &block @movi.rewind newindices, newmovi = block.call @indices, @movi unless @indices == newindices @indices.replace newindices end unless @movi == newmovi @movi.rewind newmovi.rewind while d = newmovi.read(BUFFER_SIZE) do @movi.print d end eof = @movi.pos @movi.truncate eof end end
Searches and returns RIFF values with the passed search args
. args
should point the ids of the tree structured RIFF data under the ‘AVI ’ chunk without omission, like:
avi.search 'hdrl', 'strl', 'indx'
It returns a list of RiffChunk object which can be modified directly. (RiffChunk class which is returned through this method also has a search
method with the same interface as this class.) This method only seeks in the first RIFF ‘AVI ’ tree.
# File lib/aviglitch/avi.rb, line 384 def search *args @riff.first.search *args end
Detects the passed file was an AVI2.0 file.
# File lib/aviglitch/avi.rb, line 174 def was_avi2? @was_avi2 end