fileshunter 0.1.0.20130725

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,620 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ module FilesHunter
4
+
5
+ module Decoders
6
+
7
+ class MP4 < BeginPatternDecoder
8
+
9
+ BEGIN_PATTERN_MOV_1 = 'pnot'.force_encoding(Encoding::ASCII_8BIT)
10
+ BEGIN_PATTERN_MOV_2 = 'mdat'.force_encoding(Encoding::ASCII_8BIT)
11
+ BEGIN_PATTERN_MOV_3 = 'moov'.force_encoding(Encoding::ASCII_8BIT)
12
+ BEGIN_PATTERN_MP4 = Regexp.new("(ftyp|#{BEGIN_PATTERN_MOV_1}|#{BEGIN_PATTERN_MOV_2}|#{BEGIN_PATTERN_MOV_3})", nil, 'n')
13
+
14
+ # List taken from http://www.iso.org/iso/home/store/catalogue_ics/catalogue_detail_ics.htm?csnumber=61988
15
+ # Completed with http://xhelmboyx.tripod.com/formats/mp4-layout.txt
16
+ # Completed with http://www.etsi.org/deliver/etsi_ts/126200_126299/126244/10.02.00_60/ts_126244v100200p.pdf
17
+ # Completed with https://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap2/qtff2.html
18
+ ACCEPTABLE_BOX_TYPES_ALL = {
19
+ 'free' => nil,
20
+ 'skip' => nil
21
+ }
22
+ ACCEPTABLE_BOX_TYPES_UDTA = { :box_info => { :ignore_unknown_boxes => true, :nbr_bytes_possible_padding => 4 },
23
+ 'cprt' => nil,
24
+ 'tsel' => nil,
25
+ 'strk' => {
26
+ 'stri' => nil,
27
+ 'strd' => nil
28
+ },
29
+ # Following were completed but are not part of ISO
30
+ 'albm' => nil,
31
+ 'AllF' => nil,
32
+ 'auth' => nil,
33
+ 'clsf' => nil,
34
+ 'coll' => nil,
35
+ 'dscp' => nil,
36
+ 'gnre' => nil,
37
+ 'hinf' => nil,
38
+ 'hnti' => nil,
39
+ 'kywd' => nil,
40
+ 'loci' => nil,
41
+ 'LOOP' => nil,
42
+ 'name' => nil,
43
+ 'perf' => nil,
44
+ 'ptv ' => nil,
45
+ 'rtng' => nil,
46
+ 'SelO' => nil,
47
+ 'tagc' => nil,
48
+ 'thmb' => nil,
49
+ 'titl' => nil,
50
+ 'tnam' => nil,
51
+ 'urat' => nil,
52
+ 'WLOC' => nil,
53
+ 'yrrc' => nil,
54
+ "\xA9arg" => nil,
55
+ "\xA9ark" => nil,
56
+ "\xA9cok" => nil,
57
+ "\xA9com" => nil,
58
+ "\xA9cpy" => nil,
59
+ "\xA9day" => nil,
60
+ "\xA9dir" => nil,
61
+ "\xA9ed1" => nil,
62
+ "\xA9ed2" => nil,
63
+ "\xA9ed3" => nil,
64
+ "\xA9ed4" => nil,
65
+ "\xA9ed5" => nil,
66
+ "\xA9ed6" => nil,
67
+ "\xA9ed7" => nil,
68
+ "\xA9ed8" => nil,
69
+ "\xA9ed9" => nil,
70
+ "\xA9fmt" => nil,
71
+ "\xA9inf" => nil,
72
+ "\xA9isr" => nil,
73
+ "\xA9lab" => nil,
74
+ "\xA9lal" => nil,
75
+ "\xA9mak" => nil,
76
+ "\xA9mal" => nil,
77
+ "\xA9nak" => nil,
78
+ "\xA9nam" => nil,
79
+ "\xA9pdk" => nil,
80
+ "\xA9phg" => nil,
81
+ "\xA9prd" => nil,
82
+ "\xA9prf" => nil,
83
+ "\xA9prk" => nil,
84
+ "\xA9prl" => nil,
85
+ "\xA9req" => nil,
86
+ "\xA9snk" => nil,
87
+ "\xA9snm" => nil,
88
+ "\xA9src" => nil,
89
+ "\xA9swf" => nil,
90
+ "\xA9swk" => nil,
91
+ "\xA9swr" => nil,
92
+ "\xA9wrt" => nil,
93
+ 'meta' => { :box_info => { :data_size => 4 },
94
+ 'hdlr' => nil,
95
+ 'xml ' => nil,
96
+ 'bxml' => nil,
97
+ 'iloc' => nil,
98
+ 'pitm' => nil,
99
+ 'ipro' => { :box_info => { :data_size => 6, :nbr_children_range => [4, 5] },
100
+ 'sinf' => {
101
+ 'frma' => nil,
102
+ 'imif' => nil,
103
+ 'schm' => nil,
104
+ 'schi' => nil
105
+ }
106
+ },
107
+ 'ilst' => {
108
+ "\xA9nam" => { 'data' => nil, 'mean' => nil, 'name' => nil },
109
+ "\xA9cmt" => { 'data' => nil, 'mean' => nil, 'name' => nil },
110
+ "\xA9day" => { 'data' => nil, 'mean' => nil, 'name' => nil },
111
+ "\xA9ART" => { 'data' => nil, 'mean' => nil, 'name' => nil },
112
+ "\xA9trk" => { 'data' => nil, 'mean' => nil, 'name' => nil },
113
+ "\xA9alb" => { 'data' => nil, 'mean' => nil, 'name' => nil },
114
+ "\xA9com" => { 'data' => nil, 'mean' => nil, 'name' => nil },
115
+ "\xA9wrt" => { 'data' => nil, 'mean' => nil, 'name' => nil },
116
+ "\xA9too" => { 'data' => nil, 'mean' => nil, 'name' => nil },
117
+ 'gnre' => { 'data' => nil, 'mean' => nil, 'name' => nil },
118
+ 'disk' => { 'data' => nil, 'mean' => nil, 'name' => nil },
119
+ 'trkn' => { 'data' => nil, 'mean' => nil, 'name' => nil },
120
+ 'tmpo' => { 'data' => nil, 'mean' => nil, 'name' => nil },
121
+ 'cpil' => { 'data' => nil, 'mean' => nil, 'name' => nil },
122
+ 'covr' => { 'data' => nil, 'mean' => nil, 'name' => nil },
123
+ '----' => { 'data' => nil, 'mean' => nil, 'name' => nil }
124
+ }
125
+ },
126
+ # Following were encountered but not documented
127
+ 'CNCV' => nil,
128
+ 'CNDB' => nil,
129
+ 'CNFV' => nil,
130
+ 'CNMN' => nil,
131
+ 'hinv' => nil,
132
+ 'TAGS' => nil
133
+ }
134
+ ACCEPTABLE_BOX_TYPES = {
135
+ 'ftyp' => nil,
136
+ 'pdin' => nil,
137
+ 'moov' => {
138
+ 'mvhd' => nil,
139
+ 'trak' => {
140
+ 'tkhd' => nil,
141
+ 'tref' => {
142
+ # Following were completed but are not part of ISO
143
+ 'hint' => nil,
144
+ 'dpnd' => nil,
145
+ 'ipir' => nil,
146
+ 'mpod' => nil,
147
+ 'sync' => nil,
148
+ 'tmcd' => nil,
149
+ 'chap' => nil,
150
+ 'scpt' => nil,
151
+ 'ssrc' => nil
152
+ },
153
+ 'trgr' => nil,
154
+ 'edts' => {
155
+ 'elst' => nil
156
+ },
157
+ 'mdia' => {
158
+ 'mdhd' => nil,
159
+ 'hdlr' => nil,
160
+ 'minf' => {
161
+ 'vmhd' => nil,
162
+ 'smhd' => nil,
163
+ 'hmhd' => nil,
164
+ 'nmhd' => nil,
165
+ 'dinf' => {
166
+ 'dref' => { :box_info => { :data_size => 8, :nbr_children_range => [4, 7] },
167
+ # Following were completed but are not part of ISO
168
+ 'url ' => nil,
169
+ 'urn ' => nil,
170
+ 'alis' => nil,
171
+ 'rsrc' => nil
172
+ },
173
+ # Following were completed but are not part of ISO
174
+ 'url ' => nil,
175
+ 'urn ' => nil
176
+ },
177
+ 'stbl' => {
178
+ 'stsd' => nil,
179
+ # To complex to be parsed
180
+ # 'stsd' => {
181
+ # # Following were completed but are not part of ISO
182
+ # 'sinf' => {
183
+ # 'frma' => nil,
184
+ # 'imif' => nil,
185
+ # 'schm' => nil,
186
+ # 'schi' => nil
187
+ # },
188
+ # 'd263' => {
189
+ # 'bitr' => nil
190
+ # },
191
+ # 'damr' => nil,
192
+ # 'avcC' => nil,
193
+ # 'esds' => nil,
194
+ # 'm4ds' => nil,
195
+ # 'gama' => nil,
196
+ # 'fiel' => nil,
197
+ # 'mjqt' => nil,
198
+ # 'mjht' => nil
199
+ # },
200
+ 'stts' => nil,
201
+ 'ctts' => nil,
202
+ 'cslg' => nil,
203
+ 'stsc' => nil,
204
+ 'stsz' => nil,
205
+ 'stz2' => nil,
206
+ 'stco' => nil,
207
+ 'co64' => nil,
208
+ 'stss' => nil,
209
+ 'stsh' => nil,
210
+ 'padb' => nil,
211
+ 'stdp' => nil,
212
+ 'sdtp' => nil,
213
+ 'sbgp' => nil,
214
+ 'sgpd' => nil,
215
+ 'subs' => nil,
216
+ 'saiz' => nil,
217
+ 'saio' => nil
218
+ },
219
+ # Following were completed but are not part of ISO
220
+ 'hint' => nil,
221
+ 'hdlr' => nil
222
+ }
223
+ },
224
+ 'udta' => ACCEPTABLE_BOX_TYPES_UDTA,
225
+ # Following were completed but are not part of ISO
226
+ 'clip' => nil,
227
+ 'matt' => {
228
+ 'kmat' => nil
229
+ },
230
+ 'load' => nil,
231
+ 'imap' => {
232
+ "\x00\x00in" => { :box_info => { :data_size => 12 },
233
+ "\x00\x00ty" => nil,
234
+ 'obid' => nil
235
+ }
236
+ }
237
+ },
238
+ 'mvex' => {
239
+ 'mehd' => nil,
240
+ 'trex' => nil,
241
+ 'leva' => nil
242
+ },
243
+ # Following were completed but are not part of ISO
244
+ 'mdra' => {
245
+ 'dref' => nil
246
+ },
247
+ 'cmov' => {
248
+ 'dcom' => nil,
249
+ 'cmvd' => nil
250
+ },
251
+ 'rmra' => {
252
+ 'rmda' => {
253
+ 'rdrf' => nil,
254
+ 'rmqu' => nil,
255
+ 'rmcs' => nil,
256
+ 'rmvc' => nil,
257
+ 'rmcd' => nil,
258
+ 'rmdr' => nil,
259
+ 'rmla' => nil,
260
+ 'rmag' => nil
261
+ }
262
+ },
263
+ 'iods' => nil,
264
+ 'clip' => {
265
+ 'crgn' => nil
266
+ },
267
+ 'udta' => ACCEPTABLE_BOX_TYPES_UDTA
268
+ },
269
+ 'moof' => {
270
+ 'mfhd' => nil,
271
+ 'traf' => {
272
+ 'tfhd' => nil,
273
+ 'trun' => nil,
274
+ 'sbgp' => nil,
275
+ 'sgpd' => nil,
276
+ 'subs' => nil,
277
+ 'saiz' => nil,
278
+ 'saio' => nil,
279
+ 'tfdt' => nil
280
+ }
281
+ },
282
+ 'mfra' => {
283
+ 'tfra' => nil,
284
+ 'mfro' => nil
285
+ },
286
+ 'mdat' => nil,
287
+ 'meta' => { :box_info => { :data_size => 4 },
288
+ 'hdlr' => nil,
289
+ 'dinf' => {
290
+ 'dref' => nil
291
+ },
292
+ 'iloc' => nil,
293
+ 'ipro' => { :box_info => { :data_size => 6, :nbr_children_range => [4, 5] },
294
+ 'sinf' => {
295
+ 'frma' => nil,
296
+ 'schm' => nil,
297
+ 'schi' => nil,
298
+ # Following were completed but are not part of ISO
299
+ 'imif' => nil
300
+ }
301
+ },
302
+ 'iinf' => nil,
303
+ 'xml ' => nil,
304
+ 'bxml' => nil,
305
+ 'pitm' => nil,
306
+ 'fiin' => {
307
+ 'paen' => {
308
+ 'fire' => nil,
309
+ 'fpar' => nil,
310
+ 'fecr' => nil
311
+ },
312
+ 'segr' => nil,
313
+ 'gitn' => nil
314
+ },
315
+ 'idat' => nil,
316
+ 'iref' => nil
317
+ },
318
+ 'meco' => {
319
+ 'mere' => nil
320
+ },
321
+ 'styp' => nil,
322
+ 'sidx' => nil,
323
+ 'ssix' => nil,
324
+ 'prft' => nil,
325
+ # Following were completed but are not part of ISO
326
+ 'wide' => nil,
327
+ # Following were encountered but not documented
328
+ 'PICT' => nil,
329
+ 'pnot' => nil
330
+ }
331
+
332
+ # List taken from here: http://www.ftyps.com/
333
+ KNOWN_EXTENSIONS = {
334
+ '3g2a' => '3g2',
335
+ '3g2b' => '3g2',
336
+ '3g2c' => '3g2',
337
+ '3ge6' => '3gp',
338
+ '3ge7' => '3gp',
339
+ '3gg6' => '3gp',
340
+ '3gp1' => '3gp',
341
+ '3gp2' => '3gp',
342
+ '3gp3' => '3gp',
343
+ '3gp4' => '3gp',
344
+ '3gp5' => '3gp',
345
+ '3gp6' => '3gp',
346
+ '3gp6' => '3gp',
347
+ '3gp6' => '3gp',
348
+ '3gs7' => '3gp',
349
+ 'avc1' => 'mp4',
350
+ 'CAEP' => 'mp4',
351
+ 'caqv' => 'mp4',
352
+ 'CDes' => 'mp4',
353
+ 'da0a' => 'mp4',
354
+ 'da0b' => 'mp4',
355
+ 'da1a' => 'mp4',
356
+ 'da1b' => 'mp4',
357
+ 'da2a' => 'mp4',
358
+ 'da2b' => 'mp4',
359
+ 'da3a' => 'mp4',
360
+ 'da3b' => 'mp4',
361
+ 'dmb1' => 'mp4',
362
+ 'dmpf' => 'mp4',
363
+ 'drc1' => 'mp4',
364
+ 'dv1a' => 'mp4',
365
+ 'dv1b' => 'mp4',
366
+ 'dv2a' => 'mp4',
367
+ 'dv2b' => 'mp4',
368
+ 'dv3a' => 'mp4',
369
+ 'dv3b' => 'mp4',
370
+ 'dvr1' => 'mp4',
371
+ 'dvt1' => 'mp4',
372
+ 'F4V ' => 'f4v',
373
+ 'F4P ' => 'f4p',
374
+ 'F4A ' => 'f4a',
375
+ 'F4B ' => 'f4b',
376
+ 'isc2' => 'mp4',
377
+ 'iso2' => 'mp4',
378
+ 'isom' => 'mp4',
379
+ 'JP2 ' => 'jp2',
380
+ 'JP20' => 'jp2',
381
+ 'jpm ' => 'jpm',
382
+ 'jpx ' => 'jpx',
383
+ 'KDDI' => '3gp',
384
+ 'M4A ' => 'm4a',
385
+ 'M4B ' => 'mp4',
386
+ 'M4P ' => 'mp4',
387
+ 'M4V ' => 'm4v',
388
+ 'M4VH' => 'm4v',
389
+ 'M4VP' => 'm4v',
390
+ 'mj2s' => 'mj2',
391
+ 'mjp2' => 'mj2',
392
+ 'mmp4' => 'mp4',
393
+ 'mp21' => 'mp4',
394
+ 'mp41' => 'mp4',
395
+ 'mp42' => 'mp4',
396
+ 'mp71' => 'mp4',
397
+ 'MPPI' => 'mp4',
398
+ 'mqt ' => 'mqv',
399
+ 'MSNV' => 'mp4',
400
+ 'NDAS' => 'mp4',
401
+ 'NDSC' => 'mp4',
402
+ 'NDSH' => 'mp4',
403
+ 'NDSM' => 'mp4',
404
+ 'NDSP' => 'mp4',
405
+ 'NDSS' => 'mp4',
406
+ 'NDXC' => 'mp4',
407
+ 'NDXH' => 'mp4',
408
+ 'NDXM' => 'mp4',
409
+ 'NDXP' => 'mp4',
410
+ 'NDXS' => 'mp4',
411
+ 'odcf' => 'mp4',
412
+ 'opf2' => 'mp4',
413
+ 'opx2' => 'mp4',
414
+ 'pana' => 'mp4',
415
+ 'qt ' => 'mov',
416
+ 'ROSS' => 'mp4',
417
+ 'sdv ' => 'mp4',
418
+ 'ssc1' => 'mp4',
419
+ 'ssc2' => 'mp4'
420
+ }
421
+
422
+ def get_begin_pattern
423
+ return BEGIN_PATTERN_MP4, { :begin_pattern_offset_in_segment => 4, :offset_inc => 4, :max_regexp_size => 4 }
424
+ end
425
+
426
+ def decode(offset)
427
+ ending_offset = nil
428
+
429
+ found_ftyp = false
430
+ found_mdat = false
431
+ valid_mp4 = false
432
+ nbr_boxes_parsed = 0
433
+ ending_offset, nbr_boxes = parse_mp4_box(offset, ACCEPTABLE_BOX_TYPES) do |box_hierarchy, box_cursor, box_size|
434
+ # If we browsed enough in the file, mark it as valid
435
+ if ((!valid_mp4) and
436
+ ((box_hierarchy.size > 2) or
437
+ (nbr_boxes_parsed > 1)))
438
+ valid_mp4 = true
439
+ found_relevant_data(:mov) if (!found_ftyp)
440
+ end
441
+ # Get data from parsed boxes
442
+ case box_hierarchy[-1]
443
+ when 'mdat'
444
+ found_mdat = true
445
+ metadata( :mdat_size => box_size )
446
+ when 'ftyp'
447
+ # Get the extension
448
+ ftyp_id = @data[box_cursor+8..box_cursor+11]
449
+ log_debug "@#{box_cursor} - Found ftyp #{ftyp_id}."
450
+ known_extension = KNOWN_EXTENSIONS[ftyp_id]
451
+ invalid_data("@#{box_cursor} - Unknown MP4 ftyp: #{ftyp_id}") if (known_extension == nil)
452
+ found_relevant_data(known_extension.to_sym)
453
+ found_ftyp = true
454
+ when 'mvhd'
455
+ version = @data[box_cursor+8].ord
456
+ #flags = BinData::Uint24be.read(@data[box_cursor+9..box_cursor+11])
457
+ cursor = box_cursor + 12
458
+ creation_time = nil
459
+ modification_time = nil
460
+ timescale = nil
461
+ duration = nil
462
+ if (version == 0)
463
+ creation_time = BinData::Uint32be.read(@data[cursor..cursor+3])
464
+ modification_time = BinData::Uint32be.read(@data[cursor+4..cursor+7])
465
+ timescale = BinData::Uint32be.read(@data[cursor+8..cursor+11])
466
+ duration = BinData::Uint32be.read(@data[cursor+12..cursor+15])
467
+ cursor += 16
468
+ else
469
+ creation_time = BinData::Uint64be.read(@data[cursor..cursor+7])
470
+ modification_time = BinData::Uint64be.read(@data[cursor+8..cursor+15])
471
+ timescale = BinData::Uint32be.read(@data[cursor+16..cursor+19])
472
+ duration = BinData::Uint64be.read(@data[cursor+20..cursor+27])
473
+ cursor += 28
474
+ end
475
+ rate = BinData::Uint32be.read(@data[cursor..cursor+3])
476
+ volume = BinData::Uint16be.read(@data[cursor+4..cursor+5])
477
+ metadata(
478
+ :creation_time => creation_time,
479
+ :modification_time => modification_time,
480
+ :timescale => timescale,
481
+ :duration => duration,
482
+ :rate => rate,
483
+ :volume => volume
484
+ )
485
+ when 'CNMN'
486
+ metadata( :CNMN => @data[box_cursor+8..box_cursor+box_size-1].gsub("\x00", '').strip )
487
+ when 'CNCV'
488
+ metadata( :CNCV => @data[box_cursor+8..box_cursor+box_size-1].gsub("\x00", '').strip )
489
+ when "\xA9fmt"
490
+ metadata( :fmt => @data[box_cursor+12..box_cursor+box_size-1].strip )
491
+ when "\xA9inf"
492
+ metadata( :inf => @data[box_cursor+12..box_cursor+box_size-1].strip )
493
+ end
494
+ nbr_boxes_parsed += 1
495
+ end
496
+ # An MP4 without ftyp is surely a .mov
497
+ found_relevant_data(:mov) if (!found_ftyp)
498
+ metadata(
499
+ :nbr_boxes => nbr_boxes
500
+ )
501
+ truncated_data("@#{ending_offset} - Missing mdat box.", ending_offset) if (!found_mdat)
502
+ # TODO: Find a way to detect the end of a stream (usually size 0 applies to mdat boxes)
503
+ invalid_data('Cannot decode MP4 to the end of file',) if (ending_offset == nil)
504
+
505
+ return ending_offset
506
+ end
507
+
508
+ private
509
+
510
+ # Parse a MP4 box, calling a callback for each sub-box read (recursively)
511
+ #
512
+ # Parameters::
513
+ # * *cursor* (_Fixnum_): Current parsing cursor
514
+ # * *box_names* (<em>map<String,Object></em>): Possible box names, with their possible sub-boxes (or nil if none).
515
+ # * *hierarchy* (<em>list<String></em>): The hierarchy of box names leading to this box [default = []]
516
+ # * *max_cursor* (_Fixnum_): Maximal cursor for the box. This is set using the size of the box containing the ones being parsed. Can be nil if unknown. [default = nil]
517
+ # * *&proc* (_Proc_): Code block called for each box encountered.
518
+ # * Parameters::
519
+ # * *box_hierarchy* (<em>list<String></em>): Complete box names hierarchy leading to this box
520
+ # * *box_cursor* (_Fixnum_): Cursor of the beginning of this box (including size and name)
521
+ # * *box_size* (_Fixnum_): Size of this box (including size and name)
522
+ # Result::
523
+ # * _Fixnum_: The new cursor after having parsed this box. Can be nil if the size of one of the sub-boxes is unknown.
524
+ # * _Fixnum_: The number of boxes parsed
525
+ def parse_mp4_box(cursor, box_names, hierarchy = [], max_cursor = nil, &proc)
526
+ #log_debug "=== @#{cursor} - Parsing #{@data[cursor..cursor+31].inspect} ..."
527
+ nbr_boxes = 0
528
+ nbr_direct_subboxes = 0
529
+ container_box_max_cursor = ((max_cursor == nil) ? @end_offset : max_cursor)
530
+ # Compute the size of data before looking for sub-boxes
531
+ data_size = 0
532
+ data_size += box_names[:box_info][:data_size] if ((box_names[:box_info] != nil) and (box_names[:box_info][:data_size] != nil))
533
+ nbr_expected_subboxes = nil
534
+ if ((box_names[:box_info] != nil) and
535
+ (box_names[:box_info][:nbr_children_range] != nil))
536
+ str_nbr_subboxes = @data[cursor+box_names[:box_info][:nbr_children_range][0]..cursor+box_names[:box_info][:nbr_children_range][1]]
537
+ case str_nbr_subboxes.size
538
+ when 1
539
+ nbr_expected_subboxes = str_nbr_subboxes.ord
540
+ when 2
541
+ nbr_expected_subboxes = BinData::Uint16be.read(str_nbr_subboxes)
542
+ when 3
543
+ nbr_expected_subboxes = BinData::Uint24be.read(str_nbr_subboxes)
544
+ when 4
545
+ nbr_expected_subboxes = BinData::Uint32be.read(str_nbr_subboxes)
546
+ when 8
547
+ nbr_expected_subboxes = BinData::Uint64be.read(str_nbr_subboxes)
548
+ else
549
+ # Can't read it. Will not check for them.
550
+ end
551
+ end
552
+ cursor += data_size
553
+ # Compute the map of possible box names
554
+ complete_box_names = box_names.merge(ACCEPTABLE_BOX_TYPES_ALL)
555
+ while (cursor < container_box_max_cursor)
556
+ size = BinData::Uint32be.read(@data[cursor..cursor+3])
557
+ name = @data[cursor+4..cursor+7]
558
+ # Check the validity of the box
559
+ if (!complete_box_names.has_key?(name))
560
+ log_debug "@#{cursor} - Invalid box type: #{name.inspect} within #{hierarchy.join('/')}. Known ones are: #{complete_box_names.keys.join(', ')}."
561
+ if ((box_names[:box_info] == nil) or
562
+ (box_names[:box_info][:ignore_unknown_boxes] != true))
563
+ if (max_cursor == nil)
564
+ # We consider the file is finished, as the box being parsed is the root one.
565
+ return cursor, nbr_boxes
566
+ else
567
+ truncated_data("@#{cursor} - No valid box type found, but container box has not been parsed completely.")
568
+ end
569
+ end
570
+ end
571
+ # This box is valid, or we don't care (in this case we will have to skip its contents)
572
+ nbr_boxes += 1
573
+ nbr_direct_subboxes += 1
574
+ box_cursor = cursor
575
+ box_hierarchy = hierarchy + [name]
576
+ log_debug "=== @#{cursor} - Found box #{box_hierarchy.join('/')} of size #{size}"
577
+ cursor += 8
578
+ if (size == 1)
579
+ size = BinData::Uint64be.read(@data[cursor..cursor+7])
580
+ log_debug "=== @#{cursor} - Real size is #{size}"
581
+ cursor += 8
582
+ end
583
+ if (max_cursor == nil)
584
+ # For root elements, this error is synonym of truncated data as container_box_max_cursor is set arbitrarily to @end_offset
585
+ truncated_data("@#{cursor} - Box #{box_hierarchy.join('/')} with size #{size} should finish at cursor #{box_cursor + size}, but container box set maximal cursor to #{container_box_max_cursor}.", container_box_max_cursor) if (box_cursor + size > container_box_max_cursor)
586
+ else
587
+ invalid_data("@#{cursor} - Box #{box_hierarchy.join('/')} with size #{size} should finish at cursor #{box_cursor + size}, but container box set maximal cursor to #{container_box_max_cursor}.") if (box_cursor + size > container_box_max_cursor)
588
+ end
589
+ yield(box_hierarchy, box_cursor, size)
590
+ if (size == 0)
591
+ # Last box, to the end.
592
+ return nil, nbr_boxes
593
+ else
594
+ if (complete_box_names[name] != nil)
595
+ # Now call sub-boxes that should start at current cursor
596
+ new_cursor, nbr_subboxes = parse_mp4_box(cursor, complete_box_names[name], box_hierarchy, box_cursor + size, &proc)
597
+ nbr_boxes += nbr_subboxes
598
+ # Check cursor is at the correct position
599
+ invalid_data("@#{new_cursor} - After parsing box #{box_hierarchy.join('/')}, cursor should have been @#{box_cursor+size}") if ((new_cursor != nil) and (new_cursor != box_cursor + size))
600
+ end
601
+ cursor = box_cursor + size
602
+ end
603
+ # Check for an eventual padding if any
604
+ cursor += box_names[:box_info][:nbr_bytes_possible_padding] if ((box_names[:box_info] != nil) and
605
+ (box_names[:box_info][:nbr_bytes_possible_padding] != nil) and
606
+ (cursor == container_box_max_cursor - box_names[:box_info][:nbr_bytes_possible_padding]) and
607
+ (@data[cursor..container_box_max_cursor-1] == "\x00" * box_names[:box_info][:nbr_bytes_possible_padding]))
608
+ progress(cursor)
609
+ end
610
+ # If we were expecting a given number of direct subboxes, compare them now
611
+ invalid_data("@#{cursor} - Was expecting #{nbr_expected_subboxes} sub-boxes, but read #{nbr_direct_subboxes}.") if ((nbr_expected_subboxes != nil) and (nbr_direct_subboxes != nbr_expected_subboxes))
612
+
613
+ return cursor, nbr_boxes
614
+ end
615
+
616
+ end
617
+
618
+ end
619
+
620
+ end
@@ -0,0 +1,30 @@
1
+ module FilesHunter
2
+
3
+ module Decoders
4
+
5
+ class MPG_Video < BeginPatternDecoder
6
+
7
+ BEGIN_PATTERN_MPG = "\x00\x00\x01\xBA\x21\x00\x01\x00\x01\x80".force_encoding(Encoding::ASCII_8BIT)
8
+ END_PATTERN_MPG = "\x00\x00\x01\xB7\x00\x00\x01\xB9".force_encoding(Encoding::ASCII_8BIT)
9
+
10
+ def get_begin_pattern
11
+ return BEGIN_PATTERN_MPG, { :offset_inc => 10 }
12
+ end
13
+
14
+ def decode(offset)
15
+ ending_offset = nil
16
+
17
+ found_relevant_data(:mpg)
18
+ end_pattern_offset = @data.index(END_PATTERN_MPG, offset + 10)
19
+ log_debug "=== @#{offset} - Found ending offset: #{end_pattern_offset.inspect}"
20
+ truncated_data if ((end_pattern_offset == nil) or (end_pattern_offset + 8 > @end_offset))
21
+ ending_offset = end_pattern_offset + 8
22
+
23
+ return ending_offset
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end