depix 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,8 @@
1
- === 1.0.2 / 2008-12-23
1
+ === 1.0.4 / 2008-12-23
2
+
3
+ * Add brief inspect
4
+
5
+ === 1.0.3 / 2008-12-23
2
6
 
3
7
  * Do not cleanup null bytes and terminators in the middle of char[] attributes (something useful might be in there)
4
8
 
@@ -7,9 +7,13 @@ bin/depix-describe
7
7
  lib/depix.rb
8
8
  lib/depix/struct_explainer.rb
9
9
  lib/depix/structs.rb
10
+ lib/depix/benchmark.rb
10
11
  lib/depix/compact_structs.rb
11
12
  lib/depix/enums.rb
12
13
  lib/depix/dict.rb
14
+ lib/depix/reader.rb
15
+ lib/depix/editor.rb
16
+ test/test_dict.rb
13
17
  test/test_depix.rb
14
18
  test/samples/E012_P001_L000002_lin.0001.dpx
15
19
  test/samples/E012_P001_L000002_lin.0002.dpx
data/README.txt CHANGED
@@ -4,12 +4,22 @@
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- Read DPX file metadata
7
+ Read and write DPX file metadata
8
8
 
9
9
  == SYNOPSIS:
10
10
 
11
+ Reading headers
12
+
11
13
  meta = Depix.from_file(dpx_file_path)
12
14
  puts meta.time_code #=> 10:00:00:02
15
+
16
+ Writing headers
17
+
18
+ editor = Depix::Editor.new(dpx_file_path)
19
+
20
+ # Advance the time code by one frame and save
21
+ editor.headers.time_code = editor.headers.time_code + 1
22
+ editor.commit!
13
23
 
14
24
  The data returned is described in the DPX_HEADER_STRUCTURE[link:files/DPX_HEADER_STRUCTURE_txt.html]. It's
15
25
  a vanilla Ruby object with no extra methods except for the readers that have the same name as the specified
@@ -19,11 +29,15 @@ The gem also contains an executable called depix-desribe which can be used from
19
29
 
20
30
  $book depix-describe 001_PTAPE_001.001.dpx
21
31
 
22
- == NOTES:
32
+ for a long description or
33
+
34
+ $book depix-describe -s 001_PTAPE_001.001.dpx
23
35
 
24
- In the future there will be a possibility to modify and commit the headers, but it's not a priority at this time.
36
+ for a short description
37
+
38
+ == NOTES:
25
39
 
26
- Autodesk IFFS systems write the reel name for the file to the orientation.device field
40
+ Autodesk IFFS systems write the reel name for the file to the orientation.device field, some scanners write it into user data.
27
41
 
28
42
  == REQUIREMENTS:
29
43
 
@@ -10,13 +10,24 @@ OptionParser.new do |opts|
10
10
  opts.on("-c", "--compact", "Compact output (only fields that change per frame)") do |v|
11
11
  options[:compact] = true
12
12
  end
13
+
14
+ opts.on("-s", "--synthetics", "Output only synthetic fields (like time code and aspect)") do |v|
15
+ options[:synthetics] = true
16
+ end
17
+
13
18
  end.parse!
14
19
 
15
20
  ARGV.each do | file |
16
- puts "Describing DPX #{file}. Empty elements are omitted.\n\n"
17
- puts "===================================================\n\n"
21
+ puts "Describing DPX #{file}. Empty elements are omitted."
22
+ puts "===================================================\n"
18
23
  begin
19
- puts Depix.describe_file(file, options[:compact])
24
+ if options[:synthetics]
25
+ puts Depix.describe_brief(file)
26
+ elsif options[:compact]
27
+ puts Depix.describe_file(file, true)
28
+ else
29
+ puts Depix.describe_file(file)
30
+ end
20
31
  rescue Depix::InvalidHeader
21
32
  puts " - Invalid header data"
22
33
  end
@@ -1,4 +1,3 @@
1
- require 'stringio'
2
1
  require 'rubygems'
3
2
  require 'timecode'
4
3
 
@@ -6,14 +5,18 @@ require File.dirname(__FILE__) + '/depix/dict'
6
5
  require File.dirname(__FILE__) + '/depix/structs'
7
6
  require File.dirname(__FILE__) + '/depix/compact_structs'
8
7
  require File.dirname(__FILE__) + '/depix/enums'
8
+ require File.dirname(__FILE__) + '/depix/reader'
9
+ require File.dirname(__FILE__) + '/depix/editor'
10
+
9
11
 
10
12
  module Depix
11
- VERSION = '1.0.3'
13
+ VERSION = '1.0.4'
12
14
 
13
15
  class InvalidHeader < RuntimeError; end
14
16
 
15
17
  # Offers convenience access to a few common attributes bypassing the piecemeal structs
16
18
  module Synthetics
19
+
17
20
  def keycode
18
21
  [film.id, film.type, film.offset, film.prefix, film.count].compact.join(' ')
19
22
  end
@@ -21,13 +24,23 @@ module Depix
21
24
  # Return the flame reel name. The data after the first null byte is not meant to be seen and is used by Flame internally
22
25
  # as it seems
23
26
  def flame_reel
