aviglitch 0.1.6 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -58,6 +58,26 @@ module AviGlitch
58
58
  @id[2, 2] == 'wb'
59
59
  end
60
60
 
61
+ ##
62
+ # Returns if it is a frame in +frame_type+.
63
+ def is? frame_type
64
+ return true if frame_type == :all
65
+ detection = "is_#{frame_type.to_s.sub(/frames$/, 'frame')}?"
66
+ begin
67
+ self.send detection
68
+ rescue NoMethodError => e
69
+ false
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Compares its content.
75
+ def == other
76
+ self.id == other.id &&
77
+ self.flag == other.flag &&
78
+ self.data == other.data
79
+ end
80
+
61
81
  end
62
82
  end
63
83
 
@@ -18,45 +18,13 @@ module AviGlitch
18
18
  class Frames
19
19
  include Enumerable
20
20
 
21
- # :stopdoc:
22
-
23
- ##
24
- SAFE_FRAMES_COUNT = 150000
25
- @@warn_if_frames_are_too_large = true
26
-
27
- # :startdoc:
28
-
29
- attr_reader :meta
21
+ attr_reader :avi
30
22
 
31
23
  ##
32
24
  # Creates a new AviGlitch::Frames object.
33
- def initialize io
34
- io.rewind
35
- io.pos = 12 # /^RIFF[\s\S]{4}AVI $/
36
- while io.read(4) =~ /^(?:LIST|JUNK)$/ do
37
- s = io.read(4).unpack('V').first
38
- @pos_of_movi = io.pos - 4 if io.read(4) == 'movi'
39
- io.pos += s - 4
40
- end
41
- @pos_of_idx1 = io.pos - 4 # here must be idx1
42
- s = io.read(4).unpack('V').first + io.pos
43
- @meta = []
44
- while chunk_id = io.read(4) do
45
- break if io.pos >= s
46
- @meta << {
47
- :id => chunk_id,
48
- :flag => io.read(4).unpack('V').first,
49
- :offset => io.read(4).unpack('V').first,
50
- :size => io.read(4).unpack('V').first,
51
- }
52
- end
53
- fix_offsets_if_needed io
54
- unless safe_frames_count? @meta.size
55
- io.close!
56
- exit
57
- end
58
- io.rewind
59
- @io = io
25
+ # It takes AviGlitch::Avi as an argument.
26
+ def initialize avi
27
+ @avi = avi
60
28
  end
61
29
 
62
30
  ##
@@ -64,10 +32,29 @@ module AviGlitch
64
32
  # It returns Enumerator if a block is not given.
65
33
  def each &block
66
34
  if block_given?
67
- temp = Tempfile.new 'frames', binmode: true
68
- frames_data_as_io(temp, block)
69
- overwrite temp
70
- temp.close!
35
+ Tempfile.open('aviglitch-temp', @avi.tmpdir, binmode: true) do |newmovi|
36
+ @avi.process_movi do |indices, movi|
37
+ newindices = indices.select do |m|
38
+ movi.pos = m[:offset] + 8 # 8 for id and size
39
+ frame = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
40
+ block.call frame
41
+ unless frame.data.nil?
42
+ m[:offset] = newmovi.pos
43
+ m[:size] = frame.data.size
44
+ m[:flag] = frame.flag
45
+ m[:id] = frame.id
46
+ newmovi.print m[:id]
47
+ newmovi.print [frame.data.size].pack('V')
48
+ newmovi.print frame.data
49
+ newmovi.print "\0" if frame.data.size % 2 == 1
50
+ true
51
+ else
52
+ false
53
+ end
54
+ end
55
+ [newindices, newmovi]
56
+ end
57
+ end
71
58
  else
72
59
  self.enum_for :each
73
60
  end
@@ -76,84 +63,34 @@ module AviGlitch
76
63
  ##
77
64
  # Returns the number of frames.
78
65
  def size
