static DB_playItem_t * aac_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) { trace ("adding %s\n", fname); DB_FILE *fp = deadbeef->fopen (fname); if (!fp) { trace ("not found\n"); return NULL; } aac_info_t info = {0}; info.junk = deadbeef->junk_get_leading_size (fp); if (info.junk >= 0) { trace ("junk: %d\n", info.junk); deadbeef->fseek (fp, info.junk, SEEK_SET); } else { info.junk = 0; } const char *ftype = NULL; float duration = -1; int totalsamples = 0; int samplerate = 0; int channels = 0; int mp4track = -1; MP4FILE mp4 = NULL; if (fp->vfs->is_streaming ()) { trace ("streaming aac (%s)\n", fname); ftype = "RAW AAC"; } else { // slowwww! info.file = fp; MP4FILE_CB cb = { #ifdef USE_MP4FF .read = aac_fs_read, .write = NULL, .seek = aac_fs_seek, .truncate = NULL, .user_data = &info #else .open = aac_fs_open, .seek = aac_fs_seek, .read = aac_fs_read, .write = NULL, .close = aac_fs_close #endif }; int res = aac_probe (fp, fname, &cb, &duration, &samplerate, &channels, &totalsamples, &mp4track, &mp4); if (res == -1) { deadbeef->fclose (fp); return NULL; } else if (res == 0) { ftype = "MP4 AAC"; } else if (res == 1) { ftype = "RAW AAC"; } } DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, plugin.plugin.id); deadbeef->pl_add_meta (it, ":FILETYPE", ftype); deadbeef->plt_set_item_duration (plt, it, duration); trace ("duration: %f sec\n", duration); // read tags if (mp4) { #ifdef USE_MP4FF aac_load_tags (it, mp4); mp4ff_close (mp4); #else const MP4Tags *tags = MP4TagsAlloc (); MP4TagsFetch (tags, mp4); deadbeef->pl_add_meta (it, "title", tags->name); deadbeef->pl_add_meta (it, "artist", tags->artist); deadbeef->pl_add_meta (it, "albumArtist", tags->albumArtist); deadbeef->pl_add_meta (it, "album", tags->album); deadbeef->pl_add_meta (it, "composer", tags->composer); deadbeef->pl_add_meta (it, "comments", tags->comments); deadbeef->pl_add_meta (it, "genre", tags->genre); deadbeef->pl_add_meta (it, "year", tags->releaseDate); char s[10]; if (tags->track) { snprintf (s, sizeof (s), "%d", tags->track->index); deadbeef->pl_add_meta (it, "track", s); snprintf (s, sizeof (s), "%d", tags->track->total); deadbeef->pl_add_meta (it, "numtracks", s); } if (tags->disk) { snprintf (s, sizeof (s), "%d", tags->disk->index); deadbeef->pl_add_meta (it, "disc", s); snprintf (s, sizeof (s), "%d", tags->disk->total); deadbeef->pl_add_meta (it, "numdiscs", s); } deadbeef->pl_add_meta (it, "copyright", tags->copyright); deadbeef->pl_add_meta (it, "vendor", tags->encodedBy); deadbeef->pl_add_meta (it, "title", NULL); MP4TagsFree (tags); MP4Close (mp4); #endif } int apeerr = deadbeef->junk_apev2_read (it, fp); int v2err = deadbeef->junk_id3v2_read (it, fp); int v1err = deadbeef->junk_id3v1_read (it, fp); deadbeef->pl_add_meta (it, "title", NULL); int64_t fsize = deadbeef->fgetlength (fp); deadbeef->fclose (fp); if (duration > 0) { char s[100]; snprintf (s, sizeof (s), "%lld", fsize); deadbeef->pl_add_meta (it, ":FILE_SIZE", s); deadbeef->pl_add_meta (it, ":BPS", "16"); snprintf (s, sizeof (s), "%d", channels); deadbeef->pl_add_meta (it, ":CHANNELS", s); snprintf (s, sizeof (s), "%d", samplerate); deadbeef->pl_add_meta (it, ":SAMPLERATE", s); int br = (int)roundf(fsize / duration * 8 / 1000); snprintf (s, sizeof (s), "%d", br); deadbeef->pl_add_meta (it, ":BITRATE", s); // embedded cue deadbeef->pl_lock (); const char *cuesheet = deadbeef->pl_find_meta (it, "cuesheet"); DB_playItem_t *cue = NULL; if (cuesheet) { cue = deadbeef->plt_insert_cue_from_buffer (plt, after, it, cuesheet, strlen (cuesheet), totalsamples, samplerate); if (cue) { deadbeef->pl_item_unref (it); deadbeef->pl_item_unref (cue); deadbeef->pl_unlock (); return cue; } } deadbeef->pl_unlock (); cue = deadbeef->plt_insert_cue (plt, after, it, totalsamples, samplerate); if (cue) { deadbeef->pl_item_unref (it); deadbeef->pl_item_unref (cue); return cue; } } deadbeef->pl_add_meta (it, "title", NULL); after = deadbeef->plt_insert_item (plt, after, it); deadbeef->pl_item_unref (it); return after; } static const char * exts[] = { "aac", "mp4", "m4a", NULL }; // define plugin interface static DB_decoder_t plugin = { .plugin.api_vmajor = 1, .plugin.api_vminor = 0, .plugin.version_major = 1, .plugin.version_minor = 0, .plugin.type = DB_PLUGIN_DECODER, .plugin.id = "aac", .plugin.name = "AAC player", .plugin.descr = "plays aac files, supports raw aac files, as well as mp4 container", .plugin.copyright = "Copyright (C) 2009-2012 Alexey Yakovenko <*****@*****.**>\n" "\n" "Uses modified libmp4ff (C) 2003-2005 M. Bakker, Nero AG, http://www.nero.com\n" "\n" "This program is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU General Public License\n" "as published by the Free Software Foundation; either version 2\n" "of the License, or (at your option) any later version.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software\n" "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n" , .plugin.website = "http://deadbeef.sf.net", .open = aac_open, .init = aac_init, .free = aac_free, .read = aac_read, .seek = aac_seek, .seek_sample = aac_seek_sample, .insert = aac_insert, .read_metadata = aac_read_metadata, #ifdef USE_MP4FF // mp4ff metadata writer doesn't work // .write_metadata = aac_write_metadata, #else #endif .exts = exts, }; DB_plugin_t * aac_load (DB_functions_t *api) { deadbeef = api; return DB_PLUGIN (&plugin); }
DWORD CTag_Mp4::Save(LPCTSTR szFileName) { DWORD dwWin32errorCode = ERROR_SUCCESS; if(!m_bEnable) { return -1; } char *pFileName = TstrToDataAlloc(szFileName, -1, NULL, DTC_CODE_UTF8); if (pFileName == NULL) { return -1; } MP4FileHandle mp4file = MP4Modify(pFileName); free(pFileName); if(mp4file == MP4_INVALID_FILE_HANDLE) { return -1; } #ifdef USE_OLD_TAG_API if(m_strMetadata_Name.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Name, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataName(mp4file,buf); free(buf); } } else { MP4DeleteMetadataName(mp4file); } if(m_strMetadata_Artist.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Artist, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataArtist(mp4file,buf); free(buf); } } else { MP4DeleteMetadataArtist(mp4file); } if(m_strMetadata_Album.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Album, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataAlbum(mp4file,buf); free(buf); } } else { MP4DeleteMetadataAlbum(mp4file); } if(m_strMetadata_Group.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Group, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataGrouping(mp4file,buf); free(buf); } } else { MP4DeleteMetadataGrouping(mp4file); } if(m_strMetadata_Composer.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Composer, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataWriter(mp4file,buf); free(buf); } } else { MP4DeleteMetadataWriter(mp4file); } if(m_strMetadata_Genre.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Genre, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataGenre(mp4file,buf); free(buf); } } else { MP4DeleteMetadataGenre(mp4file); } if((m_iMetadata_Track1 == -1) && (m_iMetadata_Track2 == -1)) { MP4DeleteMetadataTrack(mp4file); } else if(m_iMetadata_Track1 == -1) { MP4SetMetadataTrack(mp4file,0,m_iMetadata_Track2); } else if(m_iMetadata_Track2 == -1) { MP4SetMetadataTrack(mp4file,m_iMetadata_Track1,0); } else { MP4SetMetadataTrack(mp4file,m_iMetadata_Track1,m_iMetadata_Track2); } if((m_iMetadata_Disc1 == -1) && (m_iMetadata_Disc2 == -1)) { MP4DeleteMetadataDisk(mp4file); } else if(m_iMetadata_Disc1 == -1) { MP4SetMetadataDisk(mp4file,0,m_iMetadata_Disc2); } else if(m_iMetadata_Disc2 == -1) { MP4SetMetadataDisk(mp4file,m_iMetadata_Disc1,0); } else { MP4SetMetadataDisk(mp4file,m_iMetadata_Disc1,m_iMetadata_Disc2); } if(m_iMetadata_Tempo == -1) { MP4DeleteMetadataTempo(mp4file); } else { MP4SetMetadataTempo(mp4file,m_iMetadata_Tempo); } if(m_strMetadata_Year.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Year, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataYear(mp4file,buf); free(buf); } } else { MP4DeleteMetadataYear(mp4file); } if(m_iMetadata_Compilation != -1) { MP4SetMetadataCompilation(mp4file,1); } else { MP4DeleteMetadataCompilation(mp4file); } if(m_strMetadata_Comment.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Comment, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataComment(mp4file,buf); free(buf); } } else { MP4DeleteMetadataComment(mp4file); } if(m_strMetadata_Tool.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Tool, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4SetMetadataTool(mp4file,buf); free(buf); } } else { MP4DeleteMetadataTool(mp4file); } #else const MP4Tags* tags = MP4TagsAlloc(); if(tags) { MP4TagsFetch(tags, mp4file); if(m_strMetadata_Name.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Name, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetName(tags, buf); free(buf); } } else { MP4TagsSetName(tags, NULL); } if(m_strMetadata_Artist.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Artist, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetArtist(tags, buf); free(buf); } } else { MP4TagsSetArtist(tags, NULL); } if(m_strMetadata_Album.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Album, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetAlbum(tags, buf); free(buf); } } else { MP4TagsSetAlbum(tags, NULL); } if(m_strMetadata_Group.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Group, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetGrouping(tags, buf); free(buf); } } else { MP4TagsSetGrouping(tags, NULL); } if(m_strMetadata_Composer.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Composer, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetComposer(tags, buf); free(buf); } } else { MP4TagsSetComposer(tags, NULL); } if(m_strMetadata_Genre.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Genre, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetGenre(tags, buf); free(buf); } } else { MP4TagsSetGenre(tags, NULL); } if((m_iMetadata_Track1 == -1) && (m_iMetadata_Track2 == -1)) { MP4TagsSetTrack(tags, NULL); } else { MP4TagTrack track; track.index = (m_iMetadata_Track1 == -1) ? 0 : m_iMetadata_Track1; track.total = (m_iMetadata_Track2 == -1) ? 0 : m_iMetadata_Track2; MP4TagsSetTrack(tags, &track); } if((m_iMetadata_Disc1 == -1) && (m_iMetadata_Disc2 == -1)) { MP4TagsSetDisk(tags, NULL); } else { MP4TagDisk disk; disk.index = (m_iMetadata_Disc1 == -1) ? 0 : m_iMetadata_Disc1; disk.total = (m_iMetadata_Disc2 == -1) ? 0 : m_iMetadata_Disc2; MP4TagsSetDisk(tags, &disk); } if(m_iMetadata_Tempo == -1) { MP4TagsSetTempo(tags, NULL); } else { uint16_t tempo = m_iMetadata_Tempo; MP4TagsSetTempo(tags, &tempo); } if(m_strMetadata_Year.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Year, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetReleaseDate(tags, buf); free(buf); } } else { MP4TagsSetReleaseDate(tags, NULL); } if(m_iMetadata_Compilation != -1) { uint8_t compilation = 1; MP4TagsSetCompilation(tags, &compilation); } else { MP4TagsSetCompilation(tags, NULL); } if(m_strMetadata_Comment.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Comment, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetComments(tags, buf); free(buf); } } else { MP4TagsSetComments(tags, NULL); } if(m_strMetadata_Tool.GetLength()) { char *buf = TstrToDataAlloc(m_strMetadata_Tool, -1, NULL, DTC_CODE_UTF8); if (buf != NULL) { MP4TagsSetEncodingTool(tags, buf); free(buf); } } else { MP4TagsSetEncodingTool(tags, NULL); } MP4TagsStore(tags, mp4file); MP4TagsFree(tags); } #endif MP4Close(mp4file); return dwWin32errorCode; }
bool MP4Metadata::ReadMetadata(CFErrorRef *error) { // Start from scratch CFDictionaryRemoveAllValues(mMetadata); CFDictionaryRemoveAllValues(mChangedMetadata); UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, FALSE, buf, PATH_MAX)) return false; // Open the file for reading MP4FileHandle file = MP4Read(reinterpret_cast<const char *>(buf)); if(MP4_INVALID_FILE_HANDLE == file) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG-4 file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an MPEG-4 file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataFileFormatNotRecognizedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } // Read the properties if(0 < MP4GetNumberOfTracks(file)) { // Should be type 'soun', media data name'mp4a' MP4TrackId trackID = MP4FindTrackId(file, 0); // Verify this is an MPEG-4 audio file if(MP4_INVALID_TRACK_ID == trackID || strncmp("soun", MP4GetTrackType(file, trackID), 4)) { MP4Close(file), file = NULL; if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG-4 file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an MPEG-4 file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataFileFormatNotSupportedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } MP4Duration mp4Duration = MP4GetTrackDuration(file, trackID); uint32_t mp4TimeScale = MP4GetTrackTimeScale(file, trackID); CFNumberRef totalFrames = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &mp4Duration); CFDictionarySetValue(mMetadata, kPropertiesTotalFramesKey, totalFrames); CFRelease(totalFrames), totalFrames = NULL; CFNumberRef sampleRate = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &mp4TimeScale); CFDictionarySetValue(mMetadata, kPropertiesSampleRateKey, sampleRate); CFRelease(sampleRate), sampleRate = NULL; double length = static_cast<double>(mp4Duration / mp4TimeScale); CFNumberRef duration = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &length); CFDictionarySetValue(mMetadata, kPropertiesDurationKey, duration); CFRelease(duration), duration = NULL; // "mdia.minf.stbl.stsd.*[0].channels" int channels = MP4GetTrackAudioChannels(file, trackID); CFNumberRef channelsPerFrame = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &channels); CFDictionaryAddValue(mMetadata, kPropertiesChannelsPerFrameKey, channelsPerFrame); CFRelease(channelsPerFrame), channelsPerFrame = NULL; // ALAC files if(MP4HaveTrackAtom(file, trackID, "mdia.minf.stbl.stsd.alac")) { CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("Apple Lossless")); uint64_t sampleSize; uint8_t *decoderConfig; uint32_t decoderConfigSize; if(MP4GetTrackBytesProperty(file, trackID, "mdia.minf.stbl.stsd.alac.alac.decoderConfig", &decoderConfig, &decoderConfigSize) && 28 <= decoderConfigSize) { // The ALAC magic cookie seems to have the following layout (28 bytes, BE): // Byte 10: Sample size // Bytes 25-28: Sample rate CFNumberRef bitsPerChannel = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, decoderConfig + 9); CFDictionaryAddValue(mMetadata, kPropertiesBitsPerChannelKey, bitsPerChannel); CFRelease(bitsPerChannel), bitsPerChannel = NULL; double losslessBitrate = static_cast<double>(mp4TimeScale * channels * sampleSize) / 1000; CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &losslessBitrate); CFDictionarySetValue(mMetadata, kPropertiesBitrateKey, bitrate); CFRelease(bitrate), bitrate = NULL; free(decoderConfig), decoderConfig = NULL; } else if(MP4GetTrackIntegerProperty(file, trackID, "mdia.minf.stbl.stsd.alac.sampleSize", &sampleSize)) { CFNumberRef bitsPerChannel = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &sampleSize); CFDictionaryAddValue(mMetadata, kPropertiesBitsPerChannelKey, bitsPerChannel); CFRelease(bitsPerChannel), bitsPerChannel = NULL; double losslessBitrate = static_cast<double>(mp4TimeScale * channels * sampleSize) / 1000; CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &losslessBitrate); CFDictionarySetValue(mMetadata, kPropertiesBitrateKey, bitrate); CFRelease(bitrate), bitrate = NULL; } } // AAC files if(MP4HaveTrackAtom(file, trackID, "mdia.minf.stbl.stsd.mp4a")) { CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("AAC")); // "mdia.minf.stbl.stsd.*.esds.decConfigDescr.avgBitrate" uint32_t trackBitrate = MP4GetTrackBitRate(file, trackID) / 1000; CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &trackBitrate); CFDictionaryAddValue(mMetadata, kPropertiesBitrateKey, bitrate); CFRelease(bitrate), bitrate = NULL; } } // No valid tracks in file else { MP4Close(file), file = NULL; if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG-4 file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an MPEG-4 file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataFileFormatNotSupportedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } // Read the tags const MP4Tags *tags = MP4TagsAlloc(); if(NULL == tags) { MP4Close(file), file = NULL; if(error) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, NULL); return false; } MP4TagsFetch(tags, file); // Album title if(tags->album) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->album, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataAlbumTitleKey, str); CFRelease(str), str = NULL; } // Artist if(tags->artist) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->artist, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataArtistKey, str); CFRelease(str), str = NULL; } // Album Artist if(tags->albumArtist) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->albumArtist, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataAlbumArtistKey, str); CFRelease(str), str = NULL; } // Genre if(tags->genre) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->genre, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataGenreKey, str); CFRelease(str), str = NULL; } // Release date if(tags->releaseDate) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->releaseDate, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataReleaseDateKey, str); CFRelease(str), str = NULL; } // Composer if(tags->composer) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->composer, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataComposerKey, str); CFRelease(str), str = NULL; } // Comment if(tags->comments) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->comments, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataCommentKey, str); CFRelease(str), str = NULL; } // Track title if(tags->name) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->name, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataTitleKey, str); CFRelease(str), str = NULL; } // Track number if(tags->track) { if(tags->track->index) { CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->track->index); CFDictionarySetValue(mMetadata, kMetadataTrackNumberKey, num); CFRelease(num), num = NULL; } if(tags->track->total) { CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->track->total); CFDictionarySetValue(mMetadata, kMetadataTrackTotalKey, num); CFRelease(num), num = NULL; } } // Disc number if(tags->disk) { if(tags->disk->index) { CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->disk->index); CFDictionarySetValue(mMetadata, kMetadataDiscNumberKey, num); CFRelease(num), num = NULL; } if(tags->disk->total) { CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->disk->total); CFDictionarySetValue(mMetadata, kMetadataDiscTotalKey, num); CFRelease(num), num = NULL; } } // Compilation if(tags->compilation) CFDictionarySetValue(mMetadata, kMetadataCompilationKey, *(tags->compilation) ? kCFBooleanTrue : kCFBooleanFalse); // BPM if(tags->tempo) { } // Lyrics if(tags->lyrics) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->lyrics, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataLyricsKey, str); CFRelease(str), str = NULL; } // Album art if(tags->artworkCount) { for(uint32_t i = 0; i < tags->artworkCount; ++i) { CFDataRef data = CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(tags->artwork[i].data), tags->artwork[i].size); CFDictionarySetValue(mMetadata, kAlbumArtFrontCoverKey, data); CFRelease(data), data = NULL; } } // ReplayGain // Reference loudness MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_reference_loudness"); if(NULL != items) { float referenceLoudnessValue; if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &referenceLoudnessValue)) { CFNumberRef referenceLoudness = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &referenceLoudnessValue); CFDictionaryAddValue(mMetadata, kReplayGainReferenceLoudnessKey, referenceLoudness); CFRelease(referenceLoudness), referenceLoudness = NULL; } MP4ItmfItemListFree(items), items = NULL; } // Track gain items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_gain"); if(NULL != items) { float trackGainValue; if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &trackGainValue)) { CFNumberRef trackGain = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &trackGainValue); CFDictionaryAddValue(mMetadata, kReplayGainTrackGainKey, trackGain); CFRelease(trackGain), trackGain = NULL; } MP4ItmfItemListFree(items), items = NULL; } // Track peak items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_peak"); if(NULL != items) { float trackPeakValue; if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &trackPeakValue)) { CFNumberRef trackPeak = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &trackPeakValue); CFDictionaryAddValue(mMetadata, kReplayGainTrackPeakKey, trackPeak); CFRelease(trackPeak), trackPeak = NULL; } MP4ItmfItemListFree(items), items = NULL; } // Album gain items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_gain"); if(NULL != items) { float albumGainValue; if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &albumGainValue)) { CFNumberRef albumGain = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &albumGainValue); CFDictionaryAddValue(mMetadata, kReplayGainAlbumGainKey, albumGain); CFRelease(albumGain), albumGain = NULL; } MP4ItmfItemListFree(items), items = NULL; } // Album peak items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_peak"); if(NULL != items) { float albumPeakValue; if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &albumPeakValue)) { CFNumberRef albumPeak = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &albumPeakValue); CFDictionaryAddValue(mMetadata, kReplayGainAlbumPeakKey, albumPeak); CFRelease(albumPeak), albumPeak = NULL; } MP4ItmfItemListFree(items), items = NULL; } // Clean up MP4TagsFree(tags), tags = NULL; MP4Close(file), file = NULL; return true; }
DWORD CTag_Mp4::Load(LPCTSTR szFileName) { DWORD dwWin32errorCode = ERROR_SUCCESS; Release(); char *pFileName = TstrToDataAlloc(szFileName, -1, NULL, DTC_CODE_UTF8); if (pFileName == NULL) { return -1; } char *info = MP4FileInfo(pFileName); if(!info) { free(pFileName); Release(); return -1; } m_strTrackInfo = info; free(info); m_strTrackInfo.Replace(_T("\n"),_T("\r\n")); // Audio/Video _StripAudioInfo(m_strTrackInfo,m_strAudioInfo,m_strVideoInfo); MP4FileHandle mp4file = MP4Read(pFileName); free(pFileName); if(mp4file == MP4_INVALID_FILE_HANDLE) { Release(); return -1; } m_bEnable = TRUE; char *value; uint16_t numvalue, numvalue2; uint8_t int8value; #ifdef USE_OLD_TAG_API if(MP4GetMetadataName(mp4file,&value) && (value != NULL)) { m_strMetadata_Name = _CnvMetadata(value); } if(MP4GetMetadataArtist(mp4file,&value) && (value != NULL)) { m_strMetadata_Artist = _CnvMetadata(value); } if(MP4GetMetadataAlbum(mp4file,&value) && (value != NULL)) { m_strMetadata_Album = _CnvMetadata(value); } if(MP4GetMetadataGrouping(mp4file,&value) && (value != NULL)) { m_strMetadata_Group = _CnvMetadata(value); } if(MP4GetMetadataWriter(mp4file,&value) && (value != NULL)) { m_strMetadata_Composer = _CnvMetadata(value); } if(MP4GetMetadataGenre(mp4file,&value) && (value != NULL)) { m_strMetadata_Genre = _CnvMetadata(value); } if(MP4GetMetadataTrack(mp4file,&numvalue,&numvalue2)) { m_iMetadata_Track1 = numvalue; m_iMetadata_Track2 = numvalue2; } if(MP4GetMetadataDisk(mp4file,&numvalue,&numvalue2)) { m_iMetadata_Disc1 = numvalue; m_iMetadata_Disc2 = numvalue2; } if(MP4GetMetadataTempo(mp4file,&numvalue)) { m_iMetadata_Tempo = numvalue; } if(MP4GetMetadataYear(mp4file,&value) && (value != NULL)) { m_strMetadata_Year = _CnvMetadata(value); } if(MP4GetMetadataCompilation(mp4file,&int8value)) { m_iMetadata_Compilation = int8value; } if(MP4GetMetadataComment(mp4file,&value) && (value != NULL)) { m_strMetadata_Comment = _CnvMetadata(value); } if(MP4GetMetadataTool(mp4file,&value) && (value != NULL)) { m_strMetadata_Tool = _CnvMetadata(value); } #else const MP4Tags* tags = MP4TagsAlloc(); if(tags) { MP4TagsFetch(tags, mp4file); if(tags->name) { m_strMetadata_Name = _CnvMetadata(tags->name); } if(tags->artist) { m_strMetadata_Artist = _CnvMetadata(tags->artist); } if(tags->album) { m_strMetadata_Album = _CnvMetadata(tags->album); } if(tags->grouping) { m_strMetadata_Group = _CnvMetadata(tags->grouping); } if(tags->composer) { m_strMetadata_Composer = _CnvMetadata(tags->composer); } if(tags->genre) { m_strMetadata_Genre = _CnvMetadata(tags->genre); } if(tags->track) { m_iMetadata_Track1 = tags->track->index; m_iMetadata_Track2 = tags->track->total; } if(tags->disk) { m_iMetadata_Disc1 = tags->disk->index; m_iMetadata_Disc2 = tags->disk->total; } if(tags->tempo) { m_iMetadata_Tempo = *tags->tempo; } if(tags->releaseDate) { m_strMetadata_Year = _CnvMetadata(tags->releaseDate); } if(tags->compilation) { m_iMetadata_Compilation = *tags->compilation; } if(tags->comments) { m_strMetadata_Comment = _CnvMetadata(tags->comments); } if(tags->encodingTool) { m_strMetadata_Tool = _CnvMetadata(tags->encodingTool); } MP4TagsFree(tags); } #endif MP4Close(mp4file); return dwWin32errorCode; }
bool MP4Metadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; // Open the file for modification MP4FileHandle file = MP4Modify(reinterpret_cast<const char *>(buf)); if(MP4_INVALID_FILE_HANDLE == file) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG-4 file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an MPEG file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } // Read the tags const MP4Tags *tags = MP4TagsAlloc(); if(NULL == tags) { MP4Close(file), file = NULL; if(error) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, NULL); return false; } MP4TagsFetch(tags, file); // Album Title CFStringRef str = GetAlbumTitle(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetAlbum(tags, cString); } else MP4TagsSetAlbum(tags, NULL); // Artist str = GetArtist(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetArtist(tags, cString); } else MP4TagsSetArtist(tags, NULL); // Album Artist str = GetAlbumArtist(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetAlbumArtist(tags, cString); } else MP4TagsSetAlbumArtist(tags, NULL); // Genre str = GetGenre(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetGenre(tags, cString); } else MP4TagsSetGenre(tags, NULL); // Release date str = GetReleaseDate(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetReleaseDate(tags, cString); } else MP4TagsSetReleaseDate(tags, NULL); // Composer str = GetComposer(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetComposer(tags, cString); } else MP4TagsSetComposer(tags, NULL); // Comment str = GetComment(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetComments(tags, cString); } else MP4TagsSetComments(tags, NULL); // Track title str = GetTitle(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetName(tags, cString); } else MP4TagsSetName(tags, NULL); // Track number and total MP4TagTrack trackInfo; memset(&trackInfo, 0, sizeof(MP4TagTrack)); if(GetTrackNumber()) CFNumberGetValue(GetTrackNumber(), kCFNumberSInt32Type, &trackInfo.index); if(GetTrackTotal()) CFNumberGetValue(GetTrackTotal(), kCFNumberSInt32Type, &trackInfo.total); MP4TagsSetTrack(tags, &trackInfo); // Disc number and total MP4TagDisk discInfo; memset(&discInfo, 0, sizeof(MP4TagDisk)); if(GetDiscNumber()) CFNumberGetValue(GetDiscNumber(), kCFNumberSInt32Type, &discInfo.index); if(GetDiscTotal()) CFNumberGetValue(GetDiscTotal(), kCFNumberSInt32Type, &discInfo.total); MP4TagsSetDisk(tags, &discInfo); // Compilation if(GetCompilation()) { uint8_t comp = CFBooleanGetValue(GetCompilation()); MP4TagsSetCompilation(tags, &comp); } else MP4TagsSetCompilation(tags, NULL); // Lyrics str = GetLyrics(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetLyrics(tags, cString); } else MP4TagsSetLyrics(tags, NULL); // Album art CFDataRef artData = GetFrontCoverArt(); if(artData) { MP4TagArtwork artwork; artwork.data = reinterpret_cast<void *>(const_cast<UInt8 *>(CFDataGetBytePtr(artData))); artwork.size = static_cast<uint32_t>(CFDataGetLength(artData)); artwork.type = MP4_ART_UNDEFINED; MP4TagsAddArtwork(tags, &artwork); } // Save our changes MP4TagsStore(tags, file); // Replay Gain // Reference loudness if(GetReplayGainReferenceLoudness()) { float f; if(!CFNumberGetValue(GetReplayGainReferenceLoudness(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } char value [8]; snprintf(value, sizeof(value), "%2.1f dB", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_reference_loudness"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_reference_loudness"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Track gain if(GetReplayGainTrackGain()) { float f; if(!CFNumberGetValue(GetReplayGainTrackGain(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [10]; snprintf(value, sizeof(value), "%+2.2f dB", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_track_gain"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_gain"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Track peak if(GetReplayGainTrackPeak()) { float f; if(!CFNumberGetValue(GetReplayGainTrackPeak(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [12]; snprintf(value, sizeof(value), "%1.8f", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_track_peak"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_peak"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Album gain if(GetReplayGainAlbumGain()) { float f; if(!CFNumberGetValue(GetReplayGainAlbumGain(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [10]; snprintf(value, sizeof(value), "%+2.2f dB", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_album_gain"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_gain"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Album peak if(GetReplayGainAlbumPeak()) { float f; if(!CFNumberGetValue(GetReplayGainAlbumPeak(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [12]; snprintf(value, sizeof(value), "%1.8f", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_album_peak"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_peak"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Clean up MP4TagsFree(tags), tags = NULL; MP4Close(file), file = NULL; MergeChangedMetadataIntoMetadata(); return true; }