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