79
- @meta.size
66
+ @avi.indices.size
80
67
  end
81
68
 
82
69
  ##
83
70
  # Returns the number of the specific +frame_type+.
84
71
  def size_of frame_type
85
- detection = "is_#{frame_type.to_s.sub(/frames$/, 'frame')}?"
86
- @meta.select { |m|
87
- Frame.new(nil, m[:id], m[:flag]).send detection
72
+ @avi.indices.select { |m|
73
+ Frame.new(nil, m[:id], m[:flag]).is? frame_type
88
74
  }.size
89
75
  end
90
76
 
91
- def frames_data_as_io io = nil, block = nil #:nodoc:
92
- io = Tempfile.new('tmep', binmode: true) if io.nil?
93
- @meta = @meta.select do |m|
94
- @io.pos = @pos_of_movi + m[:offset] + 8 # 8 for id and size
95
- frame = Frame.new(@io.read(m[:size]), m[:id], m[:flag])
96
- block.call(frame) if block # accept the variable block as Proc
97
- yield frame if block_given? # or a given block (or do nothing)
98
- unless frame.data.nil?
99
- m[:offset] = io.pos + 4 # 4 for 'movi'
100
- m[:size] = frame.data.size
101
- m[:flag] = frame.flag
102
- m[:id] = frame.id
103
- io.print m[:id]
104
- io.print [frame.data.size].pack('V')
105
- io.print frame.data
106
- io.print "\000" if frame.data.size % 2 == 1
107
- true
108
- else
109
- false
110
- end
77
+ ##
78
+ # Returns the data size of total frames.
79
+ def data_size
80
+ size = 0
81
+ @avi.process_movi do |indices, movi|
82
+ size = movi.size
83
+ [indices, movi]
111
84
  end
112
- io
113
- end
114
-
115
- def overwrite data #:nodoc:
116
- unless safe_frames_count? @meta.size
117
- @io.close!
118
- exit
119
- end
120
- # Overwrite the file
121
- @io.pos = @pos_of_movi - 4 # 4 for size
122
- @io.print [data.pos + 4].pack('V') # 4 for 'movi'
123
- @io.print 'movi'
124
- data.rewind
125
- while d = data.read(BUFFER_SIZE) do
126
- @io.print d
127
- end
128
- @io.print 'idx1'
129
- @io.print [@meta.size * 16].pack('V')
130
- idx = @meta.collect { |m|
131
- m[:id] + [m[:flag], m[:offset], m[:size]].pack('V3')
132
- }.join
133
- @io.print idx
134
- eof = @io.pos
135
- @io.truncate eof
136
-
137
- # Fix info
138
- ## file size
139
- @io.pos = 4
140
- @io.print [eof - 8].pack('V')
141
- ## frame count
142
- @io.pos = 48
143
- vid_frames = @meta.select do |m|
144
- id = m[:id]
145
- id[2, 2] == 'db' || id[2, 2] == 'dc'
146
- end
147
- @io.print [vid_frames.size].pack('V')
148
-
149
- @io.pos
85
+ size
150
86
  end
151
87
 
152
88
  ##
153
89
  # Removes all frames and returns self.
154
90
  def clear
155
- @meta = []
156
- overwrite StringIO.new
91
+ @avi.process_movi do |indices, movi|
92
+ [[], StringIO.new]
93
+ end
157
94
  self
158
95
  end
159
96
 
@@ -162,27 +99,24 @@ module AviGlitch
162
99
  # It is destructive like Array does.
163
100
  def concat other_frames
164
101
  raise TypeError unless other_frames.kind_of?(Frames)
165
- # data
166
- this_data = Tempfile.new 'this', binmode: true
167
- self.frames_data_as_io this_data
168
- other_data = Tempfile.new 'other', binmode: true
169
- other_frames.frames_data_as_io other_data
170
- this_size = this_data.size
171
- other_data.rewind
172
- while d = other_data.read(BUFFER_SIZE) do
173
- this_data.print d
174
- end
175
- other_data.close!
176
- # meta
177
- other_meta = other_frames.meta.collect do |m|
178
- x = m.dup
179
- x[:offset] += this_size
180
- x
102
+ @avi.process_movi do |this_indices, this_movi|
103
+ this_size = this_movi.size
104
+ this_movi.pos = this_size
105
+ other_frames.avi.process_movi do |other_indices, other_movi|
106
+ while d = other_movi.read(BUFFER_SIZE) do
107
+ this_movi.print d
108
+ end
109
+ other_meta = other_indices.collect do |m|
110
+ x = m.dup
111
+ x[:offset] += this_size
112
+ x
113
+ end
114
+ this_indices.concat other_meta
115
+ [other_indices, other_movi]
116
+ end
117
+ [this_indices, this_movi]
181
118
  end
182
- @meta.concat other_meta
183
- # close
184
- overwrite this_data
185
- this_data.close!
119
+
186
120
  self
187
121
  end
188
122
 
@@ -203,6 +137,7 @@ module AviGlitch
203
137
  times.times do
204
138
  result.concat frames
205
139
  end
140
+ frames.terminate
206
141
  result
207
142
  end
208
143
 
@@ -242,7 +177,12 @@ module AviGlitch
242
177
  l = 1 if l.nil?
243
178
  tail = self.slice((b + l)..-1)
244
179
  self.clear
245
- self.concat head + tail
180
+ temp = head + tail
181
+ self.concat temp
182
+ temp.terminate
183
+ head.terminate
184
+ tail.terminate
185
+
246
186
  sliced
247
187
  end
248
188
 
@@ -266,16 +206,25 @@ module AviGlitch
266
206
 
267
207
  self.clear
268
208
  self.concat new_frames
209
+
210
+ new_frames.terminate
211
+ head.terminate
212
+ rest.terminate
269
213
  end
270
214
 
271
215
  ##
272
216
  # Returns one Frame object at the given index.
273
217
  def at n
274
- m = @meta[n]
275
- return nil if m.nil?
276
- @io.pos = @pos_of_movi + m[:offset] + 8
277
- frame = Frame.new(@io.read(m[:size]), m[:id], m[:flag])
278
- @io.rewind
218
+ frame = nil
219
+ @avi.process_movi do |indices, movi|
220
+ m = indices[n]
221
+ unless m.nil?
222
+ movi.pos = m[:offset] + 8
223
+ frame = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
224
+ movi.rewind
225
+ end
226
+ [indices, movi]
227
+ end
279
228
  frame
280
229
  end
281
230
 
@@ -291,28 +240,101 @@ module AviGlitch
291
240
  self.slice(self.size - 1)
292
241
  end
293
242
 
243
+ ##
244
+ # Returns the first Frame object in +frame_type+.
245
+ def first_of frame_type
246
+ frame = nil
247
+ @avi.process_movi do |indices, movi|
248
+ indices.each do |m|
249
+ movi.pos = m[:offset] + 8
250
+ f = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
251
+ if f.is?(frame_type)
252
+ frame = f
253
+ break
254
+ end
255
+ end
256
+ [indices, movi]
257
+ end
258
+ frame
259
+ end
260
+
261
+ ##
262
+ # Returns the last Frame object in +frame_type+.
263
+ def last_of frame_type
264
+ frame = nil
265
+ @avi.process_movi do |indices, movi|
266
+ indices.reverse.each do |m|
267
+ movi.pos = m[:offset] + 8
268
+ f = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
269
+ if f.is?(frame_type)
270
+ frame = f
271
+ break
272
+ end
273
+ end
274
+ [indices, movi]
275
+ end
276
+ frame
277
+ end
278
+
279
+ ##
280
+ # Returns an index of the first found +frame+.
281
+ def index frame
282
+ n = -1
283
+ @avi.process_movi do |indices, movi|
284
+ indices.each_with_index do |m, i|
285
+ movi.pos = m[:offset] + 8
286
+ f = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
287
+ if f == frame
288
+ n = i
289
+ break
290
+ end
291
+ end
292
+ [indices, movi]
293
+ end
294
+ n
295
+ end
296
+
297
+ ##
298
+ # Alias for index
299
+ alias_method :find_index, :index
300
+
301
+ ##
302
+ # Returns an index of the first found +frame+, starting from the last.
303
+ def rindex frame
304
+ n = -1
305
+ @avi.process_movi do |indices, movi|
306
+ indices.reverse.each_with_index do |m, i|
307
+ movi.pos = m[:offset] + 8
308
+ f = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
309
+ if f == frame
310
+ n = indices.size - 1 - i
311
+ break
312
+ end
313
+ end
314
+ [indices, movi]
315
+ end
316
+ n
317
+ end
318
+
294
319
  ##
295
320
  # Appends the given Frame into the tail of self.
296
321
  def push frame
297
322
  raise TypeError unless frame.kind_of? Frame
298
- # data
299
- this_data = Tempfile.new 'this', binmode: true
300
- self.frames_data_as_io this_data
301
- this_size = this_data.size
302
- this_data.print frame.id
303
- this_data.print [frame.data.size].pack('V')
304
- this_data.print frame.data
305
- this_data.print "\000" if frame.data.size % 2 == 1
306
- # meta
307
- @meta << {
308
- :id => frame.id,
309
- :flag => frame.flag,
310
- :offset => this_size + 4, # 4 for 'movi'
311
- :size => frame.data.size,
312
- }
313
- # close
314
- overwrite this_data
315
- this_data.close!
323
+ @avi.process_movi do |indices, movi|
324
+ this_size = movi.size
325
+ movi.pos = this_size
326
+ movi.print frame.id
327
+ movi.print [frame.data.size].pack('V')
328
+ movi.print frame.data
329
+ movi.print "\0" if frame.data.size % 2 == 1
330
+ indices << {
331
+ :id => frame.id,
332
+ :flag => frame.flag,
333
+ :offset => this_size,
334
+ :size => frame.data.size,
335
+ }
336
+ [indices, movi]
337
+ end
316
338
  self
317
339
  end
318
340
 
@@ -331,6 +353,7 @@ module AviGlitch
331
353
 
332
354
  self.clear
333
355
  self.concat new_frames
356
+ new_frames.terminate
334
357
  self
335
358
  end
336
359
 
@@ -355,17 +378,24 @@ module AviGlitch
355
378
  ##
356
379
  # Returns true if +other+'s frames are same as self's frames.
357
380
  def == other
358
- @meta == other.meta
381
+ @avi == other.avi
382
+ end
383
+
384
+ ##
385
+ # Closes the internal temp file explicitly. This instance becomes unusable.
386
+ def terminate
387
+ @avi.close
388
+ @avi = nil
359
389
  end
360
390
 
361
391
  ##
362
392
  # Generates new AviGlitch::Base instance using self.
363
393
  def to_avi
364
- AviGlitch.open @io.path
394
+ AviGlitch::Base.new @avi.clone
365
395
  end
366
396
 
367
- def inspect # :nodec:
368
- "#<#{self.class.name}:#{sprintf("0x%x", object_id)} @io=#{@io.inspect} size=#{self.size}>"
397
+ def inspect #:nodoc:
398
+ "#<#{self.class.name}:#{sprintf("0x%x", object_id)} size=#{self.size}>"
369
399
  end
370
400
 
371
401
  def get_beginning_and_length *args #:nodoc:
@@ -373,46 +403,18 @@ module AviGlitch
373
403
  if args.first.kind_of? Range
374
404
  r = args.first
375
405
  b = r.begin
376
- e = r.end >= 0 ? r.end : @meta.size + r.end
406
+ e = r.end >= 0 ? r.end : self.size + r.end
377
407
  l = e - b + 1
378
408
  end
379
- b = b >= 0 ? b : @meta.size + b
409
+ b = b >= 0 ? b : self.size + b
380
410
  [b, l]
381
411
  end
382
412
 
383
413
  def safe_frames_count? count #:nodoc:
384
- r = true
385
- if @@warn_if_frames_are_too_large && count >= SAFE_FRAMES_COUNT
386
- trap(:INT) do
387
- @io.close!
388
- exit
389
- end
390
- m = ["WARNING: The avi data has too many frames (#{count}).\n",
391
- "It may use a large memory to process. ",
392
- "We recommend to chop the movie to smaller chunks before you glitch.\n",
393
- "Do you want to continue anyway? [yN] "].join('')
394
- a = Readline.readline m
395
- r = a == 'y'
396
- @@warn_if_frames_are_too_large = !r
397
- end
398
- r
399
- end
400
-
401
- def fix_offsets_if_needed io #:nodoc:
402
- # rarely data offsets begin from 0 of the file
403
- return if @meta.empty?
404
- pos = io.pos
405
- m = @meta.first
406
- io.pos = @pos_of_movi + m[:offset]
407
- unless io.read(4) == m[:id]
408
- @meta.each do |x|
409
- x[:offset] -= @pos_of_movi
410
- end
411
- end
412
- io.pos = pos
414
+ warn "[DEPRECATION] `safe_frames_count?` is deprecated."
415
+ true
413
416
  end
414
417
 
415
- protected :frames_data_as_io, :meta
416
- private :overwrite, :get_beginning_and_length, :fix_offsets_if_needed
418
+ private :get_beginning_and_length
417
419
  end
418
420
  end
data/lib/aviglitch.rb CHANGED
@@ -1,8 +1,7 @@
1
1
  require 'tempfile'
2
- require 'fileutils'
3
- require 'readline'
4
2
  require 'pathname'
5
3
  require 'stringio'
4
+ require 'aviglitch/avi'
6
5
  require 'aviglitch/base'
7
6
  require 'aviglitch/frame'
8
7
  require 'aviglitch/frames'
@@ -29,9 +28,16 @@ require 'aviglitch/frames'
29
28
  # end
30
29
  # avi.output '/path/to/broken.avi'
31
30
  #
31
+ # Since v0.2.2, it allows to specify the temporary directory. This library
32
+ # duplicates and processes a input file in the temporary directory, which
33
+ # by default is +Dir.tmpdir+. To specify the custom temporary directory, use
34
+ # +tmpdir:+ option, like:
35
+ #
36
+ # avi = AviGlitch.open '/path/to/your.avi', tmpdir: '/path/to/tmpdir'
37
+ #
32
38
  module AviGlitch
33
39
 
34
- VERSION = '0.1.6'
40
+ VERSION = '0.2.2'
35
41
 
36
42
  BUFFER_SIZE = 2 ** 24
37
43
 
@@ -39,11 +45,12 @@ module AviGlitch
39
45
  ##
40
46
  # Returns AviGlitch::Base instance.
41
47
  # It requires +path_or_frames+ as String or Pathname, or Frames instance.
42
- def open path_or_frames
48
+ # Additionally, it allows +tmpdir:+ as the internal temporary directory.
49
+ def open path_or_frames, tmpdir: nil
43
50
  if path_or_frames.kind_of?(Frames)
44
51
  path_or_frames.to_avi
45
52
  else
46
- AviGlitch::Base.new(Pathname(path_or_frames))
53
+ AviGlitch::Base.new(Pathname(path_or_frames), tmpdir: tmpdir)
47
54
  end
48
55
  end
49
56
  end
data/spec/avi2_spec.rb ADDED
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe AviGlitch, 'AVI2.0' do
4
+
5
+ it 'should save same file when nothing has changed' do
6
+ avi = AviGlitch.open @in2
7
+ avi.glitch do |d|
8
+ d
9
+ end
10
+ avi.output @out
11
+ FileUtils.cmp(@in2, @out).should be true
12
+ end
13
+
14
+ it 'should be AVI1.0 when its size has reduced less than 1GB' do
15
+ a = AviGlitch.open @in2
16
+ size = 0
17
+ a.glitch do |d|
18
+ size += d.size
19
+ size < 1024 ** 3 ? d : nil
20
+ end
21
+ a.output @out
22
+ b = AviGlitch.open @out
23
+ b.avi.was_avi2?.should be false
24
+ b.close
25
+ end
26
+
27
+ it 'should be AVI2.0 when its size has increased over 1GB' do
28
+ a = AviGlitch.open @in
29
+ n = Math.log(1024.0 ** 3 / a.frames.data_size.to_f, 2).ceil
30
+ f = a.frames[0..-1]
31
+ n.times do
32
+ fx = f[0..-1]
33
+ f.concat fx
34
+ fx.terminate
35
+ end
36
+ f.to_avi.output @out
37
+ b = AviGlitch.open @out
38
+ b.avi.was_avi2?.should be true
39
+ b.close
40
+ end
41
+ end
@@ -2,24 +2,10 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe AviGlitch do
4
4
 
5
- before :all do
6
- FileUtils.mkdir OUTPUT_DIR unless File.exist? OUTPUT_DIR
7
- @in = FILES_DIR + 'sample.avi'
8
- @out = OUTPUT_DIR + 'out.avi'
9
- end
10
-
11
- after :each do
12
- FileUtils.rm Dir.glob((OUTPUT_DIR + '*').to_s)
13
- end
14
-
15
- after :all do
16
- FileUtils.rmdir OUTPUT_DIR
17
- end
18
-
19
5
  it 'should raise an error against unsupported files' do
20
6
  lambda {
21
7
  avi = AviGlitch.open __FILE__
22
- }.should raise_error
8
+ }.should raise_error(RuntimeError)
23
9
  end