24
- orientation.device.split("\000").shift
27
+ orientation.device.split(0x00.chr).shift
28
+ end
29
+
30
+ # Assign reel name
31
+ def flame_reel=(new_reel)
32
+ orientation.device = new_reel
25
33
  end
26
34
 
27
35
  def time_code
28
36
  Timecode.from_uint(television.time_code) #, film.frame_rate)
29
37
  end
30
38
 
39
+ # Assign frame rate and timecode from a Timecode object
40
+ def time_code=(new_tc)
41
+ television.time_code, film.frame_rate = new_tc.to_uint, new_tc.fps
42
+ end
43
+
31
44
  # Get the name of the transfer function (Linear, Logarithmic, ...)
32
45
  def colorimetric
33
46
  COLORIMETRIC.invert[image.image_elements[0].colorimetric]
@@ -38,6 +51,11 @@ module Depix
38
51
  COMPONENT_TYPE.invert[image.image_elements[0].descriptor]
39
52
  end
40
53
 
54
+ # Aspect in it's traditional repr
55
+ def aspect
56
+ "%.2f" % (orientation.aspect_ratio[0].to_f / orientation.aspect_ratio[1].to_f)
57
+ end
58
+
41
59
  # Is this DPX file little-endian? This would be an exception, but still useful
42
60
  def le?
43
61
  file.magic == 'XPDS'
@@ -65,78 +83,9 @@ module Depix
65
83
  Reader.new.describe_file(path, compact)
66
84
  end
67
85
 
68
- class Reader
69
-
70
- # Returns a printable report on all the headers present in the file at the path passed
71
- def describe_file(path, compact = false)
72
- header = File.open(path, 'r') { |f| f.read(DPX.length) }
73
- describe_struct(parse(header, false))
74
- end
75
-
76
- def from_file(path, compact)
77
- header = File.open(path, 'r') { |f| f.read(DPX.length) }
78
- begin
79
- parse(header, compact)
80
- rescue InvalidHeader => e
81
- raise InvalidHeader, "Invalid header in file #{path}"
82
- end
83
- end
84
-
85
- # The hear of Depix
86
- def parse(data, compact)
87
- magic = data[0..3]
88
-
89
- raise InvalidHeader unless %w( SDPX XPDS).include?(magic)
90
-
91
- struct = compact ? CompactDPX : DPX
92
-
93
- is_be = (magic == "SDPX")
94
- version_check = FileInfo.only(:magic, :version)
95
-
96
- result = begin
97
- if is_be
98
- version_check.consume!(data.unpack(version_check.pattern))
99
- else
100
- version_check.consume!(data.unpack(make_le(version_check.pattern)))
101
- end
102
- rescue ArgumentError
103
- raise InvalidHeader
104
- end
105
-
106
- raise InvalidHeader unless result.version == "V1.0"
107
-
108
- template = is_be ? DPX.pattern : make_le(DPX.pattern)
109
- struct.consume!(data.unpack(struct.pattern))
110
- end
111
-
112
- # Describe a filled DPX structure
113
- def describe_struct(result, pad_offset = 0)
114
- result.class.fields.inject([]) do | info, field |
115
- value = result.send(field.name)
116
- parts = []
117
- if value
118
- parts << field.desc if field.desc
119
- parts << if field.is_a?(InnerField)
120
- describe_struct(value, pad_offset + 1)
121
- elsif field.is_a?(ArrayField)
122
- # Exception for image elements
123
- value = result.image_elements[0...result.number_elements] if field.name == :image_elements
124
- value.map { | v | v.is_a?(Dict) ? describe_struct(v, pad_offset + 2) : v }
125
- else
126
- value
127
- end
128
- end
129
- if parts.any?
130
- info << parts.join(' ')
131
- end
132
- info
133
- end.map{|e| (' ' * pad_offset) + e }.join("\n")
134
- end
135
-
136
- # Convert an unpack pattern to LE
137
- def make_le(pattern)
138
- pattern.gsub(/n/, "v").gsub(/N/, "V").gsub(/g/, "f")
139
- end
140
-
86
+ # Return a formatted description of the DPX file at path, showing only synthetic attributes
87
+ def self.describe_brief(path)
88
+ Reader.new.describe_synthetics_of_struct(from_file(path))
141
89
  end
90
+
142
91
  end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/../depix'
2
+
3
+ require 'benchmark'
4
+
5
+ iter = 10000
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
+ }
@@ -50,6 +50,26 @@ module Depix
50
50
  def consume!(stack)
51
51
  clean(stack.shift)
52
52
  end
53
+
54
+ # Check that the passed value:
55
+ # a) Matches the Ruby type expected
56
+ # b) Fits into the slot
57
+ # c) Does not overflow
58
+ # When the validation fails should raise
59
+ def validate!(value)
60
+ raise "#{name} value required, but got nil in #{name}".strip if value.nil? && req?
61
+ raise "Value expected to be #{rtype} but was #{value.class}" if !value.nil? && rtype && !value.is_a?(rtype)
62
+ end
63
+
64
+ # Pack a value passed into a string
65
+ def pack(value)
66
+ raise "No pattern defined for #{self}" unless pattern
67
+ if value.nil?
68
+ [self.class.const_get(:BLANK)].pack(pattern)
69
+ else
70
+ [value].pack(pattern)
71
+ end
72
+ end
53
73
  end
