depix 1.1.6 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/{DPX_HEADER_STRUCTURE.txt → DPX_HEADER_STRUCTURE.rdoc} +0 -0
- data/History.txt +5 -0
- data/Manifest.txt +6 -4
- data/{README.txt → README.rdoc} +3 -3
- data/Rakefile +4 -1
- data/lib/depix.rb +12 -73
- data/lib/depix/binary/descriptor.rb +56 -0
- data/lib/depix/binary/fields.rb +302 -0
- data/lib/depix/binary/structure.rb +239 -0
- data/lib/depix/compact_structs.rb +1 -1
- data/lib/depix/reader.rb +5 -4
- data/lib/depix/structs.rb +9 -12
- data/lib/depix/synthetics.rb +64 -0
- data/test/test_depix.rb +8 -11
- data/test/test_dict.rb +52 -52
- metadata +16 -48
- data/lib/depix/benchmark.rb +0 -15
- data/lib/depix/dict.rb +0 -531
data/.gemtest
ADDED
File without changes
|
File without changes
|
data/History.txt
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
=== 2.0.0 / 2011-06-14
|
2
|
+
|
3
|
+
* Depix::Binary is now public, and it's a nice library. Check it out.
|
4
|
+
* Ensure 1.9 compatibility. Note that we COMPLACENTLY force strings to US-ASCII when saving them to DPX.
|
5
|
+
|
1
6
|
=== 1.1.6 / 2010-10-20
|
2
7
|
|
3
8
|
* Fix unpacking reals and longs from little-endian DPX headers
|
data/Manifest.txt
CHANGED
@@ -1,18 +1,20 @@
|
|
1
|
-
DPX_HEADER_STRUCTURE.
|
1
|
+
DPX_HEADER_STRUCTURE.rdoc
|
2
2
|
History.txt
|
3
3
|
Manifest.txt
|
4
|
-
README.
|
4
|
+
README.rdoc
|
5
5
|
Rakefile
|
6
6
|
bin/depix-describe
|
7
7
|
lib/depix.rb
|
8
|
-
lib/depix/
|
8
|
+
lib/depix/binary/descriptor.rb
|
9
|
+
lib/depix/binary/fields.rb
|
10
|
+
lib/depix/binary/structure.rb
|
9
11
|
lib/depix/compact_structs.rb
|
10
|
-
lib/depix/dict.rb
|
11
12
|
lib/depix/editor.rb
|
12
13
|
lib/depix/enums.rb
|
13
14
|
lib/depix/reader.rb
|
14
15
|
lib/depix/struct_explainer.rb
|
15
16
|
lib/depix/structs.rb
|
17
|
+
lib/depix/synthetics.rb
|
16
18
|
test/samples/026_FROM_HERO_TAPE_5-3-1_MOV.0029.dpx
|
17
19
|
test/samples/E012_P001_L000002_lin.0001.dpx
|
18
20
|
test/samples/E012_P001_L000002_lin.0002.dpx
|
data/{README.txt → README.rdoc}
RENAMED
@@ -21,9 +21,9 @@ Writing headers
|
|
21
21
|
editor.headers.time_code = editor.headers.time_code + 1
|
22
22
|
editor.commit!
|
23
23
|
|
24
|
-
The data returned is described in the DPX_HEADER_STRUCTURE
|
25
|
-
a vanilla Ruby object with no extra methods except for the readers that
|
26
|
-
fields
|
24
|
+
The data returned is described in the DPX_HEADER_STRUCTURE.rdoc
|
25
|
+
It's a vanilla Ruby object with no extra methods except for the readers that
|
26
|
+
have the same name as the specified fields.
|
27
27
|
|
28
28
|
The gem also contains an executable called depix-desribe which can be used from the command line
|
29
29
|
|
data/Rakefile
CHANGED
@@ -8,10 +8,13 @@ Hoe.spec('depix') do |p|
|
|
8
8
|
p.rubyforge_name = 'guerilla-di'
|
9
9
|
p.extra_deps << ['timecode']
|
10
10
|
p.remote_rdoc_dir = 'depix'
|
11
|
+
p.readme_file = 'README.rdoc'
|
12
|
+
p.extra_rdoc_files = FileList['*.rdoc']
|
13
|
+
|
11
14
|
p.clean_globs = %w( **/.DS_Store )
|
12
15
|
end
|
13
16
|
|
14
17
|
task :describe_structs do
|
15
18
|
require File.dirname(__FILE__) + '/lib/depix/struct_explainer'
|
16
|
-
File.open('DPX_HEADER_STRUCTURE.
|
19
|
+
File.open('DPX_HEADER_STRUCTURE.rdoc', 'w') {|f| f << RdocExplainer.new.get_rdoc_for(Depix::DPX) }
|
17
20
|
end
|
data/lib/depix.rb
CHANGED
@@ -1,85 +1,24 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'timecode'
|
3
3
|
|
4
|
-
require File.dirname(__FILE__) + '/depix/
|
5
|
-
require File.dirname(__FILE__) + '/depix/
|
6
|
-
|
7
|
-
require File.dirname(__FILE__) + '/depix/
|
8
|
-
require File.dirname(__FILE__) + '/depix/
|
9
|
-
require File.dirname(__FILE__) + '/depix/
|
4
|
+
require File.expand_path(File.dirname(__FILE__)) + '/depix/binary/fields'
|
5
|
+
require File.expand_path(File.dirname(__FILE__)) + '/depix/binary/structure'
|
6
|
+
|
7
|
+
require File.expand_path(File.dirname(__FILE__)) + '/depix/structs'
|
8
|
+
require File.expand_path(File.dirname(__FILE__)) + '/depix/compact_structs'
|
9
|
+
require File.expand_path(File.dirname(__FILE__)) + '/depix/enums'
|
10
|
+
|
11
|
+
require File.expand_path(File.dirname(__FILE__)) + '/depix/synthetics'
|
12
|
+
require File.expand_path(File.dirname(__FILE__)) + '/depix/reader'
|
13
|
+
require File.expand_path(File.dirname(__FILE__)) + '/depix/editor'
|
10
14
|
|
11
15
|
|
12
16
|
module Depix
|
13
|
-
VERSION = '
|
17
|
+
VERSION = '2.0.0'
|
14
18
|
|
15
19
|
class InvalidHeader < RuntimeError; end
|
16
20
|
|
17
|
-
|
18
|
-
module Synthetics
|
19
|
-
|
20
|
-
DEFAULT_DPX_FPS = 25
|
21
|
-
|
22
|
-
# Get formatted keycode as string, empty elements are omitted
|
23
|
-
def keycode
|
24
|
-
[film.id, film.type, film.offset, film.prefix, film.count].compact.join(' ')
|
25
|
-
end
|
26
|
-
|
27
|
-
# Return the flame reel name. The data after the first null byte is not meant to be seen
|
28
|
-
# and is used by Flame internally
|
29
|
-
# as it seems
|
30
|
-
def flame_reel
|
31
|
-
return nil unless orientation.device
|
32
|
-
orientation.device.split(0x00.chr).shift
|
33
|
-
end
|
34
|
-
|
35
|
-
# Assign reel name
|
36
|
-
def flame_reel=(new_reel)
|
37
|
-
orientation.device = new_reel
|
38
|
-
end
|
39
|
-
|
40
|
-
# Get television.time_code as a Timecode object with a framerate.
|
41
|
-
# We explicitly use the television frame rate since Northlight
|
42
|
-
# writes different rates for television and film time code
|
43
|
-
def time_code
|
44
|
-
framerates = [television.frame_rate, film.frame_rate, DEFAULT_DPX_FPS]
|
45
|
-
framerate = framerates.find{|e| !e.nil? && !e.zero? }
|
46
|
-
if television.time_code
|
47
|
-
Timecode.from_uint(television.time_code, framerate)
|
48
|
-
else
|
49
|
-
# Assume frame position
|
50
|
-
Timecode.new(film.frame_position, framerate)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Assign frame rate and timecode from a Timecode object
|
55
|
-
def time_code=(new_tc)
|
56
|
-
television.time_code, television.frame_rate = new_tc.to_uint, new_tc.fps
|
57
|
-
end
|
58
|
-
|
59
|
-
# Get the name of the transfer function (Linear, Logarithmic, ...)
|
60
|
-
def colorimetric
|
61
|
-
COLORIMETRIC.invert[image.image_elements[0].colorimetric]
|
62
|
-
end
|
63
|
-
|
64
|
-
# Get the name of the compnent type (RGB, YCbCr, ...)
|
65
|
-
def component_type
|
66
|
-
COMPONENT_TYPE.invert[image.image_elements[0].descriptor]
|
67
|
-
end
|
68
|
-
|
69
|
-
# Aspect in it's traditional representation (1.77 for 16x9 and so on)
|
70
|
-
def aspect
|
71
|
-
"%.2f" % (orientation.aspect_ratio[0].to_f / orientation.aspect_ratio[1].to_f)
|
72
|
-
end
|
73
|
-
|
74
|
-
# Is this DPX file little-endian? This would be an exception, but still useful
|
75
|
-
def le?
|
76
|
-
file.magic == 'XPDS'
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
class DPX < Dict
|
81
|
-
include Synthetics
|
82
|
-
end
|
21
|
+
DPX.send(:include, Synthetics)
|
83
22
|
|
84
23
|
# Return a DPX object describing a file at path.
|
85
24
|
# The second argument specifies whether you need a compact or a full description
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Generates a description of the structure in RDoc format
|
2
|
+
class Depix::Binary::StructureExplainer
|
3
|
+
attr_accessor :io, :attr_template, :struct_template
|
4
|
+
|
5
|
+
TPL = <<eof
|
6
|
+
= DPX header structure description
|
7
|
+
|
8
|
+
DPX metadata gets returned as a Depix::DPX object with nested properties.
|
9
|
+
|
10
|
+
meta.file.magic # => "SDPX"
|
11
|
+
|
12
|
+
== Metadata structure
|
13
|
+
|
14
|
+
%s
|
15
|
+
eof
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@padding = ' '
|
19
|
+
@attr_template = "%s* <tt>%s</tt> %s"
|
20
|
+
@struct_template = "%s* <tt>%s</tt> %s:"
|
21
|
+
@array_template = "%s* <tt>%s</tt> %s:"
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_rdoc_for(struct)
|
25
|
+
@io = StringIO.new
|
26
|
+
explain_struct(struct)
|
27
|
+
TPL % @io.string
|
28
|
+
end
|
29
|
+
|
30
|
+
def explain_struct(struct, padding = '') #:nodoc:
|
31
|
+
struct.fields.each do | e |
|
32
|
+
if e.is_a?(InnerField)
|
33
|
+
|
34
|
+
@io.puts( @struct_template % [padding, e.name, e.explain])
|
35
|
+
explain_struct(e.rtype, padding + @padding)
|
36
|
+
|
37
|
+
elsif e.is_a?(ArrayField)
|
38
|
+
|
39
|
+
@io.puts( @array_template % [padding, e.name, e.explain])
|
40
|
+
|
41
|
+
inner_struct = e.members[0]
|
42
|
+
|
43
|
+
if inner_struct.is_a?(InnerField)
|
44
|
+
explain_struct(inner_struct.rtype, padding + @padding)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
explain_attr(padding, e)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def explain_attr(padding, e)
|
53
|
+
type_name = e.rtype ? "(#{e.rtype})" : nil
|
54
|
+
@io.puts( @attr_template % [padding, e.name, e.explain])
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
module Depix
|
2
|
+
module Binary
|
3
|
+
module Fields
|
4
|
+
|
5
|
+
# Base class for a padded field in a struct
|
6
|
+
class Field
|
7
|
+
attr_accessor :name, # Field name
|
8
|
+
:length, # Field length in bytes, including any possible padding
|
9
|
+
:pattern, # The unpack pattern that defines the field
|
10
|
+
:req, # Is the field required?
|
11
|
+
:desc, # Field description
|
12
|
+
:rtype # To which Ruby type this has to be cast (and which type is accepted as value)
|
13
|
+
alias_method :req?, :req
|
14
|
+
|
15
|
+
# Hash init
|
16
|
+
def initialize(opts = {})
|
17
|
+
opts.each_pair {|k, v| send(k.to_s + '=', v) }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return a cleaned value (like a null-terminated string truncated up to null)
|
21
|
+
def clean(v)
|
22
|
+
v
|
23
|
+
end
|
24
|
+
|
25
|
+
# Show a nice textual explanation of the field
|
26
|
+
def explain
|
27
|
+
[rtype ? ("(%s)" % rtype) : nil, desc, (req? ? "- required" : nil)].compact.join(' ')
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return the actual values from the stack. The stack will begin on the element we need,
|
31
|
+
# so the default consumption is shift. Normally all fields shift the stack
|
32
|
+
# as they go, and if they contain nested substructs they will pop the stack as well
|
33
|
+
def consume!(stack)
|
34
|
+
clean(stack.shift)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Check that the passed value:
|
38
|
+
# a) Matches the Ruby type expected
|
39
|
+
# b) Fits into the slot
|
40
|
+
# c) Does not overflow
|
41
|
+
# When the validation fails should raise
|
42
|
+
def validate!(value)
|
43
|
+
raise "#{name} value required, but got nil in #{name}".strip if value.nil? && req?
|
44
|
+
raise "Value expected to be #{rtype} but was #{value.class}" if !value.nil? && rtype && !value.is_a?(rtype)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Pack a value passed into a string
|
48
|
+
def pack(value)
|
49
|
+
raise "No pattern defined for #{self}" unless pattern
|
50
|
+
if value.nil?
|
51
|
+
[self.class.const_get(:BLANK)].pack(pattern)
|
52
|
+
else
|
53
|
+
[value].pack(pattern)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# unit32 field
|
59
|
+
class U32Field < Field
|
60
|
+
BLANK = 0xFFFFFFFF
|
61
|
+
undef :length=, :pattern=
|
62
|
+
|
63
|
+
def pattern
|
64
|
+
"N"
|
65
|
+
end
|
66
|
+
|
67
|
+
def length
|
68
|
+
4
|
69
|
+
end
|
70
|
+
|
71
|
+
def clean(value)
|
72
|
+
value == BLANK ? nil : value
|
73
|
+
end
|
74
|
+
|
75
|
+
# Override - might be Bignum although cast to Integer sometimes
|
76
|
+
def validate!(value)
|
77
|
+
raise "#{name} value required, but got nil".strip if value.nil? && req?
|
78
|
+
raise "#{name} value expected to be #{rtype} but was #{value.class}" if !value.nil? && (!value.is_a?(Integer) && !value.is_a?(Bignum))
|
79
|
+
raise "#{name} value #{value} overflows" if !value.nil? && (value < 0 || value >= BLANK)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
# uint8 field
|
85
|
+
class U8Field < Field
|
86
|
+
undef :length=, :pattern=
|
87
|
+
|
88
|
+
BLANK = 0xFF
|
89
|
+
|
90
|
+
def pattern
|
91
|
+
"c"
|
92
|
+
end
|
93
|
+
|
94
|
+
def length
|
95
|
+
1
|
96
|
+
end
|
97
|
+
|
98
|
+
def rtype
|
99
|
+
Integer
|
100
|
+
end
|
101
|
+
|
102
|
+
def clean(v)
|
103
|
+
(v == BLANK || v == -1) ? nil : v
|
104
|
+
end
|
105
|
+
|
106
|
+
def validate!(value)
|
107
|
+
super(value)
|
108
|
+
raise "#{name} value #{value} out of bounds for 8 bit unsigned int".lstrip if (!value.nil? && (value < 0 || value >= BLANK))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Zero-padded filler, can be used to maintain offsets
|
113
|
+
class Filler < Field
|
114
|
+
undef :pattern=
|
115
|
+
def pattern
|
116
|
+
"x#{length ? length.to_i : 1}"
|
117
|
+
end
|
118
|
+
|
119
|
+
# Leave the stack alone since we skipped
|
120
|
+
def consume(stack)
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def pack(data)
|
125
|
+
raise "This is a filler, it cannot be reconstructed from a value"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# uint16 field
|
130
|
+
class U16Field < Field
|
131
|
+
BLANK = 0xFFFF
|
132
|
+
undef :length=, :pattern=
|
133
|
+
|
134
|
+
def pattern
|
135
|
+
"n"
|
136
|
+
end
|
137
|
+
|
138
|
+
def length
|
139
|
+
2
|
140
|
+
end
|
141
|
+
|
142
|
+
def rtype
|
143
|
+
Integer
|
144
|
+
end
|
145
|
+
|
146
|
+
def clean(v)
|
147
|
+
v == BLANK ? nil : v
|
148
|
+
end
|
149
|
+
|
150
|
+
def validate!(value)
|
151
|
+
super(value)
|
152
|
+
raise "#{name} value #{value} out of bounds for 16bit unsigned int" if (value < 0 || value >= BLANK)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# real32 field
|
157
|
+
class R32Field < Field
|
158
|
+
undef :length=, :pattern=
|
159
|
+
BLANK = 0xFFFFFFFF
|
160
|
+
|
161
|
+
def pattern
|
162
|
+
"g"
|
163
|
+
end
|
164
|
+
|
165
|
+
def clean(v)
|
166
|
+
v.nan? ? nil : v
|
167
|
+
end
|
168
|
+
|
169
|
+
def length
|
170
|
+
4
|
171
|
+
end
|
172
|
+
|
173
|
+
def rtype
|
174
|
+
Float
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# null-terminated string field with fixed padding
|
179
|
+
class CharField < Field
|
180
|
+
BLANK = "\0"
|
181
|
+
undef :pattern=
|
182
|
+
|
183
|
+
BLANKING_VALUES = [0x00.chr, 0xFF.chr]
|
184
|
+
BLANKING_PATTERNS = BLANKING_VALUES.inject([]) do | p, char |
|
185
|
+
p << /^([#{char}]+)/ << /([#{char}]+)$/mu
|
186
|
+
end
|
187
|
+
|
188
|
+
def initialize(opts = {})
|
189
|
+
super({:length => 1}.merge(opts))
|
190
|
+
end
|
191
|
+
|
192
|
+
def pattern
|
193
|
+
"A#{(length || 1).to_i}"
|
194
|
+
end
|
195
|
+
|
196
|
+
def clean(v)
|
197
|
+
if v == BLANK
|
198
|
+
nil
|
199
|
+
else
|
200
|
+
# Truncate everything from the null byte up
|
201
|
+
upto_nulb = v.split(0x00.chr).shift
|
202
|
+
(upto_nulb.nil? || upto_nulb.empty?) ? nil : upto_nulb
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def rtype
|
207
|
+
String
|
208
|
+
end
|
209
|
+
|
210
|
+
def validate!(value)
|
211
|
+
super(value)
|
212
|
+
raise "#{value} overflows the #{length} bytes allocated" if !value.nil? && value.length > length
|
213
|
+
end
|
214
|
+
|
215
|
+
def pack(value)
|
216
|
+
value.ljust(length, "\000") rescue ("\000" * length)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Wrapper for an array structure
|
221
|
+
class ArrayField < Field
|
222
|
+
attr_accessor :members
|
223
|
+
undef :length=, :pattern=
|
224
|
+
|
225
|
+
def length
|
226
|
+
members.inject(0){|_, s| _ + s.length }
|
227
|
+
end
|
228
|
+
|
229
|
+
def pattern
|
230
|
+
members.inject(''){|_, s| _ + s.pattern }
|
231
|
+
end
|
232
|
+
|
233
|
+
def consume!(stack)
|
234
|
+
members.map{|m| m.consume!(stack)}
|
235
|
+
end
|
236
|
+
|
237
|
+
def rtype
|
238
|
+
Array
|
239
|
+
end
|
240
|
+
|
241
|
+
def explain
|
242
|
+
return 'Empty array' if (!members || members.empty?)
|
243
|
+
tpl = "(Array of %d %s fields)" % [ members.length, members[0].rtype]
|
244
|
+
r = (req? ? "- required" : nil)
|
245
|
+
[tpl, desc, r].compact.join(' ')
|
246
|
+
end
|
247
|
+
|
248
|
+
def validate!(array)
|
249
|
+
raise "This value would overflow, #{array.length} elements passed but only #{members.length} fit" unless array.length <= members.length
|
250
|
+
raise "This value is required, but the array is empty" if req? && array.empty?
|
251
|
+
array.zip(members).map do | v, m |
|
252
|
+
m.validate!(v) unless (v.nil? && !m.req?)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def pack(values)
|
257
|
+
# For members that are present, get values. For members that are missing, fill with null bytes upto length.
|
258
|
+
# For values that are nil, skip packing
|
259
|
+
members.zip(values).map do |m, v|
|
260
|
+
if !m.req? && v.nil?
|
261
|
+
raise "#{m} needs to provide length" unless m.length
|
262
|
+
"\377" * m.length
|
263
|
+
else
|
264
|
+
v.respond_to?(:pack) ? v.pack : m.pack(v)
|
265
|
+
end
|
266
|
+
end.join
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Wrapper for a contained structure
|
271
|
+
class InnerField < Field
|
272
|
+
attr_accessor :cast
|
273
|
+
undef :length=, :pattern=
|
274
|
+
|
275
|
+
def length
|
276
|
+
cast.length
|
277
|
+
end
|
278
|
+
|
279
|
+
def pattern
|
280
|
+
cast.pattern
|
281
|
+
end
|
282
|
+
|
283
|
+
def consume!(stack)
|
284
|
+
cast.consume!(stack)
|
285
|
+
end
|
286
|
+
|
287
|
+
def rtype
|
288
|
+
cast
|
289
|
+
end
|
290
|
+
|
291
|
+
def validate!(value)
|
292
|
+
super(value)
|
293
|
+
cast.validate!(value) if cast.respond_to?(:validate!) && (!value.nil? || req?)
|
294
|
+
end
|
295
|
+
|
296
|
+
def pack(value)
|
297
|
+
cast.pack(value)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|