cog/Frameworks/WavPack/Files/tag_utils.c

598 lines
21 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) //
////////////////////////////////////////////////////////////////////////////
// tag_utils.c
// This module provides the high-level API for creating, reading and editing
// APEv2 tags on WavPack files. Read-only support is also provided for ID3v1
// tags, but their use is not recommended.
#include <stdlib.h>
#include <string.h>
#include "wavpack_local.h"
#ifdef _WIN32
#define stricmp(x,y) _stricmp(x,y)
#else
#define stricmp strcasecmp
#endif
static int get_ape_tag_item (M_Tag *m_tag, const char *item, char *value, int size, int type);
static int get_id3_tag_item (M_Tag *m_tag, const char *item, char *value, int size);
static int get_ape_tag_item_indexed (M_Tag *m_tag, int index, char *item, int size, int type);
static int get_id3_tag_item_indexed (M_Tag *m_tag, int index, char *item, int size);
static int append_ape_tag_item (WavpackContext *wpc, const char *item, const char *value, int vsize, int type);
static int write_tag_blockout (WavpackContext *wpc);
static int write_tag_reader (WavpackContext *wpc);
static void tagcpy (char *dest, char *src, int tag_size);
static int tagdata (char *src, int tag_size);
//////////////////// Global functions part of external API /////////////////////////
// Count and return the total number of tag items in the specified file.
int WavpackGetNumTagItems (WavpackContext *wpc)
{
int i = 0;
while (WavpackGetTagItemIndexed (wpc, i, NULL, 0))
++i;
return i;
}
// Count and return the total number of binary tag items in the specified file. This applies
// only to APEv2 tags and was implemented as a separate function to avoid breaking the old API.
int WavpackGetNumBinaryTagItems (WavpackContext *wpc)
{
int i = 0;
while (WavpackGetBinaryTagItemIndexed (wpc, i, NULL, 0))
++i;
return i;
}
// Attempt to get the specified item from the specified file's ID3v1 or APEv2
// tag. The "size" parameter specifies the amount of space available at "value",
// if the desired item will not fit in this space then ellipses (...) will
// be appended and the string terminated. Only text data are supported. The
// actual length of the string is returned (or 0 if no matching value found).
// Note that with APEv2 tags the length might not be the same as the number of
// characters because UTF-8 encoding is used. Also, APEv2 tags can have multiple
// (NULL separated) strings for a single value (this is why the length is
// returned). If this function is called with a NULL "value" pointer (or a
// zero "length") then only the actual length of the value data is returned
// (not counting the terminating NULL). This can be used to determine the
// actual memory to be allocated beforehand.
int WavpackGetTagItem (WavpackContext *wpc, const char *item, char *value, int size)
{
M_Tag *m_tag = &wpc->m_tag;
if (value && size)
*value = 0;
if (m_tag->ape_tag_hdr.ID [0] == 'A')
return get_ape_tag_item (m_tag, item, value, size, APE_TAG_TYPE_TEXT);
else if (m_tag->id3_tag.tag_id [0] == 'T')
return get_id3_tag_item (m_tag, item, value, size);
else
return 0;
}
// Attempt to get the specified binary item from the specified file's APEv2
// tag. The "size" parameter specifies the amount of space available at "value".
// If the desired item will not fit in this space then nothing will be copied
// and 0 will be returned, otherwise the actual size will be returned. If this
// function is called with a NULL "value" pointer (or a zero "length") then only
// the actual length of the value data is returned and can be used to determine
// the actual memory to be allocated beforehand.
int WavpackGetBinaryTagItem (WavpackContext *wpc, const char *item, char *value, int size)
{
M_Tag *m_tag = &wpc->m_tag;
if (value && size)
*value = 0;
if (m_tag->ape_tag_hdr.ID [0] == 'A')
return get_ape_tag_item (m_tag, item, value, size, APE_TAG_TYPE_BINARY);
else
return 0;
}
// This function looks up the tag item name by index and is used when the
// application wants to access all the items in the file's ID3v1 or APEv2 tag.
// Note that this function accesses only the item's name; WavpackGetTagItem()
// still must be called to get the actual value. The "size" parameter specifies
// the amount of space available at "item", if the desired item will not fit in
// this space then ellipses (...) will be appended and the string terminated.
// The actual length of the string is returned (or 0 if no item exists for
// index). If this function is called with a NULL "value" pointer (or a
// zero "length") then only the actual length of the item name is returned
// (not counting the terminating NULL). This can be used to determine the
// actual memory to be allocated beforehand. For binary tag values use the
// otherwise identical WavpackGetBinaryTagItemIndexed ();
int WavpackGetTagItemIndexed (WavpackContext *wpc, int index, char *item, int size)
{
M_Tag *m_tag = &wpc->m_tag;
if (item && size)
*item = 0;
if (m_tag->ape_tag_hdr.ID [0] == 'A')
return get_ape_tag_item_indexed (m_tag, index, item, size, APE_TAG_TYPE_TEXT);
else if (m_tag->id3_tag.tag_id [0] == 'T')
return get_id3_tag_item_indexed (m_tag, index, item, size);
else
return 0;
}
int WavpackGetBinaryTagItemIndexed (WavpackContext *wpc, int index, char *item, int size)
{
M_Tag *m_tag = &wpc->m_tag;
if (item && size)
*item = 0;
if (m_tag->ape_tag_hdr.ID [0] == 'A')
return get_ape_tag_item_indexed (m_tag, index, item, size, APE_TAG_TYPE_BINARY);
else
return 0;
}
// These two functions are used to append APEv2 tags to WavPack files; one is
// for text values (UTF-8 encoded) and the other is for binary values. If no tag
// has been started, then an empty one will be allocated first. When finished,
// use WavpackWriteTag() to write the completed tag to the file. The purpose of
// the passed size parameter is obvious for binary values, but might not be for
// text values. Keep in mind that APEv2 text values can have multiple values
// that are NULL separated, so the size is required to know the extent of the
// value (although the final terminating NULL is not included in the passed
// size). If the specified item already exists, it will be replaced with the
// new value. ID3v1 tags are not supported.
int WavpackAppendTagItem (WavpackContext *wpc, const char *item, const char *value, int vsize)
{
while (WavpackDeleteTagItem (wpc, item));
return append_ape_tag_item (wpc, item, value, vsize, APE_TAG_TYPE_TEXT);
}
int WavpackAppendBinaryTagItem (WavpackContext *wpc, const char *item, const char *value, int vsize)
{
while (WavpackDeleteTagItem (wpc, item));
return append_ape_tag_item (wpc, item, value, vsize, APE_TAG_TYPE_BINARY);
}
// Delete the specified tag item from the APEv2 tag on the specified WavPack file
// (fields cannot be deleted from ID3v1 tags). A return value of TRUE indicates
// that the item was found and successfully deleted.
int WavpackDeleteTagItem (WavpackContext *wpc, const char *item)
{
M_Tag *m_tag = &wpc->m_tag;
if (m_tag->ape_tag_hdr.ID [0] == 'A') {
unsigned char *p = m_tag->ape_tag_data;
unsigned char *q = p + m_tag->ape_tag_hdr.length - sizeof (APE_Tag_Hdr);
int i;
for (i = 0; i < m_tag->ape_tag_hdr.item_count; ++i) {
int vsize, isize;
vsize = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24); p += 8; // skip flags because we don't need them
for (isize = 0; p[isize] && p + isize < q; ++isize);
if (vsize < 0 || vsize > m_tag->ape_tag_hdr.length || p + isize + vsize + 1 > q)
break;
if (isize && vsize && !stricmp (item, (char *) p)) {
unsigned char *d = p - 8;
p += isize + vsize + 1;
while (p < q)
*d++ = *p++;
m_tag->ape_tag_hdr.length = (int32_t)(d - m_tag->ape_tag_data) + sizeof (APE_Tag_Hdr);
m_tag->ape_tag_hdr.item_count--;
return 1;
}
else
p += isize + vsize + 1;
}
}
return 0;
}
// Once a APEv2 tag has been created with WavpackAppendTag(), this function is
// used to write the completed tag to the end of the WavPack file. Note that
// this function uses the same "blockout" function that is used to write
// regular WavPack blocks, although that's where the similarity ends. It is also
// used to write tags that have been edited on existing files.
int WavpackWriteTag (WavpackContext *wpc)
{
if (wpc->blockout) // this is the case for creating fresh WavPack files
return write_tag_blockout (wpc);
else // otherwise we are editing existing tags (OPEN_EDIT_TAGS)
return write_tag_reader (wpc);
}
////////////////////////// local static functions /////////////////////////////
static int get_ape_tag_item (M_Tag *m_tag, const char *item, char *value, int size, int type)
{
unsigned char *p = m_tag->ape_tag_data;
unsigned char *q = p + m_tag->ape_tag_hdr.length - sizeof (APE_Tag_Hdr);
int i;
for (i = 0; i < m_tag->ape_tag_hdr.item_count && q - p > 8; ++i) {
int vsize, flags, isize;
vsize = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24); p += 4;
flags = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24); p += 4;
for (isize = 0; p[isize] && p + isize < q; ++isize);
if (vsize < 0 || vsize > m_tag->ape_tag_hdr.length || p + isize + vsize + 1 > q)
break;
if (isize && vsize && !stricmp (item, (char *) p) && ((flags & 6) >> 1) == type) {
if (!value || !size)
return vsize;
if (type == APE_TAG_TYPE_BINARY) {
if (vsize <= size) {
memcpy (value, p + isize + 1, vsize);
return vsize;
}
else
return 0;
}
else if (vsize < size) {
memcpy (value, p + isize + 1, vsize);
value [vsize] = 0;
return vsize;
}
else if (size >= 4) {
memcpy (value, p + isize + 1, size - 1);
value [size - 4] = value [size - 3] = value [size - 2] = '.';
value [size - 1] = 0;
return size - 1;
}
else
return 0;
}
else
p += isize + vsize + 1;
}
return 0;
}
static int get_id3_tag_item (M_Tag *m_tag, const char *item, char *value, int size)
{
char lvalue [64];
int len;
lvalue [0] = 0;
if (!stricmp (item, "title"))
tagcpy (lvalue, m_tag->id3_tag.title, sizeof (m_tag->id3_tag.title));
else if (!stricmp (item, "artist"))
tagcpy (lvalue, m_tag->id3_tag.artist, sizeof (m_tag->id3_tag.artist));
else if (!stricmp (item, "album"))
tagcpy (lvalue, m_tag->id3_tag.album, sizeof (m_tag->id3_tag.album));
else if (!stricmp (item, "year"))
tagcpy (lvalue, m_tag->id3_tag.year, sizeof (m_tag->id3_tag.year));
else if (!stricmp (item, "comment"))
tagcpy (lvalue, m_tag->id3_tag.comment, sizeof (m_tag->id3_tag.comment));
else if (!stricmp (item, "track") && m_tag->id3_tag.comment [29] && !m_tag->id3_tag.comment [28])
sprintf (lvalue, "%d", m_tag->id3_tag.comment [29]);
else
return 0;
len = (int) strlen (lvalue);
if (!value || !size)
return len;
if (len < size) {
strcpy (value, lvalue);
return len;
}
else if (size >= 4) {
strncpy (value, lvalue, size - 1);
value [size - 4] = value [size - 3] = value [size - 2] = '.';
value [size - 1] = 0;
return size - 1;
}
else
return 0;
}
static int get_ape_tag_item_indexed (M_Tag *m_tag, int index, char *item, int size, int type)
{
unsigned char *p = m_tag->ape_tag_data;
unsigned char *q = p + m_tag->ape_tag_hdr.length - sizeof (APE_Tag_Hdr);
int i;
for (i = 0; i < m_tag->ape_tag_hdr.item_count && index >= 0 && q - p > 8; ++i) {
int vsize, flags, isize;
vsize = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24); p += 4;
flags = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24); p += 4;
for (isize = 0; p[isize] && p + isize < q; ++isize);
if (vsize < 0 || vsize > m_tag->ape_tag_hdr.length || p + isize + vsize + 1 > q)
break;
if (isize && vsize && ((flags & 6) >> 1) == type && !index--) {
if (!item || !size)
return isize;
if (isize < size) {
memcpy (item, p, isize);
item [isize] = 0;
return isize;
}
else if (size >= 4) {
memcpy (item, p, size - 1);
item [size - 4] = item [size - 3] = item [size - 2] = '.';
item [size - 1] = 0;
return size - 1;
}
else
return 0;
}
else
p += isize + vsize + 1;
}
return 0;
}
static int get_id3_tag_item_indexed (M_Tag *m_tag, int index, char *item, int size)
{
char lvalue [16];
int len;
lvalue [0] = 0;
if (tagdata (m_tag->id3_tag.title, sizeof (m_tag->id3_tag.title)) && !index--)
strcpy (lvalue, "Title");
else if (tagdata (m_tag->id3_tag.artist, sizeof (m_tag->id3_tag.artist)) && !index--)
strcpy (lvalue, "Artist");
else if (tagdata (m_tag->id3_tag.album, sizeof (m_tag->id3_tag.album)) && !index--)
strcpy (lvalue, "Album");
else if (tagdata (m_tag->id3_tag.year, sizeof (m_tag->id3_tag.year)) && !index--)
strcpy (lvalue, "Year");
else if (tagdata (m_tag->id3_tag.comment, sizeof (m_tag->id3_tag.comment)) && !index--)
strcpy (lvalue, "Comment");
else if (m_tag->id3_tag.comment [29] && !m_tag->id3_tag.comment [28] && !index--)
strcpy (lvalue, "Track");
else
return 0;
len = (int) strlen (lvalue);
if (!item || !size)
return len;
if (len < size) {
strcpy (item, lvalue);
return len;
}
else if (size >= 4) {
strncpy (item, lvalue, size - 1);
item [size - 4] = item [size - 3] = item [size - 2] = '.';
item [size - 1] = 0;
return size - 1;
}
else
return 0;
}
static int append_ape_tag_item (WavpackContext *wpc, const char *item, const char *value, int vsize, int type)
{
M_Tag *m_tag = &wpc->m_tag;
int isize = (int) strlen (item);
if (!m_tag->ape_tag_hdr.ID [0]) {
memcpy (m_tag->ape_tag_hdr.ID, "APETAGEX", sizeof (m_tag->ape_tag_hdr.ID));
m_tag->ape_tag_hdr.version = 2000;
m_tag->ape_tag_hdr.length = sizeof (m_tag->ape_tag_hdr);
m_tag->ape_tag_hdr.item_count = 0;
m_tag->ape_tag_hdr.flags = APE_TAG_CONTAINS_HEADER; // we will include header on tags we originate
}
if (m_tag->ape_tag_hdr.ID [0] == 'A') {
int new_item_len = vsize + isize + 9, flags = type << 1;
unsigned char *p;
if (m_tag->ape_tag_hdr.length + new_item_len > APE_TAG_MAX_LENGTH) {
strcpy (wpc->error_message, "APEv2 tag exceeds maximum allowed length!");
return FALSE;
}
m_tag->ape_tag_hdr.item_count++;
m_tag->ape_tag_hdr.length += new_item_len;
p = m_tag->ape_tag_data = (unsigned char*)realloc (m_tag->ape_tag_data, m_tag->ape_tag_hdr.length);
p += m_tag->ape_tag_hdr.length - sizeof (APE_Tag_Hdr) - new_item_len;
*p++ = (unsigned char) vsize;
*p++ = (unsigned char) (vsize >> 8);
*p++ = (unsigned char) (vsize >> 16);
*p++ = (unsigned char) (vsize >> 24);
*p++ = (unsigned char) flags;
*p++ = (unsigned char) (flags >> 8);
*p++ = (unsigned char) (flags >> 16);
*p++ = (unsigned char) (flags >> 24);
strcpy ((char *) p, item);
p += isize + 1;
memcpy (p, value, vsize);
return TRUE;
}
else
return FALSE;
}
// Append the stored APEv2 tag to the file being created using the "blockout" function callback.
static int write_tag_blockout (WavpackContext *wpc)
{
M_Tag *m_tag = &wpc->m_tag;
int result = TRUE;
if (m_tag->ape_tag_hdr.ID [0] == 'A' && m_tag->ape_tag_hdr.item_count) {
// only write header if it's specified in the flags
if (m_tag->ape_tag_hdr.flags & APE_TAG_CONTAINS_HEADER) {
m_tag->ape_tag_hdr.flags |= APE_TAG_THIS_IS_HEADER;
WavpackNativeToLittleEndian (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
result = wpc->blockout (wpc->wv_out, &m_tag->ape_tag_hdr, sizeof (m_tag->ape_tag_hdr));
WavpackLittleEndianToNative (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
}
if (m_tag->ape_tag_hdr.length > sizeof (m_tag->ape_tag_hdr))
result = wpc->blockout (wpc->wv_out, m_tag->ape_tag_data, m_tag->ape_tag_hdr.length - sizeof (m_tag->ape_tag_hdr));
m_tag->ape_tag_hdr.flags &= ~APE_TAG_THIS_IS_HEADER; // this is NOT header
WavpackNativeToLittleEndian (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
result = wpc->blockout (wpc->wv_out, &m_tag->ape_tag_hdr, sizeof (m_tag->ape_tag_hdr));
WavpackLittleEndianToNative (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
}
if (!result)
strcpy (wpc->error_message, "can't write WavPack data, disk probably full!");
return result;
}
// Write the [potentially] edited tag to the existing WavPack file using the reader callback functions.
static int write_tag_reader (WavpackContext *wpc)
{
M_Tag *m_tag = &wpc->m_tag;
int32_t tag_size = 0;
int result;
// before we write an edited (or new) tag into an existing file, make sure it's safe and possible
if (m_tag->tag_begins_file) {
strcpy (wpc->error_message, "can't edit tags located at the beginning of files!");
return FALSE;
}
if (!wpc->reader->can_seek (wpc->wv_in)) {
strcpy (wpc->error_message, "can't edit tags on pipes or unseekable files!");
return FALSE;
}
if (!(wpc->open_flags & OPEN_EDIT_TAGS)) {
strcpy (wpc->error_message, "can't edit tags without OPEN_EDIT_TAGS flag!");
return FALSE;
}
if (m_tag->ape_tag_hdr.ID [0] == 'A' && m_tag->ape_tag_hdr.item_count &&
m_tag->ape_tag_hdr.length > sizeof (m_tag->ape_tag_hdr))
tag_size = m_tag->ape_tag_hdr.length;
// only write header if it's specified in the flags
if (tag_size && (m_tag->ape_tag_hdr.flags & APE_TAG_CONTAINS_HEADER))
tag_size += sizeof (m_tag->ape_tag_hdr);
result = !wpc->reader->set_pos_rel (wpc->wv_in, m_tag->tag_file_pos, SEEK_END);
if (result && tag_size < -m_tag->tag_file_pos && !wpc->reader->truncate_here) {
int nullcnt = (int) (-m_tag->tag_file_pos - tag_size);
char zero [1] = { 0 };
while (nullcnt--)
wpc->reader->write_bytes (wpc->wv_in, &zero, 1);
}
if (result && tag_size) {
if (m_tag->ape_tag_hdr.flags & APE_TAG_CONTAINS_HEADER) {
m_tag->ape_tag_hdr.flags |= APE_TAG_THIS_IS_HEADER;
WavpackNativeToLittleEndian (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
result = (wpc->reader->write_bytes (wpc->wv_in, &m_tag->ape_tag_hdr, sizeof (m_tag->ape_tag_hdr)) == sizeof (m_tag->ape_tag_hdr));
WavpackLittleEndianToNative (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
}
result = (wpc->reader->write_bytes (wpc->wv_in, m_tag->ape_tag_data, m_tag->ape_tag_hdr.length - sizeof (m_tag->ape_tag_hdr)) == sizeof (m_tag->ape_tag_hdr));
m_tag->ape_tag_hdr.flags &= ~APE_TAG_THIS_IS_HEADER; // this is NOT header
WavpackNativeToLittleEndian (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
result = (wpc->reader->write_bytes (wpc->wv_in, &m_tag->ape_tag_hdr, sizeof (m_tag->ape_tag_hdr)) == sizeof (m_tag->ape_tag_hdr));
WavpackLittleEndianToNative (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
}
if (result && tag_size < -m_tag->tag_file_pos && wpc->reader->truncate_here)
result = !wpc->reader->truncate_here (wpc->wv_in);
if (!result)
strcpy (wpc->error_message, "can't write WavPack data, disk probably full!");
return result;
}
// Copy the specified ID3v1 tag value (with specified field size) from the
// source pointer to the destination, eliminating leading spaces and trailing
// spaces and nulls.
static void tagcpy (char *dest, char *src, int tag_size)
{
char *s1 = src, *s2 = src + tag_size - 1;
if (*s2 && !s2 [-1])
s2--;
while (s1 <= s2)
if (*s1 == ' ')
++s1;
else if (!*s2 || *s2 == ' ')
--s2;
else
break;
while (*s1 && s1 <= s2)
*dest++ = *s1++;
*dest = 0;
}
static int tagdata (char *src, int tag_size)
{
char *s1 = src, *s2 = src + tag_size - 1;
if (*s2 && !s2 [-1])
s2--;
while (s1 <= s2)
if (*s1 == ' ')
++s1;
else if (!*s2 || *s2 == ' ')
--s2;
else
break;
return (*s1 && s1 <= s2);
}