aviglitch 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,33 +7,29 @@ module AviGlitch
7
7
 
8
8
  # AviGlitch::Frames object generated from the +file+.
9
9
  attr_reader :frames
10
- # The input file (copied tempfile).
11
- attr_reader :file
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 path
18
- File.open(path, 'rb') do |f|
19
- # copy as tempfile
20
- @file = Tempfile.new 'aviglitch', binmode: true
21
- f.rewind
22
- while d = f.read(BUFFER_SIZE) do
23
- @file.print d
16
+ # It requires +path+ as Pathname or an instance of AviGlirtch::Avi.
17
+ def initialize path_or_object
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 path_or_object
25
25
  end
26
-
27
- unless AviGlitch::Base.surely_formatted? @file
28
- raise 'Unsupported file passed.'
29
- end
30
- @frames = Frames.new @file
26
+ @frames = Frames.new @avi
31
27
  end
32
28
 
33
29
  ##
34
30
  # Outputs the glitched file to +path+, and close the file.
35
31
  def output path, do_file_close = true
36
- FileUtils.cp @file.path, path
32
+ @avi.output path
37
33
  close if do_file_close
38
34
  self
39
35
  end
@@ -41,7 +37,7 @@ module AviGlitch
41
37
  ##
42
38
  # An explicit file close.
43
39
  def close
44
- @file.close!
40
+ @avi.close
45
41
  end
46
42
 
47
43
  ##
@@ -62,7 +58,7 @@ module AviGlitch
62
58
  def glitch target = :all, &block # :yield: data
63
59
  if block_given?
64
60
  @frames.each do |frame|
65
- if valid_target? target, frame
61
+ if frame.is? target
66
62
  frame.data = yield frame.data
67
63
  end
68
64
  end
@@ -127,55 +123,29 @@ module AviGlitch
127
123
  alias_method :write, :output
128
124
  alias_method :has_keyframes?, :has_keyframe?
129
125
 
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
126
  class << self
142
127
  ##
143
128
  # Checks if the +file+ is a correctly formetted AVI file.
144
129
  # +file+ can be String or Pathname or IO.
145
130
  def surely_formatted? file, debug = false
146
- answer = true
147
- is_io = file.respond_to?(:seek) # Probably IO.
148
- file = File.open(file, 'rb') unless is_io
131
+ passed = true
149
132
  begin
150
- file.rewind
151
- unless file.read(4) == 'RIFF'
152
- answer = false
153
- warn 'RIFF sign is not found' if debug
154
- end
155
- len = file.read(4).unpack('V').first
156
- unless file.read(4) == 'AVI '
157
- answer = false
158
- warn 'AVI sign is not found' if debug
159
- end
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
133
+ riff = Avi.rifftree file
134
+ {
135
+ 'RIFF-AVI sign': /^RIFF \(\d+\) ’AVI ’$/,
136
+ 'movi': /^\s+LIST \(\d+\) ’movi’$/,
137
+ 'idx1': /^\s+idx1 \(\d+\)$/
138
+ }.each do |m, r|
139
+ unless riff =~ r
140
+ warn "#{m} is not found." if debug
141
+ passed = false
142
+ end
169
143
  end
170
- s = file.read(4).unpack('V').first
171
- file.pos += s
172
- rescue => err
173
- warn err.message if debug
174
- answer = false
175
- ensure
176
- file.close unless is_io
144
+ rescue => e
145
+ warn e.message if debug
146
+ passed = false
177
147
  end
178
- answer
148
+ passed
179
149
  end
180
150
  end
181
151
  end
@@ -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,12 @@ 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
+ def initialize avi
26
+ @avi = avi
60
27
  end
61
28
 
62
29
  ##
@@ -64,10 +31,29 @@ module AviGlitch
64
31
  # It returns Enumerator if a block is not given.
65
32
  def each &block
66
33
  if block_given?