54
74
 
55
75
  class U32Field < Field
@@ -67,6 +87,14 @@ module Depix
67
87
  def clean(value)
68
88
  value == BLANK ? nil : value
69
89
  end
90
+
91
+ # Override - might be Bignum although cast to Integer sometimes
92
+ def validate!(value)
93
+ raise "#{name} value required, but got nil".strip if value.nil? && req?
94
+ raise "#{name} value expected to be #{rtype} but was #{value.class}" if !value.nil? && (!value.is_a?(Integer) && !value.is_a?(Bignum))
95
+ raise "#{name} value #{value} overflows" if !value.nil? && (value < 0 || value >= BLANK)
96
+ end
97
+
70
98
  end
71
99
 
72
100
  class U8Field < Field
@@ -87,7 +115,12 @@ module Depix
87
115
  end
88
116
 
89
117
  def clean(v)
90
- v == BLANK ? nil : v
118
+ (v == BLANK || v == -1) ? nil : v
119
+ end
120
+
121
+ def validate!(value)
122
+ super(value)
123
+ raise "#{name} value #{value} out of bounds for 8 bit unsigned int".lstrip if (!value.nil? && (value < 0 || value >= BLANK))
91
124
  end
92
125
  end
93
126
 
@@ -101,6 +134,10 @@ module Depix
101
134
  def consume(stack)
102
135
  nil
103
136
  end
137
+
138
+ def pack(data)
139
+ raise "This is a filler, it cannot be reconstructed from a value"
140
+ end
104
141
  end
105
142
 
106
143
  class U16Field < Field
@@ -122,10 +159,16 @@ module Depix
122
159
  def clean(v)
123
160
  v == BLANK ? nil : v
124
161
  end
162
+
163
+ def validate!(value)
164
+ super(value)
165
+ raise "#{name} value #{value} out of bounds for 16bit unsigned int" if (value < 0 || value >= BLANK)
166
+ end
125
167
  end
126
168
 
127
169
  class R32Field < Field
128
170
  undef :length=, :pattern=
171
+ BLANK = 0xFFFFFFFF
129
172
 
130
173
  def pattern
131
174
  "g"
@@ -169,6 +212,15 @@ module Depix
169
212
  def rtype
170
213
  String
171
214
  end
215
+
216
+ def validate!(value)
217
+ super(value)
218
+ raise "#{value} overflows the #{length} bytes allocated" if !value.nil? && value.length > length
219
+ end
220
+
221
+ def pack(value)
222
+ value.ljust(length, "\000") rescue ("\000" * length)
223
+ end
172
224
  end
173
225
 
174
226
  # Wrapper for an array structure
@@ -198,6 +250,27 @@ module Depix
198
250
  r = (req? ? "- required" : nil)
199
251
  [tpl, desc, r].compact.join(' ')
200
252
  end
253
+
254
+ def validate!(array)
255
+ raise "This value would overflow, #{array.length} elements passed but only #{members.length} fit" unless array.length <= members.length
256
+ raise "This value is required, but the array is empty" if req? && array.empty?
257
+ array.zip(members).map do | v, m |
258
+ m.validate!(v) unless (v.nil? && !m.req?)
259
+ end
260
+ end
261
+
262
+ def pack(values)
263
+ # For members that are present, get values. For members that are missing, fill with null bytes upto length.
264
+ # For values that are nil, skip packing
265
+ members.zip(values).map do |m, v|
266
+ if !m.req? && v.nil?
267
+ raise "#{m} needs to provide length" unless m.length
268
+ "\377" * m.length
269
+ else
270
+ v.respond_to?(:pack) ? v.pack : m.pack(v)
271
+ end
272
+ end.join
273
+ end
201
274
  end
202
275
 
203
276
  # Wrapper for a contained structure
@@ -220,6 +293,15 @@ module Depix
220
293
  def rtype
221
294
  cast
222
295
  end
296
+
297
+ def validate!(value)
298
+ super(value)
299
+ cast.validate!(value) if cast.respond_to?(:validate!) && (!value.nil? || req?)
300
+ end
301
+
302
+ def pack(value)
303
+ cast.pack(value)
304
+ end
223
305
  end
224
306
 
225
307
  # Base class for a struct. Could also be implemented as a module actually
@@ -232,7 +314,14 @@ module Depix
232
314
  def fields
233
315
  @fields ||= []
234
316
  end
235
-
317
+
318
+ # Validate a passed instance
319
+ def validate!(instance)
320
+ fields.each do | f |
321
+ f.validate!(instance.send(f.name)) if f.name
322
+ end
323
+ end
324
+
236
325
  # Define a 4-byte unsigned integer
237
326
  def u32(name, *extras)
238
327
  count, opts = count_and_opts_from(extras)
@@ -273,6 +362,7 @@ module Depix
273
362
  else
274
363
  [Field.send("emit_#{mapped_to}")] * count
275
364
  end
365
+ yield a.members if block_given?
276
366
  fields << a
277
367
  end
