#include "ID3v2Metadata.h" #include "metadata/MetadataKeys.h" #include "nswasabi/ReferenceCounted.h" #include #include 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; }