format_parser 2.3.0 → 2.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -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 +342 -376
- 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 +53 -178
- data/spec/parsers/iso_base_media_file_format/utils_spec.rb +632 -0
- data/spec/parsers/mov_parser_spec.rb +90 -0
- data/spec/parsers/mp4_parser_spec.rb +114 -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
@@ -1,7 +1,7 @@
|
|
1
|
-
# This class provides generic methods for parsing file formats based on QuickTime-style "
|
1
|
+
# This class provides generic methods for parsing file formats based on QuickTime-style "boxes", such as those seen in
|
2
2
|
# the ISO base media file format (ISO/IEC 14496-12), a.k.a MPEG-4, and those that extend it (MP4, CR3, HEIF, etc.).
|
3
3
|
#
|
4
|
-
# For more information on
|
4
|
+
# For more information on boxes, see https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html
|
5
5
|
# or https://b.goeswhere.com/ISO_IEC_14496-12_2015.pdf.
|
6
6
|
#
|
7
7
|
# TODO: The vast majority of the methods have been commented out here. This decision was taken to expedite the release
|
@@ -9,66 +9,38 @@
|
|
9
9
|
# entirety. We should migrate existing formats that are based on the ISO base media file format and reintroduce these
|
10
10
|
# methods with tests down-the-line.
|
11
11
|
|
12
|
+
require 'matrix'
|
13
|
+
require 'parsers/iso_base_media_file_format/box'
|
14
|
+
|
12
15
|
module FormatParser
|
13
16
|
module ISOBaseMediaFileFormat
|
14
17
|
class Decoder
|
15
18
|
include FormatParser::IOUtils
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
super
|
20
|
-
self.fields ||= {}
|
21
|
-
self.children ||= []
|
22
|
-
end
|
23
|
-
|
24
|
-
# Find and return the first descendent (using depth-first search) of a given type.
|
25
|
-
#
|
26
|
-
# @param [Array<String>] types
|
27
|
-
# @return [Atom, nil]
|
28
|
-
def find_first_descendent(types)
|
29
|
-
children.each do |child|
|
30
|
-
return child if types.include?(child.type)
|
31
|
-
if (descendent = child.find_first_descendent(types))
|
32
|
-
return descendent
|
33
|
-
end
|
34
|
-
end
|
35
|
-
nil
|
36
|
-
end
|
37
|
-
|
38
|
-
# Find and return all descendents of a given type.
|
39
|
-
#
|
40
|
-
# @param [Array<String>] types
|
41
|
-
# @return [Array<Atom>]
|
42
|
-
def select_descendents(types)
|
43
|
-
children.map do |child|
|
44
|
-
descendents = child.select_descendents(types)
|
45
|
-
types.include?(child.type) ? [child] + descendents : descendents
|
46
|
-
end.flatten
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
20
|
+
# Attempt to build the ISOBMFF box tree represented in the given IO object.
|
21
|
+
#
|
50
22
|
# @param [Integer] max_read
|
51
|
-
# @param [IO, FormatParser::IOConstraint] io
|
52
|
-
# @return [Array<
|
53
|
-
def
|
23
|
+
# @param [IO, StringIO, FormatParser::IOConstraint] io
|
24
|
+
# @return [Array<Box>]
|
25
|
+
def build_box_tree(max_read, io = nil)
|
54
26
|
@buf = FormatParser::IOConstraint.new(io) if io
|
55
27
|
raise ArgumentError, "IO missing - supply a valid IO object" unless @buf
|
56
|
-
|
28
|
+
boxes = []
|
57
29
|
max_pos = @buf.pos + max_read
|
58
30
|
loop do
|
59
31
|
break if @buf.pos >= max_pos
|
60
|
-
|
61
|
-
break unless
|
62
|
-
|
32
|
+
box = parse_box
|
33
|
+
break unless box
|
34
|
+
boxes << box
|
63
35
|
end
|
64
|
-
|
36
|
+
boxes
|
65
37
|
end
|
66
38
|
|
67
39
|
protected
|
68
40
|
|
69
|
-
# A mapping of
|
70
|
-
# and return the
|
71
|
-
|
41
|
+
# A mapping of box types to their respective parser methods. Each method must take a single Integer parameter, size,
|
42
|
+
# and return the box's fields and children where appropriate as a Hash and Array of Boxes respectively.
|
43
|
+
BOX_PARSERS = {
|
72
44
|
# 'bxml' => :bxml,
|
73
45
|
# 'co64' => :co64,
|
74
46
|
# 'cprt' => :cprt,
|
@@ -81,9 +53,9 @@ module FormatParser
|
|
81
53
|
# 'fiin' => :fiin,
|
82
54
|
# 'fire' => :fire,
|
83
55
|
# 'fpar' => :fpar,
|
84
|
-
|
56
|
+
'ftyp' => :typ,
|
85
57
|
# 'gitn' => :gitn,
|
86
|
-
|
58
|
+
'hdlr' => :hdlr,
|
87
59
|
# 'hmhd' => :hmhd,
|
88
60
|
# 'iinf' => :iinf,
|
89
61
|
# 'iloc' => :iloc,
|
@@ -91,7 +63,7 @@ module FormatParser
|
|
91
63
|
# 'ipro' => :ipro,
|
92
64
|
# 'iref' => :iref,
|
93
65
|
# 'leva' => :leva,
|
94
|
-
|
66
|
+
'mdhd' => :mdhd,
|
95
67
|
'mdia' => :container,
|
96
68
|
'meco' => :container,
|
97
69
|
# 'mehd' => :mehd,
|
@@ -104,7 +76,7 @@ module FormatParser
|
|
104
76
|
'moof' => :container,
|
105
77
|
'moov' => :container,
|
106
78
|
'mvex' => :container,
|
107
|
-
|
79
|
+
'mvhd' => :mvhd,
|
108
80
|
'nmhd' => :empty,
|
109
81
|
# 'padb' => :padb,
|
110
82
|
'paen' => :container,
|
@@ -131,16 +103,16 @@ module FormatParser
|
|
131
103
|
# 'stri' => :stri,
|
132
104
|
'strk' => :container,
|
133
105
|
# 'stsc' => :stsc,
|
134
|
-
|
106
|
+
'stsd' => :stsd,
|
135
107
|
# 'stsh' => :stsh,
|
136
108
|
# 'stss' => :stss,
|
137
109
|
# 'stsz' => :stsz,
|
138
|
-
|
110
|
+
'stts' => :stts,
|
139
111
|
# 'styp' => :typ,
|
140
112
|
# 'stz2' => :stz2,
|
141
113
|
# 'subs' => :subs,
|
142
114
|
# 'tfra' => :tfra,
|
143
|
-
|
115
|
+
'tkhd' => :tkhd,
|
144
116
|
'trak' => :container,
|
145
117
|
# 'trex' => :trex,
|
146
118
|
# 'tsel' => :tsel,
|
@@ -152,69 +124,69 @@ module FormatParser
|
|
152
124
|
# 'xml ' => :xml,
|
153
125
|
}
|
154
126
|
|
155
|
-
# Parse the
|
127
|
+
# Parse the box at the IO's current position.
|
156
128
|
#
|
157
|
-
# @return [
|
158
|
-
def
|
129
|
+
# @return [Box, nil]
|
130
|
+
def parse_box
|
159
131
|
position = @buf.pos
|
160
132
|
|
161
|
-
size =
|
133
|
+
size = read_int
|
162
134
|
type = read_string(4)
|
163
|
-
size =
|
135
|
+
size = read_int(n: 8) if size == 1
|
164
136
|
body_size = size - (@buf.pos - position)
|
165
|
-
|
166
|
-
|
167
|
-
if self.class::
|
168
|
-
fields, children = method(self.class::
|
169
|
-
if @buf.pos !=
|
170
|
-
# We should never end up in this state. If we do, it likely indicates a bug in the
|
171
|
-
warn("Unexpected IO position after parsing #{type}
|
172
|
-
@buf.seek(
|
137
|
+
next_box_position = position + size
|
138
|
+
|
139
|
+
if self.class::BOX_PARSERS.include?(type)
|
140
|
+
fields, children = method(self.class::BOX_PARSERS[type]).call(body_size)
|
141
|
+
if @buf.pos != next_box_position
|
142
|
+
# We should never end up in this state. If we do, it likely indicates a bug in the box's parser method.
|
143
|
+
warn("Unexpected IO position after parsing #{type} box at position #{position}. Box size: #{size}. Expected position: #{next_box_position}. Actual position: #{@buf.pos}.")
|
144
|
+
@buf.seek(next_box_position)
|
173
145
|
end
|
174
|
-
|
146
|
+
Box.new(type, position, size, fields, children)
|
175
147
|
else
|
176
148
|
skip_bytes(body_size)
|
177
|
-
|
149
|
+
Box.new(type, position, size)
|
178
150
|
end
|
179
151
|
rescue FormatParser::IOUtils::InvalidRead
|
180
152
|
nil
|
181
153
|
end
|
182
154
|
|
183
|
-
# Parse any
|
155
|
+
# Parse any box that serves as a container, with only children and no fields of its own.
|
184
156
|
def container(size)
|
185
|
-
[nil,
|
157
|
+
[nil, build_box_tree(size)]
|
186
158
|
end
|
187
159
|
|
188
|
-
# Parse only an
|
160
|
+
# Parse only an box's version and flags, skipping the remainder of the box's body.
|
189
161
|
def empty(size)
|
190
162
|
fields = read_version_and_flags
|
191
163
|
skip_bytes(size - 4)
|
192
164
|
[fields, nil]
|
193
165
|
end
|
194
166
|
|
195
|
-
# Parse a binary XML
|
167
|
+
# Parse a binary XML box.
|
196
168
|
# def bxml(size)
|
197
169
|
# fields = read_version_and_flags.merge({
|
198
|
-
# data: (size - 4).times.map {
|
170
|
+
# data: (size - 4).times.map { read_int(n: 1) }
|
199
171
|
# })
|
200
172
|
# [fields, nil]
|
201
173
|
# end
|
202
174
|
|
203
|
-
# Parse a chunk large offset
|
175
|
+
# Parse a chunk large offset box.
|
204
176
|
# def co64(_)
|
205
177
|
# fields = read_version_and_flags
|
206
|
-
# entry_count =
|
178
|
+
# entry_count = read_int
|
207
179
|
# fields.merge!({
|
208
180
|
# entry_count: entry_count,
|
209
|
-
# entries: entry_count.times.map { { chunk_offset:
|
181
|
+
# entries: entry_count.times.map { { chunk_offset: read_int(n: 8) } }
|
210
182
|
# })
|
211
183
|
# [fields, nil]
|
212
184
|
# end
|
213
185
|
|
214
|
-
# Parse a copyright
|
186
|
+
# Parse a copyright box.
|
215
187
|
# def cprt(size)
|
216
188
|
# fields = read_version_and_flags
|
217
|
-
# tmp =
|
189
|
+
# tmp = read_int(n: 2)
|
218
190
|
# fields.merge!({
|
219
191
|
# language: [(tmp >> 10) & 0x1F, (tmp >> 5) & 0x1F, tmp & 0x1F],
|
220
192
|
# notice: read_string(size - 6)
|
@@ -222,45 +194,45 @@ module FormatParser
|
|
222
194
|
# [fields, nil]
|
223
195
|
# end
|
224
196
|
|
225
|
-
# Parse a composition to decode
|
197
|
+
# Parse a composition to decode box.
|
226
198
|
# def cslg(_)
|
227
199
|
# fields = read_version_and_flags
|
228
200
|
# version = fields[:version]
|
229
201
|
# fields.merge!({
|
230
|
-
# composition_to_dts_shift: version == 1 ?
|
231
|
-
# least_decode_to_display_delta: version == 1 ?
|
232
|
-
# greatest_decode_to_display_delta: version == 1 ?
|
233
|
-
# composition_start_time: version == 1 ?
|
234
|
-
# composition_end_time: version == 1 ?
|
202
|
+
# composition_to_dts_shift: version == 1 ? read_int(n: 8) : read_int,
|
203
|
+
# least_decode_to_display_delta: version == 1 ? read_int(n: 8) : read_int,
|
204
|
+
# greatest_decode_to_display_delta: version == 1 ? read_int(n: 8) : read_int,
|
205
|
+
# composition_start_time: version == 1 ? read_int(n: 8) : read_int,
|
206
|
+
# composition_end_time: version == 1 ? read_int(n: 8) : read_int,
|
235
207
|
# })
|
236
208
|
# [fields, nil]
|
237
209
|
# end
|
238
210
|
|
239
|
-
# Parse a composition time to sample
|
211
|
+
# Parse a composition time to sample box.
|
240
212
|
# def ctts(_)
|
241
213
|
# fields = read_version_and_flags
|
242
|
-
# entry_count =
|
214
|
+
# entry_count = read_int
|
243
215
|
# fields.merge!({
|
244
216
|
# entry_count: entry_count,
|
245
217
|
# entries: entry_count.times.map do
|
246
218
|
# {
|
247
|
-
# sample_count:
|
248
|
-
# sample_offset:
|
219
|
+
# sample_count: read_int,
|
220
|
+
# sample_offset: read_int
|
249
221
|
# }
|
250
222
|
# end
|
251
223
|
# })
|
252
224
|
# [fields, nil]
|
253
225
|
# end
|
254
226
|
|
255
|
-
# Parse a data reference
|
227
|
+
# Parse a data reference box.
|
256
228
|
# def dref(size)
|
257
229
|
# fields = read_version_and_flags.merge({
|
258
|
-
# entry_count:
|
230
|
+
# entry_count: read_int
|
259
231
|
# })
|
260
|
-
# [fields,
|
232
|
+
# [fields, build_box_tree(size - 8)]
|
261
233
|
# end
|
262
234
|
|
263
|
-
# Parse a data reference URL entry
|
235
|
+
# Parse a data reference URL entry box.
|
264
236
|
# def dref_url(size)
|
265
237
|
# fields = read_version_and_flags.merge({
|
266
238
|
# location: read_string(size - 4)
|
@@ -268,7 +240,7 @@ module FormatParser
|
|
268
240
|
# [fields, nil]
|
269
241
|
# end
|
270
242
|
|
271
|
-
# Parse a data reference URN entry
|
243
|
+
# Parse a data reference URN entry box.
|
272
244
|
# def dref_urn(size)
|
273
245
|
# fields = read_version_and_flags
|
274
246
|
# name, location = read_bytes(size - 4).unpack('Z2')
|
@@ -279,58 +251,58 @@ module FormatParser
|
|
279
251
|
# [fields, nil]
|
280
252
|
# end
|
281
253
|
|
282
|
-
# Parse an FEC reservoir
|
254
|
+
# Parse an FEC reservoir box.
|
283
255
|
# def fecr(_)
|
284
256
|
# fields = read_version_and_flags
|
285
257
|
# version = fields[:version]
|
286
|
-
# entry_count = version == 0 ?
|
258
|
+
# entry_count = version == 0 ? read_int(n: 2) : read_int
|
287
259
|
# fields.merge!({
|
288
260
|
# entry_count: entry_count,
|
289
261
|
# entries: entry_count.times.map do
|
290
262
|
# {
|
291
|
-
# item_id: version == 0 ?
|
292
|
-
# symbol_count:
|
263
|
+
# item_id: version == 0 ? read_int(n: 2) : read_int,
|
264
|
+
# symbol_count: read_int(n: 1)
|
293
265
|
# }
|
294
266
|
# end
|
295
267
|
# })
|
296
268
|
# end
|
297
269
|
|
298
|
-
# Parse an FD item information
|
270
|
+
# Parse an FD item information box.
|
299
271
|
# def fiin(size)
|
300
272
|
# fields = read_version_and_flags.merge({
|
301
|
-
# entry_count:
|
273
|
+
# entry_count: read_int(n: 2)
|
302
274
|
# })
|
303
|
-
# [fields,
|
275
|
+
# [fields, build_box_tree(size - 6)]
|
304
276
|
# end
|
305
277
|
|
306
|
-
# Parse a file reservoir
|
278
|
+
# Parse a file reservoir box.
|
307
279
|
# def fire(_)
|
308
280
|
# fields = read_version_and_flags
|
309
|
-
# entry_count = version == 0 ?
|
281
|
+
# entry_count = version == 0 ? read_int(n: 2) : read_int
|
310
282
|
# fields.merge!({
|
311
283
|
# entry_count: entry_count,
|
312
284
|
# entries: entry_count.times.map do
|
313
285
|
# {
|
314
|
-
# item_id: version == 0 ?
|
315
|
-
# symbol_count:
|
286
|
+
# item_id: version == 0 ? read_int(n: 2) : read_int,
|
287
|
+
# symbol_count: read_int
|
316
288
|
# }
|
317
289
|
# end
|
318
290
|
# })
|
319
291
|
# [fields, nil]
|
320
292
|
# end
|
321
293
|
|
322
|
-
# Parse a file partition
|
294
|
+
# Parse a file partition box.
|
323
295
|
# def fpar(_)
|
324
296
|
# fields = read_version_and_flags
|
325
297
|
# version = fields[:version]
|
326
298
|
# fields.merge!({
|
327
|
-
# item_id: version == 0 ?
|
328
|
-
# packet_payload_size:
|
329
|
-
# fec_encoding_id: skip_bytes(1) {
|
330
|
-
# fec_instance_id:
|
331
|
-
# max_source_block_length:
|
332
|
-
# encoding_symbol_length:
|
333
|
-
# max_number_of_encoding_symbols:
|
299
|
+
# item_id: version == 0 ? read_int(n: 2) : read_int,
|
300
|
+
# packet_payload_size: read_int(n: 2),
|
301
|
+
# fec_encoding_id: skip_bytes(1) { read_int(n: 1) },
|
302
|
+
# fec_instance_id: read_int(n: 2),
|
303
|
+
# max_source_block_length: read_int(n: 2),
|
304
|
+
# encoding_symbol_length: read_int(n: 2),
|
305
|
+
# max_number_of_encoding_symbols: read_int(n: 2),
|
334
306
|
# })
|
335
307
|
# # TODO: Parse scheme_specific_info, entry_count and entries { block_count, block_size }.
|
336
308
|
# skip_bytes(size - 20)
|
@@ -338,10 +310,10 @@ module FormatParser
|
|
338
310
|
# [fields, nil]
|
339
311
|
# end
|
340
312
|
|
341
|
-
# Parse a group ID to name
|
313
|
+
# Parse a group ID to name box.
|
342
314
|
# def gitn(size)
|
343
315
|
# fields = read_version_and_flags
|
344
|
-
# entry_count =
|
316
|
+
# entry_count = read_int(n: 2)
|
345
317
|
# fields.merge!({
|
346
318
|
# entry_count: entry_count
|
347
319
|
# })
|
@@ -350,43 +322,43 @@ module FormatParser
|
|
350
322
|
# [fields, nil]
|
351
323
|
# end
|
352
324
|
|
353
|
-
# Parse a handler
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
325
|
+
# Parse a handler box.
|
326
|
+
def hdlr(size)
|
327
|
+
fields = read_version_and_flags.merge({
|
328
|
+
handler_type: skip_bytes(4) { read_string(4) },
|
329
|
+
name: skip_bytes(12) { read_string(size - 24) }
|
330
|
+
})
|
331
|
+
[fields, nil]
|
332
|
+
end
|
361
333
|
|
362
|
-
# Parse a hint media header
|
334
|
+
# Parse a hint media header box.
|
363
335
|
# def hmhd(_)
|
364
336
|
# fields = read_version_and_flags.merge({
|
365
|
-
# max_pdu_size:
|
366
|
-
# avg_pdu_size:
|
367
|
-
# max_bitrate:
|
368
|
-
# avg_bitrate:
|
337
|
+
# max_pdu_size: read_int(n: 2),
|
338
|
+
# avg_pdu_size: read_int(n: 2),
|
339
|
+
# max_bitrate: read_int,
|
340
|
+
# avg_bitrate: read_int
|
369
341
|
# })
|
370
342
|
# skip_bytes(4)
|
371
343
|
# [fields, nil]
|
372
344
|
# end
|
373
345
|
|
374
|
-
# Parse an item info
|
346
|
+
# Parse an item info box.
|
375
347
|
# def iinf(size)
|
376
348
|
# fields = read_version_and_flags.merge({
|
377
|
-
# entry_count: version == 0 ?
|
349
|
+
# entry_count: version == 0 ? read_int(n: 2) : read_int
|
378
350
|
# })
|
379
|
-
# [fields,
|
351
|
+
# [fields, build_box_tree(size - 8)]
|
380
352
|
# end
|
381
353
|
|
382
|
-
# Parse an item location
|
354
|
+
# Parse an item location box.
|
383
355
|
# def iloc(_)
|
384
356
|
# fields = read_version_and_flags
|
385
|
-
# tmp =
|
357
|
+
# tmp = read_int(n: 2)
|
386
358
|
# item_count = if version < 2
|
387
|
-
#
|
359
|
+
# read_int(n: 2)
|
388
360
|
# elsif version == 2
|
389
|
-
#
|
361
|
+
# read_int
|
390
362
|
# end
|
391
363
|
# offset_size = (tmp >> 12) & 0x7
|
392
364
|
# length_size = (tmp >> 8) & 0x7
|
@@ -400,15 +372,15 @@ module FormatParser
|
|
400
372
|
# items: item_count.times.map do
|
401
373
|
# item = {
|
402
374
|
# item_id: if version < 2
|
403
|
-
#
|
375
|
+
# read_int(n: 2)
|
404
376
|
# elsif version == 2
|
405
|
-
#
|
377
|
+
# read_int
|
406
378
|
# end
|
407
379
|
# }
|
408
|
-
# item[:construction_method] =
|
409
|
-
# item[:data_reference_index] =
|
380
|
+
# item[:construction_method] = read_int(n: 2) & 0x7 if version == 1 || version == 2
|
381
|
+
# item[:data_reference_index] = read_int(n: 2)
|
410
382
|
# skip_bytes(base_offset_size) # TODO: Dynamically parse base_offset based on base_offset_size
|
411
|
-
# extent_count =
|
383
|
+
# extent_count = read_int(n: 2)
|
412
384
|
# item[:extent_count] = extent_count
|
413
385
|
# # TODO: Dynamically parse extent_index, extent_offset and extent_length based on their respective sizes.
|
414
386
|
# skip_bytes(extent_count * (offset_size + length_size))
|
@@ -417,34 +389,34 @@ module FormatParser
|
|
417
389
|
# })
|
418
390
|
# end
|
419
391
|
|
420
|
-
# Parse an item info entry
|
392
|
+
# Parse an item info entry box.
|
421
393
|
# def infe(size)
|
422
|
-
# # TODO: This
|
394
|
+
# # TODO: This box is super-complicated with optional and/or version-dependent fields and children.
|
423
395
|
# empty(size)
|
424
396
|
# end
|
425
397
|
|
426
|
-
# Parse an item protection
|
398
|
+
# Parse an item protection box.
|
427
399
|
# def ipro(size)
|
428
400
|
# fields = read_version_and_flags.merge({
|
429
|
-
# protection_count:
|
401
|
+
# protection_count: read_int(n: 2)
|
430
402
|
# })
|
431
|
-
# [fields,
|
403
|
+
# [fields, build_box_tree(size - 6)]
|
432
404
|
# end
|
433
405
|
|
434
|
-
# Parse an item reference
|
406
|
+
# Parse an item reference box.
|
435
407
|
# def iref(_)
|
436
|
-
# [read_version_and_flags,
|
408
|
+
# [read_version_and_flags, build_box_tree(size - 4)]
|
437
409
|
# end
|
438
410
|
|
439
|
-
# Parse a level assignment
|
411
|
+
# Parse a level assignment box.
|
440
412
|
# def leva(_)
|
441
413
|
# fields = read_version_and_flags
|
442
|
-
# level_count =
|
414
|
+
# level_count = read_int(n: 1)
|
443
415
|
# fields.merge!({
|
444
416
|
# level_count: level_count,
|
445
417
|
# levels: level_count.times.map do
|
446
|
-
# track_id =
|
447
|
-
# tmp =
|
418
|
+
# track_id = read_int
|
419
|
+
# tmp = read_int(n: 1)
|
448
420
|
# assignment_type = tmp & 0x7F
|
449
421
|
# level = {
|
450
422
|
# track_id: track_id,
|
@@ -452,14 +424,14 @@ module FormatParser
|
|
452
424
|
# assignment_type: assignment_type
|
453
425
|
# }
|
454
426
|
# if assignment_type == 0
|
455
|
-
# level[:grouping_type] =
|
427
|
+
# level[:grouping_type] = read_int
|
456
428
|
# elsif assignment_type == 1
|
457
429
|
# level.merge!({
|
458
|
-
# grouping_type:
|
459
|
-
# grouping_type_parameter:
|
430
|
+
# grouping_type: read_int,
|
431
|
+
# grouping_type_parameter: read_int
|
460
432
|
# })
|
461
433
|
# elsif assignment_type == 4
|
462
|
-
# level[:sub_track_id] =
|
434
|
+
# level[:sub_track_id] = read_int
|
463
435
|
# end
|
464
436
|
# level
|
465
437
|
# end
|
@@ -467,87 +439,87 @@ module FormatParser
|
|
467
439
|
# [fields, nil]
|
468
440
|
# end
|
469
441
|
|
470
|
-
# Parse a media header
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
442
|
+
# Parse a media header box.
|
443
|
+
def mdhd(_)
|
444
|
+
fields = read_version_and_flags
|
445
|
+
version = fields[:version]
|
446
|
+
fields.merge!({
|
447
|
+
creation_time: version == 1 ? read_int(n: 8) : read_int,
|
448
|
+
modification_time: version == 1 ? read_int(n: 8) : read_int,
|
449
|
+
timescale: read_int,
|
450
|
+
duration: version == 1 ? read_int(n: 8) : read_int,
|
451
|
+
})
|
452
|
+
tmp = read_int(n: 2)
|
453
|
+
fields[:language] = [(tmp >> 10) & 0x1F, (tmp >> 5) & 0x1F, tmp & 0x1F]
|
454
|
+
skip_bytes(2)
|
455
|
+
[fields, nil]
|
456
|
+
end
|
485
457
|
|
486
|
-
# Parse a movie extends header
|
458
|
+
# Parse a movie extends header box.
|
487
459
|
# def mehd(_)
|
488
460
|
# fields = read_version_and_flags
|
489
461
|
# version = fields[:version]
|
490
|
-
# fields[:fragment_duration] = version == 1 ?
|
462
|
+
# fields[:fragment_duration] = version == 1 ? read_int(n: 8) : read_int
|
491
463
|
# [fields, nil]
|
492
464
|
# end
|
493
465
|
|
494
|
-
# Parse an metabox relation
|
466
|
+
# Parse an metabox relation box.
|
495
467
|
# def mere(_)
|
496
468
|
# fields = read_version_and_flags.merge({
|
497
|
-
# first_metabox_handler_type:
|
498
|
-
# second_metabox_handler_type:
|
499
|
-
# metabox_relation:
|
469
|
+
# first_metabox_handler_type: read_int,
|
470
|
+
# second_metabox_handler_type: read_int,
|
471
|
+
# metabox_relation: read_int(n: 1)
|
500
472
|
# })
|
501
473
|
# [fields, nil]
|
502
474
|
# end
|
503
475
|
|
504
|
-
# Parse a meta
|
476
|
+
# Parse a meta box.
|
505
477
|
# def meta(size)
|
506
478
|
# fields = read_version_and_flags
|
507
|
-
# [fields,
|
479
|
+
# [fields, build_box_tree(size - 4)]
|
508
480
|
# end
|
509
481
|
|
510
|
-
# Parse a movie fragment header
|
482
|
+
# Parse a movie fragment header box.
|
511
483
|
# def mfhd(_)
|
512
484
|
# fields = read_version_and_flags.merge({
|
513
|
-
# sequence_number:
|
485
|
+
# sequence_number: read_int
|
514
486
|
# })
|
515
487
|
# [fields, nil]
|
516
488
|
# end
|
517
489
|
|
518
|
-
# Parse a movie fragment random access offset
|
490
|
+
# Parse a movie fragment random access offset box.
|
519
491
|
# def mfro(_)
|
520
492
|
# fields = read_version_and_flags.merge({
|
521
|
-
# size:
|
493
|
+
# size: read_int
|
522
494
|
# })
|
523
495
|
# [fields, nil]
|
524
496
|
# end
|
525
497
|
|
526
|
-
# Parse a movie header
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
498
|
+
# Parse a movie header box.
|
499
|
+
def mvhd(_)
|
500
|
+
fields = read_version_and_flags
|
501
|
+
version = fields[:version]
|
502
|
+
fields.merge!({
|
503
|
+
creation_time: version == 1 ? read_int(n: 8) : read_int,
|
504
|
+
modification_time: version == 1 ? read_int(n: 8) : read_int,
|
505
|
+
timescale: read_int,
|
506
|
+
duration: version == 1 ? read_int(n: 8) : read_int,
|
507
|
+
rate: read_fixed_point(n: 4),
|
508
|
+
volume: read_fixed_point(n: 2, signed: true),
|
509
|
+
matrix: skip_bytes(10) { read_matrix },
|
510
|
+
next_trak_id: skip_bytes(24) { read_int },
|
511
|
+
})
|
512
|
+
[fields, nil]
|
513
|
+
end
|
542
514
|
|
543
|
-
# Parse a padding bits
|
515
|
+
# Parse a padding bits box.
|
544
516
|
# def padb(_)
|
545
517
|
# fields = read_version_and_flags
|
546
|
-
# sample_count =
|
518
|
+
# sample_count = read_int
|
547
519
|
# fields.merge!({
|
548
520
|
# sample_count: sample_count,
|
549
521
|
# padding: ((sample_count + 1) / 2).times.map do
|
550
|
-
# tmp =
|
522
|
+
# tmp = read_int(n: 1)
|
551
523
|
# {
|
552
524
|
# padding_1: tmp >> 4,
|
553
525
|
# padding_2: tmp & 0x07
|
@@ -557,170 +529,170 @@ module FormatParser
|
|
557
529
|
# [fields, nil]
|
558
530
|
# end
|
559
531
|
|
560
|
-
# Parse a progressive download information
|
532
|
+
# Parse a progressive download information box.
|
561
533
|
# def pdin(size)
|
562
534
|
# fields = read_version_and_flags.merge({
|
563
535
|
# entries: ((size - 4) / 8).times.map do
|
564
536
|
# {
|
565
|
-
# rate:
|
566
|
-
# initial_delay:
|
537
|
+
# rate: read_int,
|
538
|
+
# initial_delay: read_int
|
567
539
|
# }
|
568
540
|
# end
|
569
541
|
# })
|
570
542
|
# [fields, nil]
|
571
543
|
# end
|
572
544
|
|
573
|
-
# Parse a primary item
|
545
|
+
# Parse a primary item box.
|
574
546
|
# def pitm(_)
|
575
547
|
# fields = read_version_and_flags.merge({
|
576
|
-
# item_id: version == 0 ?
|
548
|
+
# item_id: version == 0 ? read_int(n: 2) : read_int
|
577
549
|
# })
|
578
550
|
# [fields, nil]
|
579
551
|
# end
|
580
552
|
|
581
|
-
# Parse a producer reference time
|
553
|
+
# Parse a producer reference time box.
|
582
554
|
# def prft(_)
|
583
555
|
# fields = read_version_and_flags
|
584
556
|
# version = fields[:version]
|
585
557
|
# fields.merge!({
|
586
|
-
# reference_track_id:
|
587
|
-
# ntp_timestamp:
|
588
|
-
# media_time: version == 0 ?
|
558
|
+
# reference_track_id: read_int,
|
559
|
+
# ntp_timestamp: read_int(n: 8),
|
560
|
+
# media_time: version == 0 ? read_int : read_int(n: 8)
|
589
561
|
# })
|
590
562
|
# [fields, nil]
|
591
563
|
# end
|
592
564
|
|
593
|
-
# Parse a sample auxiliary information offsets
|
565
|
+
# Parse a sample auxiliary information offsets box.
|
594
566
|
# def saio(_)
|
595
567
|
# fields = read_version_and_flags
|
596
568
|
# version = field[:version]
|
597
569
|
# flags = fields[:flags]
|
598
570
|
# fields.merge!({
|
599
|
-
# aux_info_type:
|
600
|
-
# aux_info_type_parameter:
|
571
|
+
# aux_info_type: read_int,
|
572
|
+
# aux_info_type_parameter: read_int
|
601
573
|
# }) if flags & 0x1
|
602
|
-
# entry_count =
|
574
|
+
# entry_count = read_int
|
603
575
|
# fields.merge!({
|
604
576
|
# entry_count: entry_count,
|
605
|
-
# offsets: entry_count.times.map { version == 0 ?
|
577
|
+
# offsets: entry_count.times.map { version == 0 ? read_int : read_int(n: 8) }
|
606
578
|
# })
|
607
579
|
# [fields, nil]
|
608
580
|
# end
|
609
581
|
|
610
|
-
# Parse a sample auxiliary information sizes
|
582
|
+
# Parse a sample auxiliary information sizes box.
|
611
583
|
# def saiz(_)
|
612
584
|
# fields = read_version_and_flags
|
613
585
|
# flags = fields[:flags]
|
614
586
|
# fields.merge!({
|
615
|
-
# aux_info_type:
|
616
|
-
# aux_info_type_parameter:
|
587
|
+
# aux_info_type: read_int,
|
588
|
+
# aux_info_type_parameter: read_int
|
617
589
|
# }) if flags & 0x1
|
618
|
-
# default_sample_info_size =
|
619
|
-
# sample_count =
|
590
|
+
# default_sample_info_size = read_int(n: 1)
|
591
|
+
# sample_count = read_int
|
620
592
|
# fields.merge!({
|
621
593
|
# default_sample_info_size: default_sample_info_size,
|
622
594
|
# sample_count: sample_count
|
623
595
|
# })
|
624
|
-
# fields[:sample_info_sizes] = sample_count.times.map {
|
596
|
+
# fields[:sample_info_sizes] = sample_count.times.map { read_int(n: 1) } if default_sample_info_size == 0
|
625
597
|
# [fields, nil]
|
626
598
|
# end
|
627
599
|
|
628
|
-
# Parse a sample to group
|
600
|
+
# Parse a sample to group box.
|
629
601
|
# def sbgp(_)
|
630
602
|
# fields = read_version_and_flags
|
631
|
-
# fields[:grouping_type] =
|
632
|
-
# fields[:grouping_type_parameter] =
|
633
|
-
# entry_count =
|
603
|
+
# fields[:grouping_type] = read_int
|
604
|
+
# fields[:grouping_type_parameter] = read_int if fields[:version] == 1
|
605
|
+
# entry_count = read_int
|
634
606
|
# fields.merge!({
|
635
607
|
# entry_count: entry_count,
|
636
608
|
# entries: entry_count.times.map do
|
637
609
|
# {
|
638
|
-
# sample_count:
|
639
|
-
# group_description_index:
|
610
|
+
# sample_count: read_int,
|
611
|
+
# group_description_index: read_int
|
640
612
|
# }
|
641
613
|
# end
|
642
614
|
# })
|
643
615
|
# [fields, nil]
|
644
616
|
# end
|
645
617
|
|
646
|
-
# Parse a scheme type
|
618
|
+
# Parse a scheme type box.
|
647
619
|
# def schm(_)
|
648
620
|
# fields = read_version_and_flags.merge({
|
649
621
|
# scheme_type: read_string(4),
|
650
|
-
# scheme_version:
|
622
|
+
# scheme_version: read_int,
|
651
623
|
# })
|
652
|
-
# fields[:scheme_uri] = (size - 12).times.map {
|
624
|
+
# fields[:scheme_uri] = (size - 12).times.map { read_int(n: 1) } if flags & 0x1 != 0
|
653
625
|
# [fields, nil]
|
654
626
|
# end
|
655
627
|
|
656
|
-
# Parse an independent and disposable samples
|
628
|
+
# Parse an independent and disposable samples box.
|
657
629
|
# def sdtp(size)
|
658
|
-
# # TODO: Parsing this
|
630
|
+
# # TODO: Parsing this box needs the sample_count from the sample size box (`stsz`).
|
659
631
|
# empty(size)
|
660
632
|
# end
|
661
633
|
|
662
|
-
# Parse an FD session group
|
634
|
+
# Parse an FD session group box.
|
663
635
|
# def segr(_)
|
664
|
-
# num_session_groups =
|
636
|
+
# num_session_groups = read_int(n: 2)
|
665
637
|
# fields = {
|
666
638
|
# num_session_groups: num_session_groups,
|
667
639
|
# session_groups: num_session_groups.times.map do
|
668
|
-
# entry_count =
|
640
|
+
# entry_count = read_int(n: 1)
|
669
641
|
# session_group = {
|
670
642
|
# entry_count: entry_count,
|
671
|
-
# entries: entry_count.times.map { { group_id:
|
643
|
+
# entries: entry_count.times.map { { group_id: read_int } }
|
672
644
|
# }
|
673
|
-
# num_channels_in_session_group =
|
645
|
+
# num_channels_in_session_group = read_int(n: 2)
|
674
646
|
# session_group.merge({
|
675
647
|
# num_channels_in_session_group: num_channels_in_session_group,
|
676
|
-
# channels: num_channels_in_session_group.times.map { { hint_track_id:
|
648
|
+
# channels: num_channels_in_session_group.times.map { { hint_track_id: read_int } }
|
677
649
|
# })
|
678
650
|
# end
|
679
651
|
# }
|
680
652
|
# [fields, nil]
|
681
653
|
# end
|
682
654
|
|
683
|
-
# Parse a sample group description
|
655
|
+
# Parse a sample group description box.
|
684
656
|
# def sgpd(_)
|
685
657
|
# fields = read_version_and_flags
|
686
658
|
# version = fields[:version]
|
687
|
-
# fields[:grouping_type] =
|
688
|
-
# fields[:default_length] =
|
689
|
-
# fields[:default_sample_description_index] =
|
690
|
-
# entry_count =
|
659
|
+
# fields[:grouping_type] = read_int
|
660
|
+
# fields[:default_length] = read_int if version == 1
|
661
|
+
# fields[:default_sample_description_index] = read_int if version >= 2
|
662
|
+
# entry_count = read_int
|
691
663
|
# fields.merge!({
|
692
664
|
# entry_count: entry_count,
|
693
665
|
# entries: entry_count.times.map do
|
694
666
|
# entry = {}
|
695
|
-
# entry[:description_length] =
|
696
|
-
# entry[:
|
667
|
+
# entry[:description_length] = read_int if version == 1 && fields[:default_length] == 0
|
668
|
+
# entry[:box] = parse_box
|
697
669
|
# end
|
698
670
|
# })
|
699
671
|
# [fields, nil]
|
700
672
|
# end
|
701
673
|
|
702
|
-
# Parse a segment index
|
674
|
+
# Parse a segment index box.
|
703
675
|
# def sidx(_)
|
704
676
|
# fields = read_version_and_flags.merge({
|
705
|
-
# reference_id:
|
706
|
-
# timescale:
|
677
|
+
# reference_id: read_int,
|
678
|
+
# timescale: read_int
|
707
679
|
# })
|
708
680
|
# version = fields[:version]
|
709
681
|
# fields.merge!({
|
710
|
-
# earliest_presentation_time: version == 0 ?
|
711
|
-
# first_offset: version == 0 ?
|
682
|
+
# earliest_presentation_time: version == 0 ? read_int : read_int(n: 8),
|
683
|
+
# first_offset: version == 0 ? read_int : read_int(n: 8),
|
712
684
|
# })
|
713
|
-
# reference_count = skip_bytes(2) {
|
685
|
+
# reference_count = skip_bytes(2) { read_int(n: 2) }
|
714
686
|
# fields.merge!({
|
715
687
|
# reference_count: reference_count,
|
716
688
|
# references: reference_count.times.map do
|
717
|
-
# tmp =
|
689
|
+
# tmp = read_int
|
718
690
|
# reference = {
|
719
691
|
# reference_type: tmp >> 31,
|
720
692
|
# referenced_size: tmp & 0x7FFFFFFF,
|
721
|
-
# subsegment_duration:
|
693
|
+
# subsegment_duration: read_int
|
722
694
|
# }
|
723
|
-
# tmp =
|
695
|
+
# tmp = read_int
|
724
696
|
# reference.merge({
|
725
697
|
# starts_with_sap: tmp >> 31,
|
726
698
|
# sap_type: (tmp >> 28) & 0x7,
|
@@ -731,27 +703,27 @@ module FormatParser
|
|
731
703
|
# [fields, nil]
|
732
704
|
# end
|
733
705
|
|
734
|
-
# Parse a sound media header
|
706
|
+
# Parse a sound media header box.
|
735
707
|
# def smhd(_)
|
736
708
|
# fields = read_version_and_flags.merge({
|
737
|
-
# balance:
|
709
|
+
# balance: read_fixed_point(n: 2, signed: true),
|
738
710
|
# })
|
739
711
|
# skip_bytes(2)
|
740
712
|
# [fields, nil]
|
741
713
|
# end
|
742
714
|
|
743
|
-
# Parse a subsegment index
|
715
|
+
# Parse a subsegment index box.
|
744
716
|
# def ssix(_)
|
745
717
|
# fields = read_version_and_flags
|
746
|
-
# subsegment_count =
|
718
|
+
# subsegment_count = read_int
|
747
719
|
# fields.merge!({
|
748
720
|
# subsegment_count: subsegment_count,
|
749
721
|
# subsegments: subsegment_count.times.map do
|
750
|
-
# range_count =
|
722
|
+
# range_count = read_int
|
751
723
|
# {
|
752
724
|
# range_count: range_count,
|
753
725
|
# ranges: range_count.times.map do
|
754
|
-
# tmp =
|
726
|
+
# tmp = read_int
|
755
727
|
# {
|
756
728
|
# level: tmp >> 24,
|
757
729
|
# range_size: tmp & 0x00FFFFFF
|
@@ -763,142 +735,142 @@ module FormatParser
|
|
763
735
|
# [fields, nil]
|
764
736
|
# end
|
765
737
|
|
766
|
-
# Parse a chunk offset
|
738
|
+
# Parse a chunk offset box.
|
767
739
|
# def stco(_)
|
768
740
|
# fields = read_version_and_flags
|
769
|
-
# entry_count =
|
741
|
+
# entry_count = read_int
|
770
742
|
# fields.merge!({
|
771
743
|
# entry_count: entry_count,
|
772
|
-
# entries: entry_count.times.map { { chunk_offset:
|
744
|
+
# entries: entry_count.times.map { { chunk_offset: read_int } }
|
773
745
|
# })
|
774
746
|
# [fields, nil]
|
775
747
|
# end
|
776
748
|
|
777
|
-
# Parse a degradation priority
|
749
|
+
# Parse a degradation priority box.
|
778
750
|
# def stdp(size)
|
779
|
-
# # TODO: Parsing this
|
751
|
+
# # TODO: Parsing this box needs the sample_count from the sample size box (`stsz`).
|
780
752
|
# empty(size)
|
781
753
|
# end
|
782
754
|
|
783
|
-
# Parse a sub track information
|
755
|
+
# Parse a sub track information box.
|
784
756
|
# def stri(size)
|
785
757
|
# fields = read_version_and_flags.merge({
|
786
|
-
# switch_group:
|
787
|
-
# alternate_group:
|
788
|
-
# sub_track_id:
|
789
|
-
# attribute_list: ((size - 12) / 4).times.map {
|
758
|
+
# switch_group: read_int(n: 2),
|
759
|
+
# alternate_group: read_int(n: 2),
|
760
|
+
# sub_track_id: read_int,
|
761
|
+
# attribute_list: ((size - 12) / 4).times.map { read_int }
|
790
762
|
# })
|
791
763
|
# [fields, nil]
|
792
764
|
# end
|
793
765
|
|
794
|
-
# Parse a sample to chunk
|
766
|
+
# Parse a sample to chunk box.
|
795
767
|
# def stsc(_)
|
796
768
|
# fields = read_version_and_flags
|
797
|
-
# entry_count =
|
769
|
+
# entry_count = read_int
|
798
770
|
# fields.merge!({
|
799
771
|
# entry_count: entry_count,
|
800
772
|
# entries: entry_count.times.map do
|
801
773
|
# {
|
802
|
-
# first_chunk:
|
803
|
-
# samples_per_chunk:
|
804
|
-
# sample_description_index:
|
774
|
+
# first_chunk: read_int,
|
775
|
+
# samples_per_chunk: read_int,
|
776
|
+
# sample_description_index: read_int
|
805
777
|
# }
|
806
778
|
# end
|
807
779
|
# })
|
808
780
|
# [fields, nil]
|
809
781
|
# end
|
810
782
|
|
811
|
-
# Parse a sample descriptions
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
783
|
+
# Parse a sample descriptions box.
|
784
|
+
def stsd(size)
|
785
|
+
fields = read_version_and_flags.merge({
|
786
|
+
entry_count: read_int
|
787
|
+
})
|
788
|
+
[fields, build_box_tree(size - 8)]
|
789
|
+
end
|
818
790
|
|
819
|
-
# Parse a shadow sync sample
|
791
|
+
# Parse a shadow sync sample box.
|
820
792
|
# def stsh(_)
|
821
793
|
# fields = read_version_and_flags
|
822
|
-
# entry_count =
|
794
|
+
# entry_count = read_int
|
823
795
|
# fields.merge!({
|
824
796
|
# entry_count: entry_count,
|
825
797
|
# entries: entry_count.times.map {
|
826
798
|
# {
|
827
|
-
# shadowed_sample_number:
|
828
|
-
# sync_sample_number:
|
799
|
+
# shadowed_sample_number: read_int,
|
800
|
+
# sync_sample_number: read_int
|
829
801
|
# }
|
830
802
|
# }
|
831
803
|
# })
|
832
804
|
# [fields, nil]
|
833
805
|
# end
|
834
806
|
|
835
|
-
# Parse a sync sample
|
807
|
+
# Parse a sync sample box.
|
836
808
|
# def stss(_)
|
837
809
|
# fields = read_version_and_flags
|
838
|
-
# entry_count =
|
810
|
+
# entry_count = read_int
|
839
811
|
# fields.merge!({
|
840
812
|
# entry_count: entry_count,
|
841
|
-
# entries: entry_count.times.map { { sample_number:
|
813
|
+
# entries: entry_count.times.map { { sample_number: read_int } }
|
842
814
|
# })
|
843
815
|
# [fields, nil]
|
844
816
|
# end
|
845
817
|
|
846
|
-
# Parse a sample size
|
818
|
+
# Parse a sample size box.
|
847
819
|
# def stsz(_)
|
848
820
|
# fields = read_version_and_flags
|
849
|
-
# sample_size =
|
850
|
-
# sample_count =
|
821
|
+
# sample_size = read_int
|
822
|
+
# sample_count = read_int
|
851
823
|
# fields.merge!({
|
852
824
|
# sample_size: sample_size,
|
853
825
|
# sample_count: sample_count,
|
854
826
|
# })
|
855
|
-
# fields[:entries] = sample_count.times.map { { entry_size:
|
827
|
+
# fields[:entries] = sample_count.times.map { { entry_size: read_int } } if sample_size == 0
|
856
828
|
# [fields, nil]
|
857
829
|
# end
|
858
830
|
|
859
|
-
# Parse a decoding time to sample
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
831
|
+
# Parse a decoding time to sample box.
|
832
|
+
def stts(_)
|
833
|
+
fields = read_version_and_flags
|
834
|
+
entry_count = read_int
|
835
|
+
fields.merge!({
|
836
|
+
entry_count: entry_count,
|
837
|
+
entries: entry_count.times.map do
|
838
|
+
{
|
839
|
+
sample_count: read_int,
|
840
|
+
sample_delta: read_int
|
841
|
+
}
|
842
|
+
end
|
843
|
+
})
|
844
|
+
[fields, nil]
|
845
|
+
end
|
874
846
|
|
875
|
-
# Parse a compact sample size
|
847
|
+
# Parse a compact sample size box.
|
876
848
|
# def stz2(size)
|
877
849
|
# fields = read_version_and_flags.merge({
|
878
|
-
# field_size: skip_bytes(3) {
|
879
|
-
# sample_count:
|
850
|
+
# field_size: skip_bytes(3) { read_int(n: 1) },
|
851
|
+
# sample_count: read_int
|
880
852
|
# })
|
881
853
|
# # TODO: Handling for parsing entry sizes dynamically based on field size.
|
882
854
|
# skip_bytes(size - 12)
|
883
855
|
# [fields, nil]
|
884
856
|
# end
|
885
857
|
|
886
|
-
# Parse a sub-sample information
|
858
|
+
# Parse a sub-sample information box.
|
887
859
|
# def subs(_)
|
888
860
|
# fields = read_version_and_flags
|
889
|
-
# entry_count =
|
861
|
+
# entry_count = read_int
|
890
862
|
# fields[:entries] = entry_count.times.map do
|
891
|
-
# sample_delta =
|
892
|
-
# subsample_count =
|
863
|
+
# sample_delta = read_int
|
864
|
+
# subsample_count = read_int(n: 2)
|
893
865
|
# {
|
894
866
|
# sample_delta: sample_delta,
|
895
867
|
# subsample_count: subsample_count,
|
896
868
|
# subsample_information: subsample_count.times.map do
|
897
869
|
# {
|
898
|
-
# subsample_size: version == 1 ?
|
899
|
-
# subsample_priority:
|
900
|
-
# discardable:
|
901
|
-
# codec_specific_parameters:
|
870
|
+
# subsample_size: version == 1 ? read_int : read_int(n: 2),
|
871
|
+
# subsample_priority: read_int(n: 1),
|
872
|
+
# discardable: read_int(n: 1),
|
873
|
+
# codec_specific_parameters: read_int
|
902
874
|
# }
|
903
875
|
# end
|
904
876
|
# }
|
@@ -906,17 +878,17 @@ module FormatParser
|
|
906
878
|
# [fields, nil]
|
907
879
|
# end
|
908
880
|
|
909
|
-
# Parse a track fragment random access
|
881
|
+
# Parse a track fragment random access box.
|
910
882
|
# def tfra(_)
|
911
883
|
# fields = read_version_and_flags
|
912
884
|
# version = fields[:version]
|
913
|
-
# fields[:track_id] =
|
885
|
+
# fields[:track_id] = read_int
|
914
886
|
# skip_bytes(3)
|
915
|
-
# tmp =
|
887
|
+
# tmp = read_int(n: 1)
|
916
888
|
# size_of_traf_number = (tmp >> 4) & 0x3
|
917
889
|
# size_of_trun_number = (tmp >> 2) & 0x3
|
918
890
|
# size_of_sample_number = tmp & 0x3
|
919
|
-
# entry_count =
|
891
|
+
# entry_count = read_int
|
920
892
|
# fields.merge!({
|
921
893
|
# size_of_traf_number: size_of_traf_number,
|
922
894
|
# size_of_trun_number: size_of_trun_number,
|
@@ -924,8 +896,8 @@ module FormatParser
|
|
924
896
|
# entry_count: entry_count,
|
925
897
|
# entries: entry_count.times.map do
|
926
898
|
# entry = {
|
927
|
-
# time: version == 1 ?
|
928
|
-
# moof_offset: version == 1 ?
|
899
|
+
# time: version == 1 ? read_int(n: 8) : read_int,
|
900
|
+
# moof_offset: version == 1 ? read_int(n: 8) : read_int
|
929
901
|
# }
|
930
902
|
# # TODO: Handling for parsing traf_number, trun_number and sample_number dynamically based on their sizes.
|
931
903
|
# skip_bytes(size_of_traf_number + size_of_trun_number + size_of_sample_number + 3)
|
@@ -935,74 +907,74 @@ module FormatParser
|
|
935
907
|
# [fields, nil]
|
936
908
|
# end
|
937
909
|
|
938
|
-
# Parse a track header
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
910
|
+
# Parse a track header box.
|
911
|
+
def tkhd(_)
|
912
|
+
fields = read_version_and_flags
|
913
|
+
version = fields[:version]
|
914
|
+
fields.merge!({
|
915
|
+
creation_time: version == 1 ? read_int(n: 8) : read_int,
|
916
|
+
modification_time: version == 1 ? read_int(n: 8) : read_int,
|
917
|
+
track_id: read_int,
|
918
|
+
duration: skip_bytes(4) { version == 1 ? read_int(n: 8) : read_int },
|
919
|
+
layer: skip_bytes(8) { read_int(n: 2) },
|
920
|
+
alternate_group: read_int(n: 2),
|
921
|
+
volume: read_fixed_point(n: 2, signed: true),
|
922
|
+
matrix: skip_bytes(2) { read_matrix },
|
923
|
+
width: read_fixed_point(n: 4),
|
924
|
+
height: read_fixed_point(n: 4)
|
925
|
+
})
|
926
|
+
[fields, nil]
|
927
|
+
end
|
956
928
|
|
957
|
-
# Parse a track extends
|
929
|
+
# Parse a track extends box.
|
958
930
|
# def trex(_)
|
959
931
|
# fields = read_version_and_flags.merge({
|
960
|
-
# track_id:
|
961
|
-
# default_sample_description_index:
|
962
|
-
# default_sample_duration:
|
963
|
-
# default_sample_size:
|
964
|
-
# default_sample_flags:
|
932
|
+
# track_id: read_int,
|
933
|
+
# default_sample_description_index: read_int,
|
934
|
+
# default_sample_duration: read_int,
|
935
|
+
# default_sample_size: read_int,
|
936
|
+
# default_sample_flags: read_int
|
965
937
|
# })
|
966
938
|
# [fields, nil]
|
967
939
|
# end
|
968
940
|
|
969
|
-
# Parse a track selection
|
941
|
+
# Parse a track selection box.
|
970
942
|
# def tsel(size)
|
971
943
|
# fields = read_version_and_flags.merge({
|
972
|
-
# switch_group:
|
973
|
-
# attribute_list: ((size - 8) / 4).times.map {
|
944
|
+
# switch_group: read_int,
|
945
|
+
# attribute_list: ((size - 8) / 4).times.map { read_int }
|
974
946
|
# })
|
975
947
|
# [fields, nil]
|
976
948
|
# end
|
977
949
|
|
978
|
-
# Parse a file/segment type compatibility
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
950
|
+
# Parse a file/segment type compatibility box.
|
951
|
+
def typ(size)
|
952
|
+
compatible_brands_count = (size - 8) / 4
|
953
|
+
fields = {
|
954
|
+
major_brand: read_string(4),
|
955
|
+
minor_version: read_int,
|
956
|
+
compatible_brands: compatible_brands_count.times.map { read_string(4) }
|
957
|
+
}
|
958
|
+
[fields, nil]
|
959
|
+
end
|
988
960
|
|
989
|
-
# Parse a UUID
|
961
|
+
# Parse a UUID box.
|
990
962
|
def uuid(size)
|
991
963
|
fields = { usertype: read_bytes(16).unpack('H*').first }
|
992
964
|
skip_bytes(size - 16)
|
993
965
|
[fields, nil]
|
994
966
|
end
|
995
967
|
|
996
|
-
# Parse a video media header
|
968
|
+
# Parse a video media header box.
|
997
969
|
# def vmhd(_)
|
998
970
|
# fields = read_version_and_flags.merge({
|
999
|
-
# graphics_mode:
|
1000
|
-
# op_color: (1..3).map {
|
971
|
+
# graphics_mode: read_int(n: 2),
|
972
|
+
# op_color: (1..3).map { read_int(n: 2) }
|
1001
973
|
# })
|
1002
974
|
# [fields, nil]
|
1003
975
|
# end
|
1004
976
|
|
1005
|
-
# Parse an XML
|
977
|
+
# Parse an XML box.
|
1006
978
|
# def xml(size)
|
1007
979
|
# fields = read_version_and_flags.merge({
|
1008
980
|
# xml: read_string(size - 4)
|
@@ -1017,22 +989,16 @@ module FormatParser
|
|
1017
989
|
#
|
1018
990
|
# See https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737.
|
1019
991
|
def read_matrix
|
1020
|
-
|
1021
|
-
if i % 3 == 2
|
1022
|
-
read_fixed_point_32_2_30
|
1023
|
-
else
|
1024
|
-
read_fixed_point_32
|
1025
|
-
end
|
1026
|
-
end
|
992
|
+
Matrix.build(3) { |_, c| read_fixed_point(fractional_digits: c % 3 == 2 ? 30 : 16, signed: true) }
|
1027
993
|
end
|
1028
994
|
|
1029
|
-
# Parse an
|
995
|
+
# Parse an box's version and flags.
|
1030
996
|
#
|
1031
|
-
# It's common for
|
997
|
+
# It's common for boxes to begin with a single byte representing the version followed by three bytes representing any
|
1032
998
|
# associated flags. Both of these are often 0.
|
1033
999
|
def read_version_and_flags
|
1034
1000
|
{
|
1035
|
-
version:
|
1001
|
+
version: read_int(n: 1),
|
1036
1002
|
flags: read_bytes(3)
|
1037
1003
|
}
|
1038
1004
|
end
|