static void _parse_wav_peak(ScanData s, Buffer *buf, uint32_t chunk_size, uint8_t big_endian) { uint16_t channels = 0; AV *peaklist = newAV(); SV **entry = my_hv_fetch( info, "channels" ); if ( entry != NULL ) { channels = SvIV(*entry); } // Skip version/timestamp buffer_consume(buf, 8); while ( channels-- ) { HV *peak = newHV(); my_hv_store( peak, "value", newSVnv( big_endian ? buffer_get_float32(buf) : buffer_get_float32_le(buf) ) ); my_hv_store( peak, "position", newSVuv( big_endian ? buffer_get_int(buf) : buffer_get_int_le(buf) ) ); av_push( peaklist, newRV_noinc( (SV *)peak) ); } my_hv_store( info, "peak", newRV_noinc( (SV *)peaklist ) ); }
void _parse_wav_fmt(Buffer *buf, uint32_t chunk_size, HV *info) { uint16_t format = buffer_get_short_le(buf); my_hv_store( info, "format", newSVuv(format) ); my_hv_store( info, "channels", newSVuv( buffer_get_short_le(buf) ) ); my_hv_store( info, "samplerate", newSVuv( buffer_get_int_le(buf) ) ); my_hv_store( info, "bitrate", newSVuv( buffer_get_int_le(buf) * 8 ) ); my_hv_store( info, "block_align", newSVuv( buffer_get_short_le(buf) ) ); my_hv_store( info, "bits_per_sample", newSVuv( buffer_get_short_le(buf) ) ); if ( chunk_size > 16 ) { uint16_t extra_len = buffer_get_short_le(buf); // Bug 14462, a WAV file with only an 18-byte fmt chunk should ignore extra_len bytes if (extra_len && chunk_size > 18) { DEBUG_TRACE(" skipping extra_len bytes in fmt: %d\n", extra_len); buffer_consume(buf, extra_len); } } }
static void _parse_wav_fmt(ScanData s, Buffer *buf, uint32_t chunk_size) { uint16_t format = buffer_get_short_le(buf); //my_hv_store( info, "format", newSVuv(format) ); s->streams[0].channels = buffer_get_short_le(buf); s->streams[0].samplerate = buffer_get_int_le(buf); s->streams[0].bitrate = s->bitrate = buffer_get_int_le(buf) * 8; s->streams[0].block_align = buffer_get_short_le(buf); s->streams[0].bit_depth = buffer_get_short_le(buf); if ( chunk_size > 16 ) { uint16_t extra_len = buffer_get_short_le(buf); // Bug 14462, a WAV file with only an 18-byte fmt chunk should ignore extra_len bytes if (extra_len && chunk_size > 18) { LOG_DEBUG(" skipping extra_len bytes in fmt: %d\n", extra_len); buffer_consume(buf, extra_len); } } }
int image_bmp_read_header(image *im) { int offset, palette_colors; buffer_consume(im->buf, 10); offset = buffer_get_int_le(im->buf); buffer_consume(im->buf, 4); im->width = buffer_get_int_le(im->buf); im->height = buffer_get_int_le(im->buf); buffer_consume(im->buf, 2); im->bpp = buffer_get_short_le(im->buf); im->compression = buffer_get_int_le(im->buf); DEBUG_TRACE("BMP offset %d, width %d, height %d, bpp %d, compression %d\n", offset, im->width, im->height, im->bpp, im->compression); if (im->compression > 3) { // JPEG/PNG warn("Image::Scale unsupported BMP compression type: %d (%s)\n", im->compression, SvPVX(im->path)); return 0; } // Negative height indicates a flipped image if (im->height < 0) { croak("flipped\n"); im->flipped = 1; im->height = abs(im->height); } // Not used during reading, but lets output PNG be correct im->channels = 4; // Skip BMP size, resolution buffer_consume(im->buf, 12); palette_colors = buffer_get_int_le(im->buf); // Skip number of important colors buffer_consume(im->buf, 4); // < 16-bit always has a palette if (!palette_colors && im->bpp < 16) { switch (im->bpp) { case 8: palette_colors = 256; break; case 4: palette_colors = 16; break; case 1: palette_colors = 2; break; } } DEBUG_TRACE("palette_colors %d\n", palette_colors); if (palette_colors) { // Read palette int i; if (palette_colors > 256) { warn("Image::Scale cannot read BMP with palette > 256 colors (%s)\n", SvPVX(im->path)); return 0; } New(0, im->palette, 1, palette); for (i = 0; i < palette_colors; i++) { int b = buffer_get_char(im->buf); int g = buffer_get_char(im->buf); int r = buffer_get_char(im->buf); buffer_consume(im->buf, 1); im->palette->colors[i] = COL(r, g, b); DEBUG_TRACE("palette %d = %08x\n", i, im->palette->colors[i]); } } else if (im->compression == BMP_BI_BITFIELDS) { int pos, bit, i; if (im->bpp == 16) { // Read 16-bit bitfield masks for (i = 0; i < 3; i++) { masks[i] = buffer_get_int_le(im->buf); // Determine shift value pos = 0; bit = masks[i] & -masks[i]; while (bit) { pos++; bit >>= 1; } shifts[i] = pos - 1; // green can be 6 bits if (i == 1) { if (masks[1] == 0x7e0) ncolors[1] = (1 << 6) - 1; else ncolors[1] = (1 << 5) - 1; } DEBUG_TRACE("16bpp mask %d: %08x >> %d, ncolors %d\n", i, masks[i], shifts[i], ncolors[i]); } } else { // 32-bit bitfields // Read 32-bit bitfield masks for (i = 0; i < 3; i++) {
// green can be 6 bits if (i == 1) { if (masks[1] == 0x7e0) ncolors[1] = (1 << 6) - 1; else ncolors[1] = (1 << 5) - 1; } DEBUG_TRACE("16bpp mask %d: %08x >> %d, ncolors %d\n", i, masks[i], shifts[i], ncolors[i]); } } else { // 32-bit bitfields // Read 32-bit bitfield masks for (i = 0; i < 3; i++) { masks[i] = buffer_get_int_le(im->buf); // Determine shift value pos = 0; bit = masks[i] & -masks[i]; while (bit) { pos++; bit >>= 1; } shifts[i] = pos - 1; DEBUG_TRACE("32bpp mask %d: %08x >> %d\n", i, masks[i], shifts[i]); } } }
static void _parse_wav(ScanData s, Buffer *buf) { uint32_t offset = 12; s->type_name = "wav"; mediascan_add_StreamData(s, 1); while ( offset < s->size - 8 ) { char chunk_id[5]; uint32_t chunk_size; // Verify we have at least 8 bytes if ( !buffer_check_load(buf, s->fp, 8, BLOCK_SIZE) ) { return; } strncpy( chunk_id, (char *)buffer_ptr(buf), 4 ); chunk_id[4] = '\0'; buffer_consume(buf, 4); chunk_size = buffer_get_int_le(buf); // Adjust for padding if ( chunk_size % 2 ) { chunk_size++; } offset += 8; LOG_DEBUG("%s size %d\n", chunk_id, chunk_size); // Seek past data, everything else we parse // XXX: Are there other large chunks we should ignore? if ( !strcmp( chunk_id, "data" ) ) { s->audio_offset = offset; s->audio_size = chunk_size; // Calculate duration, unless we already know it (i.e. from 'fact') if ( !s->duration_ms ) { if (s->bitrate) { s->duration_ms = (chunk_size / (s->bitrate / 8.)) * 1000; } } // sanity check size, this is inside the data chunk code // to support setting audio_offset even when the data size is wrong if (chunk_size > s->size - offset) { LOG_DEBUG("data size > file_size, skipping\n"); return; } // Seek past data if there are more chunks after it if ( s->size > offset + chunk_size ) { fseek(s->fp, offset + chunk_size, SEEK_SET); } buffer_clear(buf); } else if ( !strcmp( chunk_id, "id3 " ) || !strcmp( chunk_id, "ID3 " ) || !strcmp( chunk_id, "ID32" ) ) { // Read header to verify version unsigned char *bptr = buffer_ptr(buf); if ( (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') && bptr[3] < 0xff && bptr[4] < 0xff && bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80 ) { // Start parsing ID3 from offset //parse_id3(infile, file, info, tags, offset, file_size); } // Seek past ID3 and clear buffer fseek(s->fp, offset + chunk_size, SEEK_SET); buffer_clear(buf); } else { // sanity check size if (chunk_size > s->size - offset) { LOG_DEBUG("chunk_size > file_size, skipping\n"); return; } // Make sure we have enough data if ( !buffer_check_load(buf, s->fp, chunk_size, BLOCK_SIZE) ) { return; } if ( !strcmp( chunk_id, "fmt " ) ) { _parse_wav_fmt(s, buf, chunk_size); } else if ( !strcmp( chunk_id, "LIST" ) ) { //_parse_wav_list(buf, chunk_size, tags); } else if ( !strcmp( chunk_id, "PEAK" ) ) { _parse_wav_peak(s, buf, chunk_size, 0); } else if ( !strcmp( chunk_id, "fact" ) ) { // A 4-byte fact chunk in a non-PCM wav is the number of samples // Use it to calculate duration if ( chunk_size == 4 ) { uint32_t num_samples = buffer_get_int_le(buf); if (s->streams[0].samplerate) { s->duration_ms = (num_samples * 1000) / s->streams[0].samplerate; } } else { // Unknown, skip it buffer_consume(buf, chunk_size); } } else { if ( !strcmp(chunk_id, "SAUR") // Wavosour data chunk || !strcmp(chunk_id, "otom") // Wavosaur? || !strcmp(chunk_id, "PAD ") // Padding ) { // Known chunks to skip } else { // Warn about unknown chunks so we can investigate them LOG_DEBUG("Unhandled WAV chunk %s size %d (skipped)\n", chunk_id, chunk_size); } buffer_consume(buf, chunk_size); } } offset += chunk_size; } }
int wav_scan(ScanData s) { int ret = 1; Buffer buf; uint32_t chunk_size; buffer_init(&buf, BLOCK_SIZE); if ( !buffer_check_load(&buf, s->fp, 12, BLOCK_SIZE) ) { ret = 0; goto out; } if ( !strncmp( (char *)buffer_ptr(&buf), "RIFF", 4 ) ) { // We've got a RIFF file buffer_consume(&buf, 4); chunk_size = buffer_get_int_le(&buf); // Check format if ( strncmp( (char *)buffer_ptr(&buf), "WAVE", 4 ) ) { LOG_ERROR("Invalid WAV file: missing WAVE header: %s\n", s->path); ret = 0; goto out; } buffer_consume(&buf, 4); _parse_wav(s, &buf); } else if ( !strncmp( (char *)buffer_ptr(&buf), "FORM", 4 ) ) { // We've got an AIFF file char *bptr; buffer_consume(&buf, 4); chunk_size = buffer_get_int(&buf); // Check format bptr = buffer_ptr(&buf); if ( bptr[0] == 'A' && bptr[1] == 'I' && bptr[2] == 'F' && (bptr[3] == 'F' || bptr[3] == 'C') ) { buffer_consume(&buf, 4); //_parse_aiff(s, &buf); } else { LOG_ERROR("Invalid AIFF file: missing AIFF header: %s\n", s->path); ret = 0; goto out; } } else { LOG_ERROR("Invalid WAV file: missing RIFF header: %s\n", s->path); ret = 0; goto out; } out: buffer_free(&buf); return ret; }
int _wavpack_parse_old(wvpinfo *wvp) { int ret = 1; char chunk_id[5]; uint32_t chunk_size; WavpackHeader3 wphdr; WaveHeader3 wavhdr; unsigned char *bptr; uint32_t total_samples; uint32_t song_length_ms; Zero(&wavhdr, sizeof(wavhdr), char); Zero(&wphdr, sizeof(wphdr), char); DEBUG_TRACE("Parsing old WavPack version\n"); // Verify RIFF header if ( strncmp( (char *)buffer_ptr(wvp->buf), "RIFF", 4 ) ) { PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing RIFF header: %s\n", wvp->file); ret = 0; goto out; } buffer_consume(wvp->buf, 4); chunk_size = buffer_get_int_le(wvp->buf); // Check format if ( strncmp( (char *)buffer_ptr(wvp->buf), "WAVE", 4 ) ) { PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing WAVE header: %s\n", wvp->file); ret = 0; goto out; } buffer_consume(wvp->buf, 4); wvp->file_offset += 12; // Verify we have at least 8 bytes if ( !_check_buf(wvp->infile, wvp->buf, 8, WAVPACK_BLOCK_SIZE) ) { ret = 0; goto out; } // loop through all chunks, read fmt, and break at data while ( buffer_len(wvp->buf) >= 8 ) { strncpy( chunk_id, (char *)buffer_ptr(wvp->buf), 4 ); chunk_id[4] = '\0'; buffer_consume(wvp->buf, 4); chunk_size = buffer_get_int_le(wvp->buf); wvp->file_offset += 8; // Adjust for padding if ( chunk_size % 2 ) { chunk_size++; } DEBUG_TRACE(" %s size %d\n", chunk_id, chunk_size); if ( !strcmp( chunk_id, "data" ) ) { break; } wvp->file_offset += chunk_size; if ( !strcmp( chunk_id, "fmt " ) ) { if ( !_check_buf(wvp->infile, wvp->buf, chunk_size, WAV_BLOCK_SIZE) ) { ret = 0; goto out; } if (chunk_size < sizeof(wavhdr)) { ret = 0; goto out; } // Read wav header wavhdr.FormatTag = buffer_get_short_le(wvp->buf); wavhdr.NumChannels = buffer_get_short_le(wvp->buf); wavhdr.SampleRate = buffer_get_int_le(wvp->buf); wavhdr.BytesPerSecond = buffer_get_int_le(wvp->buf); wavhdr.BlockAlign = buffer_get_short_le(wvp->buf); wavhdr.BitsPerSample = buffer_get_short_le(wvp->buf); // Skip rest of fmt chunk if necessary if (chunk_size > 16) { _wavpack_skip(wvp, chunk_size - 16); } } else { // Skip it _wavpack_skip(wvp, chunk_size); } // Verify we have at least 8 bytes if ( !_check_buf(wvp->infile, wvp->buf, 8, WAVPACK_BLOCK_SIZE) ) { ret = 0; goto out; } } // Verify wav header, this code comes from unpack3.c if ( wavhdr.FormatTag != 1 || !wavhdr.NumChannels || wavhdr.NumChannels > 2 || !wavhdr.SampleRate || wavhdr.BitsPerSample < 16 || wavhdr.BitsPerSample > 24 || wavhdr.BlockAlign / wavhdr.NumChannels > 3 || wavhdr.BlockAlign % wavhdr.NumChannels || wavhdr.BlockAlign / wavhdr.NumChannels < (wavhdr.BitsPerSample + 7) / 8 ) { ret = 0; goto out; } // chunk_size here is the size of the data chunk total_samples = chunk_size / wavhdr.NumChannels / ((wavhdr.BitsPerSample > 16) ? 3 : 2); // read WavpackHeader3 (differs for each version) bptr = buffer_ptr(wvp->buf); if ( bptr[0] != 'w' || bptr[1] != 'v' || bptr[2] != 'p' || bptr[3] != 'k' ) { PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing wvpk header: %s\n", wvp->file); ret = 0; goto out; } buffer_consume(wvp->buf, 4); wphdr.ckSize = buffer_get_int_le(wvp->buf); wphdr.version = buffer_get_short_le(wvp->buf); if (wphdr.version >= 2) { wphdr.bits = buffer_get_short_le(wvp->buf); } if (wphdr.version == 3) { wphdr.flags = buffer_get_short_le(wvp->buf); wphdr.shift = buffer_get_short_le(wvp->buf); wphdr.total_samples = buffer_get_int_le(wvp->buf); total_samples = wphdr.total_samples; } DEBUG_TRACE("wvpk header @ %llu:\n", wvp->file_offset); DEBUG_TRACE(" size: %u\n", wphdr.ckSize); DEBUG_TRACE(" version: %d\n", wphdr.version); DEBUG_TRACE(" bits: 0x%x\n", wphdr.bits); DEBUG_TRACE(" flags: 0x%x\n", wphdr.flags); DEBUG_TRACE(" shift: 0x%x\n", wphdr.shift); DEBUG_TRACE(" total_samples: %d\n", wphdr.total_samples); my_hv_store( wvp->info, "encoder_version", newSVuv(wphdr.version) ); my_hv_store( wvp->info, "bits_per_sample", newSVuv(wavhdr.BitsPerSample) ); my_hv_store( wvp->info, "channels", newSVuv(wavhdr.NumChannels) ); my_hv_store( wvp->info, "samplerate", newSVuv(wavhdr.SampleRate) ); my_hv_store( wvp->info, "total_samples", newSVuv(total_samples) ); song_length_ms = ((total_samples * 1.0) / wavhdr.SampleRate) * 1000; my_hv_store( wvp->info, "song_length_ms", newSVuv(song_length_ms) ); my_hv_store( wvp->info, "bitrate", newSVuv( _bitrate(wvp->file_size - wvp->audio_offset, song_length_ms) ) ); out: return ret; }
int _wavpack_parse_block(wvpinfo *wvp) { unsigned char *bptr; uint16_t remaining; bptr = buffer_ptr(wvp->buf); // Verify wvpk signature if ( bptr[0] != 'w' || bptr[1] != 'v' || bptr[2] != 'p' || bptr[3] != 'k' ) { DEBUG_TRACE("Invalid wvpk header at %llu\n", wvp->file_offset); return 1; } buffer_consume(wvp->buf, 4); wvp->header->ckSize = buffer_get_int_le(wvp->buf); wvp->header->version = buffer_get_short_le(wvp->buf); wvp->header->track_no = buffer_get_char(wvp->buf); wvp->header->index_no = buffer_get_char(wvp->buf); wvp->header->total_samples = buffer_get_int_le(wvp->buf); wvp->header->block_index = buffer_get_int_le(wvp->buf); wvp->header->block_samples = buffer_get_int_le(wvp->buf); wvp->header->flags = buffer_get_int_le(wvp->buf); wvp->header->crc = buffer_get_int_le(wvp->buf); DEBUG_TRACE("wvpk header @ %llu:\n", wvp->file_offset); DEBUG_TRACE(" size: %u\n", wvp->header->ckSize); DEBUG_TRACE(" version: 0x%x\n", wvp->header->version); DEBUG_TRACE(" track_no: 0x%x\n", wvp->header->track_no); DEBUG_TRACE(" index_no: 0x%x\n", wvp->header->index_no); DEBUG_TRACE(" total_samples: %u\n", wvp->header->total_samples); DEBUG_TRACE(" block_index: %u\n", wvp->header->block_index); DEBUG_TRACE(" block_samples: %u\n", wvp->header->block_samples); DEBUG_TRACE(" flags: 0x%x\n", wvp->header->flags); DEBUG_TRACE(" crc: 0x%x\n", wvp->header->crc); wvp->file_offset += 32; my_hv_store( wvp->info, "encoder_version", newSVuv(wvp->header->version) ); if (wvp->header->version < 0x4) { // XXX old version and not handled by 'R' check above for old version PerlIO_printf(PerlIO_stderr(), "Unsupported old WavPack version: 0x%x\n", wvp->header->version); return 1; } // Read data from flags my_hv_store( wvp->info, "bits_per_sample", newSVuv( 8 * ((wvp->header->flags & 0x3) + 1) ) ); // Encoding mode my_hv_store( wvp->info, (wvp->header->flags & 0x8) ? "hybrid" : "lossless", newSVuv(1) ); { // samplerate, may be overridden by a later ID_SAMPLE_RATE metadata block uint32_t samplerate_index = (wvp->header->flags & 0x7800000) >> 23; if ( samplerate_index >= 0 && samplerate_index < 0xF ) { my_hv_store( wvp->info, "samplerate", newSVuv( wavpack_sample_rates[samplerate_index] ) ); } else { // Default to 44.1 just in case my_hv_store( wvp->info, "samplerate", newSVuv(44100) ); } } // Channels, may be overridden by a later ID_CHANNEL_INFO metadata block my_hv_store( wvp->info, "channels", newSVuv( (wvp->header->flags & 0x4) ? 1 : 2 ) ); // Parse metadata sub-blocks remaining = wvp->header->ckSize - 24; // ckSize is 8 less than the block size // If block_samples is 0, we need to skip to the next block if ( !wvp->header->block_samples ) { wvp->file_offset += remaining; _wavpack_skip(wvp, remaining); return 0; } while (remaining > 0) { // Read sub-block header (2-4 bytes) unsigned char id; uint32_t size; DEBUG_TRACE("remaining: %d\n", remaining); if ( !_check_buf(wvp->infile, wvp->buf, 4, WAVPACK_BLOCK_SIZE) ) { return 0; } id = buffer_get_char(wvp->buf); remaining--; // Size is in words if (id & ID_LARGE) { // 24-bit large size id &= ~ID_LARGE; size = buffer_get_int24_le(wvp->buf) << 1; remaining -= 3; DEBUG_TRACE(" ID_LARGE, changed to %x\n", id); } else { // 8-bit size size = buffer_get_char(wvp->buf) << 1; remaining--; } if (id & ID_ODD_SIZE) { id &= ~ID_ODD_SIZE; size--; DEBUG_TRACE(" ID_ODD_SIZE, changed to %x\n", id); } if ( id == ID_WV_BITSTREAM || !size ) { // Found the bitstream, don't read any farther DEBUG_TRACE(" Sub-Chunk: WV_BITSTREAM (size %u)\n", size); break; } // We only care about 0x27 (ID_SAMPLE_RATE) and 0xd (ID_CHANNEL_INFO) switch (id) { case ID_SAMPLE_RATE: DEBUG_TRACE(" Sub-Chunk: ID_SAMPLE_RATE (size: %u)\n", size); _wavpack_parse_sample_rate(wvp, size); break; case ID_CHANNEL_INFO: DEBUG_TRACE(" Sub-Chunk: ID_CHANNEL_INFO (size: %u)\n", size); _wavpack_parse_channel_info(wvp, size); break; default: // Skip it DEBUG_TRACE(" Sub-Chunk: %x (size: %u) (skipped)\n", id, size); _wavpack_skip(wvp, size); } remaining -= size; // If size was odd, skip a byte if (size & 0x1) { if ( buffer_len(wvp->buf) ) { buffer_consume(wvp->buf, 1); } else { _wavpack_skip(wvp, 1); } remaining--; } } // Calculate bitrate if ( wvp->header->total_samples && wvp->file_size > 0 ) { SV **samplerate = my_hv_fetch( wvp->info, "samplerate" ); if (samplerate != NULL) { uint32_t song_length_ms = ((wvp->header->total_samples * 1.0) / SvIV(*samplerate)) * 1000; my_hv_store( wvp->info, "song_length_ms", newSVuv(song_length_ms) ); my_hv_store( wvp->info, "bitrate", newSVuv( _bitrate(wvp->file_size - wvp->audio_offset, song_length_ms) ) ); my_hv_store( wvp->info, "total_samples", newSVuv(wvp->header->total_samples) ); } } return 1; }
void _parse_wav(PerlIO *infile, Buffer *buf, char *file, uint32_t file_size, HV *info, HV *tags) { uint32_t offset = 12; while ( offset < file_size - 8 ) { char chunk_id[5]; uint32_t chunk_size; // Verify we have at least 8 bytes if ( !_check_buf(infile, buf, 8, WAV_BLOCK_SIZE) ) { return; } strncpy( chunk_id, (char *)buffer_ptr(buf), 4 ); chunk_id[4] = '\0'; buffer_consume(buf, 4); chunk_size = buffer_get_int_le(buf); // Adjust for padding if ( chunk_size % 2 ) { chunk_size++; } offset += 8; DEBUG_TRACE("%s size %d\n", chunk_id, chunk_size); // Seek past data, everything else we parse // XXX: Are there other large chunks we should ignore? if ( !strcmp( chunk_id, "data" ) ) { SV **bitrate; my_hv_store( info, "audio_offset", newSVuv(offset) ); my_hv_store( info, "audio_size", newSVuv(chunk_size) ); // Calculate duration, unless we already know it (i.e. from 'fact') if ( !my_hv_fetch( info, "song_length_ms" ) ) { bitrate = my_hv_fetch( info, "bitrate" ); if (bitrate != NULL) { my_hv_store( info, "song_length_ms", newSVuv( (chunk_size / (SvIV(*bitrate) / 8.)) * 1000 ) ); } } // sanity check size, this is inside the data chunk code // to support setting audio_offset even when the data size is wrong if (chunk_size > file_size - offset) { DEBUG_TRACE("data size > file_size, skipping\n"); return; } // Seek past data if there are more chunks after it if ( file_size > offset + chunk_size ) { PerlIO_seek(infile, offset + chunk_size, SEEK_SET); } buffer_clear(buf); } else if ( !strcmp( chunk_id, "id3 " ) || !strcmp( chunk_id, "ID3 " ) || !strcmp( chunk_id, "ID32" ) ) { // Read header to verify version unsigned char *bptr = buffer_ptr(buf); if ( (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') && bptr[3] < 0xff && bptr[4] < 0xff && bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80 ) { // Start parsing ID3 from offset parse_id3(infile, file, info, tags, offset, file_size); } // Seek past ID3 and clear buffer PerlIO_seek(infile, offset + chunk_size, SEEK_SET); buffer_clear(buf); } else { // sanity check size if (chunk_size > file_size - offset) { DEBUG_TRACE("chunk_size > file_size, skipping\n"); return; } // Make sure we have enough data if ( !_check_buf(infile, buf, chunk_size, WAV_BLOCK_SIZE) ) { return; } if ( !strcmp( chunk_id, "fmt " ) ) { _parse_wav_fmt(buf, chunk_size, info); } else if ( !strcmp( chunk_id, "LIST" ) ) { _parse_wav_list(buf, chunk_size, tags); } else if ( !strcmp( chunk_id, "PEAK" ) ) { _parse_wav_peak(buf, chunk_size, info, 0); } else if ( !strcmp( chunk_id, "fact" ) ) { // A 4-byte fact chunk in a non-PCM wav is the number of samples // Use it to calculate duration if ( chunk_size == 4 ) { uint32_t num_samples = buffer_get_int_le(buf); SV **samplerate = my_hv_fetch( info, "samplerate" ); if (samplerate != NULL) { my_hv_store( info, "song_length_ms", newSVuv( (num_samples * 1000) / SvIV(*samplerate) ) ); } } else { // Unknown, skip it buffer_consume(buf, chunk_size); } } else { if ( !strcmp(chunk_id, "SAUR") // Wavosour data chunk || !strcmp(chunk_id, "otom") // Wavosaur? || !strcmp(chunk_id, "PAD ") // Padding ) { // Known chunks to skip } else { // Warn about unknown chunks so we can investigate them PerlIO_printf(PerlIO_stderr(), "Unhandled WAV chunk %s size %d (skipped)\n", chunk_id, chunk_size); } buffer_consume(buf, chunk_size); } } offset += chunk_size; } }
void _parse_wav_list(Buffer *buf, uint32_t chunk_size, HV *tags) { char type_id[5]; uint32_t pos = 4; strncpy( type_id, (char *)buffer_ptr(buf), 4 ); type_id[4] = '\0'; buffer_consume(buf, 4); DEBUG_TRACE(" LIST type %s\n", type_id); if ( !strcmp( type_id, "adtl" ) ) { // XXX need test file PerlIO_printf(PerlIO_stderr(), "Unhandled LIST type adtl\n"); buffer_consume(buf, chunk_size - 4); } else if ( !strcmp( type_id, "INFO" ) ) { while ( pos < chunk_size ) { uint32_t len; uint32_t nulls = 0; SV *key; SV *value; unsigned char *bptr; key = newSVpvn( buffer_ptr(buf), 4 ); buffer_consume(buf, 4); pos += 4; len = buffer_get_int_le(buf); // Bug 12250, apparently some WAV files don't use the padding byte // so we can't read them. if ( len > chunk_size - pos ) { PerlIO_printf(PerlIO_stderr(), "Invalid data in WAV LIST INFO chunk (len %d > chunk_size - pos %d)\n", len, chunk_size - pos); break; } pos += 4 + len; // Bug 14946, Strip any nulls from the end of the value bptr = buffer_ptr(buf); while ( len && bptr[len - 1] == '\0' ) { len--; nulls++; } value = newSVpvn( buffer_ptr(buf), len ); buffer_consume(buf, len + nulls); DEBUG_TRACE(" %s / %s (%d + %d nulls)\n", SvPVX(key), SvPVX(value), len, nulls); my_hv_store_ent( tags, key, value ); SvREFCNT_dec(key); // Handle padding if ( (len + nulls) % 2 ) { buffer_consume(buf, 1); pos++; } } } else { PerlIO_printf(PerlIO_stderr(), "Unhandled LIST type %s\n", type_id); buffer_consume(buf, chunk_size - 4); } }
static int get_wav_metadata(PerlIO *infile, char *file, HV *info, HV *tags) { Buffer buf; off_t file_size; int err = 0; uint32_t chunk_size; file_size = _file_size(infile); buffer_init(&buf, WAV_BLOCK_SIZE); if ( !_check_buf(infile, &buf, 12, WAV_BLOCK_SIZE) ) { err = -1; goto out; } if ( !strncmp( (char *)buffer_ptr(&buf), "RIFF", 4 ) ) { // We've got a RIFF file buffer_consume(&buf, 4); chunk_size = buffer_get_int_le(&buf); // Check format if ( strncmp( (char *)buffer_ptr(&buf), "WAVE", 4 ) ) { PerlIO_printf(PerlIO_stderr(), "Invalid WAV file: missing WAVE header: %s\n", file); err = -1; goto out; } buffer_consume(&buf, 4); my_hv_store( info, "file_size", newSVuv(file_size) ); _parse_wav(infile, &buf, file, file_size, info, tags); } else if ( !strncmp( (char *)buffer_ptr(&buf), "FORM", 4 ) ) { // We've got an AIFF file char *bptr; buffer_consume(&buf, 4); chunk_size = buffer_get_int(&buf); // Check format bptr = buffer_ptr(&buf); if ( bptr[0] == 'A' && bptr[1] == 'I' && bptr[2] == 'F' && (bptr[3] == 'F' || bptr[3] == 'C') ) { buffer_consume(&buf, 4); my_hv_store( info, "file_size", newSVuv(file_size) ); _parse_aiff(infile, &buf, file, file_size, info, tags); } else { PerlIO_printf(PerlIO_stderr(), "Invalid AIFF file: missing AIFF header: %s\n", file); err = -1; goto out; } } else { PerlIO_printf(PerlIO_stderr(), "Invalid WAV file: missing RIFF header: %s\n", file); err = -1; goto out; } out: buffer_free(&buf); if (err) return err; return 0; }