diff options
Diffstat (limited to 'Src/replicant/nswasabi/ID3v2Metadata.cpp')
-rw-r--r-- | Src/replicant/nswasabi/ID3v2Metadata.cpp | 1087 |
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; +} |