aviglitch 0.1.3 → 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 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