Class: FilesHunter::Decoders::MP4

Inherits:
BeginPatternDecoder show all
Defined in:
lib/fileshunter/Decoders/MP4.rb

Constant Summary collapse

BEGIN_PATTERN_MOV_1 =
'pnot'.force_encoding(Encoding::ASCII_8BIT)
BEGIN_PATTERN_MOV_2 =
'mdat'.force_encoding(Encoding::ASCII_8BIT)
BEGIN_PATTERN_MOV_3 =
'moov'.force_encoding(Encoding::ASCII_8BIT)
BEGIN_PATTERN_MP4 =
Regexp.new("(ftyp|#{BEGIN_PATTERN_MOV_1}|#{BEGIN_PATTERN_MOV_2}|#{BEGIN_PATTERN_MOV_3})", nil, 'n')
ACCEPTABLE_BOX_TYPES_ALL =
{
  'free' => nil,
  'skip' => nil
}
ACCEPTABLE_BOX_TYPES_UDTA =
{ :box_info => { :ignore_unknown_boxes => true, :nbr_bytes_possible_padding => 4 },
  'cprt' => nil,
  'tsel' => nil,
  'strk' => {
    'stri' => nil,
    'strd' => nil
  },
  # Following were completed but are not part of ISO

  'albm' => nil,
  'AllF' => nil,
  'auth' => nil,
  'clsf' => nil,
  'coll' => nil,
  'dscp' => nil,
  'gnre' => nil,
  'hinf' => nil,
  'hnti' => nil,
  'kywd' => nil,
  'loci' => nil,
  'LOOP' => nil,
  'name' => nil,
  'perf' => nil,
  'ptv ' => nil,
  'rtng' => nil,
  'SelO' => nil,
  'tagc' => nil,
  'thmb' => nil,
  'titl' => nil,
  'tnam' => nil,
  'urat' => nil,
  'WLOC' => nil,
  'yrrc' => nil,
  "\xA9arg" => nil,
  "\xA9ark" => nil,
  "\xA9cok" => nil,
  "\xA9com" => nil,
  "\xA9cpy" => nil,
  "\xA9day" => nil,
  "\xA9dir" => nil,
  "\xA9ed1" => nil,
  "\xA9ed2" => nil,
  "\xA9ed3" => nil,
  "\xA9ed4" => nil,
  "\xA9ed5" => nil,
  "\xA9ed6" => nil,
  "\xA9ed7" => nil,
  "\xA9ed8" => nil,
  "\xA9ed9" => nil,
  "\xA9fmt" => nil,
  "\xA9inf" => nil,
  "\xA9isr" => nil,
  "\xA9lab" => nil,
  "\xA9lal" => nil,
  "\xA9mak" => nil,
  "\xA9mal" => nil,
  "\xA9nak" => nil,
  "\xA9nam" => nil,
  "\xA9pdk" => nil,
  "\xA9phg" => nil,
  "\xA9prd" => nil,
  "\xA9prf" => nil,
  "\xA9prk" => nil,
  "\xA9prl" => nil,
  "\xA9req" => nil,
  "\xA9snk" => nil,
  "\xA9snm" => nil,
  "\xA9src" => nil,
  "\xA9swf" => nil,
  "\xA9swk" => nil,
  "\xA9swr" => nil,
  "\xA9wrt" => nil,
  'meta' => { :box_info => { :data_size => 4 },
    'hdlr' => nil,
    'xml ' => nil,
    'bxml' => nil,
    'iloc' => nil,
    'pitm' => nil,
    'ipro' => { :box_info => { :data_size => 6, :nbr_children_range => [4, 5] },
      'sinf' => {
        'frma' => nil,
        'imif' => nil,
        'schm' => nil,
        'schi' => nil
      }
    },
    'ilst' => {
      "\xA9nam" => { 'data' => nil, 'mean' => nil, 'name' => nil },
      "\xA9cmt" => { 'data' => nil, 'mean' => nil, 'name' => nil },
      "\xA9day" => { 'data' => nil, 'mean' => nil, 'name' => nil },
      "\xA9ART" => { 'data' => nil, 'mean' => nil, 'name' => nil },
      "\xA9trk" => { 'data' => nil, 'mean' => nil, 'name' => nil },
      "\xA9alb" => { 'data' => nil, 'mean' => nil, 'name' => nil },
      "\xA9com" => { 'data' => nil, 'mean' => nil, 'name' => nil },
      "\xA9wrt" => { 'data' => nil, 'mean' => nil, 'name' => nil },
      "\xA9too" => { 'data' => nil, 'mean' => nil, 'name' => nil },
      'gnre' => { 'data' => nil, 'mean' => nil, 'name' => nil },
      'disk' => { 'data' => nil, 'mean' => nil, 'name' => nil },
      'trkn' => { 'data' => nil, 'mean' => nil, 'name' => nil },
      'tmpo' => { 'data' => nil, 'mean' => nil, 'name' => nil },
      'cpil' => { 'data' => nil, 'mean' => nil, 'name' => nil },
      'covr' => { 'data' => nil, 'mean' => nil, 'name' => nil },
      '----' => { 'data' => nil, 'mean' => nil, 'name' => nil }
    }
  },
  # Following were encountered but not documented

  'CNCV' => nil,
  'CNDB' => nil,
  'CNFV' => nil,
  'CNMN' => nil,
  'hinv' => nil,
  'TAGS' => nil
}
ACCEPTABLE_BOX_TYPES =
{
  'ftyp' => nil,
  'pdin' => nil,
  'moov' => {
    'mvhd' => nil,
    'trak' => {
      'tkhd' => nil,
      'tref' => {
        # Following were completed but are not part of ISO

        'hint' => nil,
        'dpnd' => nil,
        'ipir' => nil,
        'mpod' => nil,
        'sync' => nil,
        'tmcd' => nil,
        'chap' => nil,
        'scpt' => nil,
        'ssrc' => nil
      },
      'trgr' => nil,
      'edts' => {
        'elst' => nil
      },
      'mdia' => {
        'mdhd' => nil,
        'hdlr' => nil,
        'minf' => {
          'vmhd' => nil,
          'smhd' => nil,
          'hmhd' => nil,
          'nmhd' => nil,
          'dinf' => {
            'dref' => { :box_info => { :data_size => 8, :nbr_children_range => [4, 7] },
              # Following were completed but are not part of ISO

              'url ' => nil,
              'urn ' => nil,
              'alis' => nil,
              'rsrc' => nil
            },
            # Following were completed but are not part of ISO

            'url ' => nil,
            'urn ' => nil
          },
          'stbl' => {
            'stsd' => nil,
            # To complex to be parsed

            # 'stsd' => {

            #   # Following were completed but are not part of ISO

            #   'sinf' => {

            #     'frma' => nil,

            #     'imif' => nil,

            #     'schm' => nil,

            #     'schi' => nil

            #   },

            #   'd263' => {

            #     'bitr' => nil

            #   },

            #   'damr' => nil,

            #   'avcC' => nil,

            #   'esds' => nil,

            #   'm4ds' => nil,

            #   'gama' => nil,

            #   'fiel' => nil,

            #   'mjqt' => nil,

            #   'mjht' => nil

            # },

            'stts' => nil,
            'ctts' => nil,
            'cslg' => nil,
            'stsc' => nil,
            'stsz' => nil,
            'stz2' => nil,
            'stco' => nil,
            'co64' => nil,
            'stss' => nil,
            'stsh' => nil,
            'padb' => nil,
            'stdp' => nil,
            'sdtp' => nil,
            'sbgp' => nil,
            'sgpd' => nil,
            'subs' => nil,
            'saiz' => nil,
            'saio' => nil
          },
          # Following were completed but are not part of ISO

          'hint' => nil,
          'hdlr' => nil
        }
      },
      'udta' => ACCEPTABLE_BOX_TYPES_UDTA,
      # Following were completed but are not part of ISO

      'clip' => nil,
      'matt' => {
        'kmat' => nil
      },
      'load' => nil,
      'imap' => {
        "\x00\x00in" => { :box_info => { :data_size => 12 },
          "\x00\x00ty" => nil,
          'obid' => nil
        }
      }
    },
    'mvex' => {
      'mehd' => nil,
      'trex' => nil,
      'leva' => nil
    },
    # Following were completed but are not part of ISO

    'mdra' => {
      'dref' => nil
    },
    'cmov' => {
      'dcom' => nil,
      'cmvd' => nil
    },
    'rmra' => {
      'rmda' => {
        'rdrf' => nil,
        'rmqu' => nil,
        'rmcs' => nil,
        'rmvc' => nil,
        'rmcd' => nil,
        'rmdr' => nil,
        'rmla' => nil,
        'rmag' => nil
      }
    },
    'iods' => nil,
    'clip' => {
      'crgn' => nil
    },
    'udta' => ACCEPTABLE_BOX_TYPES_UDTA
  },
  'moof' => {
    'mfhd' => nil,
    'traf' => {
      'tfhd' => nil,
      'trun' => nil,
      'sbgp' => nil,
      'sgpd' => nil,
      'subs' => nil,
      'saiz' => nil,
      'saio' => nil,
      'tfdt' => nil
    }
  },
  'mfra' => {
    'tfra' => nil,
    'mfro' => nil
  },
  'mdat' => nil,
  'meta' => { :box_info => { :data_size => 4 },
    'hdlr' => nil,
    'dinf' => {
      'dref' => nil
    },
    'iloc' => nil,
    'ipro' => { :box_info => { :data_size => 6, :nbr_children_range => [4, 5] },
      'sinf' => {
        'frma' => nil,
        'schm' => nil,
        'schi' => nil,
        # Following were completed but are not part of ISO

        'imif' => nil
      }
    },
    'iinf' => nil,
    'xml ' => nil,
    'bxml' => nil,
    'pitm' => nil,
    'fiin' => {
      'paen' => {
        'fire' => nil,
        'fpar' => nil,
        'fecr' => nil
      },
      'segr' => nil,
      'gitn' => nil
    },
    'idat' => nil,
    'iref' => nil
  },
  'meco' => {
    'mere' => nil
  },
  'styp' => nil,
  'sidx' => nil,
  'ssix' => nil,
  'prft' => nil,
  # Following were completed but are not part of ISO

  'wide' => nil,
  # Following were encountered but not documented

  'PICT' => nil,
  'pnot' => nil
}
KNOWN_EXTENSIONS =

List taken from here: www.ftyps.com/

{
  '3g2a' => '3g2',
  '3g2b' => '3g2',
  '3g2c' => '3g2',
  '3ge6' => '3gp',
  '3ge7' => '3gp',
  '3gg6' => '3gp',
  '3gp1' => '3gp',
  '3gp2' => '3gp',
  '3gp3' => '3gp',
  '3gp4' => '3gp',
  '3gp5' => '3gp',
  '3gp6' => '3gp',
  '3gp6' => '3gp',
  '3gp6' => '3gp',
  '3gs7' => '3gp',
  'avc1' => 'mp4',
  'CAEP' => 'mp4',
  'caqv' => 'mp4',
  'CDes' => 'mp4',
  'da0a' => 'mp4',
  'da0b' => 'mp4',
  'da1a' => 'mp4',
  'da1b' => 'mp4',
  'da2a' => 'mp4',
  'da2b' => 'mp4',
  'da3a' => 'mp4',
  'da3b' => 'mp4',
  'dmb1' => 'mp4',
  'dmpf' => 'mp4',
  'drc1' => 'mp4',
  'dv1a' => 'mp4',
  'dv1b' => 'mp4',
  'dv2a' => 'mp4',
  'dv2b' => 'mp4',
  'dv3a' => 'mp4',
  'dv3b' => 'mp4',
  'dvr1' => 'mp4',
  'dvt1' => 'mp4',
  'F4V ' => 'f4v',
  'F4P ' => 'f4p',
  'F4A ' => 'f4a',
  'F4B ' => 'f4b',
  'isc2' => 'mp4',
  'iso2' => 'mp4',
  'isom' => 'mp4',
  'JP2 ' => 'jp2',
  'JP20' => 'jp2',
  'jpm ' => 'jpm',
  'jpx ' => 'jpx',
  'KDDI' => '3gp',
  'M4A ' => 'm4a',
  'M4B ' => 'mp4',
  'M4P ' => 'mp4',
  'M4V ' => 'm4v',
  'M4VH' => 'm4v',
  'M4VP' => 'm4v',
  'mj2s' => 'mj2',
  'mjp2' => 'mj2',
  'mmp4' => 'mp4',
  'mp21' => 'mp4',
  'mp41' => 'mp4',
  'mp42' => 'mp4',
  'mp71' => 'mp4',
  'MPPI' => 'mp4',
  'mqt ' => 'mqv',
  'MSNV' => 'mp4',
  'NDAS' => 'mp4',
  'NDSC' => 'mp4',
  'NDSH' => 'mp4',
  'NDSM' => 'mp4',
  'NDSP' => 'mp4',
  'NDSS' => 'mp4',
  'NDXC' => 'mp4',
  'NDXH' => 'mp4',
  'NDXM' => 'mp4',
  'NDXP' => 'mp4',
  'NDXS' => 'mp4',
  'odcf' => 'mp4',
  'opf2' => 'mp4',
  'opx2' => 'mp4',
  'pana' => 'mp4',
  'qt  ' => 'mov',
  'ROSS' => 'mp4',
  'sdv ' => 'mp4',
  'ssc1' => 'mp4',
  'ssc2' => 'mp4'
}