67
- temp = Tempfile.new 'frames', binmode: true
68
- frames_data_as_io(temp, block)
69
- overwrite temp
70
- temp.close!
34
+ Tempfile.open('temp', binmode: true) do |newmovi|
35
+ @avi.process_movi do |indices, movi|
36
+ newindices = indices.select do |m|
37
+ movi.pos = m[:offset] + 8 # 8 for id and size
38
+ frame = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
39
+ block.call frame
40
+ unless frame.data.nil?
41
+ m[:offset] = newmovi.pos
42
+ m[:size] = frame.data.size
43
+ m[:flag] = frame.flag
44
+ m[:id] = frame.id
45
+ newmovi.print m[:id]
46
+ newmovi.print [frame.data.size].pack('V')
47
+ newmovi.print frame.data
48
+ newmovi.print "\0" if frame.data.size % 2 == 1
49
+ true
50
+ else
51
+ false
52
+ end
53
+ end
54
+ [newindices, newmovi]
55
+ end
56
+ end
71
57
  else
72
58
  self.enum_for :each
73
59
  end
@@ -76,84 +62,34 @@ module AviGlitch
76
62
  ##
77
63
  # Returns the number of frames.
78
64
  def size
79
- @meta.size
65
+ @avi.indices.size
80
66
  end
81
67
 
82
68
  ##
83
69
  # Returns the number of the specific +frame_type+.
84
70
  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
71
+ @avi.indices.select { |m|
72
+ Frame.new(nil, m[:id], m[:flag]).is? frame_type
88
73
  }.size
89
74
  end
90
75
 
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
111
- 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'
76
+ ##
77
+ # Returns the data size of total frames.
78
+ def data_size
79
+ size = 0
80
+ @avi.process_movi do |indices, movi|
81
+ size = movi.size
82
+ [indices, movi]
146
83
  end
147
- @io.print [vid_frames.size].pack('V')
148
-
149
- @io.pos
84
+ size
150
85
  end
151
86
 
152
87
  ##
153
88
  # Removes all frames and returns self.
154
89
  def clear
155
- @meta = []
156
- overwrite StringIO.new
90
+ @avi.process_movi do |indices, movi|
91
+ [[], StringIO.new]
92
+ end
157
93
  self
158
94
  end
159
95
 
@@ -162,27 +98,24 @@ module AviGlitch
162
98
  # It is destructive like Array does.
163
99
  def concat other_frames
164
100
  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
101
+ @avi.process_movi do |this_indices, this_movi|
102
+ this_size = this_movi.size
103
+ this_movi.pos = this_size
104
+ other_frames.avi.process_movi do |other_indices, other_movi|
105
+ while d = other_movi.read(BUFFER_SIZE) do
106
+ this_movi.print d
107
+ end
108
+ other_meta = other_indices.collect do |m|
109
+ x = m.dup
110
+ x[:offset] += this_size
111
+ x
112
+ end
113
+ this_indices.concat other_meta
114
+ [other_indices, other_movi]
115
+ end
116
+ [this_indices, this_movi]
181
117
  end
182
- @meta.concat other_meta
183
- # close
184
- overwrite this_data
185
- this_data.close!
118
+
186
119
  self
187
120
  end
188
121
 
@@ -271,11 +204,16 @@ module AviGlitch
271
204
  ##
272
205
  # Returns one Frame object at the given index.
273
206
  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
207
+ frame = nil
208
+ @avi.process_movi do |indices, movi|
209
+ m = indices[n]
210
+ unless m.nil?
211
+ movi.pos = m[:offset] + 8
212
+ frame = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
213
+ movi.rewind
214
+ end
215
+ [indices, movi]
216
+ end
279
217
  frame
280
218
  end
281
219
 
@@ -291,28 +229,101 @@ module AviGlitch
291
229
  self.slice(self.size - 1)
292
230
  end
293
231
 