278
368
 
@@ -343,6 +433,41 @@ module Depix
343
433
  only([])
344
434
  end
345
435
 
436
+ # Pack the instance of this struct
437
+ def pack(instance, buffer = nil)
438
+
439
+ # Preallocate a buffer just as big as me since we want everything to remain at fixed offsets
440
+ buffer ||= ("\000" * length)
441
+
442
+ # If the instance is nil return pure padding
443
+ return buffer if instance.nil?
444
+
445
+ # Now for the important stuff. For each field that we have, replace a piece at offsets in the buffer
446
+ # with the packed results, skipping fillers
447
+ fields.each_with_index do | f, i |
448
+
449
+ # Skip blanking, we just dont touch it. TODO - test!
450
+ next if f.is_a?(Filler)
451
+
452
+ # Where should we put that value?
453
+ offset = fields[0...i].inject(0){|_, s| _ + s.length }
454
+
455
+ val = instance.send(f.name)
456
+
457
+ # Validate the passed value using the format the field supports
458
+ f.validate!(val)
459
+
460
+ packed = f.pack(val)
461
+
462
+ # Signal offset violation
463
+ raise "Improper length for #{f.name} - packed #{packed.length} bytes but #{f.length} is required to fill the slot" if packed.length != f.length
464
+
465
+ buffer[offset...(offset+f.length)] = packed
466
+ end
467
+ raise "Resulting buffer not the same length, expected #{length} bytes but compued #{buffer.length}" if buffer.length != length
468
+ buffer
469
+ end
470
+
346
471
  private
347
472
 
348
473
  # extract_options! on a diet
@@ -0,0 +1,32 @@
1
+ module Depix
2
+ # Used to edit DPX headers. Create an Editor object and pass the path to the file to it. Change the headers variable to contain the edited
3
+ # DPX headers and call commit!. Note that the DPX header will be overwritten in place - if you want to save another version you need to manage it yourself
4
+ class Editor
5
+
6
+ # Stores the path to file
7
+ attr_reader :path
8
+
9
+ # Stores the Depix::DPX object with headers
10
+ attr_accessor :headers
11
+
12
+ # Create a new editor for the file at path
13
+ def initialize(file_path)
14
+ @path = file_path
15
+ @headers = Depix.from_file(@path)
16
+ end
17
+
18
+ # Save the headers to file at path, overwriting the old ones
19
+ def commit!
20
+ raise "No headers" unless @headers
21
+ raise "Cannot pack LE headers" if @headers.le?
22
+ packed = @headers.class.pack(@headers)
23
+
24
+ # Validate that we can unpack first - what if something went wrong?
25
+ Depix::Reader.new.parse(packed, false)
26
+
27
+ File.open(@path, 'rb+') do | f |
28
+ f.seek(0, IO::SEEK_SET); f.write(packed)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,83 @@
1
+ module Depix
2
+ class Reader
3
+
4
+ # Returns a printable report on all the headers present in the file at the path passed
5
+ def describe_file(path, compact = false)
6
+ header = File.open(path, 'r') { |f| f.read(DPX.length) }
7
+ struct = parse(header, false)
8
+ describe_struct(struct) + describe_synthetics_of_struct(struct)
9
+ end
10
+
11
+ def describe_synthetics_of_struct(struct)
12
+ Synthetics.instance_methods.reject{|m| m.include?('=')}.map do | m |
13
+ [m, struct.send(m)].join(' : ')
14
+ end.unshift("============").unshift("\nSynthetic properties").join("\n")
15
+ end
16
+
17
+ def from_file(path, compact)
18
+ header = File.open(path, 'r') { |f| f.read(DPX.length) }
19
+ begin
20
+ parse(header, compact)
21
+ rescue InvalidHeader => e
22
+ raise InvalidHeader, "Invalid header in file #{path} - #{e.message}"
23
+ end
24
+ end
25
+
26
+ # The hear of Depix
27
+ def parse(data, compact)
28
+ magic = data[0..3]
29
+
30
+ raise InvalidHeader, "No magic bytes found at start" unless %w( SDPX XPDS).include?(magic)
31
+
32
+ struct = compact ? CompactDPX : DPX
33
+
34
+ is_be = (magic == "SDPX")
35
+ version_check = FileInfo.only(:magic, :version)
36
+
37
+ result = begin
38
+ if is_be
39
+ version_check.consume!(data.unpack(version_check.pattern))
40
+ else
41
+ version_check.consume!(data.unpack(make_le(version_check.pattern)))
42
+ end
43
+ rescue ArgumentError
44
+ raise InvalidHeader
45
+ end
46
+
47
+ raise InvalidHeader, "Unknown version tag #{result.version}" unless result.version == "V1.0"
48
+
49
+ template = is_be ? DPX.pattern : make_le(DPX.pattern)
50
+ struct.consume!(data.unpack(struct.pattern))
51
+ end
52
+
53
+ # Describe a filled DPX structure
54
+ def describe_struct(result, pad_offset = 0)
55
+ result.class.fields.inject([]) do | info, field |
56
+ value = result.send(field.name)
57
+ parts = []
58
+ if value
59
+ parts << field.desc if field.desc
60
+ parts << if field.is_a?(InnerField)
61
+ describe_struct(value, pad_offset + 1)
62
+ elsif field.is_a?(ArrayField)
63
+ # Exception for image elements
64
+ value = result.image_elements[0...result.number_elements] if field.name == :image_elements
65
+ value.map { | v | v.is_a?(Dict) ? describe_struct(v, pad_offset + 2) : v }
66
+ else
67
+ value
68
+ end
69
+ end
70
+ if parts.any?
71
+ info << parts.join(' ')
72
+ end
73
+ info
74
+ end.map{|e| (' ' * pad_offset) + e }.join("\n")
75
+ end
76
+
77
+ # Convert an unpack pattern to LE
78
+ def make_le(pattern)
79
+ pattern.gsub(/n/, "v").gsub(/N/, "V").gsub(/g/, "f")
80
+ end
81
+
82
+ end
83
+ end
@@ -121,17 +121,26 @@ module Depix
121
121
 
