Class: AviGlitch::Avi
- Inherits:
-
Object
- Object
- AviGlitch::Avi
- 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
-
#indices ⇒ Object
List of indices for ‘movi’ data.
-
#path ⇒ Object
writeonly
Set
path
of the source file. -
#riff ⇒ Object
Object which represents RIFF structure.
-
#tmpdir ⇒ Object
:nodoc:.
Class Method Summary collapse
-
.print_rifftree(file) ⇒ Object
Parses the
file
and prints the RIFF structure to stdout. -
.rifftree(file, out = nil) ⇒ Object
Parses the
file
and returns the RIFF structure.
Instance Method Summary collapse
-
#==(other) ⇒ Object
Returns true if
other
‘s indices are same as self’s indices. -
#close ⇒ Object
Closes the file.
-
#initialize(path = nil) ⇒ Avi
constructor
Generates an instance.
-
#initialize_copy(avi) ⇒ Object
:nodoc:.
-
#inspect ⇒ Object
:nodoc:.
-
#is_avi2? ⇒ Boolean
Detects the current data will be an AVI2.0 file.
-
#output(path) ⇒ Object
Saves data to AVI formatted file.
-
#parse_riff(io, target, len = 0, is_movi = false) ⇒ Object
Parses the passed RIFF formated file recursively.
-
#process_movi(&block) ⇒ Object
Provides internal accesses to movi binary data.
-
#search(*args) ⇒ Object
Searches and returns RIFF values with the passed search
args
. -
#was_avi2? ⇒ Boolean
Detects the passed file was an AVI2.0 file.
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
#indices ⇒ Object
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 |
#riff ⇒ Object
Object which represents RIFF structure.
80 81 82 |
# File 'lib/aviglitch/avi.rb', line 80 def riff @riff end |
#tmpdir ⇒ Object
:nodoc:
82 83 84 |
# File 'lib/aviglitch/avi.rb', line 82 def tmpdir @tmpdir end |
Class Method Details
.print_rifftree(file) ⇒ Object
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 |
#close ⇒ Object
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 |
#inspect ⇒ Object
: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.
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.
174 175 176 |
# File 'lib/aviglitch/avi.rb', line 174 def was_avi2? @was_avi2 end |