aviglitch 0.1.6 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +5 -21
- data/.gitignore +1 -0
- data/ChangeLog.md +14 -0
- data/Gemfile +5 -0
- data/LICENSE +1 -1
- data/README.md +0 -4
- data/aviglitch.gemspec +1 -2
- data/bin/datamosh +13 -3
- data/lib/aviglitch/avi.rb +557 -0
- data/lib/aviglitch/base.rb +31 -59
- data/lib/aviglitch/frame.rb +20 -0
- data/lib/aviglitch/frames.rb +187 -185
- data/lib/aviglitch.rb +12 -5
- data/spec/avi2_spec.rb +41 -0
- data/spec/aviglitch_spec.rb +20 -18
- data/spec/datamosh_spec.rb +4 -14
- data/spec/frames_spec.rb +39 -28
- data/spec/spec_helper.rb +45 -0
- metadata +7 -6
- data/spec/files/sample.avi +0 -0
@@ -0,0 +1,557 @@
|
|
1
|
+
module AviGlitch
|
2
|
+
|
3
|
+
# Avi parses the passed RIFF-AVI file and maintains binary data as
|
4
|
+
# a structured object.
|
5
|
+
# It contains headers, frame's raw data, and indices of frames.
|
6
|
+
# The AviGlitch library accesses the data through this class internally.
|
7
|
+
#
|
8
|
+
class Avi
|
9
|
+
|
10
|
+
# :stopdoc:
|
11
|
+
|
12
|
+
# RiffChunk represents a parsed RIFF chunk.
|
13
|
+
class RiffChunk
|
14
|
+
|
15
|
+
attr_accessor :id, :list, :value, :binsize
|
16
|
+
|
17
|
+
def initialize id, size, value, list = false
|
18
|
+
@binsize = size
|
19
|
+
@is_list = list.kind_of? Array
|
20
|
+
unless is_list?
|
21
|
+
@id = id
|
22
|
+
@value = value
|
23
|
+
else
|
24
|
+
@id = value
|
25
|
+
@list = id
|
26
|
+
@value = list
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_list?
|
31
|
+
@is_list
|
32
|
+
end
|
33
|
+
|
34
|
+
def children id
|
35
|
+
if is_list?
|
36
|
+
value.filter do |chk|
|
37
|
+
chk.id == id
|
38
|
+
end
|
39
|
+
else
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def child id
|
45
|
+
children(id).first
|
46
|
+
end
|
47
|
+
|
48
|
+
def search *args
|
49
|
+
a1 = args.shift
|
50
|
+
r = value.filter { |v|
|
51
|
+
v.id == a1
|
52
|
+
}.collect { |v|
|
53
|
+
if args.size > 0
|
54
|
+
v.search *args
|
55
|
+
else
|
56
|
+
v
|
57
|
+
end
|
58
|
+
}
|
59
|
+
r.flatten
|
60
|
+
end
|
61
|
+
|
62
|
+
def inspect
|
63
|
+
if @is_list
|
64
|
+
"{list: \"#{list}\", id: \"#{id}\", binsize: #{binsize}, value: #{value}}"
|
65
|
+
elsif !value.nil?
|
66
|
+
"{id: \"#{id}\", binsize: #{binsize}, value: \"#{value}\"}"
|
67
|
+
else
|
68
|
+
"{id: \"#{id}\", binsize: #{binsize}}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# :startdoc:
|
75
|
+
|
76
|
+
MAX_RIFF_SIZE = 1024 ** 3
|
77
|
+
# List of indices for 'movi' data.
|
78
|
+
attr_accessor :indices
|
79
|
+
# Object which represents RIFF structure.
|
80
|
+
attr_accessor :riff
|
81
|
+
|
82
|
+
attr_accessor :path, :movi, :tmpdir #:nodoc:
|
83
|
+
protected :path, :path=, :movi, :movi=
|
84
|
+
|
85
|
+
##
|
86
|
+
# Generates an instance.
|
87
|
+
def initialize path = nil
|
88
|
+
return unless @movi.nil? # don't reconfigure the path when cloning
|
89
|
+
self.path = path unless path.nil?
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Set +path+ of the source file.
|
94
|
+
def path= path #:nodoc:
|
95
|
+
@path = path
|
96
|
+
File.open(path, 'rb') do |io|
|
97
|
+
@movi = Tempfile.new 'aviglitch', @tmpdir, binmode: true
|
98
|
+
@riff = []
|
99
|
+
@indices = []
|
100
|
+
@superidx = []
|
101
|
+
@was_avi2 = false
|
102
|
+
io.rewind
|
103
|
+
parse_riff io, @riff
|
104
|
+
if was_avi2?
|
105
|
+
@indices.sort_by! { |ix| ix[:offset] }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Parses the passed RIFF formated file recursively.
|
112
|
+
def parse_riff io, target, len = 0, is_movi = false
|
113
|
+
offset = io.pos
|
114
|
+
binoffset = @movi.pos
|
115
|
+
while id = io.read(4) do
|
116
|
+
if len > 0 && io.pos >= offset + len
|
117
|
+
io.pos -= 4
|
118
|
+
break
|
119
|
+
end
|
120
|
+
size = io.read(4).unpack('V').first
|
121
|
+
if id == 'RIFF' || id == 'LIST'
|
122
|
+
lid = io.read(4)
|
123
|
+
newarr = []
|
124
|
+
chunk = RiffChunk.new id, size, lid, newarr
|
125
|
+
target << chunk
|
126
|
+
parse_riff io, newarr, size, lid == 'movi'
|
127
|
+
else
|
128
|
+
value = nil
|
129
|
+
if is_movi
|
130
|
+
if id =~ /^ix/
|
131
|
+
v = io.read size
|
132
|
+
# confirm the super index surely has information
|
133
|
+
@superidx.each do |sidx|
|
134
|
+
nent = sidx[4, 4].unpack('v').first
|
135
|
+
cid = sidx[8, 4]
|
136
|
+
nent.times do |i|
|
137
|
+
ent = sidx[24 + 16 * i, 16]
|
138
|
+
# we can check other informations thuogh
|
139
|
+
valid = ent[0, 8].unpack('q').first == io.pos - v.size - 8
|
140
|
+
parse_avi2_indices(v, binoffset) if valid
|
141
|
+
end
|
142
|
+
end
|
143
|
+
else
|
144
|
+
io.pos -= 8
|
145
|
+
v = io.read(size + 8)
|
146
|
+
@movi.print v
|
147
|
+
@movi.print "\0" if size % 2 == 1
|
148
|
+
end
|
149
|
+
elsif id == 'idx1'
|
150
|
+
v = io.read size
|
151
|
+
parse_avi1_indices v unless was_avi2?
|
152
|
+
else
|
153
|
+
value = io.read size
|
154
|
+
if id == 'indx'
|
155
|
+
@superidx << value
|
156
|
+
@was_avi2 = true
|
157
|
+
end
|
158
|
+
end
|
159
|
+
chunk = RiffChunk.new id, size, value
|
160
|
+
target << chunk
|
161
|
+
io.pos += 1 if size % 2 == 1
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Closes the file.
|
168
|
+
def close
|
169
|
+
@movi.close!
|
170
|
+
end
|
171
|
+
|
172
|
+
##
|
173
|
+
# Detects the passed file was an AVI2.0 file.
|
174
|
+
def was_avi2?
|
175
|
+
@was_avi2
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Detects the current data will be an AVI2.0 file.
|
180
|
+
def is_avi2?
|
181
|
+
@movi.size >= MAX_RIFF_SIZE
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Saves data to AVI formatted file.
|
186
|
+
def output path
|
187
|
+
@index_pos = 0
|
188
|
+
# prepare headers by reusing existing ones
|
189
|
+
strl = search 'hdrl', 'strl'
|
190
|
+
if is_avi2?
|
191
|
+
# indx
|
192
|
+
vid_frames_size = 0
|
193
|
+
@indexinfo = @indices.collect { |ix|
|
194
|
+
vid_frames_size += 1 if ix[:id] =~ /d[bc]$/
|
195
|
+
ix[:id]
|
196
|
+
}.uniq.sort.collect { |d|
|
197
|
+
[d, {}]
|
198
|
+
}.to_h # should be like: {"00dc"=>{}, "01wb"=>{}}
|
199
|
+
strl.each_with_index do |sl, i|
|
200
|
+
indx = sl.child 'indx'
|
201
|
+
if indx.nil?
|
202
|
+
indx = RiffChunk.new('indx', 4120, "\0" * 4120)
|
203
|
+
indx.value[0, 8] = [4, 0, 0, 0].pack('vccV')
|
204
|
+
sl.value.push indx
|
205
|
+
else
|
206
|
+
indx.value[4, 4] = [0].pack('V')
|
207
|
+
indx.value[24..-1] = "\0" * (indx.value.size - 24)
|
208
|
+
end
|
209
|
+
preid = indx.value[8, 4]
|
210
|
+
info = @indexinfo.find do |key, val|
|
211
|
+
# more strict way must exist though..
|
212
|
+
if preid == "\0\0\0\0"
|
213
|
+
key.start_with? "%02d" % i
|
214
|
+
else
|
215
|
+
key == preid
|
216
|
+
end
|
217
|
+
end
|
218
|
+
indx.value[8, 4] = info.first if preid == "\0\0\0\0"
|
219
|
+
info.last[:indx] = indx
|
220
|
+
info.last[:fcc] = 'ix' + info.first[0, 2]
|
221
|
+
info.last[:cur] = []
|
222
|
+
end
|
223
|
+
# odml
|
224
|
+
odml = search('hdrl', 'odml').first
|
225
|
+
if odml.nil?
|
226
|
+
odml = RiffChunk.new(
|
227
|
+
'LIST', 260, 'odml', [RiffChunk.new('dmlh', 248, "\0" * 248)]
|
228
|
+
)
|
229
|
+
@riff.first.child('hdrl').value.push odml
|
230
|
+
end
|
231
|
+
odml.child('dmlh').value[0, 4] = [@indices.size].pack('V')
|
232
|
+
else
|
233
|
+
strl.each do |sl|
|
234
|
+
indx = sl.child 'indx'
|
235
|
+
unless indx.nil?
|
236
|
+
sl.value.delete indx
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# movi
|
242
|
+
write_movi = ->(io) do
|
243
|
+
vid_frames_size = 0
|
244
|
+
io.print 'LIST'
|
245
|
+
io.print "\0\0\0\0"
|
246
|
+
data_offset = io.pos
|
247
|
+
io.print 'movi'
|
248
|
+
while io.pos - data_offset <= MAX_RIFF_SIZE
|
249
|
+
ix = @indices[@index_pos]
|
250
|
+
@indexinfo[ix[:id]][:cur] << {
|
251
|
+
pos: io.pos, size: ix[:size], flag: ix[:flag]
|
252
|
+
} if is_avi2?
|
253
|
+
io.print ix[:id]
|
254
|
+
vid_frames_size += 1 if ix[:id] =~ /d[bc]$/
|
255
|
+
io.print [ix[:size]].pack('V')
|
256
|
+
@movi.pos += 8
|
257
|
+
io.print @movi.read(ix[:size])
|
258
|
+
if ix[:size] % 2 == 1
|
259
|
+
io.print "\0"
|
260
|
+
@movi.pos += 1
|
261
|
+
end
|
262
|
+
@index_pos += 1
|
263
|
+
break if @index_pos > @indices.size - 1
|
264
|
+
end
|
265
|
+
# standard index
|
266
|
+
if is_avi2?
|
267
|
+
@indexinfo.each do |key, info|
|
268
|
+
ix_offset = io.pos
|
269
|
+
io.print info[:fcc]
|
270
|
+
io.print [24 + 8 * info[:cur].size].pack('V')
|
271
|
+
io.print [2, 0, 1, info[:cur].size].pack('vccV')
|
272
|
+
io.print key
|
273
|
+
io.print [data_offset, 0].pack('qV')
|
274
|
+
info[:cur].each.with_index do |cur, i|
|
275
|
+
io.print [cur[:pos] - data_offset + 8].pack('V') # 8 for LIST####
|
276
|
+
sz = cur[:size]
|
277
|
+
if cur[:flag] & Frame::AVIIF_KEYFRAME == 0 # is not keyframe
|
278
|
+
sz = sz | 0b1000_0000_0000_0000_0000_0000_0000_0000
|
279
|
+
end
|
280
|
+
io.print [sz].pack('V')
|
281
|
+
end
|
282
|
+
# rewrite indx
|
283
|
+
indx = info[:indx]
|
284
|
+
nent = indx.value[4, 4].unpack('V').first + 1
|
285
|
+
indx.value[4, 4] = [nent].pack('V')
|
286
|
+
indx.value[24 + 16 * (nent - 1), 16] = [
|
287
|
+
ix_offset, io.pos - ix_offset, info[:cur].size
|
288
|
+
].pack('qVV')
|
289
|
+
io.pos = expected_position_of(indx) + 8
|
290
|
+
io.print indx.value
|
291
|
+
# clean up
|
292
|
+
info[:cur] = []
|
293
|
+
io.seek 0, IO::SEEK_END
|
294
|
+
end
|
295
|
+
end
|
296
|
+
# size of movi
|
297
|
+
size = io.pos - data_offset
|
298
|
+
io.pos = data_offset - 4
|
299
|
+
io.print [size].pack('V')
|
300
|
+
io.seek 0, IO::SEEK_END
|
301
|
+
io.print "\0" if size % 2 == 1
|
302
|
+
vid_frames_size
|
303
|
+
end
|
304
|
+
|
305
|
+
File.open(path, 'w+') do |io|
|
306
|
+
io.binmode
|
307
|
+
@movi.rewind
|
308
|
+
# normal AVI
|
309
|
+
# header
|
310
|
+
io.print 'RIFF'
|
311
|
+
io.print "\0\0\0\0"
|
312
|
+
io.print 'AVI '
|
313
|
+
@riff.first.value.each do |chunk|
|
314
|
+
break if chunk.id == 'movi'
|
315
|
+
print_chunk io, chunk
|
316
|
+
end
|
317
|
+
# movi
|
318
|
+
vid_size = write_movi.call io
|
319
|
+
# rewrite frame count in avih header
|
320
|
+
io.pos = 48
|
321
|
+
io.print [vid_size].pack('V')
|
322
|
+
io.seek 0, IO::SEEK_END
|
323
|
+
# idx1
|
324
|
+
io.print 'idx1'
|
325
|
+
io.print [@index_pos * 16].pack('V')
|
326
|
+
@indices[0..(@index_pos - 1)].each do |ix|
|
327
|
+
io.print ix[:id] + [ix[:flag], ix[:offset] + 4, ix[:size]].pack('V3')
|
328
|
+
end
|
329
|
+
# rewrite riff chunk size
|
330
|
+
avisize = io.pos - 8
|
331
|
+
io.pos = 4
|
332
|
+
io.print [avisize].pack('V')
|
333
|
+
io.seek 0, IO::SEEK_END
|
334
|
+
|
335
|
+
# AVI2.0
|
336
|
+
while @index_pos < @indices.size
|
337
|
+
io.print 'RIFF'
|
338
|
+
io.print "\0\0\0\0"
|
339
|
+
riff_offset = io.pos
|
340
|
+
io.print 'AVIX'
|
341
|
+
# movi
|
342
|
+
write_movi.call io
|
343
|
+
# rewrite total chunk size
|
344
|
+
avisize = io.pos - riff_offset
|
345
|
+
io.pos = riff_offset - 4
|
346
|
+
io.print [avisize].pack('V')
|
347
|
+
io.seek 0, IO::SEEK_END
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
##
|
353
|
+
# Provides internal accesses to movi binary data.
|
354
|
+
# It requires the yield block to return an array of pair values
|
355
|
+
# which consists of new indices array and new movi binary data.
|
356
|
+
def process_movi &block
|
357
|
+
@movi.rewind
|
358
|
+
newindices, newmovi = block.call @indices, @movi
|
359
|
+
unless @indices == newindices
|
360
|
+
@indices.replace newindices
|
361
|
+
end
|
362
|
+
unless @movi == newmovi
|
363
|
+
@movi.rewind
|
364
|
+
newmovi.rewind
|
365
|
+
while d = newmovi.read(BUFFER_SIZE) do
|
366
|
+
@movi.print d
|
367
|
+
end
|
368
|
+
eof = @movi.pos
|
369
|
+
@movi.truncate eof
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
##
|
374
|
+
# Searches and returns RIFF values with the passed search +args+.
|
375
|
+
# +args+ should point the ids of the tree structured RIFF data
|
376
|
+
# under the 'AVI ' chunk without omission, like:
|
377
|
+
#
|
378
|
+
# avi.search 'hdrl', 'strl', 'indx'
|
379
|
+
#
|
380
|
+
# It returns a list of RiffChunk object which can be modified directly.
|
381
|
+
# (RiffChunk class which is returned through this method also has a #search
|
382
|
+
# method with the same interface as this class.)
|
383
|
+
# This method only seeks in the first RIFF 'AVI ' tree.
|
384
|
+
def search *args
|
385
|
+
@riff.first.search *args
|
386
|
+
end
|
387
|
+
|
388
|
+
##
|
389
|
+
# Returns true if +other+'s indices are same as self's indices.
|
390
|
+
def == other
|
391
|
+
self.indices == other.indices
|
392
|
+
end
|
393
|
+
|
394
|
+
def inspect #:nodoc:
|
395
|
+
"#<#{self.class.name}:#{sprintf("0x%x", object_id)} @movi=#{@movi.inspect}>"
|
396
|
+
end
|
397
|
+
|
398
|
+
def initialize_copy avi #:nodoc:
|
399
|
+
md = Marshal.dump avi.indices
|
400
|
+
@indices = Marshal.load md
|
401
|
+
md = Marshal.dump avi.riff
|
402
|
+
@riff = Marshal.load md
|
403
|
+
newmovi = Tempfile.new 'aviglitch-clone', @tmpdir, binmode: true
|
404
|
+
movipos = avi.movi.pos
|
405
|
+
avi.movi.rewind
|
406
|
+
while d = avi.movi.read(BUFFER_SIZE) do
|
407
|
+
newmovi.print d
|
408
|
+
end
|
409
|
+
avi.movi.pos = movipos
|
410
|
+
newmovi.rewind
|
411
|
+
@movi = newmovi
|
412
|
+
end
|
413
|
+
|
414
|
+
def print_chunk io, chunk #:nodoc:
|
415
|
+
offset = io.pos
|
416
|
+
if chunk.is_list?
|
417
|
+
io.print chunk.list
|
418
|
+
io.print "\0\0\0\0"
|
419
|
+
io.print chunk.id
|
420
|
+
chunk.value.each do |c|
|
421
|
+
print_chunk io, c
|
422
|
+
end
|
423
|
+
else
|
424
|
+
io.print chunk.id
|
425
|
+
io.print "\0\0\0\0"
|
426
|
+
io.print chunk.value
|
427
|
+
end
|
428
|
+
# rewrite size
|
429
|
+
size = io.pos - offset - 8
|
430
|
+
io.pos = offset + 4
|
431
|
+
io.print [size].pack('V')
|
432
|
+
io.seek 0, IO::SEEK_END
|
433
|
+
io.print "\0" if size % 2 == 1
|
434
|
+
end
|
435
|
+
|
436
|
+
def expected_position_of chunk #:nodoc:
|
437
|
+
pos = -1
|
438
|
+
cur = 12
|
439
|
+
seek = -> (chk) do
|
440
|
+
if chk === chunk
|
441
|
+
pos = cur
|
442
|
+
return
|
443
|
+
end
|
444
|
+
if chk.is_list?
|
445
|
+
cur += 12
|
446
|
+
chk.value.each do |c|
|
447
|
+
seek.call c
|
448
|
+
end
|
449
|
+
else
|
450
|
+
cur += 8
|
451
|
+
cur += chk.value.nil? ? chk.binsize : chk.value.size
|
452
|
+
end
|
453
|
+
end
|
454
|
+
headers = @riff.first.value
|
455
|
+
headers.each do |c|
|
456
|
+
seek.call c
|
457
|
+
end
|
458
|
+
pos
|
459
|
+
end
|
460
|
+
|
461
|
+
def parse_avi1_indices data #:nodoc:
|
462
|
+
# The function Frames#fix_offsets_if_needed in prev versions was now removed.
|
463
|
+
i = 0
|
464
|
+
while i * 16 < data.size do
|
465
|
+
@indices << {
|
466
|
+
:id => data[i * 16, 4],
|
467
|
+
:flag => data[i * 16 + 4, 4].unpack('V').first,
|
468
|
+
:offset => data[i * 16 + 8, 4].unpack('V').first - 4,
|
469
|
+
:size => data[i * 16 + 12, 4].unpack('V').first,
|
470
|
+
}
|
471
|
+
i += 1
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
def parse_avi2_indices data, offset #:nodoc:
|
476
|
+
id = data[8, 4]
|
477
|
+
nent = data[4, 4].unpack('V').first
|
478
|
+
h = 24
|
479
|
+
i = 0
|
480
|
+
while h + i * 8 < data.size
|
481
|
+
moffset = data[h + i * 8, 4].unpack('V').first
|
482
|
+
msize = data[h + i * 8 + 4, 4].unpack('V').first
|
483
|
+
of = offset + moffset - 12 # 12 for movi + 00dc####
|
484
|
+
# bit 31 is set if this is NOT a keyframe
|
485
|
+
fl = (msize >> 31 == 1) ? 0 : Frame::AVIIF_KEYFRAME
|
486
|
+
sz = msize & 0b0111_1111_1111_1111_1111_1111_1111_1111
|
487
|
+
@indices << {
|
488
|
+
:id => id,
|
489
|
+
:flag => fl,
|
490
|
+
:offset => of,
|
491
|
+
:size => sz,
|
492
|
+
}
|
493
|
+
i += 1
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
private :print_chunk, :expected_position_of,
|
498
|
+
:parse_avi1_indices, :parse_avi2_indices
|
499
|
+
|
500
|
+
class << self
|
501
|
+
|
502
|
+
##
|
503
|
+
# Parses the +file+ and returns the RIFF structure.
|
504
|
+
def rifftree file, out = nil
|
505
|
+
returnable = out.nil?
|
506
|
+
out = StringIO.new if returnable
|
507
|
+
|
508
|
+
parse = ->(io, depth = 0, len = 0) do
|
509
|
+
offset = io.pos
|
510
|
+
while id = io.read(4) do
|
511
|
+
if len > 0 && io.pos >= offset + len
|
512
|
+
io.pos -= 4
|
513
|
+
break
|
514
|
+
end
|
515
|
+
size = io.read(4).unpack('V').first
|
516
|
+
str = depth > 0 ? ' ' * depth + id : id
|
517
|
+
if id =~ /^(?:RIFF|LIST)$/
|
518
|
+
lid = io.read(4)
|
519
|
+
str << (' (%d)' % size) + " ’#{lid}’\n"
|
520
|
+
out.print str
|
521
|
+
parse.call io, depth + 1, size
|
522
|
+
else
|
523
|
+
str << (' (%d)' % size ) + "\n"
|
524
|
+
out.print str
|
525
|
+
io.pos += size
|
526
|
+
io.pos += 1 if size % 2 == 1
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
io = file
|
532
|
+
is_io = file.respond_to?(:seek) # Probably IO.
|
533
|
+
io = File.open(file, 'rb') unless is_io
|
534
|
+
begin
|
535
|
+
io.rewind
|
536
|
+
parse.call io
|
537
|
+
io.rewind
|
538
|
+
ensure
|
539
|
+
io.close unless is_io
|
540
|
+
end
|
541
|
+
|
542
|
+
if returnable
|
543
|
+
out.rewind
|
544
|
+
out.read
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
##
|
549
|
+
# Parses the +file+ and prints the RIFF structure to stdout.
|
550
|
+
def print_rifftree file
|
551
|
+
Avi.rifftree file, $stdout
|
552
|
+
end
|
553
|
+
|
554
|
+
end
|
555
|
+
|
556
|
+
end
|
557
|
+
end
|
data/lib/aviglitch/base.rb
CHANGED
@@ -7,33 +7,31 @@ module AviGlitch
|
|
7
7
|
|
8
8
|
# AviGlitch::Frames object generated from the +file+.
|
9
9
|
attr_reader :frames
|
10
|
-
# The input file
|
11
|
-
attr_reader :
|
10
|
+
# The input file
|
11
|
+
attr_reader :avi
|
12
12
|
|
13
13
|
##
|
14
14
|
# Creates a new instance of AviGlitch::Base, open the file and
|
15
15
|
# make it ready to manipulate.
|
16
|
-
# It requires +path+ as Pathname.
|
17
|
-
def initialize
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@file.print d
|
16
|
+
# It requires +path+ as Pathname or an instance of AviGlirtch::Avi.
|
17
|
+
def initialize path_or_object, tmpdir: nil
|
18
|
+
if path_or_object.kind_of?(Avi)
|
19
|
+
@avi = path_or_object
|
20
|
+
else
|
21
|
+
unless AviGlitch::Base.surely_formatted? path_or_object
|
22
|
+
raise 'Unsupported file passed.'
|
24
23
|
end
|
24
|
+
@avi = Avi.new
|
25
|
+
@avi.tmpdir = tmpdir
|
26
|
+
@avi.path = path_or_object
|
25
27
|
end
|
26
|
-
|
27
|
-
unless AviGlitch::Base.surely_formatted? @file
|
28
|
-
raise 'Unsupported file passed.'
|
29
|
-
end
|
30
|
-
@frames = Frames.new @file
|
28
|
+
@frames = Frames.new @avi
|
31
29
|
end
|
32
30
|
|
33
31
|
##
|
34
32
|
# Outputs the glitched file to +path+, and close the file.
|
35
33
|
def output path, do_file_close = true
|
36
|
-
|
34
|
+
@avi.output path
|
37
35
|
close if do_file_close
|
38
36
|
self
|
39
37
|
end
|
@@ -41,7 +39,7 @@ module AviGlitch
|
|
41
39
|
##
|
42
40
|
# An explicit file close.
|
43
41
|
def close
|
44
|
-
@
|
42
|
+
@avi.close
|
45
43
|
end
|
46
44
|
|
47
45
|
##
|
@@ -62,7 +60,7 @@ module AviGlitch
|
|
62
60
|
def glitch target = :all, &block # :yield: data
|
63
61
|
if block_given?
|
64
62
|
@frames.each do |frame|
|
65
|
-
if
|
63
|
+
if frame.is? target
|
66
64
|
frame.data = yield frame.data
|
67
65
|
end
|
68
66
|
end
|
@@ -127,55 +125,29 @@ module AviGlitch
|
|
127
125
|
alias_method :write, :output
|
128
126
|
alias_method :has_keyframes?, :has_keyframe?
|
129
127
|
|
130
|
-
def valid_target? target, frame #:nodoc:
|
131
|
-
return true if target == :all
|
132
|
-
begin
|
133
|
-
frame.send "is_#{target.to_s.sub(/frames$/, 'frame')}?"
|
134
|
-
rescue
|
135
|
-
false
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
private :valid_target?
|
140
|
-
|
141
128
|
class << self
|
142
129
|
##
|
143
130
|
# Checks if the +file+ is a correctly formetted AVI file.
|
144
131
|
# +file+ can be String or Pathname or IO.
|
145
132
|
def surely_formatted? file, debug = false
|
146
|
-
|
147
|
-
is_io = file.respond_to?(:seek) # Probably IO.
|
148
|
-
file = File.open(file, 'rb') unless is_io
|
133
|
+
passed = true
|
149
134
|
begin
|
150
|
-
file
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
while file.read(4) =~ /^(?:LIST|JUNK)$/ do
|
161
|
-
s = file.read(4).unpack('V').first
|
162
|
-
file.pos += s
|
163
|
-
end
|
164
|
-
file.pos -= 4
|
165
|
-
# we require idx1
|
166
|
-
unless file.read(4) == 'idx1'
|
167
|
-
answer = false
|
168
|
-
warn 'idx1 is not found' if debug
|
135
|
+
riff = Avi.rifftree file
|
136
|
+
{
|
137
|
+
'RIFF-AVI sign': /^RIFF \(\d+\) ’AVI ’$/,
|
138
|
+
'movi': /^\s+LIST \(\d+\) ’movi’$/,
|
139
|
+
'idx1': /^\s+idx1 \(\d+\)$/
|
140
|
+
}.each do |m, r|
|
141
|
+
unless riff =~ r
|
142
|
+
warn "#{m} is not found." if debug
|
143
|
+
passed = false
|
144
|
+
end
|
169
145
|
end
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
warn err.message if debug
|
174
|
-
answer = false
|
175
|
-
ensure
|
176
|
-
file.close unless is_io
|
146
|
+
rescue => e
|
147
|
+
warn e.message if debug
|
148
|
+
passed = false
|
177
149
|
end
|
178
|
-
|
150
|
+
passed
|
179
151
|
end
|
180
152
|
end
|
181
153
|
end
|