122
122
  u32 :pixels_per_line, :desc => 'Pixels per horizontal line', :req => true
123
123
  u32 :lines_per_element, :desc => 'Line count', :req => true
124
- array :image_elements, ImageElement, 8, :desc => "Image elements"
124
+
125
+ array :image_elements, ImageElement, 8, :desc => "Image elements" do | elements |
126
+ elements[0].req = true
127
+ end
128
+
125
129
  char :reserve, 52
130
+
131
+ # Only expose the elements present
132
+ def image_elements #:nodoc:
133
+ @image_elements[0...number_elements]
134
+ end
126
135
  end
127
136
 
128
137
  #:include:DPX_HEADER_STRUCTURE.txt
129
138
  class DPX < Dict
130
- inner :file, FileInfo, :desc => "File information"
131
- inner :image, ImageInfo, :desc => "Image information"
132
- inner :orientation, OrientationInfo, :desc => "Orientation"
133
- inner :film, FilmInfo, :desc => "Film industry info"
134
- inner :television, TelevisionInfo, :desc => "TV industry info"
135
- inner :user, UserInfo, :desc => "User info"
139
+ inner :file, FileInfo, :desc => "File information", :req => true
140
+ inner :image, ImageInfo, :desc => "Image information", :req => true
141
+ inner :orientation, OrientationInfo, :desc => "Orientation", :req => true
142
+ inner :film, FilmInfo, :desc => "Film industry info", :req => true
143
+ inner :television, TelevisionInfo, :desc => "TV industry info", :req => true
144
+ inner :user, UserInfo, :desc => "User info", :req => true
136
145
  end
137
146
  end
@@ -1,10 +1,10 @@
1
1
  require File.dirname(__FILE__) + '/../lib/depix'
2
2
  require 'test/unit'
3
3
 
4
+ SAMPLE_DPX = File.dirname(__FILE__) + '/samples/E012_P001_L000002_lin.0001.dpx'
5
+
4
6
  class ReaderTest < Test::Unit::TestCase
5
7
 
6
- SAMPLE_DPX = File.dirname(__FILE__) + '/samples/E012_P001_L000002_lin.0001.dpx'
7
-
8
8
  def test_parsed_properly
9
9
  file = SAMPLE_DPX
10
10
  parsed = Depix.from_file(file)
@@ -69,6 +69,7 @@ class ReaderTest < Test::Unit::TestCase
69
69
  assert_equal :RGB, parsed.component_type
70
70
  assert_equal :Linear, parsed.colorimetric
71
71
  assert_equal "E012", parsed.flame_reel
72
+ assert_equal "1.33", parsed.aspect
72
73
  end
73
74
 
74
75
  def test_parsed_properly_using_compact_structs
@@ -84,6 +85,17 @@ class ReaderTest < Test::Unit::TestCase
84
85
  end
85
86
  end
86
87
 
88
+ def test_packing
89
+ original_header = File.read(SAMPLE_DPX)[0...Depix::DPX.length]
90
+
91
+ assert_nothing_raised do
92
+ dpx = Depix.from_string(original_header)
93
+ packed = Depix::DPX.pack(dpx, original_header.dup)
94
+
95
+ dpx2 = Depix.from_string(packed)
96
+ end
97
+ end
98
+
87
99
  def test_parsing_something_else_should_raise
88
100
  s = "Mary had a little lamb"
89
101
  assert_raise(Depix::InvalidHeader) { Depix.from_string(s) }
@@ -95,4 +107,29 @@ class ReaderTest < Test::Unit::TestCase
95
107
  assert_raise(Depix::InvalidHeader) { Depix.from_string(s) }
96
108
 
97
109
  end
110
+ end
111
+
112
+ class EditorTest < Test::Unit::TestCase
113
+ def test_instantiation
114
+ e = Depix::Editor.new(SAMPLE_DPX)
115
+ assert_not_nil e
116
+ assert_equal SAMPLE_DPX, e.path
117
+ assert_not_nil e.headers
118
+ end
119
+
120
+ def test_commit
121
+ temp_path = SAMPLE_DPX + ".test"
122
+ begin
123
+ FileUtils.cp(SAMPLE_DPX, temp_path)
124
+ e = Depix::Editor.new(temp_path)
125
+ e.headers.orientation.device = "E013"
126
+
127
+ assert_nothing_raised { e.commit! }
128
+
129
+ re_read = Depix.from_file(temp_path)
130
+ assert_equal "E013", re_read.orientation.device
131
+ ensure
132
+ File.unlink(temp_path)
133
+ end
134
+ end
98
135
  end