Instance Method Summary collapse

Methods inherited from BeginPatternDecoder

#find_segments

Methods inherited from FilesHunter::Decoder

#segments_found, #setup

Instance Method Details

#decode(offset) ⇒ Object



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/fileshunter/Decoders/MP4.rb', line 426

def decode(offset)
  ending_offset = nil

  found_ftyp = false
  found_mdat = false
  valid_mp4 = false
  nbr_boxes_parsed = 0
  ending_offset, nbr_boxes = parse_mp4_box(offset, ACCEPTABLE_BOX_TYPES) do |box_hierarchy, box_cursor, box_size|
    # If we browsed enough in the file, mark it as valid

    if ((!valid_mp4) and
        ((box_hierarchy.size > 2) or
         (nbr_boxes_parsed > 1)))
      valid_mp4 = true
      found_relevant_data(:mov) if (!found_ftyp)
    end
    # Get data from parsed boxes

    case box_hierarchy[-1]
    when 'mdat'
      found_mdat = true
      ( :mdat_size => box_size )
    when 'ftyp'
      # Get the extension

      ftyp_id = @data[box_cursor+8..box_cursor+11]
      log_debug "@#{box_cursor} - Found ftyp #{ftyp_id}."
      known_extension = KNOWN_EXTENSIONS[ftyp_id]
      invalid_data("@#{box_cursor} - Unknown MP4 ftyp: #{ftyp_id}") if (known_extension == nil)
      found_relevant_data(known_extension.to_sym)
      found_ftyp = true
    when 'mvhd'
      version = @data[box_cursor+8].ord
      #flags = BinData::Uint24be.read(@data[box_cursor+9..box_cursor+11])

      cursor = box_cursor + 12
      creation_time = nil
      modification_time = nil
      timescale = nil
      duration = nil
      if (version == 0)
        creation_time = BinData::Uint32be.read(@data[cursor..cursor+3])
        modification_time = BinData::Uint32be.read(@data[cursor+4..cursor+7])
        timescale = BinData::Uint32be.read(@data[cursor+8..cursor+11])
        duration = BinData::Uint32be.read(@data[cursor+12..cursor+15])
        cursor += 16
      else
        creation_time = BinData::Uint64be.read(@data[cursor..cursor+7])
        modification_time = BinData::Uint64be.read(@data[cursor+8..cursor+15])
        timescale = BinData::Uint32be.read(@data[cursor+16..cursor+19])
        duration = BinData::Uint64be.read(@data[cursor+20..cursor+27])
        cursor += 28
      end
      rate = BinData::Uint32be.read(@data[cursor..cursor+3])
      volume = BinData::Uint16be.read(@data[cursor+4..cursor+5])
      (
        :creation_time => creation_time,
        :modification_time => modification_time,
        :timescale => timescale,
        :duration => duration,
        :rate => rate,
        :volume => volume
      )
    when 'CNMN'
      ( :CNMN => @data[box_cursor+8..box_cursor+box_size-1].gsub("\x00", '').strip )
    when 'CNCV'
      ( :CNCV => @data[box_cursor+8..box_cursor+box_size-1].gsub("\x00", '').strip )
    when "\xA9fmt"
      ( :fmt => @data[box_cursor+12..box_cursor+box_size-1].strip )
    when "\xA9inf"
      ( :inf => @data[box_cursor+12..box_cursor+box_size-1].strip )
    end
    nbr_boxes_parsed += 1
  end
  # An MP4 without ftyp is surely a .mov

  found_relevant_data(:mov) if (!found_ftyp)
  (
    :nbr_boxes => nbr_boxes
  )
  truncated_data("@#{ending_offset} - Missing mdat box.", ending_offset) if (!found_mdat)
  # TODO: Find a way to detect the end of a stream (usually size 0 applies to mdat boxes)

  invalid_data('Cannot decode MP4 to the end of file',) if (ending_offset == nil)

  return ending_offset
end

#get_begin_patternObject



422
423
424
# File 'lib/fileshunter/Decoders/MP4.rb', line 422

def get_begin_pattern
  return BEGIN_PATTERN_MP4, { :begin_pattern_offset_in_segment => 4, :offset_inc => 4, :max_regexp_size => 4 }
end