julik-depix 1.0.2
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/DPX_HEADER_STRUCTURE.txt +97 -0
- data/History.txt +18 -0
- data/Manifest.txt +17 -0
- data/README.txt +59 -0
- data/Rakefile +19 -0
- data/bin/depix-describe +23 -0
- data/lib/depix/compact_structs.rb +42 -0
- data/lib/depix/dict.rb +357 -0
- data/lib/depix/enums.rb +43 -0
- data/lib/depix/struct_explainer.rb +59 -0
- data/lib/depix/structs.rb +137 -0
- data/lib/depix.rb +142 -0
- data/test/samples/E012_P001_L000002_lin.0001.dpx +0 -0
- data/test/samples/E012_P001_L000002_lin.0002.dpx +0 -0
- data/test/samples/E012_P001_L000002_log.0001.dpx +0 -0
- data/test/samples/E012_P001_L000002_log.0002.dpx +0 -0
- data/test/test_depix.rb +98 -0
- data/test/test_dict.rb +686 -0
- metadata +84 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
# Generates an RDoc description of the DPX structs from the structs.rb file
|
2
|
+
class RdocExplainer #:nodoc:
|
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
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_rdoc_for(struct)
|
26
|
+
@io = StringIO.new
|
27
|
+
explain_struct(struct)
|
28
|
+
TPL % @io.string
|
29
|
+
end
|
30
|
+
|
31
|
+
include Depix
|
32
|
+
|
33
|
+
def explain_struct(struct, padding = '') #:nodoc:
|
34
|
+
struct.fields.each do | e |
|
35
|
+
if e.is_a?(InnerField)
|
36
|
+
|
37
|
+
@io.puts( @struct_template % [padding, e.name, e.explain])
|
38
|
+
explain_struct(e.rtype, padding + @padding)
|
39
|
+
|
40
|
+
elsif e.is_a?(ArrayField)
|
41
|
+
|
42
|
+
@io.puts( @array_template % [padding, e.name, e.explain])
|
43
|
+
|
44
|
+
inner_struct = e.members[0]
|
45
|
+
|
46
|
+
if inner_struct.is_a?(InnerField)
|
47
|
+
explain_struct(inner_struct.rtype, padding + @padding)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
explain_attr(padding, e)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def explain_attr(padding, e)
|
56
|
+
type_name = e.rtype ? "(#{e.rtype})" : nil
|
57
|
+
@io.puts( @attr_template % [padding, e.name, e.explain])
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/dict'
|
2
|
+
|
3
|
+
|
4
|
+
module Depix
|
5
|
+
|
6
|
+
class FileInfo < Dict
|
7
|
+
char :magic, 4, :desc => 'Endianness (SDPX is big endian)', :req => true
|
8
|
+
u32 :image_offset, :desc => 'Offset to image data in bytes', :req => true
|
9
|
+
char :version, 8, :desc => 'Version of header format', :req => true
|
10
|
+
|
11
|
+
u32 :file_size, :desc => "Total image size in bytes", :req => true
|
12
|
+
u32 :ditto_key, :desc => 'Whether the basic headers stay the same through the sequence (1 means they do)'
|
13
|
+
u32 :generic_size, :desc => 'Generic header length'
|
14
|
+
u32 :industry_size, :desc => 'Industry header length'
|
15
|
+
u32 :user_size, :desc => 'User header length'
|
16
|
+
|
17
|
+
char :filename, 100, :desc => 'Original filename'
|
18
|
+
char :timestamp, 24, :desc => 'Creation 15'
|
19
|
+
char :creator, 100, :desc => 'Creator application'
|
20
|
+
char :roject, 200, :desc => 'Project name'
|
21
|
+
char :copyright, 200, :desc => 'Copyright'
|
22
|
+
|
23
|
+
u32 :encrypt_key, :desc => 'Encryption key'
|
24
|
+
char :reserve, 104
|
25
|
+
end
|
26
|
+
|
27
|
+
class FilmInfo < Dict
|
28
|
+
char :id, 2, :desc => 'Film mfg. ID code (2 digits from film edge code)'
|
29
|
+
char :type, 2, :desc => 'Film type (2 digits from film edge code)'
|
30
|
+
char :offset, 2, :desc => 'Offset in perfs (2 digits from film edge code)'
|
31
|
+
char :prefix, 6, :desc => 'Prefix (6 digits from film edge code'
|
32
|
+
char :count, 4, :desc => 'Count (4 digits from film edge code)'
|
33
|
+
char :format, 32, :desc => 'Format (e.g. Academy)'
|
34
|
+
|
35
|
+
u32 :frame_position, :desc => 'Frame position in sequence'
|
36
|
+
u32 :sequence_extent, :desc => 'Sequence length'
|
37
|
+
u32 :held_count, :desc => 'For how many frames the frame is held'
|
38
|
+
|
39
|
+
r32 :frame_rate, :desc => 'Frame rate'
|
40
|
+
r32 :shutter_angle, :desc => 'Shutter angle'
|
41
|
+
|
42
|
+
char :frame_id, 32, :desc => 'Frame identification (keyframe)'
|
43
|
+
char :slate, 100, :desc => 'Slate information'
|
44
|
+
char :reserve, 56
|
45
|
+
end
|
46
|
+
|
47
|
+
class ImageElement < Dict
|
48
|
+
u32 :data_sign, :desc => 'Data sign (0=unsigned, 1=signed). Core is unsigned', :req => true
|
49
|
+
|
50
|
+
u32 :low_data, :desc => 'Reference low data code value'
|
51
|
+
r32 :low_quantity, :desc => 'Reference low quantity represented'
|
52
|
+
u32 :high_data, :desc => 'Reference high data code value (1023 for 10bit per channel)'
|
53
|
+
r32 :high_quantity, :desc => 'Reference high quantity represented'
|
54
|
+
|
55
|
+
# TODO: Autoreplace with enum values.
|
56
|
+
u8 :descriptor, :desc => 'Descriptor for this image element (ie Video or Film), by enum', :req => true
|
57
|
+
u8 :transfer, :desc => 'Transfer function (ie Linear), by enum', :req => true
|
58
|
+
u8 :colorimetric, :desc => 'Colorimetric (ie YcbCr), by enum', :req => true
|
59
|
+
u8 :bit_size, :desc => 'Bit size for element (ie 10)', :req => true
|
60
|
+
|
61
|
+
u16 :packing, :desc => 'Packing (0=Packed into 32-bit words, 1=Filled to 32-bit words))', :req => true
|
62
|
+
u16 :encoding, :desc => "Encoding (0=None, 1=RLE)", :req => true
|
63
|
+
u32 :data_offset, :desc => 'Offset to data for this image element', :req => true
|
64
|
+
u32 :end_of_line_padding, :desc => "End-of-line padding for this image element"
|
65
|
+
u32 :end_of_image_padding, :desc => "End-of-line padding for this image element"
|
66
|
+
char :description, 32
|
67
|
+
end
|
68
|
+
|
69
|
+
class OrientationInfo < Dict
|
70
|
+
|
71
|
+
u32 :x_offset
|
72
|
+
u32 :y_offset
|
73
|
+
|
74
|
+
r32 :x_center
|
75
|
+
r32 :y_center
|
76
|
+
|
77
|
+
u32 :x_size, :desc => 'Original X size'
|
78
|
+
u32 :y_size, :desc => 'Original Y size'
|
79
|
+
|
80
|
+
char :filename, 100, :desc => "Source image filename"
|
81
|
+
char :timestamp, 24, :desc => "Source image/tape timestamp"
|
82
|
+
char :device, 32, :desc => "Input device or tape"
|
83
|
+
char :serial, 32, :desc => "Input device serial number"
|
84
|
+
|
85
|
+
array :border, :u16, 4, :desc => 'Border validity: XL, XR, YT, YB'
|
86
|
+
array :aspect_ratio , :u32, 2, :desc => "Aspect (H:V)"
|
87
|
+
|
88
|
+
char :reserve, 28
|
89
|
+
end
|
90
|
+
|
91
|
+
class TelevisionInfo < Dict
|
92
|
+
u32 :time_code, :desc => "Timecode, formatted as HH:MM:SS:FF in the 4 higher bits of each 8bit group"
|
93
|
+
u32 :user_bits, :desc => "Timecode UBITs"
|
94
|
+
u8 :interlace, :desc => "Interlace (0 = noninterlaced; 1 = 2:1 interlace"
|
95
|
+
|
96
|
+
u8 :field_number, :desc => 'Field number'
|
97
|
+
u8 :video_signal, :desc => "Video signal (by enum)"
|
98
|
+
u8 :padding, :desc => "Zero (for byte alignment)"
|
99
|
+
|
100
|
+
r32 :horizontal_sample_rate, :desc => 'Horizontal sampling Hz'
|
101
|
+
r32 :vertical_sample_rate, :desc => 'Vertical sampling Hz'
|
102
|
+
r32 :frame_rate, :desc => 'Frame rate'
|
103
|
+
r32 :time_offset, :desc => 'From sync pulse to first pixel'
|
104
|
+
r32 :gamma, :desc => 'Gamma'
|
105
|
+
r32 :black_level, :desc => 'Black pedestal code value'
|
106
|
+
r32 :black_gain, :desc => 'Black gain code value'
|
107
|
+
r32 :break_point, :desc => 'Break point (?)'
|
108
|
+
r32 :white_level, :desc => 'White level'
|
109
|
+
r32 :integration_times, :desc => 'Integration times (S)'
|
110
|
+
r32 :reserve
|
111
|
+
end
|
112
|
+
|
113
|
+
class UserInfo < Dict
|
114
|
+
char :id, 32, :desc => 'Name of the user data tag'
|
115
|
+
u32 :user_data_ptr
|
116
|
+
end
|
117
|
+
|
118
|
+
class ImageInfo < Dict
|
119
|
+
u16 :orientation, OrientationInfo, :desc => 'Orientation descriptor', :req => true
|
120
|
+
u16 :number_elements, :desc => 'How many elements to scan', :req => true
|
121
|
+
|
122
|
+
u32 :pixels_per_line, :desc => 'Pixels per horizontal line', :req => true
|
123
|
+
u32 :lines_per_element, :desc => 'Line count', :req => true
|
124
|
+
array :image_elements, ImageElement, 8, :desc => "Image elements"
|
125
|
+
char :reserve, 52
|
126
|
+
end
|
127
|
+
|
128
|
+
#:include:DPX_HEADER_STRUCTURE.txt
|
129
|
+
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"
|
136
|
+
end
|
137
|
+
end
|
data/lib/depix.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'timecode'
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/depix/dict'
|
6
|
+
require File.dirname(__FILE__) + '/depix/structs'
|
7
|
+
require File.dirname(__FILE__) + '/depix/compact_structs'
|
8
|
+
require File.dirname(__FILE__) + '/depix/enums'
|
9
|
+
|
10
|
+
module Depix
|
11
|
+
VERSION = '1.0.3'
|
12
|
+
|
13
|
+
class InvalidHeader < RuntimeError; end
|
14
|
+
|
15
|
+
# Offers convenience access to a few common attributes bypassing the piecemeal structs
|
16
|
+
module Synthetics
|
17
|
+
def keycode
|
18
|
+
[film.id, film.type, film.offset, film.prefix, film.count].compact.join(' ')
|
19
|
+
end
|
20
|
+
|
21
|
+
# 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
|
+
# as it seems
|
23
|
+
def flame_reel
|
24
|
+
orientation.device.split("\000").shift
|
25
|
+
end
|
26
|
+
|
27
|
+
def time_code
|
28
|
+
Timecode.from_uint(television.time_code) #, film.frame_rate)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the name of the transfer function (Linear, Logarithmic, ...)
|
32
|
+
def colorimetric
|
33
|
+
COLORIMETRIC.invert[image.image_elements[0].colorimetric]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get the name of the compnent type (RGB, YCbCr, ...)
|
37
|
+
def component_type
|
38
|
+
COMPONENT_TYPE.invert[image.image_elements[0].descriptor]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Is this DPX file little-endian? This would be an exception, but still useful
|
42
|
+
def le?
|
43
|
+
file.magic == 'XPDS'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class DPX < Dict
|
48
|
+
include Synthetics
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return a DPX object describing a file at path.
|
52
|
+
# The second argument specifies whether you need a compact or a full description
|
53
|
+
def self.from_file(path, compact = false)
|
54
|
+
Reader.new.from_file(path, compact)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return a DPX object describing headers embedded at the start of the string.
|
58
|
+
# The second argument specifies whether you need a compact or a full description
|
59
|
+
def self.from_string(string, compact = false)
|
60
|
+
Reader.new.parse(string, compact)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Retrurn a formatted description of the DPX file at path. Empty values are omitted.
|
64
|
+
def self.describe_file(path, compact = false)
|
65
|
+
Reader.new.describe_file(path, compact)
|
66
|
+
end
|
67
|
+
|
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
|
+
|
141
|
+
end
|
142
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/test/test_depix.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../lib/depix'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
class ReaderTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
SAMPLE_DPX = File.dirname(__FILE__) + '/samples/E012_P001_L000002_lin.0001.dpx'
|
7
|
+
|
8
|
+
def test_parsed_properly
|
9
|
+
file = SAMPLE_DPX
|
10
|
+
parsed = Depix.from_file(file)
|
11
|
+
assert_equal "SDPX", parsed.file.magic
|
12
|
+
assert_equal 8192, parsed.file.image_offset
|
13
|
+
assert_equal "V1.0", parsed.file.version
|
14
|
+
assert_equal 319488, parsed.file.file_size
|
15
|
+
assert_equal 1, parsed.file.ditto_key
|
16
|
+
assert_equal 1664, parsed.file.generic_size
|
17
|
+
assert_equal 384, parsed.file.industry_size
|
18
|
+
assert_equal 6144, parsed.file.user_size
|
19
|
+
assert_equal "E012_P001_L000002_lin.0001.dpx", parsed.file.filename
|
20
|
+
assert_equal "2008:12:19:01:18:37:CEST", parsed.file.timestamp
|
21
|
+
assert_equal "UTODESK", parsed.file.creator
|
22
|
+
assert_equal 0, parsed.image.orientation
|
23
|
+
assert_equal 1, parsed.image.number_elements
|
24
|
+
assert_equal 320, parsed.image.pixels_per_line
|
25
|
+
assert_equal 240, parsed.image.lines_per_element
|
26
|
+
assert_equal 0, parsed.image.image_elements[0].data_sign
|
27
|
+
assert_equal 0, parsed.image.image_elements[0].low_data
|
28
|
+
assert_equal 0.0, parsed.image.image_elements[0].low_quantity
|
29
|
+
assert_equal 1023, parsed.image.image_elements[0].high_data
|
30
|
+
assert_in_delta 2.04699993133545, parsed.image.image_elements[0].high_quantity, 1.0 ** -10
|
31
|
+
|
32
|
+
assert_equal 50, parsed.image.image_elements[0].descriptor # RGB :-)
|
33
|
+
assert_equal 2, parsed.image.image_elements[0].transfer
|
34
|
+
assert_equal 2, parsed.image.image_elements[0].colorimetric
|
35
|
+
assert_equal 10, parsed.image.image_elements[0].bit_size
|
36
|
+
assert_equal 1, parsed.image.image_elements[0].packing
|
37
|
+
assert_equal 0, parsed.image.image_elements[0].encoding
|
38
|
+
assert_equal 8192, parsed.image.image_elements[0].data_offset
|
39
|
+
assert_equal 0, parsed.image.image_elements[0].end_of_line_padding
|
40
|
+
assert_equal 0, parsed.image.image_elements[0].end_of_image_padding
|
41
|
+
assert_equal "IMAGE DESCRIPTION DATA \000P", parsed.image.image_elements[0].description
|
42
|
+
assert_equal "E012\000\000\000\000x\340\264\020\000\000\000\000\005",
|
43
|
+
parsed.orientation.device #- this is where Flame writes the reel
|
44
|
+
|
45
|
+
assert_equal 853, parsed.orientation.aspect_ratio[0]
|
46
|
+
assert_equal 640, parsed.orientation.aspect_ratio[1]
|
47
|
+
|
48
|
+
assert_equal '75', parsed.film.id
|
49
|
+
assert_equal '00', parsed.film.type
|
50
|
+
assert_equal '19', parsed.film.offset
|
51
|
+
assert_equal '740612', parsed.film.prefix
|
52
|
+
assert_equal '9841', parsed.film.count
|
53
|
+
assert_equal 1, parsed.film.frame_position
|
54
|
+
assert_equal 2, parsed.film.sequence_extent
|
55
|
+
assert_equal 1, parsed.film.held_count
|
56
|
+
assert_equal 25.0, parsed.film.frame_rate
|
57
|
+
assert_equal 18157848, parsed.television.time_code
|
58
|
+
assert_equal 0, parsed.television.user_bits
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_syntethics
|
62
|
+
assert_nothing_raised { Depix::Synthetics }
|
63
|
+
|
64
|
+
file = SAMPLE_DPX
|
65
|
+
parsed = Depix.from_file(file)
|
66
|
+
assert_equal false, parsed.le?
|
67
|
+
assert_equal "75 00 19 740612 9841", parsed.keycode
|
68
|
+
assert_equal "01:15:11:18", parsed.time_code.to_s
|
69
|
+
assert_equal :RGB, parsed.component_type
|
70
|
+
assert_equal :Linear, parsed.colorimetric
|
71
|
+
assert_equal "E012", parsed.flame_reel
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_parsed_properly_using_compact_structs
|
75
|
+
file = SAMPLE_DPX
|
76
|
+
assert_nothing_raised { Depix.from_file(file, compact = true) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_describe
|
80
|
+
assert_nothing_raised do
|
81
|
+
desc = Depix.describe_file(SAMPLE_DPX)
|
82
|
+
assert_match(/320/, desc)
|
83
|
+
assert_match(/Offset to data for this image element/, desc)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_parsing_something_else_should_raise
|
88
|
+
s = "Mary had a little lamb"
|
89
|
+
assert_raise(Depix::InvalidHeader) { Depix.from_string(s) }
|
90
|
+
|
91
|
+
s = "Mary had a little lamb" * 1000
|
92
|
+
assert_raise(Depix::InvalidHeader) { Depix.from_string(s) }
|
93
|
+
|
94
|
+
s = "SDPX Mary had a little lamb" * 1000
|
95
|
+
assert_raise(Depix::InvalidHeader) { Depix.from_string(s) }
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|