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 +7 -0
- data/.github/workflows/ruby.yml +35 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/ChangeLog.md +67 -0
- data/Gemfile +7 -0
- data/LICENSE +1 -1
- data/README.md +49 -0
- data/Rakefile +6 -43
- data/aviglitch.gemspec +27 -0
- data/bin/datamosh +16 -3
- data/lib/aviglitch/avi.rb +550 -0
- data/lib/aviglitch/base.rb +57 -76
- data/lib/aviglitch/frame.rb +20 -0
- data/lib/aviglitch/frames.rb +181 -180
- data/lib/aviglitch.rb +6 -9
- data/spec/avi2_spec.rb +40 -0
- data/spec/aviglitch_spec.rb +37 -18
- data/spec/datamosh_spec.rb +4 -14
- data/spec/frames_spec.rb +114 -28
- data/spec/spec_helper.rb +45 -1
- metadata +67 -31
- data/ChangeLog +0 -40
- data/README.rdoc +0 -42
- data/VERSION +0 -1
- data/lib/aviglitch/tempfile.rb +0 -8
- data/spec/files/sample.avi +0 -0
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
data/.rspec
ADDED
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
data/LICENSE
CHANGED
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# AviGlitch
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/aviglitch)
|
4
|
+
[](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
|
2
|
-
require
|
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 '
|
8
|
+
require 'rdoc/task'
|
37
9
|
Rake::RDocTask.new do |rdoc|
|
38
|
-
|
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.
|
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 =
|
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
|
-
|
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.
|
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
|