fileshunter 0.1.0.20130725

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.
@@ -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