24
10
 
25
11
  it 'should return AviGlitch::Base object through the method #open' do
@@ -106,7 +92,7 @@ describe AviGlitch do
106
92
  }.should raise_error(IOError)
107
93
  end
108
94
 
109
- it 'can explicit close file' do
95
+ it 'can close the file explicitly' do
110
96
  avi = AviGlitch.open @in
111
97
  avi.close
112
98
  lambda {
@@ -174,7 +160,7 @@ describe AviGlitch do
174
160
  end
175
161
  end
176
162
 
177
- it 'should check if keyframes exist.' do
163
+ it 'should check if keyframes exist' do
178
164
  a = AviGlitch.open @in
179
165
  a.has_keyframe?.should be true
180
166
  a.glitch :keyframe do |f|
@@ -203,9 +189,25 @@ describe AviGlitch do
203
189
  end
204
190
 
205
191
  expect(dc1).to eq(dc2)
192
+ end
206
193
 
194
+ it 'can be set custom temp dir' do
195
+ custom_tmpdir = Pathname.new(OUTPUT_DIR) + 'custom_tmpdir'
196
+ Dir.mkdir custom_tmpdir unless File.exist? custom_tmpdir
207
197
 
208
-
198
+ c = Dir.glob((custom_tmpdir + '*').to_s).size
199
+ b = AviGlitch.open @in, tmpdir: custom_tmpdir
200
+ b2 = b.frames[0, 100].to_avi
201
+ b2.frames.each do |f|
202
+ Dir.glob((custom_tmpdir + '*').to_s).size.should >= c
203
+ end
209
204
  end
210
205
 
206
+ it 'shoud raise an error when specified temp dir is not writable' do
207
+ custom_tmpdir = Pathname.new(OUTPUT_DIR) + 'not_dir'
208
+ lambda {
209
+ AviGlitch.open @in, tmpdir: custom_tmpdir
210
+ }.should raise_error(SystemCallError)
211
+ end
212
+
211
213
  end