depix 1.1.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -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
@@ -1,18 +1,20 @@
1
- DPX_HEADER_STRUCTURE.txt
1
+ DPX_HEADER_STRUCTURE.rdoc
2
2
  History.txt
3
3
  Manifest.txt
4
- README.txt
4
+ README.rdoc
5
5
  Rakefile
6
6
  bin/depix-describe
7
7
  lib/depix.rb
8
- lib/depix/benchmark.rb
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
@@ -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[link:files/DPX_HEADER_STRUCTURE_txt.html]. It's
25
- a vanilla Ruby object with no extra methods except for the readers that have the same name as the specified
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.txt', 'w') {|f| f << RdocExplainer.new.get_rdoc_for(Depix::DPX) }
19
+ File.open('DPX_HEADER_STRUCTURE.rdoc', 'w') {|f| f << RdocExplainer.new.get_rdoc_for(Depix::DPX) }
17
20
  end
@@ -1,85 +1,24 @@
1
1
  require 'rubygems'
2
2
  require 'timecode'
3
3
 
4
- require File.dirname(__FILE__) + '/depix/dict'
5
- require File.dirname(__FILE__) + '/depix/structs'
6
- require File.dirname(__FILE__) + '/depix/compact_structs'
7
- require File.dirname(__FILE__) + '/depix/enums'
8
- require File.dirname(__FILE__) + '/depix/reader'
9
- require File.dirname(__FILE__) + '/depix/editor'
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 = '1.1.6'
17
+ VERSION = '2.0.0'
14
18
 
15
19
  class InvalidHeader < RuntimeError; end
16
20
 
17
- # Offers convenience access to a few common attributes bypassing the piecemeal structs
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