aviglitch 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6bcaf49d8305bd36386f606954fe702468fc5196e30b0d5e4f3dea11f0bfb8a7
4
+ data.tar.gz: a1ede07ce50e3005abcd505e501ddf8f73c18b0241d7b34834ac78781d76e389
5
+ SHA512:
6
+ metadata.gz: b9a0c0905f0131bec187618e48e7281eac79d01d6067a66dad82bafbc2c923d84bd7472738b147fc2866ee1f5ef14b1064556e05b7109a7ffef5a0c73370ec6f
7
+ data.tar.gz: f698216510d97cf7713e14127cf75af8264c5c003fa876caed72e51168575eb75e0dba05930ff22c6a145c0da9a0967da9959dfc65d0276909e9db081ba6810c
@@ -0,0 +1,35 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: test
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ ruby-version: ['2.6', '2.7', '3.0']
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
28
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
29
+ # uses: ruby/setup-ruby@v1
30
+ uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
31
+ with:
32
+ ruby-version: ${{ matrix.ruby-version }}
33
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
34
+ - name: Run tests
35
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ doc
6
+ pkg
7
+ tmp
8
+ *.gem
9
+ Gemfile.lock
10
+ Guardfile
11
+ .yardoc
12
+ spec/files
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ -c
2
+ --format documentation
data/ChangeLog.md ADDED
@@ -0,0 +1,67 @@
1
+ ### 0.2.0 / 2021-09-13
2
+
3
+ * Support for AVI2.0 formatted files. Now this library can handle files larger than 1GB.
4
+ * Added methods to Frames, including #index, #rindex, #first_of, and #last_of
5
+ * Removed warnings for the file size getting large.
6
+ * Added the class AviGlitch::Avi which manages binary RIFF-AVI data.
7
+ * A lot of internal changes.
8
+
9
+ ### 0.1.6 / 2021-08-21
10
+
11
+ * Removed obsolete dependencies.
12
+
13
+ ### 0.1.5 / 2014-12-12
14
+
15
+ * Fix Frames#concat and two other method to return self.
16
+ * Some internal changes.
17
+
18
+ ### 0.1.4 / 2014-04-10
19
+
20
+ * Added an enumerator style on AviGlitch::Base#glitch and
21
+ AviGlitch::Frames#each
22
+ * Added AviGlitch::Base#remove_all_keyframes!
23
+ * Renamed #clear_keyframes! to #mutate_keyframes_into_deltaframes!
24
+ * Improved the processing speed in some measure.
25
+ * Some minor fixes.
26
+
27
+ ### 0.1.3 / 2011-08-19
28
+
29
+ * Added has_keyframe? method to AviGlitch::Base
30
+ * Added a --fake option to datamosh cli.
31
+
32
+ ### 0.1.2 / 2011-04-10
33
+
34
+ * Fix to be able to handle data with offsets from 0 of the file.
35
+ * Added clear_keyframes! method to AviGlitch::Frames and AviGlitch::Base.
36
+ * Changed to be able to access frame's meta data.
37
+ * Changed datamosh command to handle wildcard char.
38
+
39
+ ### 0.1.1 / 2010-09-09
40
+
41
+ * Fixed a bug with windows.
42
+ * Some tiny fixes.
43
+
44
+ ### 0.1.0 / 2010-07-09
45
+
46
+ * Minor version up.
47
+ * Fixed bugs with Ruby 1.8.7.
48
+ * Fixed the synchronization problem with datamosh cli.
49
+
50
+ ### 0.0.3 / 2010-07-07
51
+
52
+ * Changed AviGlitch::Frames allowing to slice and concatenate frames
53
+ (like Array).
54
+ * Changed datamosh cli to accept multiple files.
55
+
56
+ ### 0.0.2 / 2010-05-17
57
+
58
+ * Removed AviGlitch#new. Use AviGlitch#open instead of #new.
59
+ * Added warning for a large file.
60
+ * Changed datamosh command interface.
61
+ * Changed the library file layout.
62
+ * And tiny internal changes.
63
+
64
+ ### 0.0.1 / 2009-08-01
65
+
66
+ * initial release
67
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+ gemspec
3
+
4
+ group :test do
5
+ gem 'guard'
6
+ gem 'guard-rspec'
7
+ end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 ucnv
1
+ Copyright (c) 2009-2021 ucnv
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # AviGlitch
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/aviglitch.svg)](https://badge.fury.io/rb/aviglitch)
4
+ [![test](https://github.com/ucnv/aviglitch/actions/workflows/ruby.yml/badge.svg)](https://github.com/ucnv/aviglitch/actions/workflows/ruby.yml)
5
+
6
+ AviGlitch destroys your AVI files.
7
+
8
+ I can't explain why they're going to destroy their own data, but they do.
9
+
10
+ You can find a short guide at <https://ucnv.github.io/aviglitch/>.
11
+ It provides a way to manipulate the data in each AVI frames.
12
+ It will mostly be used for making datamoshing videos.
13
+ It parses only container level structure, doesn't parse codecs.
14
+
15
+ See following urls for details about visual glitch;
16
+
17
+ * vimeo <http://www.vimeo.com/groups/artifacts>
18
+ * wikipedia <http://en.wikipedia.org/wiki/Compression_artifact>
19
+
20
+ ## Usage
21
+
22
+ ```ruby
23
+ require 'aviglitch'
24
+
25
+ avi = AviGlitch.open('/path/to/your.avi')
26
+ avi.glitch(:keyframe) do |data|
27
+ data.gsub(/\d/, '0')
28
+ end
29
+ avi.output('/path/to/broken.avi')
30
+ ```
31
+
32
+ This library also includes a command line tool named `datamosh`.
33
+ It creates the keyframes removed video.
34
+
35
+ ```sh
36
+ $ datamosh /path/to/your.avi -o /path/to/broken.avi
37
+ ```
38
+
39
+ For more practical usages, please check <https://github.com/ucnv/aviglitch-utils/tree/master/bin>.
40
+
41
+ ## Installation
42
+
43
+ ```sh
44
+ gem install aviglitch
45
+ ```
46
+
47
+ ## License
48
+
49
+ This library is distributed under the terms and conditions of the [MIT license](LICENSE).
data/Rakefile CHANGED
@@ -1,50 +1,13 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "aviglitch"
8
- gem.summary = "A Ruby library to destroy your AVI files."
9
- gem.email = "ucnvvv@gmail.com"
10
- gem.homepage = "http://ucnv.github.com/aviglitch/"
11
- gem.authors = ["ucnv"]
12
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
- gem.files = %w(README.rdoc ChangeLog Rakefile VERSION) +
14
- Dir.glob("{bin,spec,lib}/**/*")
15
- gem.add_development_dependency "rspec", ">= 2.0.0"
16
-
17
- end
18
-
19
- rescue LoadError
20
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
21
- end
22
-
23
- require 'rspec/core/rake_task'
24
- RSpec::Core::RakeTask.new(:spec) do |spec|
25
- spec.pattern = FileList['spec/**/*_spec.rb']
26
- end
27
-
28
- RSpec::Core::RakeTask.new(:rcov) do |spec|
29
- spec.pattern = 'spec/**/*_spec.rb'
30
- spec.rcov = true
31
- end
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
32
3
 
4
+ RSpec::Core::RakeTask.new(:spec)
33
5
 
34
6
  task :default => :spec
35
7
 
36
- require 'rake/rdoctask'
8
+ require 'rdoc/task'
37
9
  Rake::RDocTask.new do |rdoc|
38
- if File.exist?('VERSION.yml')
39
- config = YAML.load(File.read('VERSION.yml'))
40
- version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
41
- else
42
- version = ""
43
- end
44
-
10
+ rdoc.main = "README.md"
45
11
  rdoc.rdoc_dir = 'rdoc'
46
- rdoc.title = "aviglitch #{version}"
47
- rdoc.rdoc_files.include('README*')
48
- rdoc.rdoc_files.include('lib/**/*.rb')
12
+ rdoc.rdoc_files.include(%w{LICENSE *.md lib/**/*.rb})
49
13
  end
50
-
data/aviglitch.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'aviglitch'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "aviglitch"
8
+ spec.version = AviGlitch::VERSION
9
+ spec.authors = ["ucnv"]
10
+ spec.email = ["ucnvvv@gmail.com"]
11
+ spec.summary = %q{A Ruby library to destroy your AVI files.}
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/ucnv/aviglitch"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.extra_rdoc_files = ["README.md", "LICENSE"]
22
+ spec.rdoc_options << "-m" << "README.md"
23
+
24
+ spec.add_development_dependency "bundler", ">= 2.2.10"
25
+ spec.add_development_dependency "rake", ">= 12.3.3"
26
+ spec.add_development_dependency "rspec"
27
+ end
data/bin/datamosh CHANGED
@@ -34,10 +34,18 @@ opts = OptionParser.new do |opts|
34
34
  end
35
35
  end
36
36
 
37
- input = Dir.glob opts.parse!
37
+ input = opts.parse!
38
38
  if input.empty?
39
39
  puts opts
40
40
  exit 1
41
+ else
42
+ input.each do |file|
43
+ if !File.exist?(file) || File.directory?(file)
44
+ opts.banner = "#{file}: No such file.\n\n"
45
+ puts opts
46
+ exit 1
47
+ end
48
+ end
41
49
  end
42
50
 
43
51
  a = AviGlitch.open input.shift
@@ -46,7 +54,12 @@ unless fake
46
54
  (!all && i == 0) ? frame : "" # keep the first frame
47
55
  end
48
56
  end
49
- a.clear_keyframes!(!all && !fake ? 1..a.frames.size : nil)
57
+ if !all && !fake
58
+ first = a.frames.index(a.frames.first_of(:keyframe))
59
+ a.mutate_keyframes_into_deltaframes! (first + 1)..a.frames.size
60
+ else
61
+ a.mutate_keyframes_into_deltaframes!
62
+ end
50
63
 
51
64
  input.each do |file|
52
65
  b = AviGlitch.open file
@@ -55,7 +68,7 @@ input.each do |file|
55
68
  ""
56
69
  end
57
70
  end
58
- b.clear_keyframes!
71
+ b.mutate_keyframes_into_deltaframes!
59
72
  a.frames.concat b.frames
60
73
  end
61
74
 
@@ -0,0 +1,550 @@
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 #:nodoc:
83
+ protected :path, :path=, :movi, :movi=
84
+
85
+ ##
86
+ # Generates an instance with the necessary structure from the +path+.
87
+ def initialize path = nil
88
+ return if path.nil?
89
+ @path = path
90
+ File.open(path, 'rb') do |io|
91
+ @movi = Tempfile.new 'aviglitch', binmode: true
92
+ @riff = []
93
+ @indices = []
94
+ @superidx = []
95
+ @was_avi2 = false
96
+ io.rewind
97
+ parse_riff io, @riff
98
+ if was_avi2?
99
+ @indices.sort_by! { |ix| ix[:offset] }
100
+ end
101
+ end
102
+ end
103
+
104
+ ##
105
+ # Parses the passed RIFF formated file recursively.
106
+ def parse_riff io, target, len = 0, is_movi = false
107
+ offset = io.pos
108
+ binoffset = @movi.pos
109
+ while id = io.read(4) do
110
+ if len > 0 && io.pos >= offset + len
111
+ io.pos -= 4
112
+ break
113
+ end
114
+ size = io.read(4).unpack('V').first
115
+ if id == 'RIFF' || id == 'LIST'
116
+ lid = io.read(4)
117
+ newarr = []
118
+ chunk = RiffChunk.new id, size, lid, newarr
119
+ target << chunk
120
+ parse_riff io, newarr, size, lid == 'movi'
121
+ else
122
+ value = nil
123
+ if is_movi
124
+ if id =~ /^ix/
125
+ v = io.read size
126
+ # confirm the super index surely has information
127
+ @superidx.each do |sidx|
128
+ nent = sidx[4, 4].unpack('v').first
129
+ cid = sidx[8, 4]
130
+ nent.times do |i|
131
+ ent = sidx[24 + 16 * i, 16]
132
+ # we can check other informations thuogh
133
+ valid = ent[0, 8].unpack('q').first == io.pos - v.size - 8
134
+ parse_avi2_indices(v, binoffset) if valid
135
+ end
136
+ end
137
+ else
138
+ io.pos -= 8
139
+ v = io.read(size + 8)
140
+ @movi.print v
141
+ @movi.print "\0" if size % 2 == 1
142
+ end
143
+ elsif id == 'idx1'
144
+ v = io.read size
145
+ parse_avi1_indices v unless was_avi2?
146
+ else
147
+ value = io.read size
148
+ if id == 'indx'
149
+ @superidx << value
150
+ @was_avi2 = true
151
+ end
152
+ end
153
+ chunk = RiffChunk.new id, size, value
154
+ target << chunk
155
+ io.pos += 1 if size % 2 == 1
156
+ end
157
+ end
158
+ end
159
+
160
+ ##
161
+ # Closes the file.
162
+ def close
163
+ @movi.close!
164
+ end
165
+
166
+ ##
167
+ # Detects the passed file was an AVI2.0 file.
168
+ def was_avi2?
169
+ @was_avi2
170
+ end
171
+
172
+ ##
173
+ # Detects the current data will be an AVI2.0 file.
174
+ def is_avi2?
175
+ @movi.size >= MAX_RIFF_SIZE
176
+ end
177
+
178
+ ##
179
+ # Saves data to AVI formatted file.
180
+ def output path
181
+ @index_pos = 0
182
+ # prepare headers by reusing existing ones
183
+ strl = search 'hdrl', 'strl'
184
+ if is_avi2?
185
+ # indx
186
+ vid_frames_size = 0
187
+ @indexinfo = @indices.collect { |ix|
188
+ vid_frames_size += 1 if ix[:id] =~ /d[bc]$/
189
+ ix[:id]
190
+ }.uniq.sort.collect { |d|
191
+ [d, {}]
192
+ }.to_h # should be like: {"00dc"=>{}, "01wb"=>{}}
193
+ strl.each_with_index do |sl, i|
194
+ indx = sl.child 'indx'
195
+ if indx.nil?
196
+ indx = RiffChunk.new('indx', 4120, "\0" * 4120)
197
+ indx.value[0, 8] = [4, 0, 0, 0].pack('vccV')
198
+ sl.value.push indx
199
+ else
200
+ indx.value[4, 4] = [0].pack('V')
201
+ indx.value[24..-1] = "\0" * (indx.value.size - 24)
202
+ end
203
+ preid = indx.value[8, 4]
204
+ info = @indexinfo.find do |key, val|
205
+ # more strict way must exist though..
206
+ if preid == "\0\0\0\0"
207
+ key.start_with? "%02d" % i
208
+ else
209
+ key == preid
210
+ end
211
+ end
212
+ indx.value[8, 4] = info.first if preid == "\0\0\0\0"
213
+ info.last[:indx] = indx
214
+ info.last[:fcc] = 'ix' + info.first[0, 2]
215
+ info.last[:cur] = []
216
+ end
217
+ # odml
218
+ odml = search('hdrl', 'odml').first
219
+ if odml.nil?
220
+ odml = RiffChunk.new(
221
+ 'LIST', 260, 'odml', [RiffChunk.new('dmlh', 248, "\0" * 248)]
222
+ )
223
+ @riff.first.child('hdrl').value.push odml
224
+ end
225
+ odml.child('dmlh').value[0, 4] = [@indices.size].pack('V')
226
+ else
227
+ strl.each do |sl|
228
+ indx = sl.child 'indx'
229
+ unless indx.nil?
230
+ sl.value.delete indx
231
+ end
232
+ end
233
+ end
234
+
235
+ # movi
236
+ write_movi = ->(io) do
237
+ vid_frames_size = 0
238
+ io.print 'LIST'
239
+ io.print "\0\0\0\0"
240
+ data_offset = io.pos
241
+ io.print 'movi'
242
+ while io.pos - data_offset <= MAX_RIFF_SIZE
243
+ ix = @indices[@index_pos]
244
+ @indexinfo[ix[:id]][:cur] << {
245
+ pos: io.pos, size: ix[:size], flag: ix[:flag]
246
+ } if is_avi2?
247
+ io.print ix[:id]
248
+ vid_frames_size += 1 if ix[:id] =~ /d[bc]$/
249
+ io.print [ix[:size]].pack('V')
250
+ @movi.pos += 8
251
+ io.print @movi.read(ix[:size])
252
+ if ix[:size] % 2 == 1
253
+ io.print "\0"
254
+ @movi.pos += 1
255
+ end
256
+ @index_pos += 1
257
+ break if @index_pos > @indices.size - 1
258
+ end
259
+ # standard index
260
+ if is_avi2?
261
+ @indexinfo.each do |key, info|
262
+ ix_offset = io.pos
263
+ io.print info[:fcc]
264
+ io.print [24 + 8 * info[:cur].size].pack('V')
265
+ io.print [2, 0, 1, info[:cur].size].pack('vccV')
266
+ io.print key
267
+ io.print [data_offset, 0].pack('qV')
268
+ info[:cur].each.with_index do |cur, i|
269
+ io.print [cur[:pos] - data_offset + 8].pack('V') # 8 for LIST####
270
+ sz = cur[:size]
271
+ if cur[:flag] & Frame::AVIIF_KEYFRAME == 0 # is not keyframe
272
+ sz = sz | 0b1000_0000_0000_0000_0000_0000_0000_0000
273
+ end
274
+ io.print [sz].pack('V')
275
+ end
276
+ # rewrite indx
277
+ indx = info[:indx]
278
+ nent = indx.value[4, 4].unpack('V').first + 1
279
+ indx.value[4, 4] = [nent].pack('V')
280
+ indx.value[24 + 16 * (nent - 1), 16] = [
281
+ ix_offset, io.pos - ix_offset, info[:cur].size
282
+ ].pack('qVV')
283
+ io.pos = expected_position_of(indx) + 8
284
+ io.print indx.value
285
+ # clean up
286
+ info[:cur] = []
287
+ io.seek 0, IO::SEEK_END
288
+ end
289
+ end
290
+ # size of movi
291
+ size = io.pos - data_offset
292
+ io.pos = data_offset - 4
293
+ io.print [size].pack('V')
294
+ io.seek 0, IO::SEEK_END
295
+ io.print "\0" if size % 2 == 1
296
+ vid_frames_size
297
+ end
298
+
299
+ File.open(path, 'w+') do |io|
300
+ io.binmode
301
+ @movi.rewind
302
+ # normal AVI
303
+ # header
304
+ io.print 'RIFF'
305
+ io.print "\0\0\0\0"
306
+ io.print 'AVI '
307
+ @riff.first.value.each do |chunk|
308
+ break if chunk.id == 'movi'
309
+ print_chunk io, chunk
310
+ end
311
+ # movi
312
+ vid_size = write_movi.call io
313
+ # rewrite frame count in avih header
314
+ io.pos = 48
315
+ io.print [vid_size].pack('V')
316
+ io.seek 0, IO::SEEK_END
317
+ # idx1
318
+ io.print 'idx1'
319
+ io.print [@index_pos * 16].pack('V')
320
+ @indices[0..(@index_pos - 1)].each do |ix|
321
+ io.print ix[:id] + [ix[:flag], ix[:offset] + 4, ix[:size]].pack('V3')
322
+ end
323
+ # rewrite riff chunk size
324
+ avisize = io.pos - 8
325
+ io.pos = 4
326
+ io.print [avisize].pack('V')
327
+ io.seek 0, IO::SEEK_END
328
+
329
+ # AVI2.0
330
+ while @index_pos < @indices.size
331
+ io.print 'RIFF'
332
+ io.print "\0\0\0\0"
333
+ riff_offset = io.pos
334
+ io.print 'AVIX'
335
+ # movi
336
+ write_movi.call io
337
+ # rewrite total chunk size
338
+ avisize = io.pos - riff_offset
339
+ io.pos = riff_offset - 4
340
+ io.print [avisize].pack('V')
341
+ io.seek 0, IO::SEEK_END
342
+ end
343
+ end
344
+ end
345
+
346
+ ##
347
+ # Provides internal accesses to movi binary data.
348
+ # It requires the yield block to return an array of pair values
349
+ # which consists of new indices array and new movi binary data.
350
+ def process_movi &block
351
+ @movi.rewind
352
+ newindices, newmovi = block.call @indices, @movi
353
+ unless @indices == newindices
354
+ @indices.replace newindices
355
+ end
356
+ unless @movi == newmovi
357
+ @movi.rewind
358
+ newmovi.rewind
359
+ while d = newmovi.read(BUFFER_SIZE) do
360
+ @movi.print d
361
+ end
362
+ eof = @movi.pos
363
+ @movi.truncate eof
364
+ end
365
+ end
366
+
367
+ ##
368
+ # Searches and returns RIFF values with the passed search +args+.
369
+ # +args+ should point the ids of the tree structured RIFF data
370
+ # under the 'AVI ' chunk without omission, like:
371
+ #
372
+ # avi.search 'hdrl', 'strl', 'indx'
373
+ #
374
+ # It returns a list of RiffChunk object which can be modified directly.
375
+ # (RiffChunk class which is returned through this method also has a #search
376
+ # method with the same interface as this class.)
377
+ # This method only seeks in the first RIFF 'AVI ' tree.
378
+ def search *args
379
+ @riff.first.search *args
380
+ end
381
+
382
+ ##
383
+ # Returns true if +other+'s indices are same as self's indices.
384
+ def == other
385
+ self.indices == other.indices
386
+ end
387
+
388
+ def inspect #:nodoc:
389
+ "#<#{self.class.name}:#{sprintf("0x%x", object_id)} @movi=#{@movi.inspect}>"
390
+ end
391
+
392
+ def initialize_copy avi #:nodoc:
393
+ avi.path = @path.dup
394
+ md = Marshal.dump @indices
395
+ avi.indices = Marshal.load md
396
+ md = Marshal.dump @riff
397
+ avi.riff = Marshal.load md
398
+ newmovi = Tempfile.new 'aviglitch', binmode: true
399
+ movipos = @movi.pos
400
+ @movi.rewind
401
+ newmovi.print @movi.read
402
+ @movi.pos = movipos
403
+ newmovi.rewind
404
+ avi.movi = newmovi
405
+ end
406
+
407
+ def print_chunk io, chunk #:nodoc:
408
+ offset = io.pos
409
+ if chunk.is_list?
410
+ io.print chunk.list
411
+ io.print "\0\0\0\0"
412
+ io.print chunk.id
413
+ chunk.value.each do |c|
414
+ print_chunk io, c
415
+ end
416
+ else
417
+ io.print chunk.id
418
+ io.print "\0\0\0\0"
419
+ io.print chunk.value
420
+ end
421
+ # rewrite size
422
+ size = io.pos - offset - 8
423
+ io.pos = offset + 4
424
+ io.print [size].pack('V')
425
+ io.seek 0, IO::SEEK_END
426
+ io.print "\0" if size % 2 == 1
427
+ end
428
+
429
+ def expected_position_of chunk #:nodoc:
430
+ pos = -1
431
+ cur = 12
432
+ seek = -> (chk) do
433
+ if chk === chunk
434
+ pos = cur
435
+ return
436
+ end
437
+ if chk.is_list?
438
+ cur += 12
439
+ chk.value.each do |c|
440
+ seek.call c
441
+ end
442
+ else
443
+ cur += 8
444
+ cur += chk.value.nil? ? chk.binsize : chk.value.size
445
+ end
446
+ end
447
+ headers = @riff.first.value
448
+ headers.each do |c|
449
+ seek.call c
450
+ end
451
+ pos
452
+ end
453
+
454
+ def parse_avi1_indices data #:nodoc:
455
+ # The function Frames#fix_offsets_if_needed in prev versions was now removed.
456
+ i = 0
457
+ while i * 16 < data.size do
458
+ @indices << {
459
+ :id => data[i * 16, 4],
460
+ :flag => data[i * 16 + 4, 4].unpack('V').first,
461
+ :offset => data[i * 16 + 8, 4].unpack('V').first - 4,
462
+ :size => data[i * 16 + 12, 4].unpack('V').first,
463
+ }
464
+ i += 1
465
+ end
466
+ end
467
+
468
+ def parse_avi2_indices data, offset #:nodoc:
469
+ id = data[8, 4]
470
+ nent = data[4, 4].unpack('V').first
471
+ h = 24
472
+ i = 0
473
+ while h + i * 8 < data.size
474
+ moffset = data[h + i * 8, 4].unpack('V').first
475
+ msize = data[h + i * 8 + 4, 4].unpack('V').first
476
+ of = offset + moffset - 12 # 12 for movi + 00dc####
477
+ # bit 31 is set if this is NOT a keyframe
478
+ fl = (msize >> 31 == 1) ? 0 : Frame::AVIIF_KEYFRAME
479
+ sz = msize & 0b0111_1111_1111_1111_1111_1111_1111_1111
480
+ @indices << {
481
+ :id => id,
482
+ :flag => fl,
483
+ :offset => of,
484
+ :size => sz,
485
+ }
486
+ i += 1
487
+ end
488
+ end
489
+
490
+ private :print_chunk, :expected_position_of,
491
+ :parse_avi1_indices, :parse_avi2_indices
492
+
493
+ class << self
494
+
495
+ ##
496
+ # Parses the +file+ and returns the RIFF structure.
497
+ def rifftree file, out = nil
498
+ returnable = out.nil?
499
+ out = StringIO.new if returnable
500
+
501
+ parse = ->(io, depth = 0, len = 0) do
502
+ offset = io.pos
503
+ while id = io.read(4) do
504
+ if len > 0 && io.pos >= offset + len
505
+ io.pos -= 4
506
+ break
507
+ end
508
+ size = io.read(4).unpack('V').first
509
+ str = depth > 0 ? ' ' * depth + id : id
510
+ if id =~ /^(?:RIFF|LIST)$/
511
+ lid = io.read(4)
512
+ str << (' (%d)' % size) + " ’#{lid}’\n"
513
+ out.print str
514
+ parse.call io, depth + 1, size
515
+ else
516
+ str << (' (%d)' % size ) + "\n"
517
+ out.print str
518
+ io.pos += size
519
+ io.pos += 1 if size % 2 == 1
520
+ end
521
+ end
522
+ end
523
+
524
+ io = file
525
+ is_io = file.respond_to?(:seek) # Probably IO.
526
+ io = File.open(file, 'rb') unless is_io
527
+ begin
528
+ io.rewind
529
+ parse.call io
530
+ io.rewind
531
+ ensure
532
+ io.close unless is_io
533
+ end
534
+
535
+ if returnable
536
+ out.rewind
537
+ out.read
538
+ end
539
+ end
540
+
541
+ ##
542
+ # Parses the +file+ and prints the RIFF structure to stdout.
543
+ def print_rifftree file
544
+ Avi.rifftree file, $stdout
545
+ end
546
+
547
+ end
548
+
549
+ end
550
+ end