@@ -3,6 +3,28 @@ require 'test/unit'
3
3
 
4
4
  include Depix
5
5
 
6
+ class BogusError < RuntimeError; end
7
+
8
+ class AlwaysInvalidField < Field
9
+ def validate!(value)
10
+ raise BogusError, "Never valid"
11
+ end
12
+
13
+ def pack(some_value)
14
+ raise BogusError, "Will not pack"
15
+ end
16
+ end
17
+
18
+ class AlwaysInvalidStruct
19
+ def self.validate!(value)
20
+ raise BogusError, "Never valid"
21
+ end
22
+
23
+ def self.pack(instance)
24
+ raise BogusError, "Will not pack"
25
+ end
26
+ end
27
+
6
28
  module FieldConformity
7
29
  def conform_field!(f)
8
30
  assert_respond_to f, :name
@@ -12,6 +34,8 @@ module FieldConformity
12
34
  assert_respond_to f, :req?
13
35
  assert_respond_to f, :rtype
14
36
  assert_respond_to f, :explain
37
+ assert_respond_to f, :validate!
38
+ assert_respond_to f, :pack
15
39
  end
16
40
 
17
41
  def assert_method_removed(f, method)
@@ -99,24 +123,24 @@ class TestField < Test::Unit::TestCase
99
123
  assert_equal [2,3], ar
100
124
  end
101
125
 
102
- def test_consume_for_inner_field
103
- catcher = Class.new do
104
- def self.consume!(arg)
105
- raise RuntimeError if arg == ["julik"]
106
- end
107
- end
108
-
109
- f = InnerField.new :cast => catcher
110
- assert_respond_to f, :consume!
126
+ def test_validate
127
+ f = Field.new
128
+ f.rtype = self.class
129
+ assert_nothing_raised { f.validate! nil }
111
130
 
112
- assert_raise(RuntimeError) { f.consume!(["julik"]) }
131
+ assert_raise(RuntimeError) { f.validate! "boo" }
132
+ assert_nothing_raised { f.validate! self }
113
133
  end
114
134
 
115
- def test_consume_for_array
116
- f = ArrayField.new :members => [Field.new, Field.new]
117
- assert_respond_to f, :consume!
118
-
119
- assert_equal [1,2], f.consume!([1,2])
135
+ def test_validate_if_required
136
+ f = Field.new
137
+ f.req = true
138
+ assert_raise(RuntimeError) { f.validate! nil }
139
+ end
140
+
141
+ def test_pack_raises
142
+ f = Field.new
143
+ assert_raise(RuntimeError) { f.pack("foo")}
120
144
  end
121
145
  end
122
146
 
@@ -159,6 +183,59 @@ class TestArrayField < Test::Unit::TestCase
159
183
  assert_equal 3, f.length
160
184
  assert_equal "CC2", f.pattern
161
185
  end
186
+
187
+ def test_consume
188
+ f = ArrayField.new :members => [Field.new, Field.new]
189
+ assert_respond_to f, :consume!
190
+
191
+ assert_equal [1,2], f.consume!([1,2])
192
+ end
193
+
194
+ def test_validate
195
+ f = ArrayField.new :members => [Field.new(:rtype => self.class)]
196
+
197
+ # Overflow
198
+ assert_raise(RuntimeError) { f.validate!([nil, nil]) }
199
+
200
+ # Just empty
201
+ assert_nothing_raised { f.validate!([]) }
202
+
203
+ # Nil vaue
204
+ assert_nothing_raised { f.validate!([nil]) }
205
+
206
+ # type cast
207
+ assert_raise(RuntimeError) { f.validate!(["nil"]) }
208
+
209
+ assert_nothing_raised { f.validate!([self]) }
210
+ end
211
+
212
+ def test_validate_fails_with_empty_array_and_required_field
213
+ f = ArrayField.new :members => [Field.new(:rtype => self.class)], :req => true
214
+ assert_raise(RuntimeError) { f.validate!([]) }
215
+ end
216
+
217
+ def test_validate_does_not_validate_inner_structure_if_no_value_present_and_field_is_not_required
218
+ f = ArrayField.new :members => [AlwaysInvalidField.new]
219
+ assert_nothing_raised { f.validate! [nil] }
220
+ end
221
+
222
+
223
+ def test_pack_tries_to_pack_inner_structures
224
+ f = ArrayField.new :members => [AlwaysInvalidField.new]
225
+ assert_raise(BogusError) { f.pack([1, 2]) }
226
+ end
227
+
228
+ def test_pack_pads_properly
229
+ f = ArrayField.new :members => [U32Field.new, R32Field.new, R32Field.new]
230
+ assert_equal "\000\000\000\001@\000\000\000\377\377\377\377", f.pack([1.0, 2.0])
231
+ assert_equal f.length, f.pack([1.0, 2.0]).length
232
+ end
233
+
234
+ def test_does_not_try_to_pack_nil_values
235
+ f = ArrayField.new(:members => [AlwaysInvalidField.new(:length => 2)])
236
+ assert_equal "\377\377", f.pack([])
237
+ end
238
+
162
239
  end
