180 lines
8.2 KiB
C
180 lines
8.2 KiB
C
////////////////////////////////////////////////////////////////////////////
|
|
// **** WAVPACK **** //
|
|
// Hybrid Lossless Wavefile Compressor //
|
|
// Copyright (c) 1998 - 2013 Conifer Software. //
|
|
// All Rights Reserved. //
|
|
// Distributed under the BSD Software License (see license.txt) //
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// tags.c
|
|
|
|
// This module provides support for reading metadata tags (either ID3v1 or
|
|
// APEv2) from WavPack files. No actual creation or manipulation of the tags
|
|
// is done in this module; this is just internal code to load the tags into
|
|
// memory. The high-level API functions are in the tag_utils.c module.
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "wavpack_local.h"
|
|
|
|
// This function attempts to load an ID3v1 or APEv2 tag from the specified
|
|
// file into the specified M_Tag structure. The ID3 tag fits in completely,
|
|
// but an APEv2 tag is variable length and so space must be allocated here
|
|
// to accommodate the data, and this will need to be freed later. A return
|
|
// value of TRUE indicates a valid tag was found and loaded. Note that the
|
|
// file pointer is undefined when this function exits.
|
|
|
|
int load_tag (WavpackContext *wpc)
|
|
{
|
|
int ape_tag_length, ape_tag_items;
|
|
M_Tag *m_tag = &wpc->m_tag;
|
|
|
|
CLEAR (*m_tag);
|
|
|
|
// This is a loop because we can try up to three times to look for an APEv2 tag. In order, we look:
|
|
//
|
|
// 1. At the end of the file for a APEv2 footer (this is the preferred location)
|
|
// 2. If there's instead an ID3v1 tag at the end of the file, try looking for an APEv2 footer right before that
|
|
// 3. If all else fails, look for an APEv2 header the the beginning of the file (use is strongly discouraged)
|
|
|
|
while (1) {
|
|
|
|
// seek based on specific location that we are looking for tag (see above list)
|
|
|
|
if (m_tag->tag_begins_file) // case #3
|
|
wpc->reader->set_pos_abs (wpc->wv_in, 0);
|
|
else if (m_tag->id3_tag.tag_id [0] == 'T') // case #2
|
|
wpc->reader->set_pos_rel (wpc->wv_in, -(int32_t)(sizeof (APE_Tag_Hdr) + sizeof (ID3_Tag)), SEEK_END);
|
|
else // case #1
|
|
wpc->reader->set_pos_rel (wpc->wv_in, -(int32_t)sizeof (APE_Tag_Hdr), SEEK_END);
|
|
|
|
// read a possible APEv2 tag header/footer and see if there's one there...
|
|
|
|
if (wpc->reader->read_bytes (wpc->wv_in, &m_tag->ape_tag_hdr, sizeof (APE_Tag_Hdr)) == sizeof (APE_Tag_Hdr) &&
|
|
!strncmp (m_tag->ape_tag_hdr.ID, "APETAGEX", 8)) {
|
|
|
|
WavpackLittleEndianToNative (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
|
|
|
|
if (m_tag->ape_tag_hdr.version == 2000 && m_tag->ape_tag_hdr.item_count &&
|
|
m_tag->ape_tag_hdr.length > sizeof (m_tag->ape_tag_hdr) &&
|
|
m_tag->ape_tag_hdr.length <= APE_TAG_MAX_LENGTH &&
|
|
(m_tag->ape_tag_data = (unsigned char *)malloc (m_tag->ape_tag_hdr.length)) != NULL) {
|
|
|
|
ape_tag_items = m_tag->ape_tag_hdr.item_count;
|
|
ape_tag_length = m_tag->ape_tag_hdr.length;
|
|
|
|
// If this is a APEv2 footer (which is normal if we are searching at the end of the file)...
|
|
|
|
if (!(m_tag->ape_tag_hdr.flags & APE_TAG_THIS_IS_HEADER)) {
|
|
|
|
if (m_tag->id3_tag.tag_id [0] == 'T')
|
|
m_tag->tag_file_pos = -(int32_t)sizeof (ID3_Tag);
|
|
else
|
|
m_tag->tag_file_pos = 0;
|
|
|
|
m_tag->tag_file_pos -= ape_tag_length;
|
|
|
|
// if the footer claims there is a header present also, we will read that and use it
|
|
// instead of the footer (after verifying it, of course) for enhanced robustness
|
|
|
|
if (m_tag->ape_tag_hdr.flags & APE_TAG_CONTAINS_HEADER)
|
|
m_tag->tag_file_pos -= sizeof (APE_Tag_Hdr);
|
|
|
|
wpc->reader->set_pos_rel (wpc->wv_in, m_tag->tag_file_pos, SEEK_END);
|
|
|
|
if (m_tag->ape_tag_hdr.flags & APE_TAG_CONTAINS_HEADER) {
|
|
if (wpc->reader->read_bytes (wpc->wv_in, &m_tag->ape_tag_hdr, sizeof (APE_Tag_Hdr)) !=
|
|
sizeof (APE_Tag_Hdr) || strncmp (m_tag->ape_tag_hdr.ID, "APETAGEX", 8)) {
|
|
free (m_tag->ape_tag_data);
|
|
CLEAR (*m_tag);
|
|
return FALSE; // something's wrong...
|
|
}
|
|
|
|
WavpackLittleEndianToNative (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
|
|
|
|
if (m_tag->ape_tag_hdr.version != 2000 || m_tag->ape_tag_hdr.item_count != ape_tag_items ||
|
|
m_tag->ape_tag_hdr.length != ape_tag_length) {
|
|
free (m_tag->ape_tag_data);
|
|
CLEAR (*m_tag);
|
|
return FALSE; // something's wrong...
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wpc->reader->read_bytes (wpc->wv_in, m_tag->ape_tag_data,
|
|
ape_tag_length - sizeof (APE_Tag_Hdr)) != ape_tag_length - sizeof (APE_Tag_Hdr)) {
|
|
free (m_tag->ape_tag_data);
|
|
CLEAR (*m_tag);
|
|
return FALSE; // something's wrong...
|
|
}
|
|
else {
|
|
CLEAR (m_tag->id3_tag); // ignore ID3v1 tag if we found APEv2 tag
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// we come here if the search for the APEv2 tag failed (otherwise we would have returned with it)
|
|
|
|
if (m_tag->id3_tag.tag_id [0] == 'T') { // settle for the ID3v1 tag that we found
|
|
CLEAR (m_tag->ape_tag_hdr);
|
|
return TRUE;
|
|
}
|
|
|
|
// if this was the search for the APEv2 tag at the beginning of the file (which is our
|
|
// last resort) then we have nothing, so return failure
|
|
|
|
if (m_tag->tag_begins_file) {
|
|
CLEAR (*m_tag);
|
|
return FALSE;
|
|
}
|
|
|
|
// If we get here, then we have failed the first APEv2 tag search (at end of file) and so now we
|
|
// look for an ID3v1 tag at the same position. If that succeeds, then we'll loop back and look for
|
|
// an APEv2 tag immediately before the ID3v1 tag, otherwise our last resort is to look for an
|
|
// APEv2 tag at the beginning of the file. These are strongly discouraged (and not editable) but
|
|
// they have been seen in the wild so we attempt to handle them here (at least well enough to
|
|
// allow a proper transcoding).
|
|
|
|
m_tag->tag_file_pos = -(int32_t)sizeof (ID3_Tag);
|
|
wpc->reader->set_pos_rel (wpc->wv_in, m_tag->tag_file_pos, SEEK_END);
|
|
|
|
if (wpc->reader->read_bytes (wpc->wv_in, &m_tag->id3_tag, sizeof (ID3_Tag)) != sizeof (ID3_Tag) ||
|
|
strncmp (m_tag->id3_tag.tag_id, "TAG", 3)) {
|
|
m_tag->tag_begins_file = 1; // failed ID3v1, so look for APEv2 at beginning of file
|
|
CLEAR (m_tag->id3_tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return TRUE is a valid ID3v1 or APEv2 tag has been loaded.
|
|
|
|
int valid_tag (M_Tag *m_tag)
|
|
{
|
|
if (m_tag->ape_tag_hdr.ID [0] == 'A')
|
|
return 'A';
|
|
else if (m_tag->id3_tag.tag_id [0] == 'T')
|
|
return 'T';
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
// Return FALSE if a valid APEv2 tag was only found at the beginning of the file (these are read-only
|
|
// because they cannot be edited without possibly shifting the entire file)
|
|
|
|
int editable_tag (M_Tag *m_tag)
|
|
{
|
|
return !m_tag->tag_begins_file;
|
|
}
|
|
|
|
// Free the data for any APEv2 tag that was allocated.
|
|
|
|
void free_tag (M_Tag *m_tag)
|
|
{
|
|
if (m_tag->ape_tag_data) {
|
|
free (m_tag->ape_tag_data);
|
|
m_tag->ape_tag_data = NULL;
|
|
}
|
|
}
|