aviglitch 0.1.6 → 0.2.0
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/.gitignore +1 -0
- data/ChangeLog.md +8 -0
- data/Gemfile +5 -0
- data/README.md +0 -4
- data/aviglitch.gemspec +1 -2
- data/bin/datamosh +6 -1
- data/lib/aviglitch/avi.rb +550 -0
- data/lib/aviglitch/base.rb +29 -59
- data/lib/aviglitch/frame.rb +20 -0
- data/lib/aviglitch/frames.rb +167 -184
- data/lib/aviglitch.rb +2 -3
- data/spec/avi2_spec.rb +40 -0
- data/spec/aviglitch_spec.rb +2 -19
- data/spec/datamosh_spec.rb +4 -14
- data/spec/frames_spec.rb +34 -25
- data/spec/spec_helper.rb +41 -0
- metadata +6 -5
- data/spec/files/sample.avi +0 -0
data/lib/aviglitch/base.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
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
|
-
@
|
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
|
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
|
-
|
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
|
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
|
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
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
148
|
+
passed
|
179
149
|
end
|
180
150
|
end
|
181
151
|
end
|
data/lib/aviglitch/frame.rb
CHANGED
@@ -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
|
|
data/lib/aviglitch/frames.rb
CHANGED
@@ -18,45 +18,12 @@ module AviGlitch
|
|
18
18
|
class Frames
|
19
19
|
include Enumerable
|
20
20
|
|
21
|
-
|
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
|
34
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
@
|
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
|
-
|
86
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
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
|
-
@
|
156
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
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
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
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
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
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
|
-
@
|
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.
|
375
|
+
AviGlitch::Base.new @avi.clone
|
365
376
|
end
|
366
377
|
|
367
|
-
def inspect
|
368
|
-
"#<#{self.class.name}:#{sprintf("0x%x", object_id)}
|
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 :
|
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 :
|
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
|
-
|
385
|
-
|
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
|
-
|
416
|
-
private :overwrite, :get_beginning_and_length, :fix_offsets_if_needed
|
399
|
+
private :get_beginning_and_length
|
417
400
|
end
|
418
401
|
end
|