232
+ ##
233
+ # Returns the first Frame object in +frame_type+.
234
+ def first_of frame_type
235
+ frame = nil
236
+ @avi.process_movi do |indices, movi|
237
+ indices.each do |m|
238
+ movi.pos = m[:offset] + 8
239
+ f = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
240
+ if f.is?(frame_type)
241
+ frame = f
242
+ break
243
+ end
244
+ end
245
+ [indices, movi]
246
+ end
247
+ frame
248
+ end
249
+
250
+ ##
251
+ # Returns the last Frame object in +frame_type+.
252
+ def last_of frame_type
253
+ frame = nil
254
+ @avi.process_movi do |indices, movi|
255
+ indices.reverse.each do |m|
256
+ movi.pos = m[:offset] + 8
257
+ f = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
258
+ if f.is?(frame_type)
259
+ frame = f
260
+ break
261
+ end
262
+ end
263
+ [indices, movi]
264
+ end
265
+ frame
266
+ end
267
+
268
+ ##
269
+ # Returns an index of the first found +frame+.
270
+ def index frame
271
+ n = -1
272
+ @avi.process_movi do |indices, movi|
273
+ indices.each_with_index do |m, i|
274
+ movi.pos = m[:offset] + 8
275
+ f = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
276
+ if f == frame
277
+ n = i
278
+ break
279
+ end
280
+ end
281
+ [indices, movi]
282
+ end
283
+ n
284
+ end
285
+
286
+ ##
287
+ # Alias for index
288
+ alias_method :find_index, :index
289
+
290
+ ##
291
+ # Returns an index of the first found +frame+, starting from the last.
292
+ def rindex frame
293
+ n = -1
294
+ @avi.process_movi do |indices, movi|
295
+ indices.reverse.each_with_index do |m, i|
296
+ movi.pos = m[:offset] + 8
297
+ f = Frame.new(movi.read(m[:size]), m[:id], m[:flag])
298
+ if f == frame
299
+ n = indices.size - 1 - i
300
+ break
301
+ end
302
+ end
303
+ [indices, movi]
304
+ end
305
+ n
306
+ end
307
+
294
308
  ##
295
309
  # Appends the given Frame into the tail of self.
296
310
  def push frame
297
311
  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!
312
+ @avi.process_movi do |indices, movi|
313
+ this_size = movi.size
314
+ movi.pos = this_size
315
+ movi.print frame.id
316
+ movi.print [frame.data.size].pack('V')
317
+ movi.print frame.data
318
+ movi.print "\0" if frame.data.size % 2 == 1
319
+ indices << {
320
+ :id => frame.id,
321
+ :flag => frame.flag,
322
+ :offset => this_size,
323
+ :size => frame.data.size,
324
+ }
325
+ [indices, movi]
326
+ end
316
327
  self
317
328
  end
318
329
 
@@ -355,17 +366,17 @@ module AviGlitch
355
366
  ##
356
367
  # Returns true if +other+'s frames are same as self's frames.
357
368
  def == other
358
- @meta == other.meta
369
+ @avi == other.avi
359
370
  end
360
371
 
361
372
  ##
362
373
  # Generates new AviGlitch::Base instance using self.
363
374
  def to_avi
364
- AviGlitch.open @io.path
375
+ AviGlitch::Base.new @avi.clone
365
376
  end
366
377
 
367
- def inspect # :nodec:
368
- "#<#{self.class.name}:#{sprintf("0x%x", object_id)} @io=#{@io.inspect} size=#{self.size}>"
378
+ def inspect #:nodoc:
379
+ "#<#{self.class.name}:#{sprintf("0x%x", object_id)} size=#{self.size}>"
369
380
  end
370
381
 
371
382
  def get_beginning_and_length *args #:nodoc:
@@ -373,46 +384,18 @@ module AviGlitch
373
384
  if args.first.kind_of? Range
374
385
  r = args.first
375
386
  b = r.begin
376
- e = r.end >= 0 ? r.end : @meta.size + r.end
387
+ e = r.end >= 0 ? r.end : self.size + r.end
377
388
  l = e - b + 1
378
389
  end
379
- b = b >= 0 ? b : @meta.size + b
390
+ b = b >= 0 ? b : self.size + b
380
391
  [b, l]
381
392
  end
382
393
 
383
394
  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
395
+ warn "[DEPRECATION] `safe_frames_count?` is deprecated."
396
+ true
413
397
  end
414
398
 
415
- protected :frames_data_as_io, :meta
416
- private :overwrite, :get_beginning_and_length, :fix_offsets_if_needed
399
+ private :get_beginning_and_length
417
400
  end
418
401
  end