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/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
|