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 with the necessary structure from the path.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/aviglitch/avi.rb', line 87

def initialize path = nil
  return if path.nil?
  @path = path
  File.open(path, 'rb') do |io|
    @movi = Tempfile.new 'aviglitch', binmode: true
    @riff = []
    @indices = []
    @superidx = []
    @was_avi2 = false
    io.rewind
    parse_riff io, @riff
    if was_avi2?
      @indices.sort_by! { |ix| ix[:offset] }
    end
  end
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

#riffObject

Object which represents RIFF structure.



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

def riff
  @riff
end

Class Method Details

Parses the file and prints the RIFF structure to stdout.



543
544
545
# File 'lib/aviglitch/avi.rb', line 543

def print_rifftree file   
  Avi.rifftree file, $stdout
end

.rifftree(file, out = nil) ⇒ Object

Parses the file and returns the RIFF structure.



497
498
499
500
501
502
503
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
# File 'lib/aviglitch/avi.rb', line 497

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.



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

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

#closeObject

Closes the file.



162
163
164
# File 'lib/aviglitch/avi.rb', line 162

def close
  @movi.close!
end

#initialize_copy(avi) ⇒ Object

:nodoc:



392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/aviglitch/avi.rb', line 392

def initialize_copy avi #:nodoc:
  avi.path = @path.dup
  md = Marshal.dump @indices
  avi.indices = Marshal.load md
  md = Marshal.dump @riff
  avi.riff = Marshal.load md
  newmovi = Tempfile.new 'aviglitch', binmode: true
  movipos = @movi.pos
  @movi.rewind
  newmovi.print @movi.read
  @movi.pos = movipos
  newmovi.rewind
  avi.movi = newmovi
end

#inspectObject

:nodoc:



388
389
390
# File 'lib/aviglitch/avi.rb', line 388

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)


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

def is_avi2?
  @movi.size >= MAX_RIFF_SIZE
end

#output(path) ⇒ Object

Saves data to AVI formatted file.



180
181
182
183
184
185
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
# File 'lib/aviglitch/avi.rb', line 180

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.



106
107
108
109
110
111
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
# File 'lib/aviglitch/avi.rb', line 106

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.



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/aviglitch/avi.rb', line 350

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.



378
379
380
# File 'lib/aviglitch/avi.rb', line 378

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

#was_avi2?Boolean

Detects the passed file was an AVI2.0 file.

Returns:

  • (Boolean)


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

def was_avi2?
  @was_avi2
end