format_parser 2.3.0 → 2.4.4
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +13 -6
- data/format_parser.gemspec +1 -0
- data/lib/format_parser/version.rb +1 -1
- data/lib/io_utils.rb +18 -33
- data/lib/parsers/cr3_parser/decoder.rb +2 -2
- data/lib/parsers/cr3_parser.rb +13 -11
- data/lib/parsers/heif_parser.rb +46 -46
- data/lib/parsers/iso_base_media_file_format/box.rb +80 -0
- data/lib/parsers/iso_base_media_file_format/decoder.rb +348 -377
- data/lib/parsers/iso_base_media_file_format/utils.rb +89 -0
- data/lib/parsers/mov_parser/decoder.rb +53 -0
- data/lib/parsers/mov_parser.rb +48 -0
- data/lib/parsers/mp4_parser.rb +80 -0
- data/lib/parsers/pdf_parser.rb +5 -2
- data/lib/parsers/webp_parser.rb +2 -2
- data/spec/format_parser_spec.rb +1 -1
- data/spec/parsers/cr3_parser_spec.rb +3 -3
- data/spec/parsers/iso_base_media_file_format/box_spec.rb +399 -0
- data/spec/parsers/iso_base_media_file_format/decoder_spec.rb +117 -151
- data/spec/parsers/iso_base_media_file_format/utils_spec.rb +632 -0
- data/spec/parsers/mov_parser_spec.rb +139 -0
- data/spec/parsers/mp4_parser_spec.rb +188 -0
- data/spec/parsers/pdf_parser_spec.rb +37 -23
- metadata +25 -5
- data/lib/parsers/moov_parser/decoder.rb +0 -353
- data/lib/parsers/moov_parser.rb +0 -165
- data/spec/parsers/moov_parser_spec.rb +0 -144
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3b2c55550282f0f8d7b04723decd4d0d35f73afc3d987fbaaf14e2274838bab
|
4
|
+
data.tar.gz: 24119780ac672b473a1e01c3cc2bb4dc545aec45d8a5e331a09b37cebece100b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 251a7268c1c829424613f9a811bf59d88061b96efbc44557f6bfcffbbd0d7de5c1430f98b956eeace9bc590b1fbc6ada05eeaa215c526f24fe063c236bb5b8af
|
7
|
+
data.tar.gz: 2e263df394eddd302c1ea8d4f99bcac262e276f251a1cf37abe0475ec0c6ec36fa5a2c6181d88e9a889053b00fe54568d72f1bd083395cd6994f916ff848d3ed
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
## 2.4.4
|
2
|
+
* Prevent infinite loops when parsing ISOBMFF boxes with size = 0 (meaning that the box extends to the end of the file).
|
3
|
+
|
4
|
+
## 2.4.3
|
5
|
+
* Improve resiliency in ISOBMFF parsing to missing mandatory boxes and fields.
|
6
|
+
* Simplify ISOBMFF frame rate calculations.
|
7
|
+
* Refactor.
|
8
|
+
|
9
|
+
## 2.4.2 (yanked)
|
10
|
+
* Added support for PDF 2.0
|
11
|
+
* Expanded test coverage for PDF parsing
|
12
|
+
|
13
|
+
## 2.4.1 (yanked)
|
14
|
+
* Revert change where variable frame rates in MOV and MP4 files would result in an array value for `frame_rate`.
|
15
|
+
|
16
|
+
## 2.4.0 (yanked)
|
17
|
+
* Adapt the ISOBMFF based decoder for parsing MOV and MP4 parsing.
|
18
|
+
* Fix MOV/MP4 issues:
|
19
|
+
* MP4 files being misidentified as MOV files.
|
20
|
+
* Dimensions being miscalculated when files include multiple tracks or transformations.
|
21
|
+
|
1
22
|
## 2.3.0
|
2
23
|
* Add support for `RW2` files.
|
3
24
|
|
data/README.md
CHANGED
@@ -28,6 +28,10 @@ and [dimensions,](https://github.com/sstephenson/dimensions) borrowing from them
|
|
28
28
|
* JPEG
|
29
29
|
* M3U
|
30
30
|
* M4A
|
31
|
+
* M4B
|
32
|
+
* M4P
|
33
|
+
* M4R
|
34
|
+
* M4V
|
31
35
|
* MOV
|
32
36
|
* MP3
|
33
37
|
* MP4
|
@@ -195,16 +199,15 @@ Unless specified otherwise in this section the fixture files are MIT licensed an
|
|
195
199
|
### M3U
|
196
200
|
- The M3U fixture files were created by one of the project maintainers
|
197
201
|
|
198
|
-
###
|
199
|
-
-
|
200
|
-
|
201
|
-
### MOOV
|
202
|
-
- bmff.mp4 is borrowed from the [bmff](https://github.com/zuku/bmff) project
|
203
|
-
- Test_Circular MOV files were created by one of the project maintainers and are MIT licensed
|
202
|
+
### MOV
|
203
|
+
- Fixtures were downloaded from https://pixabay.com/ (with some modifications) and are subject to the [Pixabay Licence](https://pixabay.com/service/license/).
|
204
204
|
|
205
205
|
### MP3
|
206
206
|
- Cassy.mp3 has been produced by WeTransfer and may be used with the library for the purposes of testing
|
207
207
|
|
208
|
+
### MP4
|
209
|
+
- Fixtures were downloaded from https://pixabay.com/ (with some modifications) and are subject to the [Pixabay Licence](https://pixabay.com/service/license/).
|
210
|
+
|
208
211
|
### MPEG
|
209
212
|
- The files (video 1 to 4) were downloaded from https://standaloneinstaller.com/blog/big-list-of-sample-videos-for-testers-124.html.
|
210
213
|
- Video 5 was downloaded from https://archive.org/details/ligouHDR-HC1_sample1.
|
@@ -215,6 +218,10 @@ Unless specified otherwise in this section the fixture files are MIT licensed an
|
|
215
218
|
### OGG
|
216
219
|
- `hi.ogg`, `vorbis.ogg`, `with_confusing_magic_string.ogg`, `with_garbage_at_the_end.ogg` have been generated by the project contributors
|
217
220
|
|
221
|
+
### PDF
|
222
|
+
- PDF 2.0 files downloaded from the [PDF Association public Github repository](https://github.com/pdf-association/pdf20examples). These files are licensed under the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.
|
223
|
+
- Lorem Ipsum PDF files created at WeTransfer for this project.
|
224
|
+
|
218
225
|
### PNG
|
219
226
|
- `simulator_screenie.png` provided by [Rens Verhoeven](https://github.com/renssies)
|
220
227
|
|
data/format_parser.gemspec
CHANGED
data/lib/io_utils.rb
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
module FormatParser::IOUtils
|
2
|
+
INTEGER_DIRECTIVES = {
|
3
|
+
1 => 'C',
|
4
|
+
2 => 'S',
|
5
|
+
4 => 'L',
|
6
|
+
8 => 'Q'
|
7
|
+
}
|
8
|
+
|
2
9
|
class InvalidRead < ArgumentError
|
3
10
|
end
|
4
11
|
|
@@ -26,41 +33,19 @@ module FormatParser::IOUtils
|
|
26
33
|
nil
|
27
34
|
end
|
28
35
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
read_bytes(4).unpack('N').first
|
39
|
-
end
|
40
|
-
|
41
|
-
def read_int_64
|
42
|
-
read_bytes(8).unpack('Q>').first
|
43
|
-
end
|
44
|
-
|
45
|
-
def read_little_endian_int_16
|
46
|
-
read_bytes(2).unpack('v').first
|
47
|
-
end
|
48
|
-
|
49
|
-
def read_little_endian_int_32
|
50
|
-
read_bytes(4).unpack('V').first
|
51
|
-
end
|
52
|
-
|
53
|
-
def read_fixed_point_16
|
54
|
-
read_bytes(2).unpack('C2')
|
55
|
-
end
|
56
|
-
|
57
|
-
def read_fixed_point_32
|
58
|
-
read_bytes(4).unpack('n2')
|
36
|
+
# Read an integer.
|
37
|
+
# @param [Integer] n Number of bytes. Defaults to 4 (32-bit).
|
38
|
+
# @param [Boolean] signed Signed if true, Unsigned if false. Defaults to false. (unsigned)
|
39
|
+
# @param [Boolean] big_endian Big-endian if true, little-endian if false. Defaults to true (big-endian).
|
40
|
+
def read_int(n: 4, signed: false, big_endian: true)
|
41
|
+
directive = INTEGER_DIRECTIVES[n]
|
42
|
+
directive.downcase! if signed
|
43
|
+
directive += (big_endian ? ">" : "<") if n > 1
|
44
|
+
read_bytes(n).unpack(directive).first
|
59
45
|
end
|
60
46
|
|
61
|
-
def
|
62
|
-
|
63
|
-
[n >> 30, n & 0x3fffffff]
|
47
|
+
def read_fixed_point(fractional_digits: 16, **kwargs)
|
48
|
+
read_int(**kwargs) / 2.0**fractional_digits
|
64
49
|
end
|
65
50
|
|
66
51
|
# 'n' is the number of bytes to read
|
@@ -5,7 +5,7 @@ class FormatParser::CR3Parser::Decoder < FormatParser::ISOBaseMediaFileFormat::D
|
|
5
5
|
|
6
6
|
protected
|
7
7
|
|
8
|
-
|
8
|
+
BOX_PARSERS = BOX_PARSERS.merge({
|
9
9
|
'CMT1' => :cmt1
|
10
10
|
})
|
11
11
|
CANON_METADATA_CONTAINER_UUID = '85c0b687820f11e08111f4ce462b6a48'
|
@@ -26,7 +26,7 @@ class FormatParser::CR3Parser::Decoder < FormatParser::ISOBaseMediaFileFormat::D
|
|
26
26
|
usertype = read_bytes(16).unpack('H*').first
|
27
27
|
fields = { usertype: usertype }
|
28
28
|
children = if usertype == CANON_METADATA_CONTAINER_UUID
|
29
|
-
|
29
|
+
build_box_tree(size - 16)
|
30
30
|
else
|
31
31
|
skip_bytes(size - 16)
|
32
32
|
end
|
data/lib/parsers/cr3_parser.rb
CHANGED
@@ -14,15 +14,17 @@ class FormatParser::CR3Parser
|
|
14
14
|
|
15
15
|
return unless matches_cr3_definition?
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
box_tree = Measurometer.instrument('format_parser.cr3_parser.decoder.build_box_tree') do
|
18
|
+
Decoder.new.build_box_tree(0xffffffff, @buf)
|
19
|
+
end
|
20
|
+
moov_box = box_tree.find { |box| box.type == 'moov' }
|
21
|
+
cmt1_box = moov_box&.first_descendent('CMT1')
|
22
|
+
return unless cmt1_box
|
23
|
+
|
24
|
+
width = cmt1_box.fields[:image_width]
|
25
|
+
height = cmt1_box.fields[:image_length]
|
26
|
+
rotated = cmt1_box.fields[:rotated]
|
27
|
+
orientation = cmt1_box.fields[:orientation_sym]
|
26
28
|
FormatParser::Image.new(
|
27
29
|
format: :cr3,
|
28
30
|
content_type: CR3_MIME_TYPE,
|
@@ -32,8 +34,8 @@ class FormatParser::CR3Parser
|
|
32
34
|
display_width_px: rotated ? height : width,
|
33
35
|
display_height_px: rotated ? width : height,
|
34
36
|
intrinsics: {
|
35
|
-
|
36
|
-
exif:
|
37
|
+
box_tree: box_tree,
|
38
|
+
exif: cmt1_box.fields,
|
37
39
|
},
|
38
40
|
)
|
39
41
|
end
|
data/lib/parsers/heif_parser.rb
CHANGED
@@ -121,7 +121,7 @@ class FormatParser::HEIFParser
|
|
121
121
|
end
|
122
122
|
|
123
123
|
def scan_file_type_box
|
124
|
-
file_type_box_length =
|
124
|
+
file_type_box_length = read_int
|
125
125
|
return unless read_string(4) == FILE_TYPE_BOX_MARKER
|
126
126
|
@major_brand = read_string(4)
|
127
127
|
return unless @major_brand == HEIF_MARKER || @major_brand == MIF1_MARKER
|
@@ -138,7 +138,7 @@ class FormatParser::HEIFParser
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def scan_meta_level_box
|
141
|
-
metadata_length =
|
141
|
+
metadata_length = read_int
|
142
142
|
return unless read_string(4) == META_BOX_MARKER
|
143
143
|
@metadata_start_pos = @buf.pos
|
144
144
|
@metadata_end_pos = @buf.pos + metadata_length - HEADER_LENGTH # the real data is always without the 8 initial bytes of the handler
|
@@ -146,7 +146,7 @@ class FormatParser::HEIFParser
|
|
146
146
|
|
147
147
|
# we are looking for box/containers right beneath the Meta box
|
148
148
|
# we start with the HDLR (Handler) box..
|
149
|
-
handler_length =
|
149
|
+
handler_length = read_int
|
150
150
|
return unless read_string(4) == HANDLER_MARKER
|
151
151
|
handler_length -= HEADER_LENGTH # subtract the header as usual (will not be mentioned anymore from now on)
|
152
152
|
handler_start = @buf.pos
|
@@ -158,7 +158,7 @@ class FormatParser::HEIFParser
|
|
158
158
|
@buf.seek(handler_start + handler_length) # the remaining part is reserved
|
159
159
|
|
160
160
|
# ..continue looking for the IINF box and especially for the IPRP box, containing info about the image itself
|
161
|
-
next_box_length =
|
161
|
+
next_box_length = read_int
|
162
162
|
next_box = read_string(4)
|
163
163
|
next_box_start_pos = @buf.pos
|
164
164
|
while @buf.pos < @metadata_end_pos # we iterate over all next incoming boxed but without going outside the meta-box
|
@@ -178,25 +178,25 @@ class FormatParser::HEIFParser
|
|
178
178
|
end
|
179
179
|
|
180
180
|
def read_item_info_box
|
181
|
-
version =
|
181
|
+
version = read_int(n: 1)
|
182
182
|
skip_bytes(3) # 0 flags
|
183
183
|
entry_count = if version == 0
|
184
|
-
|
184
|
+
read_int(n: 2)
|
185
185
|
else
|
186
|
-
|
186
|
+
read_int
|
187
187
|
end
|
188
188
|
@sub_items = []
|
189
189
|
entry_count.times {
|
190
|
-
item_info_entry_length =
|
190
|
+
item_info_entry_length = read_int
|
191
191
|
return unless read_string(4) == ITEM_INFO_ENTRY
|
192
192
|
item_info_end_pos = @buf.pos + item_info_entry_length - HEADER_LENGTH
|
193
|
-
version =
|
193
|
+
version = read_int(n: 1)
|
194
194
|
skip_bytes(3) # 0 flags
|
195
195
|
case version
|
196
196
|
when 2
|
197
|
-
item_id =
|
197
|
+
item_id = read_int(n: 2)
|
198
198
|
when 3
|
199
|
-
item_id =
|
199
|
+
item_id = read_int
|
200
200
|
else
|
201
201
|
return # wrong version according to standards, hence return
|
202
202
|
end
|
@@ -217,12 +217,12 @@ class FormatParser::HEIFParser
|
|
217
217
|
end
|
218
218
|
|
219
219
|
def read_primary_item_box
|
220
|
-
version =
|
220
|
+
version = read_int(n: 1)
|
221
221
|
skip_bytes(3) # flags, always 0 in this current box
|
222
222
|
@primary_item_id = if version == 0
|
223
|
-
|
223
|
+
read_int(n: 2)
|
224
224
|
else
|
225
|
-
|
225
|
+
read_int
|
226
226
|
end
|
227
227
|
end
|
228
228
|
|
@@ -232,17 +232,17 @@ class FormatParser::HEIFParser
|
|
232
232
|
# and in order to output relevant data from the format_parser we need all the properties associated to the primary_item.
|
233
233
|
# Hence the need of the association between an item and its properties, found in the ITEM_PROPERTIES_ASSOCIATION_BOX
|
234
234
|
def read_item_properties_box
|
235
|
-
ipco_length =
|
235
|
+
ipco_length = read_int
|
236
236
|
return unless read_string(4) == ITEM_PROPERTIES_CONTAINER_BOX
|
237
237
|
read_item_properties_container_box(ipco_length)
|
238
|
-
|
238
|
+
read_int # ipma_length
|
239
239
|
return unless read_string(4) == ITEM_PROPERTIES_ASSOCIATION_BOX
|
240
240
|
read_item_properties_association_box
|
241
241
|
end
|
242
242
|
|
243
243
|
def read_item_properties_container_box(box_length)
|
244
244
|
end_of_ipco_box = @buf.pos + box_length - HEADER_LENGTH
|
245
|
-
item_prop_length =
|
245
|
+
item_prop_length = read_int
|
246
246
|
item_prop_name = read_string(4)
|
247
247
|
item_prop_start_pos = @buf.pos
|
248
248
|
item_prop_index = 1
|
@@ -250,16 +250,16 @@ class FormatParser::HEIFParser
|
|
250
250
|
case item_prop_name
|
251
251
|
when IMAGE_SPATIAL_EXTENTS_BOX
|
252
252
|
read_nil_version_and_flag
|
253
|
-
width =
|
254
|
-
height =
|
253
|
+
width = read_int
|
254
|
+
height = read_int
|
255
255
|
@item_props[item_prop_index] = {
|
256
256
|
type: IMAGE_SPATIAL_EXTENTS_BOX,
|
257
257
|
width: width,
|
258
258
|
height: height
|
259
259
|
}
|
260
260
|
when PIXEL_ASPECT_RATIO_BOX
|
261
|
-
h_spacing =
|
262
|
-
v_spacing =
|
261
|
+
h_spacing = read_int
|
262
|
+
v_spacing = read_int
|
263
263
|
pixel_aspect_ratio = "#{h_spacing}/#{v_spacing}"
|
264
264
|
@item_props[item_prop_index] = {
|
265
265
|
type: PIXEL_ASPECT_RATIO_BOX,
|
@@ -267,9 +267,9 @@ class FormatParser::HEIFParser
|
|
267
267
|
}
|
268
268
|
when COLOUR_INFO_BOX
|
269
269
|
colour_info = {
|
270
|
-
colour_primaries:
|
271
|
-
transfer_characteristics:
|
272
|
-
matrix_coefficients:
|
270
|
+
colour_primaries: read_int(n: 2),
|
271
|
+
transfer_characteristics: read_int(n: 2),
|
272
|
+
matrix_coefficients: read_int(n: 2)
|
273
273
|
}
|
274
274
|
@item_props[item_prop_index] = {
|
275
275
|
type: COLOUR_INFO_BOX,
|
@@ -278,12 +278,12 @@ class FormatParser::HEIFParser
|
|
278
278
|
when PIXEL_INFO_BOX
|
279
279
|
pixel_info = []
|
280
280
|
read_nil_version_and_flag
|
281
|
-
num_channels =
|
281
|
+
num_channels = read_int(n: 1)
|
282
282
|
channel = 1
|
283
283
|
while channel <= num_channels
|
284
284
|
channel += 1
|
285
285
|
pixel_info << {
|
286
|
-
"bits_in_channel_#{channel}":
|
286
|
+
"bits_in_channel_#{channel}": read_int(n: 1)
|
287
287
|
}
|
288
288
|
end
|
289
289
|
@item_props[item_prop_index] = {
|
@@ -292,8 +292,8 @@ class FormatParser::HEIFParser
|
|
292
292
|
}
|
293
293
|
when RELATIVE_LOCATION_BOX
|
294
294
|
read_nil_version_and_flag
|
295
|
-
horizontal_offset =
|
296
|
-
vertical_offset =
|
295
|
+
horizontal_offset = read_int
|
296
|
+
vertical_offset = read_int
|
297
297
|
@item_props[item_prop_index] = {
|
298
298
|
type: RELATIVE_LOCATION_BOX,
|
299
299
|
horizontal_offset: horizontal_offset,
|
@@ -302,14 +302,14 @@ class FormatParser::HEIFParser
|
|
302
302
|
when CLEAN_APERTURE_BOX
|
303
303
|
clean_aperture = []
|
304
304
|
clean_aperture << {
|
305
|
-
clean_aperture_width_n:
|
306
|
-
clean_aperture_width_d:
|
307
|
-
clean_aperture_height_n:
|
308
|
-
clean_aperture_height_d:
|
309
|
-
horiz_off_n:
|
310
|
-
horiz_off_d:
|
311
|
-
vert_off_n:
|
312
|
-
vert_off_d:
|
305
|
+
clean_aperture_width_n: read_int,
|
306
|
+
clean_aperture_width_d: read_int,
|
307
|
+
clean_aperture_height_n: read_int,
|
308
|
+
clean_aperture_height_d: read_int,
|
309
|
+
horiz_off_n: read_int,
|
310
|
+
horiz_off_d: read_int,
|
311
|
+
vert_off_n: read_int,
|
312
|
+
vert_off_d: read_int
|
313
313
|
}
|
314
314
|
@item_props[item_prop_index] = {
|
315
315
|
type: CLEAN_APERTURE_BOX,
|
@@ -317,7 +317,7 @@ class FormatParser::HEIFParser
|
|
317
317
|
}
|
318
318
|
when IMAGE_ROTATION_BOX
|
319
319
|
read_nil_version_and_flag
|
320
|
-
binary = convert_byte_to_binary(
|
320
|
+
binary = convert_byte_to_binary(read_int(n: 1))
|
321
321
|
# we need only the last 2 bits to retrieve the angle multiplier. angle multiplier * 90 specifies the angle
|
322
322
|
rotation = binary.slice(6, 2).join.to_i(2) * 90
|
323
323
|
@item_props[item_prop_index] = {
|
@@ -331,24 +331,24 @@ class FormatParser::HEIFParser
|
|
331
331
|
end
|
332
332
|
|
333
333
|
def read_item_properties_association_box
|
334
|
-
version =
|
334
|
+
version = read_int(n: 1)
|
335
335
|
skip_bytes(2) # we skip the first 2 bytes of the flags (total of 3 bytes) cause we care only about the least significant bit
|
336
|
-
flags =
|
337
|
-
entry_count =
|
336
|
+
flags = read_int(n: 1)
|
337
|
+
entry_count = read_int
|
338
338
|
item_id = 0
|
339
339
|
entry_count.times do
|
340
340
|
item_id = if version == 0
|
341
|
-
|
341
|
+
read_int(n: 2)
|
342
342
|
else
|
343
|
-
|
343
|
+
read_int
|
344
344
|
end
|
345
345
|
|
346
|
-
association_count =
|
346
|
+
association_count = read_int(n: 1)
|
347
347
|
association_count.times do
|
348
348
|
# we need to retrieve the "essential" bit wich is just the first bit in the next byte
|
349
|
-
binary = convert_byte_to_binary(
|
349
|
+
binary = convert_byte_to_binary(read_int(n: 1))
|
350
350
|
# essential_bit = binary[0] # uncomment if needed
|
351
|
-
binary.concat(convert_byte_to_binary(
|
351
|
+
binary.concat(convert_byte_to_binary(read_int(n: 1))) if (flags & 1) == 1 # if flag is 1 we need the next 15 bits instead of only the next 7 bits
|
352
352
|
# we need to nullify the 1st bit since that one was the essential bit and doesn't count now to calculate the property index
|
353
353
|
binary[0] = 0
|
354
354
|
item_property_index = binary.join.to_i(2)
|
@@ -401,7 +401,7 @@ class FormatParser::HEIFParser
|
|
401
401
|
skip_pos = box_start_pos + box_length - HEADER_LENGTH
|
402
402
|
@buf.seek(skip_pos)
|
403
403
|
return if skip_pos >= end_pos_upper_box
|
404
|
-
next_box_length =
|
404
|
+
next_box_length = read_int
|
405
405
|
next_box_name = read_string(4)
|
406
406
|
[next_box_length, next_box_name, @buf.pos]
|
407
407
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module FormatParser
|
2
|
+
module ISOBaseMediaFileFormat
|
3
|
+
class Box < Struct.new(:type, :position, :size, :fields, :children)
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
6
|
+
self.fields ||= {}
|
7
|
+
self.children ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
# Return all children with one of the given type(s).
|
11
|
+
#
|
12
|
+
# @param [Array<String>] types The box type(s) to search for.
|
13
|
+
# @return [Array<Box>]
|
14
|
+
def all_children(*types)
|
15
|
+
children.select { |child| types.include?(child.type) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns true if there are one or more children with the given type.
|
19
|
+
#
|
20
|
+
# @param [String] type The box type to search for.
|
21
|
+
# @return [Boolean]
|
22
|
+
def child?(type)
|
23
|
+
children.any? { |child| child.type == type }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return the first child with one of the given types.
|
27
|
+
#
|
28
|
+
# @param [Array<String>] types The box type(s) to search for.
|
29
|
+
# @return [Box, nil]
|
30
|
+
def first_child(*types)
|
31
|
+
children.find { |child| types.include?(child.type) }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Find and return all descendents of a given type.
|
35
|
+
#
|
36
|
+
# @param [Array<String>] types The box type(s) to search for.
|
37
|
+
# @return [Array<Box>]
|
38
|
+
def all_descendents(*types)
|
39
|
+
children.flat_map do |child|
|
40
|
+
descendents = child.all_descendents(*types)
|
41
|
+
types.include?(child.type) ? [child] + descendents : descendents
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Find and return all descendents that exist at the given path.
|
46
|
+
#
|
47
|
+
# @param [Array<String>] path The path to search at.
|
48
|
+
# @return [Array<Box>]
|
49
|
+
def all_descendents_by_path(path)
|
50
|
+
return [] if path.empty?
|
51
|
+
next_type, *remaining_path = path
|
52
|
+
matching_children = all_children(next_type)
|
53
|
+
return matching_children if remaining_path.empty?
|
54
|
+
matching_children.flat_map { |child| child.all_descendents_by_path(remaining_path) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Find and return the first descendent (using depth-first search) of a given type.
|
58
|
+
#
|
59
|
+
# @param [Array<String>] types The box type(s) to search for.
|
60
|
+
# @return [Box, nil]
|
61
|
+
def first_descendent(*types)
|
62
|
+
children.each do |child|
|
63
|
+
return child if types.include?(child.type)
|
64
|
+
if (descendent = child.first_descendent(*types))
|
65
|
+
return descendent
|
66
|
+
end
|
67
|
+
end
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Find and return the first descendent that exists at the given path.
|
72
|
+
#
|
73
|
+
# @param [Array<String>] path The path to search at.
|
74
|
+
# @return [Box, nil]
|
75
|
+
def first_descendent_by_path(path)
|
76
|
+
all_descendents_by_path(path)[0]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|