format_parser 2.3.0 → 2.4.3
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 +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
|