aboutsummaryrefslogtreecommitdiff
path: root/Src/replicant/nswasabi/ID3v2Metadata.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/replicant/nswasabi/ID3v2Metadata.cpp')
-rw-r--r--Src/replicant/nswasabi/ID3v2Metadata.cpp1087
1 files changed, 1087 insertions, 0 deletions
diff --git a/Src/replicant/nswasabi/ID3v2Metadata.cpp b/Src/replicant/nswasabi/ID3v2Metadata.cpp
new file mode 100644
index 00000000..a1b49212
--- /dev/null
+++ b/Src/replicant/nswasabi/ID3v2Metadata.cpp
@@ -0,0 +1,1087 @@
+#include "ID3v2Metadata.h"
+#include "metadata/MetadataKeys.h"
+#include "nswasabi/ReferenceCounted.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+api_metadata *ID3v2Metadata::metadata_api=0;
+
+static inline bool TestFlag(int flags, int flag_to_check)
+{
+ if (flags & flag_to_check)
+ return true;
+ return false;
+}
+
+ID3v2Metadata::ID3v2Metadata()
+{
+ id3v2_tag=0;
+
+#ifdef __APPLE__
+ number_formatter = NULL;
+#endif
+}
+
+ID3v2Metadata::~ID3v2Metadata()
+{
+#ifdef __APPLE__
+ if (NULL != number_formatter)
+ CFRelease(number_formatter);
+#endif
+}
+
+int ID3v2Metadata::Initialize(api_metadata *metadata_api)
+{
+ ID3v2Metadata::metadata_api = metadata_api;
+ return NErr_Success;
+}
+
+int ID3v2Metadata::Initialize(nsid3v2_tag_t tag)
+{
+ id3v2_tag = tag;
+
+ return NErr_Success;
+}
+
+int ID3v2Metadata::GetGenre(int index, nx_string_t *value)
+{
+ nx_string_t genre=0;
+ int ret = NSID3v2_Tag_Text_Get(id3v2_tag, NSID3V2_FRAME_CONTENTTYPE, &genre, 0);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ if (genre)
+ {
+ *value = genre;
+#ifdef _WIN32
+ // parse the (##) out of it
+ wchar_t *tmp = genre->string;
+ while (*tmp == ' ') tmp++;
+ if (!wcsncmp(tmp, L"(RX)", 4))
+ {
+ *value = NXStringCreateFromUTF8("Remix");
+ NXStringRelease(genre);
+ if (*value)
+ return NErr_Success;
+ else
+ return NErr_OutOfMemory;
+ }
+ else if (!wcsncmp(tmp, L"(CR)", 4))
+ {
+ *value = NXStringCreateFromUTF8("Cover");
+ NXStringRelease(genre);
+ if (*value)
+ return NErr_Success;
+ else
+ return NErr_OutOfMemory;
+ }
+
+ if (*tmp == '(' || (*tmp >= '0' && *tmp <= '9')) // both (%d) and %d forms
+ {
+ int noparam = 0;
+
+ if (*tmp == '(') tmp++;
+ else noparam = 1;
+ size_t genre_index = _wtoi(tmp);
+ int cnt = 0;
+ while (*tmp >= '0' && *tmp <= '9') cnt++, tmp++;
+ while (*tmp == ' ') tmp++;
+
+ if (((!*tmp && noparam) || (!noparam && *tmp == ')')) && cnt > 0)
+ {
+ if (genre_index < 256 && metadata_api)
+ {
+ int ret = metadata_api->GetGenre(genre_index, value);
+ if (ret == NErr_Success)
+ {
+ NXStringRetain(*value);
+ NXStringRelease(genre);
+ return ret;
+ }
+ }
+ }
+ }
+#elif defined(__APPLE__)
+ int ret = NErr_Success;
+
+ CFMutableStringRef mutable_genre = CFStringCreateMutableCopy(NULL, 0, genre);
+ CFStringTrimWhitespace(mutable_genre);
+
+ CFIndex mutable_genre_length = CFStringGetLength(mutable_genre);
+
+ if (kCFCompareEqualTo == CFStringCompareWithOptionsAndLocale(mutable_genre,
+ CFSTR("(RX)"),
+ CFRangeMake(0, mutable_genre_length),
+ 0,
+ NULL))
+ {
+ NXStringRelease(genre);
+ *value = CFSTR("Remix");
+ ret = NErr_Success;
+ }
+ else if (kCFCompareEqualTo == CFStringCompareWithOptionsAndLocale(mutable_genre,
+ CFSTR("(CR)"),
+ CFRangeMake(0, mutable_genre_length),
+ 0,
+ NULL))
+ {
+ NXStringRelease(genre);
+ *value = CFSTR("Cover");
+ ret = NErr_Success;
+ }
+ else
+ {
+ CFStringTrim(mutable_genre, CFSTR("("));
+ CFStringTrim(mutable_genre, CFSTR(")"));
+ mutable_genre_length = CFStringGetLength(mutable_genre);
+ if (mutable_genre_length > 0
+ && mutable_genre_length < 4)
+ {
+ if (NULL == number_formatter)
+ {
+ CFLocaleRef locale = CFLocaleCreate(NULL, CFSTR("en_US_POSIX"));
+ number_formatter = CFNumberFormatterCreate(NULL, locale, kCFNumberFormatterDecimalStyle);
+ CFRelease(locale);
+ }
+
+ SInt8 genre_index;
+ CFRange number_range = CFRangeMake(0, mutable_genre_length);
+ if (NULL != number_formatter
+ && false != CFNumberFormatterGetValueFromString(number_formatter,
+ mutable_genre,
+ &number_range,
+ kCFNumberSInt8Type,
+ &genre_index)
+ && number_range.length == mutable_genre_length
+ && number_range.location == 0)
+ {
+
+ if (genre_index >= 0
+ && genre_index < 256
+ && metadata_api)
+ {
+ int ret = metadata_api->GetGenre(genre_index, value);
+ if (ret == NErr_Success)
+ {
+ NXStringRetain(*value);
+ NXStringRelease(genre);
+ }
+ ret = NErr_Success;
+ }
+ }
+ }
+ }
+
+ CFRelease(mutable_genre);
+ return ret;
+#elif defined(__linux__)
+ char *tmp = genre->string;
+ while (*tmp == ' ') tmp++;
+
+ if (!strncmp(tmp, "(RX)", 4))
+ {
+ NXStringRelease(genre);
+ return NXStringCreateWithUTF8(value, "Remix");
+ }
+ else if (!strncmp(tmp, "(CR)", 4))
+ {
+ NXStringRelease(genre);
+ return NXStringCreateWithUTF8(value, "Cover");
+ }
+
+ if (*tmp == '(' || (*tmp >= '0' && *tmp <= '9')) // both (%d) and %d forms
+ {
+ int noparam = 0;
+
+ if (*tmp == '(') tmp++;
+ else noparam = 1;
+ size_t genre_index = atoi(tmp);
+ int cnt = 0;
+ while (*tmp >= '0' && *tmp <= '9') cnt++, tmp++;
+ while (*tmp == ' ') tmp++;
+
+ if (((!*tmp && noparam) || (!noparam && *tmp == ')')) && cnt > 0)
+ {
+ if (genre_index < 256 && metadata_api)
+ {
+ int ret = metadata_api->GetGenre(genre_index, value);
+ if (ret == NErr_Success)
+ {
+ NXStringRetain(*value);
+ NXStringRelease(genre);
+ return ret;
+ }
+ }
+ }
+ }
+#else
+#error port me!
+#endif
+ }
+ return NErr_Success;
+}
+
+static int ID3v2_GetText(nsid3v2_tag_t id3v2_tag, int frame_enum, unsigned int index, nx_string_t *value)
+{
+ if (!id3v2_tag)
+ return NErr_Empty;
+
+ nsid3v2_frame_t frame;
+ int ret = NSID3v2_Tag_GetFrame(id3v2_tag, frame_enum, &frame);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ return NSID3v2_Frame_Text_Get(frame, value, 0);
+}
+
+static int ID3v2_GetTXXX(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t *value)
+{
+ if (!id3v2_tag)
+ return NErr_Empty;
+
+ nsid3v2_frame_t frame;
+ int ret = NSID3v2_Tag_TXXX_Find(id3v2_tag, description, &frame, 0);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ return NSID3v2_Frame_UserText_Get(frame, 0, value, 0);
+}
+
+static int ID3v2_GetComments(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t *value)
+{
+ if (!id3v2_tag)
+ return NErr_Empty;
+
+ nsid3v2_frame_t frame;
+ int ret = NSID3v2_Tag_Comments_Find(id3v2_tag, description, &frame, 0);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ return NSID3v2_Frame_Comments_Get(frame, 0, 0, value, 0);
+}
+
+// only one of value1 or value2 should be non-NULL
+static int SplitSlash(nx_string_t track, nx_string_t *value1, nx_string_t *value2)
+{
+ char track_utf8[64];
+ size_t bytes_copied;
+ int ret;
+ ret = NXStringGetBytes(&bytes_copied, track, track_utf8, 64, nx_charset_utf8, nx_string_get_bytes_size_null_terminate);
+ if (ret == NErr_Success)
+ {
+ size_t len = strcspn(track_utf8, "/");
+
+ if (value2)
+ {
+ const char *second = &track_utf8[len];
+ if (*second)
+ second++;
+
+ if (!*second)
+ return NErr_Empty;
+
+ return NXStringCreateWithUTF8(value2, second);
+ }
+ else
+ {
+ if (len == 0)
+ return NErr_Empty;
+ return NXStringCreateWithBytes(value1, track_utf8, len, nx_charset_utf8);
+ }
+
+ return NErr_Success;
+ }
+
+ return ret;
+}
+
+/* ifc_metadata implementation */
+int ID3v2Metadata::Metadata_GetField(int field, unsigned int index, nx_string_t *value)
+{
+ if (!id3v2_tag)
+ return NErr_Unknown;
+
+ int ret;
+
+ switch (field)
+ {
+ case MetadataKeys::ARTIST:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_LEADARTIST, index, value);
+
+ case MetadataKeys::ALBUM_ARTIST:
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_BAND, index, value); /* Windows Media Player style */
+ if (ret == NErr_Success || ret == NErr_EndOfEnumeration)
+ return ret;
+
+ ret = ID3v2_GetTXXX(id3v2_tag, "ALBUM ARTIST", index, value); /* foobar 2000 style */
+ if (ret == NErr_Success || ret == NErr_EndOfEnumeration)
+ return ret;
+
+ ret = ID3v2_GetTXXX(id3v2_tag, "ALBUMARTIST", index, value); /* mp3tag style */
+ if (ret == NErr_Success || ret == NErr_EndOfEnumeration)
+ return ret;
+
+ return ID3v2_GetTXXX(id3v2_tag, "Band", index, value); /* audacity style */
+
+ case MetadataKeys::ALBUM:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_ALBUM, index, value);
+
+ case MetadataKeys::TITLE:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TITLE, index, value);
+
+ case MetadataKeys::GENRE:
+ return GetGenre(index, value);
+
+ case MetadataKeys::TRACK:
+ {
+ ReferenceCountedNXString track;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track);
+ if (ret == NErr_Success)
+ return SplitSlash(track, value, 0);
+
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::TRACKS:
+{
+ ReferenceCountedNXString track;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track);
+ if (ret == NErr_Success)
+ return SplitSlash(track, 0, value);
+
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::YEAR:
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, value);
+ if (ret == NErr_Success || ret == NErr_EndOfEnumeration)
+ return ret;
+
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_YEAR, index, value);
+
+ case MetadataKeys::DISC:
+ {
+ ReferenceCountedNXString track;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track);
+ if (ret == NErr_Success)
+ return SplitSlash(track, value, 0);
+
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::DISCS:
+ {
+ ReferenceCountedNXString track;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track);
+ if (ret == NErr_Success)
+ return SplitSlash(track, 0, value);
+
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::COMPOSER:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_COMPOSER, index, value);
+
+ case MetadataKeys::PUBLISHER:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PUBLISHER, index, value);
+
+ case MetadataKeys::BPM:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_BPM, index, value);
+
+ case MetadataKeys::COMMENT:
+ return ID3v2_GetComments(id3v2_tag, "", index, value);
+ // TODO case MetadataKeys::PLAY_COUNT:
+ // TODO case MetadataKeys::RATING:
+
+ case MetadataKeys::TRACK_GAIN:
+ return ID3v2_GetTXXX(id3v2_tag, "replaygain_track_gain", index, value);
+
+ case MetadataKeys::TRACK_PEAK:
+ return ID3v2_GetTXXX(id3v2_tag, "replaygain_track_peak", index, value);
+
+ case MetadataKeys::ALBUM_GAIN:
+ return ID3v2_GetTXXX(id3v2_tag, "replaygain_album_gain", index, value);
+
+ case MetadataKeys::ALBUM_PEAK:
+ return ID3v2_GetTXXX(id3v2_tag, "replaygain_album_peak", index, value);
+ }
+
+ return NErr_Unknown;
+}
+
+static int IncSafe(const char *&value, size_t &value_length, size_t increment_length)
+{
+ /* eat leading spaces */
+ while (*value == ' ' && value_length)
+ {
+ value++;
+ value_length--;
+ }
+
+ if (increment_length > value_length)
+ return NErr_NeedMoreData;
+
+ value += increment_length;
+ value_length -= increment_length;
+ /* eat trailing spaces */
+ while (*value == ' ' && value_length)
+ {
+ value++;
+ value_length--;
+ }
+
+ return NErr_Success;
+}
+
+static int SplitSlashInteger(nx_string_t track, unsigned int *value1, unsigned int *value2)
+{
+ char track_utf8[64];
+ size_t bytes_copied;
+ int ret;
+ ret = NXStringGetBytes(&bytes_copied, track, track_utf8, 64, nx_charset_utf8, nx_string_get_bytes_size_null_terminate);
+ if (ret == NErr_Success)
+ {
+ size_t len = strcspn(track_utf8, "/");
+
+ if (track_utf8[len])
+ *value2 = strtoul(&track_utf8[len+1], 0, 10);
+ else
+ *value2 = 0;
+
+ track_utf8[len]=0;
+ *value1 = strtoul(track_utf8, 0, 10);
+
+ return NErr_Success;
+ }
+
+ return ret;
+}
+
+int ID3v2Metadata::Metadata_GetInteger(int field, unsigned int index, int64_t *value)
+{
+ if (!id3v2_tag)
+ return NErr_Unknown;
+
+ switch(field)
+ {
+ case MetadataKeys::TRACK:
+ {
+ ReferenceCountedNXString track;
+ int ret;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track);
+ if (ret == NErr_Success)
+ {
+ unsigned int itrack, itracks;
+ ret = SplitSlashInteger(track, &itrack, &itracks);
+ if (ret == NErr_Success)
+ {
+ if (itrack == 0)
+ return NErr_Empty;
+
+ *value = itrack;
+ return NErr_Success;
+
+ }
+ }
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::TRACKS:
+ {
+ ReferenceCountedNXString track;
+ int ret;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track);
+ if (ret == NErr_Success)
+ {
+ unsigned int itrack, itracks;
+ ret = SplitSlashInteger(track, &itrack, &itracks);
+ if (ret == NErr_Success)
+ {
+ if (itracks == 0)
+ return NErr_Empty;
+
+ *value = itracks;
+ return NErr_Success;
+ }
+ }
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::DISC:
+ {
+ ReferenceCountedNXString track;
+ int ret;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track);
+ if (ret == NErr_Success)
+ {
+ unsigned int idisc, idiscs;
+ ret = SplitSlashInteger(track, &idisc, &idiscs);
+ if (ret == NErr_Success)
+ {
+ if (idisc == 0)
+ return NErr_Empty;
+
+ *value = idisc;
+ return NErr_Success;
+
+ }
+ }
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::DISCS:
+ {
+ ReferenceCountedNXString track;
+ int ret;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track);
+ if (ret == NErr_Success)
+ {
+ unsigned int idisc, idiscs;
+ ret = SplitSlashInteger(track, &idisc, &idiscs);
+ if (ret == NErr_Success)
+ {
+ if (idiscs == 0)
+ return NErr_Empty;
+
+ *value = idiscs;
+ return NErr_Success;
+
+ }
+ }
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::BPM:
+ {
+ ReferenceCountedNXString bpm;
+ int ret;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_BPM, index, &bpm);
+ if (ret == NErr_Success)
+ {
+ /* TODO: benski> implement NXStringGetInt64Value */
+ int value32;
+ ret = NXStringGetIntegerValue(bpm, &value32);
+ if (ret != NErr_Success)
+ return ret;
+ *value = value32;
+ return NErr_Success;
+ }
+ return ret;
+ }
+ case MetadataKeys::PREGAP:
+ {
+ ReferenceCountedNXString str;
+ char language[3];
+ int ret = NSID3v2_Tag_Comments_Get(id3v2_tag, "iTunSMPB", language, &str, 0);
+ if (ret == NErr_Success)
+ {
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ const char *itunsmpb;
+ size_t itunsmpb_length;
+ char temp[64] = {0};
+ if (NXStringGetCString(str, temp, sizeof(temp)/sizeof(*temp), &itunsmpb, &itunsmpb_length) == NErr_Success)
+ {
+ /* skip first set of meaningless values */
+ if (IncSafe(itunsmpb, itunsmpb_length, 8) == NErr_Success && itunsmpb_length >= 8)
+ {
+ /* read pre-gap */
+ *value = strtoul(itunsmpb, 0, 16);
+ return NErr_Success;
+ }
+ }
+ return NErr_Error;
+ }
+ else
+ return ret;
+
+ }
+ case MetadataKeys::POSTGAP:
+ {
+ ReferenceCountedNXString str;
+ char language[3];
+ int ret = NSID3v2_Tag_Comments_Get(id3v2_tag, "iTunSMPB", language, &str, 0);
+ if (ret == NErr_Success)
+ {
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ const char *itunsmpb;
+ size_t itunsmpb_length;
+ char temp[64] = {0};
+ if (NXStringGetCString(str, temp, sizeof(temp)/sizeof(*temp), &itunsmpb, &itunsmpb_length) == NErr_Success)
+ {
+ /* two separate calls so we can skip spaces properly */
+ if (IncSafe(itunsmpb, itunsmpb_length, 8) == NErr_Success && itunsmpb_length >= 8
+ && IncSafe(itunsmpb, itunsmpb_length, 8) == NErr_Success && itunsmpb_length >= 8)
+ {
+ *value = strtoul(itunsmpb, 0, 16);
+ return NErr_Success;
+ }
+
+ }
+ return NErr_Error;
+ }
+ else
+ return ret;
+
+ }
+ }
+ return NErr_Unknown;
+}
+
+int ID3v2Metadata::Metadata_GetReal(int field, unsigned int index, double *value)
+{
+ if (!id3v2_tag)
+ return NErr_Unknown;
+
+ int ret;
+ nx_string_t str;
+ switch (field)
+ {
+ case MetadataKeys::TRACK_GAIN:
+ ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_track_gain", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ case MetadataKeys::TRACK_PEAK:
+ ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_track_peak", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ case MetadataKeys::ALBUM_GAIN:
+ ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_album_gain", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ case MetadataKeys::ALBUM_PEAK:
+ ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_album_peak", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ }
+ return NErr_Unknown;
+}
+
+static int ArtLookupType(uint8_t *id3v2_type, int metadata_key)
+{
+ switch(metadata_key)
+ {
+ case MetadataKeys::ALBUM:
+ *id3v2_type = 3;
+ return NErr_Success;
+ }
+ return NErr_Unknown;
+}
+
+static int NXStringCreateWithMIME(nx_string_t *mime_type, nx_string_t in)
+{
+ if (!mime_type)
+ return NErr_Success;
+
+ char temp[128];
+ size_t copied;
+ int ret = NXStringGetBytes(&copied, in, temp, 128, nx_charset_ascii, nx_string_get_bytes_size_null_terminate);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (strstr(temp, "/") != 0)
+ {
+ *mime_type = NXStringRetain(in);
+ return NErr_Success;
+ }
+ else
+ {
+ char temp2[128];
+#ifdef _WIN32
+ _snprintf(temp2, 127, "image/%s", temp);
+#else
+ snprintf(temp2, 127, "image/%s", temp);
+#endif
+ temp2[127]=0;
+
+ return NXStringCreateWithUTF8(mime_type, temp2);
+ }
+}
+
+int ID3v2Metadata::Metadata_GetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags)
+{
+ if (!id3v2_tag)
+ return NErr_Unknown;
+
+ uint8_t id3v2_picture_type;
+ int ret = ArtLookupType(&id3v2_picture_type, field);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (!id3v2_tag)
+ return NErr_Empty;
+
+ bool found_one=false;
+ nsid3v2_frame_t frame=0;
+ ret = NSID3v2_Tag_GetFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, &frame);
+ if (ret != NErr_Success)
+ return ret;
+
+ for (;;)
+ {
+ uint8_t this_type;
+ if (NSID3v2_Frame_Picture_Get(frame, 0, &this_type, 0, 0, 0, 0) == NErr_Success && (this_type == id3v2_picture_type || (id3v2_picture_type == 3 && this_type == 0)))
+ {
+ found_one=true;
+ if (index == 0)
+ {
+
+ if (artwork)
+ {
+ nx_data_t data=0;
+
+ if (flags != DATA_FLAG_NONE)
+ {
+ const void *picture_data;
+ size_t picture_length;
+ ReferenceCountedNXString mime_local, description;
+
+ ret = NSID3v2_Frame_Picture_Get(frame, TestFlag(flags, DATA_FLAG_MIME)?(&mime_local):0, &this_type, TestFlag(flags, DATA_FLAG_DESCRIPTION)?(&description):0, &picture_data, &picture_length, 0);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (TestFlag(flags, DATA_FLAG_DATA))
+ {
+ ret = NXDataCreate(&data, picture_data, picture_length);
+ if (ret != NErr_Success)
+ return ret;
+ }
+ else
+ {
+ ret = NXDataCreateEmpty(&data);
+ if (ret != NErr_Success)
+ return ret;
+ }
+
+ if (mime_local)
+ {
+ ReferenceCountedNXString mime_type;
+ ret = NXStringCreateWithMIME(&mime_type, mime_local);
+ if (ret != NErr_Success)
+ {
+ NXDataRelease(data);
+ return ret;
+ }
+ NXDataSetMIME(data, mime_type);
+ }
+
+ if (description)
+ {
+ NXDataSetDescription(data, description);
+ }
+ }
+ artwork->data = data;
+ /* id3v2 doesn't store height and width, so zero these */
+ artwork->width=0;
+ artwork->height=0;
+ }
+ return NErr_Success;
+ }
+ else
+ {
+ index--; // keep looking
+ }
+ }
+
+ if (NSID3v2_Tag_GetNextFrame(id3v2_tag, frame, &frame) != NErr_Success)
+ {
+ if (found_one)
+ return NErr_EndOfEnumeration;
+ else
+ return NErr_Empty;
+ }
+ }
+}
+
+static int SetText(nsid3v2_tag_t id3v2_tag, int frame_id, unsigned int index, nx_string_t value)
+{
+ if (index > 0)
+ return NErr_Success;
+
+ if (!value)
+ {
+ nsid3v2_frame_t frame;
+ if (NSID3v2_Tag_GetFrame(id3v2_tag, frame_id, &frame) == NErr_Success)
+ {
+ for(;;)
+ {
+ nsid3v2_frame_t next;
+ int ret = NSID3v2_Tag_GetNextFrame(id3v2_tag, frame, &next);
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ if (ret != NErr_Success)
+ break;
+ frame=next;
+ }
+ }
+ return NErr_Success;
+ }
+ else
+ {
+ return NSID3v2_Tag_Text_Set(id3v2_tag, frame_id, value, 0);
+ }
+}
+
+static int SetTXXX(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t value, int text_flags)
+{
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ if (!value)
+ {
+ nsid3v2_frame_t frame;
+ for(;;)
+ {
+ if (NSID3v2_Tag_TXXX_Find(id3v2_tag, description, &frame, text_flags) == NErr_Success)
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ else
+ return NErr_Success;
+ }
+ }
+ else
+ {
+ return NSID3v2_Tag_TXXX_Set(id3v2_tag, description, value, 0);
+ }
+}
+
+static int SetComments(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t value, int text_flags)
+{
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ if (!value)
+ {
+ nsid3v2_frame_t frame;
+ for(;;)
+ {
+ if (NSID3v2_Tag_Comments_Find(id3v2_tag, description, &frame, text_flags) == NErr_Success)
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ else
+ return NErr_Success;
+ }
+ }
+ else
+ {
+ return NSID3v2_Tag_Comments_Set(id3v2_tag, description, "\0\0\0", value, 0);
+ }
+}
+
+int ID3v2Metadata::MetadataEditor_SetField(int field, unsigned int index, nx_string_t value)
+{
+ int ret;
+
+ switch (field)
+ {
+ case MetadataKeys::ARTIST:
+ return SetText(id3v2_tag, NSID3V2_FRAME_LEADARTIST, index, value);
+
+ case MetadataKeys::ALBUM_ARTIST:
+ ret = SetText(id3v2_tag, NSID3V2_FRAME_BAND, index, value);
+ /* delete some of the alternates */
+ SetTXXX(id3v2_tag, "ALBUM ARTIST", index, 0, 0); /* foobar 2000 style */
+ SetTXXX(id3v2_tag, "ALBUMARTIST", index, 0, 0); /* mp3tag style */
+
+ if (!value) /* this might be a valid field, so only delete it if we're specifically deleting album artist (because otherwise, if it's here it's going to get picked up by GetField */
+ SetTXXX(id3v2_tag, "Band", index, 0, 0); /* audacity style */
+
+ return ret;
+
+ case MetadataKeys::ALBUM:
+ return SetText(id3v2_tag, NSID3V2_FRAME_ALBUM, index, value);
+
+ case MetadataKeys::TITLE:
+ return SetText(id3v2_tag, NSID3V2_FRAME_TITLE, index, value);
+
+ case MetadataKeys::GENRE:
+ return SetText(id3v2_tag, NSID3V2_FRAME_CONTENTTYPE, index, value);
+
+ case MetadataKeys::YEAR:
+ /* try to set "newer" style TDRC, first */
+ ret = SetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, value);
+ if (ret == NErr_Success)
+ {
+ /* if it succeeded, remove the older TYER tag */
+ SetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, 0);
+ return ret;
+ }
+
+ /* fall back to using TYER */
+ return SetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, 0);
+
+ case MetadataKeys::TRACK:
+ return SetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, value);
+
+ case MetadataKeys::DISC:
+ return SetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, value);
+
+ case MetadataKeys::COMPOSER:
+ return SetText(id3v2_tag, NSID3V2_FRAME_COMPOSER, index, value);
+
+ case MetadataKeys::PUBLISHER:
+ return SetText(id3v2_tag, NSID3V2_FRAME_PUBLISHER, index, value);
+
+ case MetadataKeys::BPM:
+ return SetText(id3v2_tag, NSID3V2_FRAME_BPM, index, value);
+
+ case MetadataKeys::COMMENT:
+ return SetComments(id3v2_tag, "", index, value, 0);
+
+ case MetadataKeys::TRACK_GAIN:
+ return SetTXXX(id3v2_tag, "replaygain_track_gain", index, value, 0);
+
+ case MetadataKeys::TRACK_PEAK:
+ return SetTXXX(id3v2_tag, "replaygain_track_peak", index, value, 0);
+
+ case MetadataKeys::ALBUM_GAIN:
+ return SetTXXX(id3v2_tag, "replaygain_album_gain", index, value, 0);
+
+ case MetadataKeys::ALBUM_PEAK:
+ return SetTXXX(id3v2_tag, "replaygain_album_peak", index, value, 0);
+ }
+
+ return NErr_Unknown;
+}
+
+static int ID3v2_SetPicture(nsid3v2_frame_t frame, uint8_t id3v2_picture_type, artwork_t *artwork, data_flags_t flags)
+{
+ int ret;
+
+ const void *picture_data;
+ size_t picture_length;
+ ret = NXDataGet(artwork->data, &picture_data, &picture_length);
+ if (ret != NErr_Success)
+ return ret;
+ ReferenceCountedNXString mime_type, description;
+ if (TestFlag(flags, DATA_FLAG_MIME))
+ NXDataGetMIME(artwork->data, &mime_type);
+ if (TestFlag(flags, DATA_FLAG_DESCRIPTION))
+ NXDataGetDescription(artwork->data, &description);
+ return NSID3v2_Frame_Picture_Set(frame, mime_type, id3v2_picture_type, description, picture_data, picture_length, 0);
+}
+
+int ID3v2Metadata::MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags)
+{
+ uint8_t id3v2_picture_type;
+ int ret = ArtLookupType(&id3v2_picture_type, field);
+ if (ret != NErr_Success)
+ return ret;
+
+
+ nsid3v2_frame_t frame=0;
+ ret = NSID3v2_Tag_GetFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, &frame);
+ if (ret != NErr_Success)
+ {
+ if (artwork && artwork->data)
+ {
+ /* create a new one and store */
+ int ret = NSID3v2_Tag_CreateFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, 0, &frame);
+ if (ret == NErr_Success)
+ {
+ ret = ID3v2_SetPicture(frame, id3v2_picture_type, artwork, flags);
+ if (ret == NErr_Success)
+ {
+ ret = NSID3v2_Tag_AddFrame(id3v2_tag, frame);
+ if (ret != NErr_Success)
+ {
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ }
+ }
+ }
+ return ret;
+ }
+ else
+ return NErr_Success;
+ }
+
+ for (;;)
+ {
+ /* iterate now, because we might delete the current frame */
+ nsid3v2_frame_t next_frame=0;
+ if (NSID3v2_Tag_GetNextFrame(id3v2_tag, frame, &next_frame) != NErr_Success)
+ next_frame=0; /* just in case */
+
+ uint8_t this_type;
+ if (NSID3v2_Frame_Picture_Get(frame, 0, &this_type, 0, 0, 0, 0) == NErr_Success && (this_type == id3v2_picture_type || (id3v2_picture_type == 3 && this_type == 0)))
+ {
+ if (index == 0)
+ {
+ if (artwork && artwork->data)
+ {
+ return ID3v2_SetPicture(frame, id3v2_picture_type, artwork, flags);
+ }
+ else
+ {
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ }
+ }
+ else
+ {
+ index--; // keep looking
+ }
+ }
+
+ if (!next_frame)
+ {
+ if (!artwork || !artwork->data)
+ return NErr_Success;
+ else
+ {
+ /* create a new one and store */
+ int ret = NSID3v2_Tag_CreateFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, 0, &frame);
+ if (ret != NErr_Success)
+ return ret;
+
+ ret = ID3v2_SetPicture(frame, id3v2_picture_type, artwork, flags);
+ if (ret != NErr_Success)
+ return ret;
+ ret = NSID3v2_Tag_AddFrame(id3v2_tag, frame);
+ if (ret != NErr_Success)
+ {
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ }
+ return ret;
+ }
+ }
+ frame = next_frame;
+ }
+
+ return NErr_NotImplemented;
+}