Class: AviGlitch::Avi

Inherits:
Object
  • Object
show all
Defined in:
lib/aviglitch/avi.rb

Overview

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.

Defined Under Namespace

Classes: RiffChunk

Constant Summary collapse

MAX_RIFF_SIZE =

:startdoc:

1024 ** 3

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path = nil) ⇒ Avi

Generates an instance.



87
88
89
90
# 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

Instance Attribute Details

#indicesObject

List of indices for ‘movi’ data.



78
79
80
# File 'lib/aviglitch/avi.rb', line 78

def indices
  @indices
end

#path=(path) ⇒ Object

Set path of the source file.



82
83
84
# File 'lib/aviglitch/avi.rb', line 82

def path=(value)
  @path = value
end

#riffObject

Object which represents RIFF structure.



80
81
82
# File 'lib/aviglitch/avi.rb', line 80

def riff
  @riff
end

#tmpdirObject

:nodoc:



82
83
84
# File 'lib/aviglitch/avi.rb', line 82

def tmpdir
  @tmpdir
end

Class Method Details

Parses the file and prints the RIFF structure to stdout.



550
551
552
# File 'lib/aviglitch/avi.rb', line 550

def print_rifftree file   
  Avi.rifftree file, $stdout
end

.rifftree(file, out = nil) ⇒ Object

Parses the file and returns the RIFF structure.



504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# 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

Instance Method Details

#==(other) ⇒ Object

Returns true if other‘s indices are same as self’s indices.



390
391
392
# File 'lib/aviglitch/avi.rb', line 390

def == other
  self.indices == other.indices
end

#closeObject

Closes the file.



168
169
170
# File 'lib/aviglitch/avi.rb', line 168

def close
  @movi.close!
end

#initialize_copy(avi) ⇒ Object

:nodoc:



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/aviglitch/avi.rb', line 398

def initialize_copy avi #:nodoc:
  md = Marshal.dump avi.indices
  @indices = Marshal.load md
  md = Marshal.dump avi.riff
  @riff = Marshal.load md
  newmovi = Tempfile.new 'aviglitch-clone', @tmpdir, binmode: true
  movipos = avi.movi.pos
  avi.movi.rewind
  while d = avi.movi.read(BUFFER_SIZE) do
    newmovi.print d
  end
  avi.movi.pos = movipos
  newmovi.rewind
  @movi = newmovi
end

#inspectObject

:nodoc:



394
395
396
# File 'lib/aviglitch/avi.rb', line 394

def inspect #:nodoc:
  "#<#{self.class.name}:#{sprintf("0x%x", object_id)} @movi=#{@movi.inspect}>"
end

#is_avi2?Boolean

Detects the current data will be an AVI2.0 file.

Returns:

  • (Boolean)


180
181
182
# File 'lib/aviglitch/avi.rb', line 180

def is_avi2?
  @movi.size >= MAX_RIFF_SIZE
end

#output(path) ⇒ Object

Saves data to AVI formatted file.



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# 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

#parse_riff(io, target, len = 0, is_movi = false) ⇒ Object

Parses the passed RIFF formated file recursively.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# 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

#process_movi(&block) ⇒ Object

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.



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# 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

#search(*args) ⇒ Object

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.



384
385
386
# File 'lib/aviglitch/avi.rb', line 384

def search *args
  @riff.first.search *args
end

#was_avi2?Boolean

Detects the passed file was an AVI2.0 file.

Returns:

  • (Boolean)


174
175
176
# File 'lib/aviglitch/avi.rb', line 174

def was_avi2?
  @was_avi2
end