depix 1.1.6 → 2.0.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.
- 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/lib/depix/benchmark.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../depix'
|
2
|
-
|
3
|
-
require 'benchmark'
|
4
|
-
|
5
|
-
iter = 1000
|
6
|
-
|
7
|
-
puts "Reading DPX header #{iter} times, all data"
|
8
|
-
puts Benchmark.measure {
|
9
|
-
iter.times { Depix.from_file(File.dirname(__FILE__)+"/../../test/samples/026_FROM_HERO_TAPE_5-3-1_MOV.0029.dpx", false) }
|
10
|
-
}
|
11
|
-
|
12
|
-
puts "Reading DPX header #{iter} times, compact data"
|
13
|
-
puts Benchmark.measure {
|
14
|
-
iter.times { Depix.from_file(File.dirname(__FILE__)+"/../../test/samples/026_FROM_HERO_TAPE_5-3-1_MOV.0029.dpx", true) }
|
15
|
-
}
|
data/lib/depix/dict.rb
DELETED
@@ -1,531 +0,0 @@
|
|
1
|
-
module Depix
|
2
|
-
|
3
|
-
#:stopdoc:
|
4
|
-
|
5
|
-
=begin
|
6
|
-
A basic C structs library (only works by value).
|
7
|
-
Here's the basic mode of operation:
|
8
|
-
1) You define a struct, with a number of fields in it
|
9
|
-
3) Each field knows how big it is and how to produce a pattern to get it's value from the byte stream
|
10
|
-
by using Ruby's "pack/unpack". Each field thus provides an unpack pattern, and patterns are ordered
|
11
|
-
into a stack, starting with the first unpack pattern
|
12
|
-
4) When you parse some bytes using the struct, heres what will happen:
|
13
|
-
- An unpack pattern will be compiled from all of the fields composing the struct,
|
14
|
-
and it will be a single string. The string gets applied to the bytes passed to parse()
|
15
|
-
- An array of unpacked values returned by unpack is then passed to the struct's consumption engine,
|
16
|
-
which lets each field take as many items off the stack as it needs. A field might happily produce
|
17
|
-
4 items for unpacking and then take the same 4 items off the stack of parsed values. Or not.
|
18
|
-
- A new structure gets created and for every named field it defines an attr_accessor. When consuming,
|
19
|
-
the values returned by Field objects get set using the accessors (so accessors can be overridden too!)
|
20
|
-
5) When you save out the struct roughly the same happens but in reverse (readers are called per field,
|
21
|
-
then it's checked whether the data can be packed and fits into the alloted number of bytes, and then
|
22
|
-
one big array of values is composed and passed on to Array#pack)
|
23
|
-
=end
|
24
|
-
|
25
|
-
class Field
|
26
|
-
attr_accessor :name, # Field name
|
27
|
-
:length, # Field length in bytes, including any possible padding
|
28
|
-
:pattern, # The unpack pattern that defines the field
|
29
|
-
:req, # Is the field required?
|
30
|
-
:desc, # Field description
|
31
|
-
:rtype # To which Ruby type this has to be cast (and which type is accepted as value)
|
32
|
-
alias_method :req?, :req
|
33
|
-
|
34
|
-
# Hash init
|
35
|
-
def initialize(opts = {})
|
36
|
-
opts.each_pair {|k, v| send(k.to_s + '=', v) }
|
37
|
-
end
|
38
|
-
|
39
|
-
# Emit an unsigned int field
|
40
|
-
def self.emit_u32(o = {})
|
41
|
-
U32Field.new(o)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Emit a short int field
|
45
|
-
def self.emit_u8(o = {})
|
46
|
-
U8Field.new(o)
|
47
|
-
end
|
48
|
-
|
49
|
-
# Emit a double int field
|
50
|
-
def self.emit_u16(o = {})
|
51
|
-
U16Field.new(o)
|
52
|
-
end
|
53
|
-
|
54
|
-
# Emit a char field
|
55
|
-
def self.emit_char(o = {})
|
56
|
-
opts = {:length => 1}.merge(o)
|
57
|
-
CharField.new(opts)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Emit a float field
|
61
|
-
def self.emit_r32(o = {})
|
62
|
-
R32Field.new(o)
|
63
|
-
end
|
64
|
-
|
65
|
-
# Return a cleaned value (like a null-terminated string truncated up to null)
|
66
|
-
def clean(v)
|
67
|
-
v
|
68
|
-
end
|
69
|
-
|
70
|
-
# Show a nice textual explanation of the field
|
71
|
-
def explain
|
72
|
-
[rtype ? ("(%s)" % rtype) : nil, desc, (req? ? "- required" : nil)].compact.join(' ')
|
73
|
-
end
|
74
|
-
|
75
|
-
# Return the actual values from the stack. The stack will begin on the element we need,
|
76
|
-
# so the default consumption is shift. Normally all fields shift the stack
|
77
|
-
# as they go, and if they contain nested substructs they will pop the stack as well
|
78
|
-
def consume!(stack)
|
79
|
-
clean(stack.shift)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Check that the passed value:
|
83
|
-
# a) Matches the Ruby type expected
|
84
|
-
# b) Fits into the slot
|
85
|
-
# c) Does not overflow
|
86
|
-
# When the validation fails should raise
|
87
|
-
def validate!(value)
|
88
|
-
raise "#{name} value required, but got nil in #{name}".strip if value.nil? && req?
|
89
|
-
raise "Value expected to be #{rtype} but was #{value.class}" if !value.nil? && rtype && !value.is_a?(rtype)
|
90
|
-
end
|
91
|
-
|
92
|
-
# Pack a value passed into a string
|
93
|
-
def pack(value)
|
94
|
-
raise "No pattern defined for #{self}" unless pattern
|
95
|
-
if value.nil?
|
96
|
-
[self.class.const_get(:BLANK)].pack(pattern)
|
97
|
-
else
|
98
|
-
[value].pack(pattern)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
class U32Field < Field
|
104
|
-
BLANK = 0xFFFFFFFF
|
105
|
-
undef :length=, :pattern=
|
106
|
-
|
107
|
-
def pattern
|
108
|
-
"N"
|
109
|
-
end
|
110
|
-
|
111
|
-
def length
|
112
|
-
4
|
113
|
-
end
|
114
|
-
|
115
|
-
def clean(value)
|
116
|
-
value == BLANK ? nil : value
|
117
|
-
end
|
118
|
-
|
119
|
-
# Override - might be Bignum although cast to Integer sometimes
|
120
|
-
def validate!(value)
|
121
|
-
raise "#{name} value required, but got nil".strip if value.nil? && req?
|
122
|
-
raise "#{name} value expected to be #{rtype} but was #{value.class}" if !value.nil? && (!value.is_a?(Integer) && !value.is_a?(Bignum))
|
123
|
-
raise "#{name} value #{value} overflows" if !value.nil? && (value < 0 || value >= BLANK)
|
124
|
-
end
|
125
|
-
|
126
|
-
end
|
127
|
-
|
128
|
-
class U8Field < Field
|
129
|
-
undef :length=, :pattern=
|
130
|
-
|
131
|
-
BLANK = 0xFF
|
132
|
-
|
133
|
-
def pattern
|
134
|
-
"c"
|
135
|
-
end
|
136
|
-
|
137
|
-
def length
|
138
|
-
1
|
139
|
-
end
|
140
|
-
|
141
|
-
def rtype
|
142
|
-
Integer
|
143
|
-
end
|
144
|
-
|
145
|
-
def clean(v)
|
146
|
-
(v == BLANK || v == -1) ? nil : v
|
147
|
-
end
|
148
|
-
|
149
|
-
def validate!(value)
|
150
|
-
super(value)
|
151
|
-
raise "#{name} value #{value} out of bounds for 8 bit unsigned int".lstrip if (!value.nil? && (value < 0 || value >= BLANK))
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
class Filler < Field
|
156
|
-
undef :pattern=
|
157
|
-
def pattern
|
158
|
-
"x#{length ? length.to_i : 1}"
|
159
|
-
end
|
160
|
-
|
161
|
-
# Leave the stack alone since we skipped
|
162
|
-
def consume(stack)
|
163
|
-
nil
|
164
|
-
end
|
165
|
-
|
166
|
-
def pack(data)
|
167
|
-
raise "This is a filler, it cannot be reconstructed from a value"
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
class U16Field < Field
|
172
|
-
BLANK = 0xFFFF
|
173
|
-
undef :length=, :pattern=
|
174
|
-
|
175
|
-
def pattern
|
176
|
-
"n"
|
177
|
-
end
|
178
|
-
|
179
|
-
def length
|
180
|
-
2
|
181
|
-
end
|
182
|
-
|
183
|
-
def rtype
|
184
|
-
Integer
|
185
|
-
end
|
186
|
-
|
187
|
-
def clean(v)
|
188
|
-
v == BLANK ? nil : v
|
189
|
-
end
|
190
|
-
|
191
|
-
def validate!(value)
|
192
|
-
super(value)
|
193
|
-
raise "#{name} value #{value} out of bounds for 16bit unsigned int" if (value < 0 || value >= BLANK)
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
class R32Field < Field
|
198
|
-
undef :length=, :pattern=
|
199
|
-
BLANK = 0xFFFFFFFF
|
200
|
-
|
201
|
-
def pattern
|
202
|
-
"g"
|
203
|
-
end
|
204
|
-
|
205
|
-
def clean(v)
|
206
|
-
v.nan? ? nil : v
|
207
|
-
end
|
208
|
-
|
209
|
-
def length
|
210
|
-
4
|
211
|
-
end
|
212
|
-
|
213
|
-
def rtype
|
214
|
-
Float
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
class CharField < Field
|
219
|
-
BLANK = "\0"
|
220
|
-
undef :pattern=
|
221
|
-
|
222
|
-
BLANKING_VALUES = [0x00.chr, 0xFF.chr]
|
223
|
-
BLANKING_PATTERNS = BLANKING_VALUES.inject([]) do | p, char |
|
224
|
-
p << /^([#{char}]+)/ << /([#{char}]+)$/mu
|
225
|
-
end
|
226
|
-
|
227
|
-
def pattern
|
228
|
-
"A#{(length || 1).to_i}"
|
229
|
-
end
|
230
|
-
|
231
|
-
def clean(v)
|
232
|
-
if v == BLANK
|
233
|
-
nil
|
234
|
-
else
|
235
|
-
# Truncate everything from the null byte up
|
236
|
-
upto_nulb = v.split(0x00.chr).shift
|
237
|
-
(upto_nulb.nil? || upto_nulb.empty?) ? nil : upto_nulb
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
def rtype
|
242
|
-
String
|
243
|
-
end
|
244
|
-
|
245
|
-
def validate!(value)
|
246
|
-
super(value)
|
247
|
-
raise "#{value} overflows the #{length} bytes allocated" if !value.nil? && value.length > length
|
248
|
-
end
|
249
|
-
|
250
|
-
def pack(value)
|
251
|
-
value.ljust(length, "\000") rescue ("\000" * length)
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
# Wrapper for an array structure
|
256
|
-
class ArrayField < Field
|
257
|
-
attr_accessor :members
|
258
|
-
undef :length=, :pattern=
|
259
|
-
|
260
|
-
def length
|
261
|
-
members.inject(0){|_, s| _ + s.length }
|
262
|
-
end
|
263
|
-
|
264
|
-
def pattern
|
265
|
-
members.inject(''){|_, s| _ + s.pattern }
|
266
|
-
end
|
267
|
-
|
268
|
-
def consume!(stack)
|
269
|
-
members.map{|m| m.consume!(stack)}
|
270
|
-
end
|
271
|
-
|
272
|
-
def rtype
|
273
|
-
Array
|
274
|
-
end
|
275
|
-
|
276
|
-
def explain
|
277
|
-
return 'Empty array' if (!members || members.empty?)
|
278
|
-
tpl = "(Array of %d %s fields)" % [ members.length, members[0].rtype]
|
279
|
-
r = (req? ? "- required" : nil)
|
280
|
-
[tpl, desc, r].compact.join(' ')
|
281
|
-
end
|
282
|
-
|
283
|
-
def validate!(array)
|
284
|
-
raise "This value would overflow, #{array.length} elements passed but only #{members.length} fit" unless array.length <= members.length
|
285
|
-
raise "This value is required, but the array is empty" if req? && array.empty?
|
286
|
-
array.zip(members).map do | v, m |
|
287
|
-
m.validate!(v) unless (v.nil? && !m.req?)
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def pack(values)
|
292
|
-
# For members that are present, get values. For members that are missing, fill with null bytes upto length.
|
293
|
-
# For values that are nil, skip packing
|
294
|
-
members.zip(values).map do |m, v|
|
295
|
-
if !m.req? && v.nil?
|
296
|
-
raise "#{m} needs to provide length" unless m.length
|
297
|
-
"\377" * m.length
|
298
|
-
else
|
299
|
-
v.respond_to?(:pack) ? v.pack : m.pack(v)
|
300
|
-
end
|
301
|
-
end.join
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
# Wrapper for a contained structure
|
306
|
-
class InnerField < Field
|
307
|
-
attr_accessor :cast
|
308
|
-
undef :length=, :pattern=
|
309
|
-
|
310
|
-
def length
|
311
|
-
cast.length
|
312
|
-
end
|
313
|
-
|
314
|
-
def pattern
|
315
|
-
cast.pattern
|
316
|
-
end
|
317
|
-
|
318
|
-
def consume!(stack)
|
319
|
-
cast.consume!(stack)
|
320
|
-
end
|
321
|
-
|
322
|
-
def rtype
|
323
|
-
cast
|
324
|
-
end
|
325
|
-
|
326
|
-
def validate!(value)
|
327
|
-
super(value)
|
328
|
-
cast.validate!(value) if cast.respond_to?(:validate!) && (!value.nil? || req?)
|
329
|
-
end
|
330
|
-
|
331
|
-
def pack(value)
|
332
|
-
cast.pack(value)
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
# Base class for a struct. Could also be implemented as a module actually
|
337
|
-
class Dict
|
338
|
-
DEF_OPTS = { :req => false, :desc => nil }
|
339
|
-
|
340
|
-
class << self
|
341
|
-
|
342
|
-
# Get the array of fields defined in this struct
|
343
|
-
def fields
|
344
|
-
@fields ||= []
|
345
|
-
end
|
346
|
-
|
347
|
-
# Validate a passed instance
|
348
|
-
def validate!(instance)
|
349
|
-
fields.each do | f |
|
350
|
-
f.validate!(instance.send(f.name)) if f.name
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
# Define a 4-byte unsigned integer
|
355
|
-
def u32(name, *extras)
|
356
|
-
count, opts = count_and_opts_from(extras)
|
357
|
-
attr_accessor name
|
358
|
-
fields << Field.emit_u32( {:name => name }.merge(opts) )
|
359
|
-
end
|
360
|
-
|
361
|
-
# Define a double-width unsigned integer
|
362
|
-
def u16(name, *extras)
|
363
|
-
count, opts = count_and_opts_from(extras)
|
364
|
-
attr_accessor name
|
365
|
-
fields << Field.emit_u16( {:name => name }.merge(opts) )
|
366
|
-
end
|
367
|
-
|
368
|
-
|
369
|
-
# Define a small unsigned integer
|
370
|
-
def u8(name, *extras)
|
371
|
-
count, opts = count_and_opts_from(extras)
|
372
|
-
attr_accessor name
|
373
|
-
fields << Field.emit_u8( {:name => name }.merge(opts) )
|
374
|
-
end
|
375
|
-
|
376
|
-
# Define a real number
|
377
|
-
def r32(name, *extras)
|
378
|
-
count, opts = count_and_opts_from(extras)
|
379
|
-
attr_accessor name
|
380
|
-
fields << Field.emit_r32( {:name => name}.merge(opts) )
|
381
|
-
end
|
382
|
-
|
383
|
-
# Define an array of values
|
384
|
-
def array(name, mapped_to, *extras)
|
385
|
-
count, opts = count_and_opts_from(extras)
|
386
|
-
attr_accessor name
|
387
|
-
|
388
|
-
a = ArrayField.new({:name => name}.merge(opts))
|
389
|
-
a.members = if mapped_to.is_a?(Class) # Array of structs
|
390
|
-
[InnerField.new(:cast => mapped_to)] * count
|
391
|
-
else
|
392
|
-
[Field.send("emit_#{mapped_to}")] * count
|
393
|
-
end
|
394
|
-
yield a.members if block_given?
|
395
|
-
fields << a
|
396
|
-
end
|
397
|
-
|
398
|
-
# Define a nested struct
|
399
|
-
def inner(name, mapped_to, *extras)
|
400
|
-
count, opts = count_and_opts_from(extras)
|
401
|
-
attr_accessor name
|
402
|
-
fields << InnerField.new({:name => name, :cast => mapped_to}.merge(opts))
|
403
|
-
end
|
404
|
-
|
405
|
-
# Define a char field
|
406
|
-
def char(name, *extras)
|
407
|
-
count, opts = count_and_opts_from(extras)
|
408
|
-
attr_accessor name
|
409
|
-
fields << Field.emit_char( {:name => name, :length => count}.merge(opts) )
|
410
|
-
end
|
411
|
-
|
412
|
-
# Get the pattern that will be used to unpack this structure and all of it's descendants
|
413
|
-
def pattern
|
414
|
-
fields.map{|f| f.pattern }.join
|
415
|
-
end
|
416
|
-
|
417
|
-
# Get the pattern that will be used to unpack this structure and all of it's descendants
|
418
|
-
# from a buffer with pieces in little-endian byte order
|
419
|
-
def pattern_le
|
420
|
-
pattern.tr("gN", "eV")
|
421
|
-
end
|
422
|
-
|
423
|
-
# How many bytes are needed to complete this structure
|
424
|
-
def length
|
425
|
-
fields.inject(0){|_, s| _ + s.length }
|
426
|
-
end
|
427
|
-
|
428
|
-
# Consume a stack of unpacked values, letting each field decide how many to consume
|
429
|
-
def consume!(stack_of_unpacked_values)
|
430
|
-
new_item = new
|
431
|
-
@fields.each do | field |
|
432
|
-
new_item.send("#{field.name}=", field.consume!(stack_of_unpacked_values)) unless field.name.nil?
|
433
|
-
end
|
434
|
-
new_item
|
435
|
-
end
|
436
|
-
|
437
|
-
# Apply this structure to data in the string, returning an instance of this structure with fields completed
|
438
|
-
def apply!(string)
|
439
|
-
consume!(string.unpack(pattern))
|
440
|
-
end
|
441
|
-
|
442
|
-
# Apply this structure to data in the string, returning an instance of this structure with fields completed
|
443
|
-
# assume little-endian fields
|
444
|
-
def apply_le!(string)
|
445
|
-
consume!(string.unpack(pattern_le))
|
446
|
-
end
|
447
|
-
|
448
|
-
# Get a class that would parse just the same, preserving only the fields passed in the array. This speeds
|
449
|
-
# up parsing because we only extract and conform the fields that we need
|
450
|
-
def only(*field_names)
|
451
|
-
distillate = fields.inject([]) do | m, f |
|
452
|
-
if field_names.include?(f.name) # preserve
|
453
|
-
m.push(f)
|
454
|
-
else # create filler
|
455
|
-
unless m[-1].is_a?(Filler)
|
456
|
-
m.push(Filler.new(:length => f.length))
|
457
|
-
else
|
458
|
-
m[-1].length += f.length
|
459
|
-
end
|
460
|
-
m
|
461
|
-
end
|
462
|
-
end
|
463
|
-
|
464
|
-
anon = Class.new(self)
|
465
|
-
anon.fields.replace(distillate)
|
466
|
-
only_items = distillate.map{|n| n.name }
|
467
|
-
|
468
|
-
anon
|
469
|
-
end
|
470
|
-
|
471
|
-
# Get an opaque struct based on this one, that will consume exactly as many bytes as this
|
472
|
-
# structure would occupy, but discard them instead
|
473
|
-
def filler
|
474
|
-
only([])
|
475
|
-
end
|
476
|
-
|
477
|
-
# Pack the instance of this struct
|
478
|
-
def pack(instance, buffer = nil)
|
479
|
-
|
480
|
-
# Preallocate a buffer just as big as me since we want everything to remain at fixed offsets
|
481
|
-
buffer ||= ("\000" * length)
|
482
|
-
|
483
|
-
# If the instance is nil return pure padding
|
484
|
-
return buffer if instance.nil?
|
485
|
-
|
486
|
-
# Now for the important stuff. For each field that we have, replace a piece at offsets in the buffer
|
487
|
-
# with the packed results, skipping fillers
|
488
|
-
fields.each_with_index do | f, i |
|
489
|
-
|
490
|
-
# Skip blanking, we just dont touch it. TODO - test!
|
491
|
-
next if f.is_a?(Filler)
|
492
|
-
|
493
|
-
# Where should we put that value?
|
494
|
-
offset = fields[0...i].inject(0){|_, s| _ + s.length }
|
495
|
-
|
496
|
-
val = instance.send(f.name)
|
497
|
-
|
498
|
-
# Validate the passed value using the format the field supports
|
499
|
-
f.validate!(val)
|
500
|
-
|
501
|
-
packed = f.pack(val)
|
502
|
-
|
503
|
-
# Signal offset violation
|
504
|
-
raise "Improper length for #{f.name} - packed #{packed.length} bytes but #{f.length} is required to fill the slot" if packed.length != f.length
|
505
|
-
|
506
|
-
buffer[offset...(offset+f.length)] = packed
|
507
|
-
end
|
508
|
-
raise "Resulting buffer not the same length, expected #{length} bytes but compued #{buffer.length}" if buffer.length != length
|
509
|
-
buffer
|
510
|
-
end
|
511
|
-
|
512
|
-
private
|
513
|
-
|
514
|
-
# extract_options! on a diet
|
515
|
-
def count_and_opts_from(args)
|
516
|
-
options, count = (args[-1].is_a?(Hash) ? DEF_OPTS.merge(args.pop) : DEF_OPTS), (args.shift || 1)
|
517
|
-
[count, options]
|
518
|
-
end
|
519
|
-
end # End class methods
|
520
|
-
|
521
|
-
def []=(field, value)
|
522
|
-
send("#{field}=", value)
|
523
|
-
end
|
524
|
-
|
525
|
-
def [](field)
|
526
|
-
send(field)
|
527
|
-
end
|
528
|
-
end
|
529
|
-
|
530
|
-
#:startdoc:
|
531
|
-
end
|