Audio-Scan

 view release on metacpan or  search on metacpan

src/id3.c  view on Meta::CPAN


  genre = buffer_get_char(id3->buf);
  if (genre < NGENRES) {
    char const *genre_string = _id3_genre_index(genre);
    my_hv_store( id3->tags, ID3_FRAME_GENRE, newSVpv(genre_string, 0) );
  }
  else if (genre < 255) {
    my_hv_store( id3->tags, ID3_FRAME_GENRE, newSVpvf("Unknown/%d", genre) );
  }

  return 1;
}

int
_id3_parse_v2(id3info *id3)
{
  int ret = 1;
  unsigned char *bptr;

  // Verify we have a valid tag
  bptr = buffer_ptr(id3->buf);
  if ( !(
    bptr[3] < 0xff && bptr[4] < 0xff &&
    bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80
  ) ) {
    PerlIO_printf(PerlIO_stderr(), "Invalid ID3v2 tag in %s\n", id3->file);
    return 0;
  }

  buffer_consume(id3->buf, 3); // ID3

  id3->version_major = buffer_get_char(id3->buf);
  id3->version_minor = buffer_get_char(id3->buf);
  id3->flags         = buffer_get_char(id3->buf);
  id3->size          = 10 + buffer_get_syncsafe(id3->buf, 4);

  id3->size_remain = id3->size - 10;

  if (id3->flags & ID3_TAG_FLAG_FOOTERPRESENT) {
    id3->size += 10;
  }

  DEBUG_TRACE("Parsing ID3v2.%d.%d tag, flags %x, size %d\n", id3->version_major, id3->version_minor, id3->flags, id3->size);

  if (id3->flags & ID3_TAG_FLAG_UNSYNCHRONISATION) {
    if (id3->version_major < 4) {
      // It's unclear but the v2.4.0-changes document seems to say that v2.4 should
      // ignore the tag-level unsync flag and only worry about frame-level unsync

      // For v2.2/v2.3, unsync the entire tag.  This is unfortunate due to
      // increased memory usage but the only way to do it, as frame size values only
      // indicate the post-unsync size, so it's not possible to unsync each frame individually
      // tested with v2.3-unsync.mp3
      if ( !_check_buf(id3->infile, id3->buf, id3->size, id3->size) ) {
        ret = 0;
        goto out;
      }

      id3->size_remain = _id3_deunsync( buffer_ptr(id3->buf), id3->size );

      DEBUG_TRACE("    Un-synchronized tag, new_size %d\n", id3->size_remain);

      my_hv_store( id3->info, "id3_was_unsynced", newSVuv(1) );
    }
    else {
      DEBUG_TRACE("  Ignoring v2.4 tag un-synchronize flag\n");
    }
  }

  if (id3->flags & ID3_TAG_FLAG_EXTENDEDHEADER) {
    uint32_t ehsize;

    // If the tag is v2.2, this bit is actually the compression bit and the tag should be ignored
    if (id3->version_major == 2) {
      ret = 0;
      goto out;
    }

    // tested with v2.3-ext-header.mp3

    // We don't care about the value of the extended flags or CRC, so just read the size and skip it
    ehsize = buffer_get_int(id3->buf);

    // ehsize may be invalid, tested with v2.3-ext-header-invalid.mp3
    if (ehsize > id3->size_remain - 4) {
      warn("Error: Invalid ID3 extended header size (%s)\n", id3->file);
      ret = 0;
      goto out;
    }

    DEBUG_TRACE("  Skipping extended header, size %d\n", ehsize);

    if ( !_check_buf(id3->infile, id3->buf, ehsize, ID3_BLOCK_SIZE) ) {
      ret = 0;
      goto out;
    }
    buffer_consume(id3->buf, ehsize);

    id3->size_remain -= ehsize + 4;
  }

  // Parse frames
  while (id3->size_remain > 0) {
    //DEBUG_TRACE("    remain: %d\n", id3->size_remain);
    if ( !_id3_parse_v2_frame(id3) ) {
      break;
    }
  }

  if (id3->version_major < 4) {
    // map old year/date/time (TYER/TDAT/TIME) frames to TDRC
    // tested in v2.3-xsop.mp3
    _id3_convert_tdrc(id3);
  }

  // Set id3_version info element, which contains all tag versions found
  {
    SV *version = newSVpvf( "ID3v2.%d.%d", id3->version_major, id3->version_minor );

    if ( my_hv_exists(id3->info, "id3_version") ) {
      SV **entry = my_hv_fetch(id3->info, "id3_version");
      if (entry != NULL) {
        sv_catpv( version, ", " );
        sv_catsv( version, *entry );
      }
    }

src/id3.c  view on Meta::CPAN


      if (size > id3->size_remain) {
        DEBUG_TRACE("    frame size too big, aborting\n");
        ret = 0;
        goto out;
      }

      // iTunes writes bad frame IDs such as 'TSA ', these should be run through compat
      // as 3-char frames
      if (id[3] == ' ') {
        id3_compat const *compat;
        compat = _id3_compat_lookup((char *)&id, 3);
        if (compat && compat->equiv) {
          strncpy(id, compat->equiv, 4);
          id[4] = 0;

          DEBUG_TRACE("    bad iTunes v2.4 tag, compat -> %s\n", id);
        }
      }

      if (flags & ID3_FRAME_FLAG_V24_GROUPINGIDENTITY) {
        // tested with v2.4-group-id.mp3
#ifdef AUDIO_SCAN_DEBUG
        DEBUG_TRACE("    group_id %d\n", buffer_get_char(id3->buf));
#else
        buffer_consume(id3->buf, 1);
#endif
        id3->size_remain--;
        size--;
      }

      if (flags & ID3_FRAME_FLAG_V24_ENCRYPTION) {
        // tested with v2.4-encrypted-frame.mp3
#ifdef AUDIO_SCAN_DEBUG
        DEBUG_TRACE("    encrypted, method %d\n", buffer_get_char(id3->buf));
#else
        buffer_consume(id3->buf, 1);
#endif

        id3->size_remain--;
        size--;

        DEBUG_TRACE("    skipping encrypted frame\n");
        _id3_skip(id3, size);
        id3->size_remain -= size;
        goto out;
      }

      if (flags & ID3_FRAME_FLAG_V24_DATALENGTHINDICATOR) {
        decoded_size = buffer_get_syncsafe(id3->buf, 4);
        id3->size_remain -= 4;
        size -= 4;

        DEBUG_TRACE("    data length indicator, size %d\n", decoded_size);
      }

      if (flags & ID3_FRAME_FLAG_V24_UNSYNCHRONISATION) {
        // Special case, do not unsync an APIC frame if not reading artwork,
        // FF's are not likely to appear in the part we care about anyway
        if ( !strcmp(id, "APIC") && _env_true("AUDIO_SCAN_NO_ARTWORK") ) {
          DEBUG_TRACE("    Would un-synchronize APIC frame, but ignoring because of AUDIO_SCAN_NO_ARTWORK\n");

          // Reset decoded_size to 0 since we aren't actually decoding.
          // XXX this would break if we have a compressed + unsync APIC frame but not very likely in the real world
          decoded_size = 0;

          id3->tag_data_safe = 0;
        }
        else {
          // tested with v2.4-unsync.mp3
          if ( !_check_buf(id3->infile, id3->buf, size, ID3_BLOCK_SIZE) ) {
            ret = 0;
            goto out;
          }

          decoded_size = _id3_deunsync( buffer_ptr(id3->buf), size );

          unsync_extra = size - decoded_size;

          DEBUG_TRACE("    Un-synchronized frame, new_size %d\n", decoded_size);
        }
      }

      if (flags & ID3_FRAME_FLAG_V24_COMPRESSION) {
        // tested with v2.4-compressed-frame.mp3
        // XXX need test for compressed + unsync
        unsigned long tmp_size;

        if ( !_check_buf(id3->infile, id3->buf, size, ID3_BLOCK_SIZE) ) {
          ret = 0;
          goto out;
        }

        DEBUG_TRACE("    decompressing\n");

        Newz(0, decompressed, sizeof(Buffer), Buffer);
        buffer_init(decompressed, decoded_size);

        tmp_size = decoded_size;
        if (
          uncompress(buffer_ptr(decompressed), &tmp_size, buffer_ptr(id3->buf), size) != Z_OK
          ||
    	    tmp_size != decoded_size
    	  ) {
          DEBUG_TRACE("    unable to decompress frame\n");
          buffer_free(decompressed);
          Safefree(decompressed);
          decompressed = 0;
        }
        else {
          // Hack buffer so it knows we've added data directly
          decompressed->end = decoded_size;
        }
      }
    }
  }

  // Special case, completely skip XHD3 frame (mp3HD) as it will be large
  // Also skip NCON, a large tag written by MusicMatch
  if ( !strcmp(id, "XHD3") || !strcmp(id, "NCON") ) {
    DEBUG_TRACE("    skipping large binary %s frame\n", id);
    _id3_skip(id3, size);
    id3->size_remain -= size;
    goto out;
  }

  frametype = _id3_frametype_lookup(id, 4);
  if (frametype == 0) {
    switch ( id[0] ) {
    case 'T':
      frametype = &id3_frametype_text;
      break;

    case 'W':
      frametype = &id3_frametype_url;
      break;

    case 'X':
    case 'Y':
    case 'Z':

src/id3.c  view on Meta::CPAN

            buffer_consume(id3->buf, 8);
            read += 8;
            DEBUG_TRACE("    date, read %d\n", read);
          }
          break;

        case ID3_FIELD_TYPE_INT8: // ETCO, MLLT, SYTC, SYLT, EQU2, RVRB, APIC,
                                  // POPM, RBUF, POSS, COMR, ENCR, GRID, SIGN, ASPI
          if (size - read >= 1) {
            av_push( framedata, newSViv( buffer_get_char(id3->buf) ) );
            read += 1;
            DEBUG_TRACE("    int8, read %d\n", read);
          }
          break;

        case ID3_FIELD_TYPE_INT16: // MLLT, RVRB, AENC, ASPI
          if (size - read >= 2) {
            av_push( framedata, newSViv( buffer_get_short(id3->buf) ) );
            read += 2;
            DEBUG_TRACE("    int16, read %d\n", read);
          }
          break;

        case ID3_FIELD_TYPE_INT24: // MLLT, RBUF
          if (size - read >= 3) {
            av_push( framedata, newSViv( buffer_get_int24(id3->buf) ) );
            read += 3;
            DEBUG_TRACE("    int24, read %d\n", read);
          }
          break;

        case ID3_FIELD_TYPE_INT32: // RBUF, SEEK, ASPI
          if (size - read >= 4) {
            av_push( framedata, newSViv( buffer_get_int(id3->buf) ) );
            read += 4;
            DEBUG_TRACE("    int32, read %d\n", read);
          }
          break;

        case ID3_FIELD_TYPE_INT32PLUS: // POPM
          if (size - read >= 4) {
            av_push( framedata, newSViv( _varint( buffer_ptr(id3->buf), size - read ) ) );
            buffer_consume(id3->buf, size - read);
            read = size;
            DEBUG_TRACE("    int32plus, read %d\n", read);
          }
          break;

        case ID3_FIELD_TYPE_BINARYDATA: // ETCO, MLLT, SYTC, SYLT, RVA2, EQU2, APIC,
                                        // GEOB, AENC, POSS, COMR, ENCR, GRID, PRIV, SIGN, ASPI
          // Special handling for APIC tags when in skip_art mode
          if (skip_art) {
            av_push( framedata, newSVuv(size - read) );

            // I don't think it's possible to obtain an APIC offset when a tag has been unsync'ed,
            // so we can't support skip_art mode in this case. See v2.3-unsync-apic-bad-offset.mp3
            if (id3->flags & ID3_TAG_FLAG_UNSYNCHRONISATION && id3->version_major < 4) {
              DEBUG_TRACE("    cannot obtain APIC offset due to v2.3 unsync tag\n");
            }
            else {
              // Record offset of APIC image data too, unless the data needs to be unsynchronized or is empty
              if (id3->tag_data_safe && (size - read) > 0)
                av_push( framedata, newSVuv(id3->offset + (id3->size - id3->size_remain) + read) );
            }

            _id3_skip(id3, size - read);
            read = size;
          }

          // Special buffering mode for APIC data, avoids a large buffer allocation
          else if (buffer_art) {
            uint32_t remain = size - read;
            uint32_t chunk_size;
            SV *artwork = newSVpv("", 0);

            while (read < size) {
              if ( !_check_buf(id3->infile, id3->buf, 1, ID3_BLOCK_SIZE) ) {
                return 0;
              }

              chunk_size = remain < buffer_len(id3->buf) ? remain : buffer_len(id3->buf);

              read += chunk_size;
              remain -= chunk_size;

              sv_catpvn( artwork, buffer_ptr(id3->buf), chunk_size );
              buffer_consume(id3->buf, chunk_size);

              DEBUG_TRACE("    buffered %d bytes of APIC data (remaining %d)\n", chunk_size, remain);
            }

            av_push( framedata, artwork );
          }

          // Special handling for RVA2 tags
          else if ( !strcmp(id, "RVA2") ) {
            read += _id3_parse_rva2(id3, size, framedata);
          }

          // Special handling for SYLT tags
          else if ( !strcmp(id, "SYLT") ) {
            read += _id3_parse_sylt(id3, encoding, size - read, framedata);
          }

          // Special handling for ETCO tags
          else if ( !strcmp(id, "ETCO") ) {
            read += _id3_parse_etco(id3, size - read, framedata);
          }

          // All other binary frames, copy as-is
          else {
            if (size - read > 1) {
              av_push( framedata, newSVpvn( buffer_ptr(id3->buf), size - read ) );
              buffer_consume(id3->buf, size - read);
              read = size;
              DEBUG_TRACE("    binarydata, read %d\n", read);
            }
          }
          break;

        default:



( run in 2.037 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )