Esempio n. 1
0
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);
}
Esempio n. 2
0
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;
}
Esempio n. 4
0
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;
}