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.
- data/AUTHORS +3 -0
- data/ChangeLog +5 -0
- data/Credits +21 -0
- data/LICENSE +31 -0
- data/README +15 -0
- data/README.md +11 -0
- data/Rakefile +7 -0
- data/ReleaseInfo +8 -0
- data/bin/fileshunt +216 -0
- data/ext/fileshunter/Decoders/_FLAC.c +233 -0
- data/ext/fileshunter/Decoders/extconf.rb +3 -0
- data/lib/fileshunter/BeginPatternDecoder.rb +218 -0
- data/lib/fileshunter/Decoder.rb +66 -0
- data/lib/fileshunter/Decoders/ASF.rb +50 -0
- data/lib/fileshunter/Decoders/BMP.rb +118 -0
- data/lib/fileshunter/Decoders/CAB.rb +140 -0
- data/lib/fileshunter/Decoders/CFBF.rb +92 -0
- data/lib/fileshunter/Decoders/EBML.rb +369 -0
- data/lib/fileshunter/Decoders/EXE.rb +505 -0
- data/lib/fileshunter/Decoders/FLAC.rb +387 -0
- data/lib/fileshunter/Decoders/ICO.rb +71 -0
- data/lib/fileshunter/Decoders/JPEG.rb +247 -0
- data/lib/fileshunter/Decoders/M2V.rb +30 -0
- data/lib/fileshunter/Decoders/MP3.rb +341 -0
- data/lib/fileshunter/Decoders/MP4.rb +620 -0
- data/lib/fileshunter/Decoders/MPG_Video.rb +30 -0
- data/lib/fileshunter/Decoders/OGG.rb +74 -0
- data/lib/fileshunter/Decoders/RIFF.rb +437 -0
- data/lib/fileshunter/Decoders/TIFF.rb +350 -0
- data/lib/fileshunter/Decoders/Text.rb +240 -0
- data/lib/fileshunter/Segment.rb +50 -0
- data/lib/fileshunter/SegmentsAnalyzer.rb +251 -0
- data/lib/fileshunter.rb +15 -0
- metadata +130 -0
@@ -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
|