163
240
 
164
241
  class TestInnerField < Test::Unit::TestCase
@@ -194,6 +271,34 @@ class TestInnerField < Test::Unit::TestCase
194
271
  casted = InnerField.new(:cast => c)
195
272
  assert_equal c, casted.rtype
196
273
  end
274
+
275
+ def test_consume
276
+ catcher = Class.new do
277
+ def self.consume!(arg)
278
+ raise RuntimeError if arg == ["julik"]
279
+ end
280
+ end
281
+
282
+ f = InnerField.new :cast => catcher
283
+ assert_respond_to f, :consume!
284
+
285
+ assert_raise(RuntimeError) { f.consume!(["julik"]) }
286
+ end
287
+
288
+ def test_validate_with_nil_and_no_requirement
289
+ f = InnerField.new :cast => AlwaysInvalidStruct, :req => true
290
+ assert_raise(RuntimeError) { f.validate!(nil) }
291
+ end
292
+
293
+ def test_validate
294
+ f = InnerField.new :cast => AlwaysInvalidStruct
295
+ assert_raise(BogusError) { f.validate!(AlwaysInvalidStruct.new) }
296
+ end
297
+
298
+ def test_pack_tries_to_pack_inner_structures
299
+ f = InnerField.new :cast => AlwaysInvalidStruct
300
+ assert_raise(BogusError) { f.pack(AlwaysInvalidStruct.new) }
301
+ end
197
302
  end
198
303
 
199
304
  class TestWideIntField < Test::Unit::TestCase
@@ -209,6 +314,23 @@ class TestWideIntField < Test::Unit::TestCase
209
314
  assert_equal 66, f.clean(66)
210
315
  assert_equal nil, f.clean(0xFFFFFFFF)
211
316
  end
317
+
318
+ def test_validate
319
+ f = U32Field.new
320
+
321
+ assert_nothing_raised { f.validate! 8 }
322
+ assert_nothing_raised { f.validate! 0 }
323
+ assert_nothing_raised { f.validate! 65536 }
324
+ assert_raise(RuntimeError) { f.validate!(0xFFFFFFFF) }
325
+ assert_nothing_raised { f.validate!(0xFFFFFFFF - 1) }
326
+
327
+ end
328
+
329
+ def test_pack
330
+ w = U32Field.new
331
+ assert_equal "\000\000\000\036", w.pack(30)
332
+ assert_equal "\377\377\377\377", w.pack(nil)
333
+ end
212
334
  end
213
335
 
214
336
  class TestCharField < Test::Unit::TestCase
@@ -240,7 +362,25 @@ class TestCharField < Test::Unit::TestCase
240
362
  assert_equal nil, f.clean("\0\0\0\0\0\0")
241
363
  assert_equal nil, f.clean("\0\0\0\377\377\0\0\0")
242
364
  assert_equal "foo\0foo", f.clean("\0\0foo\0foo\0")
243
-
365
+ end
366
+
367
+ def test_char_field_validates_overflow
368
+ f = CharField.new :length => 2
369
+ assert_raise(RuntimeError) { f.validate!("xxx")}
370
+ assert_nothing_raised { f.validate!("xx")}
371
+ assert_nothing_raised { f.validate!(nil)}
372
+ end
373
+
374
+ def test_char_field_validates_required_with_nil
375
+ f = CharField.new :length => 2, :req => true
376
+ assert_raise(RuntimeError) { f.validate!(nil)}
377
+ end
378
+
379
+ def test_char_field_packs
380
+ f = CharField.new(:length => 6)
381
+ assert_equal "xx\000\000\000\000", f.pack("xx")
382
+ assert_equal "\000\000\000\000\000\000", f.pack("")
383
+ assert_equal "\000\000\000\000\000\000", f.pack(nil)
244
384
  end
245
385
  end
246
386
 
@@ -261,6 +401,12 @@ class TestFloatField < Test::Unit::TestCase
261
401
  assert_equal Float, f.rtype
262
402
  assert_equal nil, f.clean(the_nan)
263
403
  end
404
+
405
+ def test_pack
406
+ w = R32Field.new
407
+ assert_equal "@fff", w.pack(3.6)
408
+ assert_equal "O\200\000\000", w.pack(nil)
409
+ end
264
410
  end
265
411
 
266
412
  class TestSmallintField < Test::Unit::TestCase
@@ -272,7 +418,6 @@ class TestSmallintField < Test::Unit::TestCase
272
418
 
273
419
  assert_method_removed(f, :pattern=)
274
420
  assert_method_removed(f, :length=)
275
-
276
421
  end
277
422
 
278
423
  def test_smallint_operation
@@ -285,11 +430,18 @@ class TestSmallintField < Test::Unit::TestCase
285
430
 
286
431
  def test_smallint_clean
287
432
  f = U8Field.new
288
-
289
433
  assert_equal nil, f.clean(0xFF)
290
434
  assert_equal 10, f.clean(10)
291
435
  end
292
436
 
437
+ def test_validate
438
+ f = U8Field.new
439
+ assert_nothing_raised { f.validate! 8 }
440
+ assert_nothing_raised { f.validate! 0 }
441
+ assert_raise(RuntimeError) { f.validate!( -1 ) }
442
+ assert_raise(RuntimeError) { f.validate!( 255) }
443
+ assert_raise(RuntimeError) { f.validate!( 256) }
444
+ end
293
445
  end
294
446
 
295
447
  class TestDoubleField < Test::Unit::TestCase
@@ -317,6 +469,15 @@ class TestDoubleField < Test::Unit::TestCase
317
469
  assert_equal nil, f.clean(0xFFFF)
318
470
  assert_equal 10, f.clean(10)
319
471
  end
472
+
473
+ def test_validate
474
+ f = U16Field.new
475
+ assert_nothing_raised { f.validate! 8 }
476
+ assert_nothing_raised { f.validate! 0 }
477
+ assert_raise(RuntimeError) { f.validate!( -1) }
478
+ assert_raise(RuntimeError) { f.validate!( 65535) }
479
+ assert_raise(RuntimeError) { f.validate!( 65536) }
480
+ end
320
481
  end
321
482
 
322
483
  class TestFillerField < Test::Unit::TestCase
@@ -344,7 +505,10 @@ class TestFillerField < Test::Unit::TestCase
344
505
  data.freeze
345
506
  assert_nothing_raised { Filler.new(:length => 1).consume(data) }
346
507
  end
347
-
508
+
509
+ def test_pack_raises
510
+ assert_raise(RuntimeError) { Filler.new(:length => 3).pack('xxx') }
511
+ end
348
512
  end
349
513
 
350
514
  class TestFieldEmit < Test::Unit::TestCase
@@ -401,6 +565,12 @@ class TestDict < Test::Unit::TestCase
401
565
  assert_equal [], dict_class.fields
402
566
  end
403
567
 
568
+ def test_dict_responds_to_validate
569
+ dict_class = Class.new(Dict)
570
+ assert_respond_to dict_class, :validate!
571
+ one = dict_class.new
572
+ end
573
+
404
574
  def test_dict_fields_array_not_class_shared
405
575
  d1, d2 = (0..1).map{|_| Class.new(Dict) }
406
576
 
@@ -434,7 +604,27 @@ class TestDict < Test::Unit::TestCase
434
604
  assert_equal 'A1A1', c.pattern
435
605
  assert_equal 2, c.length
436
606
  end
607
+
608
+ def test_dict_does_not_validate_inner_nil
609
+ wrapper_class = Class.new(Dict) do
610
+ u32 :bigint
611
+ inner :invalid, AlwaysInvalidStruct
612
+ end
613
+ struct = wrapper_class.new
614
+ assert_nothing_raised { wrapper_class.validate!(struct) }
615
+ end
437
616
 
617
+ def test_dict_calls_validate
618
+ wrapper_class = Class.new(Dict) do
619
+ u32 :bigint
620
+ inner :invalid, AlwaysInvalidStruct, :req => true
621
+ end
622
+
623
+ struct = wrapper_class.new
624
+ struct.invalid = AlwaysInvalidStruct.new
625
+
626
+ assert_raise(BogusError) { wrapper_class.validate!(struct) }
627
+ end
438
628
  end
439
629
 
440
630
  class TestDictConsume < Test::Unit::TestCase
@@ -449,6 +639,7 @@ class TestDictConsume < Test::Unit::TestCase
449
639
  assert_equal "a", result.foo
450
640
  assert_equal "b", result.bar
451
641
  end
642
+
452
643
  end
453
644
 
454
645
  class TestDictEmitDSL < Test::Unit::TestCase
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: depix
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-23 00:00:00 +01:00
12
+ date: 2008-12-26 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,7 +22,7 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: 1.8.2
24
24
  version:
25
- description: Read DPX file metadata
25
+ description: Read and write DPX file metadata
26
26
  email:
27
27
  - me@julik.nl
28
28
  executables:
@@ -44,9 +44,13 @@ files:
44
44
  - lib/depix.rb
45
45
  - lib/depix/struct_explainer.rb
46
46
  - lib/depix/structs.rb
47
+ - lib/depix/benchmark.rb
47
48
  - lib/depix/compact_structs.rb
48
49
  - lib/depix/enums.rb
49
50
  - lib/depix/dict.rb
51
+ - lib/depix/reader.rb
52
+ - lib/depix/editor.rb
53
+ - test/test_dict.rb
50
54
  - test/test_depix.rb
51
55
  - test/samples/E012_P001_L000002_lin.0001.dpx
52
56
  - test/samples/E012_P001_L000002_lin.0002.dpx
@@ -78,7 +82,7 @@ rubyforge_project: wiretap
78
82
  rubygems_version: 1.3.1
79
83
  signing_key:
80
84
  specification_version: 2
81
- summary: Read DPX file metadata
85
+ summary: Read and write DPX file metadata
82
86
  test_files:
83
87
  - test/test_depix.rb
84
88
  - test/test_dict.rb