bool WavPackMetadata::ReadMetadata(CFErrorRef *error)
{
	// Start from scratch
	CFDictionaryRemoveAllValues(mMetadata);
	CFDictionaryRemoveAllValues(mChangedMetadata);

	UInt8 buf [PATH_MAX];
	if(!CFURLGetFileSystemRepresentation(mURL, FALSE, buf, PATH_MAX))
		return false;

	auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true);
	TagLib::WavPack::File file(stream);

	if(!file.isValid()) {
		if(nullptr != error) {
			CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid WavPack file."), "");
			CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a WavPack file"), "");
			CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "");
			
			*error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion);
			
			CFRelease(description), description = nullptr;
			CFRelease(failureReason), failureReason = nullptr;
			CFRelease(recoverySuggestion), recoverySuggestion = nullptr;
		}

		return false;
	}
	
	CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("WavPack"));
	
	if(file.audioProperties()) {
		auto properties = file.audioProperties();
		AddAudioPropertiesToDictionary(mMetadata, properties);
		
		if(properties->bitsPerSample())
			AddIntToDictionary(mMetadata, kPropertiesBitsPerChannelKey, properties->bitsPerSample());
		if(properties->sampleFrames())
			AddIntToDictionary(mMetadata, kPropertiesTotalFramesKey, properties->sampleFrames());
	}
	
	if(file.ID3v1Tag())
		AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag());

	if(file.APETag()) {
		std::vector<AttachedPicture *> pictures;
		AddAPETagToDictionary(mMetadata, pictures, file.APETag());
		for(auto picture : pictures)
			AddSavedPicture(picture);
	}

	return true;
}
bool SFB::Audio::TrueAudioMetadata::_ReadMetadata(CFErrorRef *error)
{
    UInt8 buf [PATH_MAX];
    if(!CFURLGetFileSystemRepresentation(mURL, FALSE, buf, PATH_MAX))
        return false;

    std::unique_ptr<TagLib::FileStream> stream(new TagLib::FileStream((const char *)buf, true));
    if(!stream->isOpen()) {
        if(error) {
            SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” could not be opened for reading."), "");
            SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Input/output error"), "");
            SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file may have been renamed, moved, deleted, or you may not have appropriate permissions."), "");

            *error = CreateErrorForURL(Metadata::ErrorDomain, Metadata::InputOutputError, description, mURL, failureReason, recoverySuggestion);
        }

        return false;
    }

    TagLib::TrueAudio::File file(stream.get());
    if(!file.isValid()) {
        if(nullptr != error) {
            SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid True Audio file."), "");
            SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Not a True Audio file"), "");
            SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "");

            *error = CreateErrorForURL(Metadata::ErrorDomain, Metadata::InputOutputError, description, mURL, failureReason, recoverySuggestion);
        }

        return false;
    }

    CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("True Audio"));

    if(file.audioProperties()) {
        auto properties = file.audioProperties();
        AddAudioPropertiesToDictionary(mMetadata, properties);

        if(properties->bitsPerSample())
            AddIntToDictionary(mMetadata, kBitsPerChannelKey, properties->bitsPerSample());
        if(properties->sampleFrames())
            AddIntToDictionary(mMetadata, kTotalFramesKey, (int)properties->sampleFrames());
    }

    // Add all tags that are present
    if(file.ID3v1Tag())
        AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag());

    if(file.ID3v2Tag())
        AddID3v2TagToDictionary(mMetadata, mPictures, file.ID3v2Tag());

    return true;
}
bool SFB::Audio::AddAudioPropertiesToDictionary(CFMutableDictionaryRef dictionary, const TagLib::AudioProperties *properties)
{
	if(nullptr == dictionary || nullptr == properties)
		return false;
	
	if(properties->length())
		AddIntToDictionary(dictionary, Metadata::kDurationKey, properties->length());

	if(properties->channels())
		AddIntToDictionary(dictionary, Metadata::kChannelsPerFrameKey, properties->channels());

	if(properties->sampleRate())
		AddIntToDictionary(dictionary, Metadata::kSampleRateKey, properties->sampleRate());

	if(properties->bitrate())
		AddIntToDictionary(dictionary, Metadata::kBitrateKey, properties->bitrate());
	
	return true;
}
Exemplo n.º 4
0
SFB::Audio::AttachedPicture::AttachedPicture(CFDataRef data, AttachedPicture::Type type, CFStringRef description)
	: mMetadata(0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks), mChangedMetadata(0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks), mState(ChangeState::Saved)
{
	if(data)
		CFDictionarySetValue(mMetadata, kDataKey, data);

	AddIntToDictionary(mMetadata, kTypeKey, (int)type);

	if(description)
		CFDictionarySetValue(mMetadata, kDescriptionKey, description);
}
Exemplo n.º 5
0
SFB::Audio::AttachedPicture::AttachedPicture(CFDataRef data, AttachedPicture::Type type, CFStringRef description)
	: mState(ChangeState::Saved)
{
	mMetadata = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	mChangedMetadata = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

	if(data)
		CFDictionarySetValue(mMetadata, kDataKey, data);

	AddIntToDictionary(mMetadata, kTypeKey, (int)type);

	if(description)
		CFDictionarySetValue(mMetadata, kDescriptionKey, description);
}
bool SFB::Audio::AddTagToDictionary(CFMutableDictionaryRef dictionary, const TagLib::Tag *tag)
{
	if(nullptr == dictionary || nullptr == tag)
		return false;

	TagLib::AddStringToCFDictionary(dictionary, Metadata::kTitleKey, tag->title());
	TagLib::AddStringToCFDictionary(dictionary, Metadata::kAlbumTitleKey, tag->album());
	TagLib::AddStringToCFDictionary(dictionary, Metadata::kArtistKey, tag->artist());
	TagLib::AddStringToCFDictionary(dictionary, Metadata::kGenreKey, tag->genre());

	if(tag->year())
		AddIntToDictionaryAsString(dictionary, Metadata::kReleaseDateKey, (int)tag->year());

	if(tag->track())
		AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, (int)tag->track());

	TagLib::AddStringToCFDictionary(dictionary, Metadata::kCommentKey, tag->comment());

	return true;
}
bool TrueAudioMetadata::ReadMetadata(CFErrorRef *error)
{
	// Start from scratch
	CFDictionaryRemoveAllValues(mMetadata);
	CFDictionaryRemoveAllValues(mChangedMetadata);
	
	UInt8 buf [PATH_MAX];
	if(!CFURLGetFileSystemRepresentation(mURL, FALSE, buf, PATH_MAX))
		return false;
	
	// TODO: Use unique_ptr once the switch to C++11 STL is made
	std::auto_ptr<TagLib::FileStream> stream(new TagLib::FileStream(reinterpret_cast<const char *>(buf), true));
	if(!stream->isOpen()) {
		if(error) {
			CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” could not be opened for reading."), "");
			CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Input/output error"), "");
			CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file may have been renamed, moved, deleted, or you may not have appropriate permissions."), "");

			*error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion);

			CFRelease(description), description = nullptr;
			CFRelease(failureReason), failureReason = nullptr;
			CFRelease(recoverySuggestion), recoverySuggestion = nullptr;
		}

		return false;
	}

	TagLib::TrueAudio::File file(stream.get());
	if(!file.isValid()) {
		if(nullptr != error) {
			CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid True Audio file."), "");
			CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a True Audio file"), "");
			CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "");
			
			*error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion);
			
			CFRelease(description), description = nullptr;
			CFRelease(failureReason), failureReason = nullptr;
			CFRelease(recoverySuggestion), recoverySuggestion = nullptr;
		}

		return false;
	}
	
	CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("True Audio"));

	if(file.audioProperties()) {
		auto properties = file.audioProperties();
		AddAudioPropertiesToDictionary(mMetadata, properties);

		if(properties->bitsPerSample())
			AddIntToDictionary(mMetadata, kPropertiesBitsPerChannelKey, properties->bitsPerSample());
		if(properties->sampleFrames())
			AddIntToDictionary(mMetadata, kPropertiesTotalFramesKey, properties->sampleFrames());
	}

	// Add all tags that are present
	if(file.ID3v1Tag())
		AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag());

	if(file.ID3v2Tag()) {
		std::vector<AttachedPicture *> pictures;
		AddID3v2TagToDictionary(mMetadata, pictures, file.ID3v2Tag());
		for(auto picture : pictures)
			AddSavedPicture(picture);
	}

	return true;
}
Exemplo n.º 8
0
bool SFB::Audio::MP3Metadata::_ReadMetadata(CFErrorRef *error)
{
	UInt8 buf [PATH_MAX];
	if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX))
		return false;
	
	std::unique_ptr<TagLib::FileStream> stream(new TagLib::FileStream((const char *)buf, true));
	if(!stream->isOpen()) {
		if(error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” could not be opened for reading."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Input/output error"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file may have been renamed, moved, deleted, or you may not have appropriate permissions."), ""));

			*error = CreateErrorForURL(Metadata::ErrorDomain, Metadata::InputOutputError, description, mURL, failureReason, recoverySuggestion);
		}

		return false;
	}

	TagLib::MPEG::File file(stream.get(), TagLib::ID3v2::FrameFactory::instance());
	if(!file.isValid()) {
		if(nullptr != error) {
			SFB::CFString description(CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG file."), ""));
			SFB::CFString failureReason(CFCopyLocalizedString(CFSTR("Not an MPEG file"), ""));
			SFB::CFString recoverySuggestion(CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""));
			
			*error = CreateErrorForURL(Metadata::ErrorDomain, Metadata::InputOutputError, description, mURL, failureReason, recoverySuggestion);
		}
		
		return false;
	}
	
	CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MP3"));

	if(file.audioProperties()) {
		auto properties = file.audioProperties();
		AddAudioPropertiesToDictionary(mMetadata, properties);

		// TODO: Is this too much information?
#if 0
		switch(properties->version()) {
			case TagLib::MPEG::Header::Version1:
				switch(properties->layer()) {
					case 1:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-1 Layer I"));		break;
					case 2:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-1 Layer II"));	break;
					case 3:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-1 Layer III"));	break;
				}
				break;
			case TagLib::MPEG::Header::Version2:
				switch(properties->layer()) {
					case 1:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2 Layer I"));		break;
					case 2:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2 Layer II"));	break;
					case 3:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2 Layer III"));	break;
				}
				break;
			case TagLib::MPEG::Header::Version2_5:
				switch(properties->layer()) {
					case 1:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2.5 Layer I"));	break;
					case 2:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2.5 Layer II"));	break;
					case 3:		CFDictionarySetValue(mMetadata, kFormatNameKey, CFSTR("MPEG-2.5 Layer III"));	break;
				}
				break;
		}
#endif

		if(properties->xingHeader() && properties->xingHeader()->totalFrames())
			AddIntToDictionary(mMetadata, kTotalFramesKey, (int)properties->xingHeader()->totalFrames());
	}

	if(file.APETag())
		AddAPETagToDictionary(mMetadata, mPictures, file.APETag());

	if(file.ID3v1Tag())
		AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag());

	if(file.ID3v2Tag())
		AddID3v2TagToDictionary(mMetadata, mPictures, file.ID3v2Tag());

	return true;
}
Exemplo n.º 9
0
bool FLACMetadata::ReadMetadata(CFErrorRef *error)
{
	// Start from scratch
	CFDictionaryRemoveAllValues(mMetadata);
	CFDictionaryRemoveAllValues(mChangedMetadata);

	UInt8 buf [PATH_MAX];
	if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX))
		return false;

	auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true);
	TagLib::FLAC::File file(stream, TagLib::ID3v2::FrameFactory::instance());

	if(!file.isValid()) {
		if(nullptr != error) {
			CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), "");
			CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a FLAC file"), "");
			CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "");
			
			*error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion);
			
			CFRelease(description), description = nullptr;
			CFRelease(failureReason), failureReason = nullptr;
			CFRelease(recoverySuggestion), recoverySuggestion = nullptr;
		}

		return false;
	}

	CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("FLAC"));

	if(file.audioProperties()) {
		auto properties = file.audioProperties();
		AddAudioPropertiesToDictionary(mMetadata, properties);

		if(properties->sampleWidth())
			AddIntToDictionary(mMetadata, kPropertiesBitsPerChannelKey, properties->sampleWidth());
		if(properties->sampleFrames())
			AddLongLongToDictionary(mMetadata, kPropertiesTotalFramesKey, properties->sampleFrames());
	}

	// Add all tags that are present
	if(file.ID3v1Tag())
		AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag());

	if(file.ID3v2Tag()) {
		std::vector<AttachedPicture *> pictures;
		AddID3v2TagToDictionary(mMetadata, pictures, file.ID3v2Tag());
		for(auto picture : pictures)
			AddSavedPicture(picture);
	}

	if(file.xiphComment()) {
		std::vector<AttachedPicture *> pictures;
		AddXiphCommentToDictionary(mMetadata, pictures, file.xiphComment());
		for(auto picture : pictures)
			AddSavedPicture(picture);
	}

	// Add album art
	for(auto iter : file.pictureList()) {
		CFDataRef data = CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(iter->data().data()), iter->data().size());

		CFStringRef description = nullptr;
		if(!iter->description().isNull())
			description = CFStringCreateWithCString(kCFAllocatorDefault, iter->description().toCString(true), kCFStringEncodingUTF8);

		AttachedPicture *picture = new AttachedPicture(data, static_cast<AttachedPicture::Type>(iter->type()), description);
		AddSavedPicture(picture);

		if(data)
			CFRelease(data), data = nullptr;

		if(description)
			CFRelease(description), description = nullptr;
	}

	return true;
}
bool SFB::Audio::AddID3v2TagToDictionary(CFMutableDictionaryRef dictionary, std::vector<std::shared_ptr<AttachedPicture>>& attachedPictures, const TagLib::ID3v2::Tag *tag)
{
	if(nullptr == dictionary || nullptr == tag)
		return false;

	// Add the basic tags not specific to ID3v2
	AddTagToDictionary(dictionary, tag);

	// Release date
	auto frameList = tag->frameListMap()["TDRC"];
	if(!frameList.isEmpty()) {
		/*
		 The timestamp fields are based on a subset of ISO 8601. When being as
		 precise as possible the format of a time string is
		 yyyy-MM-ddTHH:mm:ss (year, "-", month, "-", day, "T", hour (out of
		 24), ":", minutes, ":", seconds), but the precision may be reduced by
		 removing as many time indicators as wanted. Hence valid timestamps
		 are
		 yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and
		 yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use
		 the slash character as described in 8601, and for multiple non-
		 contiguous dates, use multiple strings, if allowed by the frame
		 definition.
		 */

		TagLib::AddStringToCFDictionary(dictionary, Metadata::kReleaseDateKey, frameList.front()->toString());
	}

	// Extract composer if present
	frameList = tag->frameListMap()["TCOM"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kComposerKey, frameList.front()->toString());

	// Extract album artist
	frameList = tag->frameListMap()["TPE2"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kAlbumArtistKey, frameList.front()->toString());

	// BPM
	frameList = tag->frameListMap()["TBPM"];
	if(!frameList.isEmpty()) {
		bool ok = false;
		int BPM = frameList.front()->toString().toInt(&ok);
		if(ok)
			AddIntToDictionary(dictionary, Metadata::kBPMKey, BPM);
	}

	// Rating
	TagLib::ID3v2::PopularimeterFrame *popularimeter = nullptr;
	frameList = tag->frameListMap()["POPM"];
	if(!frameList.isEmpty() && nullptr != (popularimeter = dynamic_cast<TagLib::ID3v2::PopularimeterFrame *>(frameList.front())))
		AddIntToDictionary(dictionary, Metadata::kRatingKey, popularimeter->rating());

	// Extract total tracks if present
	frameList = tag->frameListMap()["TRCK"];
	if(!frameList.isEmpty()) {
		// Split the tracks at '/'
		TagLib::String s = frameList.front()->toString();

		bool ok;
		size_t pos = s.find("/", 0);
		if(TagLib::String::npos() != pos) {
			int trackNum = s.substr(0, pos).toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, trackNum);

			int trackTotal = s.substr(pos + 1).toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kTrackTotalKey, trackTotal);
		}
		else if(s.length()) {
			int trackNum = s.toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, trackNum);
		}
	}

	// Extract disc number and total discs
	frameList = tag->frameListMap()["TPOS"];
	if(!frameList.isEmpty()) {
		// Split the tracks at '/'
		TagLib::String s = frameList.front()->toString();

		bool ok;
		size_t pos = s.find("/", 0);
		if(TagLib::String::npos() != pos) {
			int discNum = s.substr(0, pos).toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kDiscNumberKey, discNum);

			int discTotal = s.substr(pos + 1).toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kDiscTotalKey, discTotal);
		}
		else if(s.length()) {
			int discNum = s.toInt(&ok);
			if(ok)
				AddIntToDictionary(dictionary, Metadata::kDiscNumberKey, discNum);
		}
	}

	// Lyrics
	frameList = tag->frameListMap()["USLT"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kLyricsKey, frameList.front()->toString());

	// Extract compilation if present (iTunes TCMP tag)
	frameList = tag->frameListMap()["TCMP"];
	if(!frameList.isEmpty())
		// It seems that the presence of this frame indicates a compilation
		CFDictionarySetValue(dictionary, Metadata::kCompilationKey, kCFBooleanTrue);

	frameList = tag->frameListMap()["TSRC"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kISRCKey, frameList.front()->toString());

	// MusicBrainz
	auto musicBrainzReleaseIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "MusicBrainz Album Id");
	if(musicBrainzReleaseIDFrame)
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kMusicBrainzReleaseIDKey, musicBrainzReleaseIDFrame->fieldList().back());

	auto musicBrainzRecordingIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "MusicBrainz Track Id");
	if(musicBrainzRecordingIDFrame)
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kMusicBrainzRecordingIDKey, musicBrainzRecordingIDFrame->fieldList().back());

	// Sorting and grouping
	frameList = tag->frameListMap()["TSOT"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kTitleSortOrderKey, frameList.front()->toString());

	frameList = tag->frameListMap()["TSOA"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kAlbumTitleSortOrderKey, frameList.front()->toString());

	frameList = tag->frameListMap()["TSOP"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kArtistSortOrderKey, frameList.front()->toString());

	frameList = tag->frameListMap()["TSO2"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kAlbumArtistSortOrderKey, frameList.front()->toString());

	frameList = tag->frameListMap()["TSOC"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kComposerSortOrderKey, frameList.front()->toString());

	frameList = tag->frameListMap()["TIT1"];
	if(!frameList.isEmpty())
		TagLib::AddStringToCFDictionary(dictionary, Metadata::kGroupingKey, frameList.front()->toString());

	// ReplayGain
	bool foundReplayGain = false;

	// Preference is TXXX frames, RVA2 frame, then LAME header
	auto trackGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_TRACK_GAIN");
	auto trackPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_TRACK_PEAK");
	auto albumGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_ALBUM_GAIN");
	auto albumPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_ALBUM_PEAK");

	if(!trackGainFrame)
		trackGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_track_gain");
	if(trackGainFrame) {
		SFB::CFString str(trackGainFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8);
		double num = CFStringGetDoubleValue(str);

		AddDoubleToDictionary(dictionary, Metadata::kTrackGainKey, num);
		AddDoubleToDictionary(dictionary, Metadata::kReferenceLoudnessKey, 89.0);

		foundReplayGain = true;
	}

	if(!trackPeakFrame)
		trackPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_track_peak");
	if(trackPeakFrame) {
		SFB::CFString str(trackPeakFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8);
		double num = CFStringGetDoubleValue(str);

		AddDoubleToDictionary(dictionary, Metadata::kTrackPeakKey, num);
	}

	if(!albumGainFrame)
		albumGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_album_gain");
	if(albumGainFrame) {
		SFB::CFString str(albumGainFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8);
		double num = CFStringGetDoubleValue(str);

		AddDoubleToDictionary(dictionary, Metadata::kAlbumGainKey, num);
		AddDoubleToDictionary(dictionary, Metadata::kReferenceLoudnessKey, 89.0);

		foundReplayGain = true;
	}

	if(!albumPeakFrame)
		albumPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_album_peak");
	if(albumPeakFrame) {
		SFB::CFString str(albumPeakFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8);
		double num = CFStringGetDoubleValue(str);

		AddDoubleToDictionary(dictionary, Metadata::kAlbumPeakKey, num);
	}

	// If nothing found check for RVA2 frame
	if(!foundReplayGain) {
		frameList = tag->frameListMap()["RVA2"];

		for(auto frameIterator : tag->frameListMap()["RVA2"]) {
			TagLib::ID3v2::RelativeVolumeFrame *relativeVolume = dynamic_cast<TagLib::ID3v2::RelativeVolumeFrame *>(frameIterator);
			if(!relativeVolume)
				continue;

			// Attempt to use the master volume if present
			auto channels		= relativeVolume->channels();
			auto channelType	= TagLib::ID3v2::RelativeVolumeFrame::MasterVolume;

			// Fall back on whatever else exists in the frame
			if(!channels.contains(TagLib::ID3v2::RelativeVolumeFrame::MasterVolume))
				channelType = channels.front();

			float volumeAdjustment = relativeVolume->volumeAdjustment(channelType);

			if(TagLib::String("track", TagLib::String::Latin1) == relativeVolume->identification()) {
				if((int)volumeAdjustment)
					AddFloatToDictionary(dictionary, Metadata::kTrackGainKey, volumeAdjustment);
			}
			else if(TagLib::String("album", TagLib::String::Latin1) == relativeVolume->identification()) {
				if((int)volumeAdjustment)
					AddFloatToDictionary(dictionary, Metadata::kAlbumGainKey, volumeAdjustment);
			}
			// Fall back to track gain if identification is not specified
			else {
				if((int)volumeAdjustment)
					AddFloatToDictionary(dictionary, Metadata::kTrackGainKey, volumeAdjustment);
			}
		}
	}

	// Extract album art if present
	for(auto it : tag->frameListMap()["APIC"]) {
		TagLib::ID3v2::AttachedPictureFrame *frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(it);
		if(frame) {
			SFB::CFData data((const UInt8 *)frame->picture().data(), (CFIndex)frame->picture().size());

			SFB::CFString description;
			if(!frame->description().isEmpty())
				description = CFString(frame->description().toCString(true), kCFStringEncodingUTF8);

			attachedPictures.push_back(std::make_shared<AttachedPicture>(data, (AttachedPicture::Type)frame->type(), description));
		}
	}

	return true;
}
Exemplo n.º 11
0
bool SFB::Audio::AddAPETagToDictionary(CFMutableDictionaryRef dictionary, std::vector<std::shared_ptr<AttachedPicture>>& attachedPictures, const TagLib::APE::Tag *tag)
{
	if(nullptr == dictionary || nullptr == tag)
		return false;

	if(tag->isEmpty())
		return true;

	SFB::CFMutableDictionary additionalMetadata(0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	
	for(auto iterator : tag->itemListMap()) {
		auto item = iterator.second;

		if(item.isEmpty())
			continue;

		if(TagLib::APE::Item::Text == item.type()) {
			SFB::CFString key(item.key().toCString(true), kCFStringEncodingUTF8);
			SFB::CFString value(item.toString().toCString(true), kCFStringEncodingUTF8);

			if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUM"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kAlbumTitleKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTIST"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kArtistKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTIST"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kAlbumArtistKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSER"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kComposerKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GENRE"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kGenreKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DATE"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kReleaseDateKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DESCRIPTION"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kCommentKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLE"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kTitleKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKNUMBER"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKTOTAL"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kTrackTotalKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPILATION"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kCompilationKey, CFStringGetIntValue(value) ? kCFBooleanTrue : kCFBooleanFalse);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCNUMBER"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kDiscNumberKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCTOTAL"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kDiscTotalKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("LYRICS"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kLyricsKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("BPM"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kBPMKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("RATING"), kCFCompareCaseInsensitive))
				AddIntToDictionary(dictionary, Metadata::kRatingKey, CFStringGetIntValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ISRC"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kISRCKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MCN"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kMCNKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MUSICBRAINZ_ALBUMID"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kMusicBrainzReleaseIDKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MUSICBRAINZ_TRACKID"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kMusicBrainzRecordingIDKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLESORT"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kTitleSortOrderKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMTITLESORT"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kAlbumTitleSortOrderKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTISTSORT"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kArtistSortOrderKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTISTSORT"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kAlbumArtistSortOrderKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSERSORT"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kComposerSortOrderKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GROUPING"), kCFCompareCaseInsensitive))
				CFDictionarySetValue(dictionary, Metadata::kGroupingKey, value);
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_REFERENCE_LOUDNESS"), kCFCompareCaseInsensitive))
				AddDoubleToDictionary(dictionary, Metadata::kReferenceLoudnessKey, CFStringGetDoubleValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_GAIN"), kCFCompareCaseInsensitive))
				AddDoubleToDictionary(dictionary, Metadata::kTrackGainKey, CFStringGetDoubleValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_PEAK"), kCFCompareCaseInsensitive))
				AddDoubleToDictionary(dictionary, Metadata::kTrackPeakKey, CFStringGetDoubleValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_GAIN"), kCFCompareCaseInsensitive))
				AddDoubleToDictionary(dictionary, Metadata::kAlbumGainKey, CFStringGetDoubleValue(value));
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_PEAK"), kCFCompareCaseInsensitive))
				AddDoubleToDictionary(dictionary, Metadata::kAlbumPeakKey, CFStringGetDoubleValue(value));
#if 0
			else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("METADATA_BLOCK_PICTURE"), kCFCompareCaseInsensitive)) {
				// Handle embedded pictures
				for(auto blockIterator : item.values()) {
					auto encodedBlock = blockIterator.data(TagLib::String::UTF8);

					// Decode the Base-64 encoded data
					auto decodedBlock = TagLib::DecodeBase64(encodedBlock);

					// Create the picture
					TagLib::FLAC::Picture picture;
					picture.parse(decodedBlock);

					SFB::CFData data((const UInt8 *)picture.data().data(), picture.data().size());

					SFB::CFString description = nullptr;
					if(!picture.description().isNull())
						description(picture.description().toCString(true), kCFStringEncodingUTF8);

					attachedPictures.push_back(std::make_shared<AttachedPicture>(data, (AttachedPicture::Type)picture.type(), description));
				}
			}
#endif
			// Put all unknown tags into the additional metadata
			else
				CFDictionarySetValue(additionalMetadata, key, value);
		}
		else if(TagLib::APE::Item::Binary == item.type()) {
			SFB::CFString key(item.key().toCString(true), kCFStringEncodingUTF8);

			// From http://www.hydrogenaudio.org/forums/index.php?showtopic=40603&view=findpost&p=504669
			/*
			 <length> 32 bit
			 <flags with binary bit set> 32 bit
			 <field name> "Cover Art (Front)"|"Cover Art (Back)"
			 0x00
			 <description> UTF-8 string (needs to be a file name to be recognized by AudioShell - meh)
			 0x00
			 <cover data> binary
			 */
			if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("Cover Art (Front)"), kCFCompareCaseInsensitive) || kCFCompareEqualTo == CFStringCompare(key, CFSTR("Cover Art (Back)"), kCFCompareCaseInsensitive)) {
				auto binaryData = item.binaryData();
				size_t pos = binaryData.find('\0');
				if(TagLib::ByteVector::npos() != pos && 3 < binaryData.size()) {
					SFB::CFData data((const UInt8 *)binaryData.mid(pos + 1).data(), (CFIndex)(binaryData.size() - pos - 1));
					SFB::CFString description(TagLib::String(binaryData.mid(0, pos), TagLib::String::UTF8).toCString(true), kCFStringEncodingUTF8);

					attachedPictures.push_back(std::make_shared<AttachedPicture>(data, kCFCompareEqualTo == CFStringCompare(key, CFSTR("Cover Art (Front)"), kCFCompareCaseInsensitive) ? AttachedPicture::Type::FrontCover : AttachedPicture::Type::BackCover, description));
				}
			}
		}
	}

	if(CFDictionaryGetCount(additionalMetadata))
		CFDictionarySetValue(dictionary, Metadata::kAdditionalMetadataKey, additionalMetadata);

	return true;
}
Exemplo n.º 12
0
bool MP3Metadata::ReadMetadata(CFErrorRef *error)
{
	// Start from scratch
	CFDictionaryRemoveAllValues(mMetadata);
	CFDictionaryRemoveAllValues(mChangedMetadata);
	
	UInt8 buf [PATH_MAX];
	if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX))
		return false;
	
	auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true);
	TagLib::MPEG::File file(stream, TagLib::ID3v2::FrameFactory::instance());
	
	if(!file.isValid()) {
		if(nullptr != error) {
			CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MPEG file."), "");
			CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not an MPEG file"), "");
			CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "");
			
			*error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion);
			
			CFRelease(description), description = nullptr;
			CFRelease(failureReason), failureReason = nullptr;
			CFRelease(recoverySuggestion), recoverySuggestion = nullptr;
		}
		
		return false;
	}
	
	CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MP3"));

	if(file.audioProperties()) {
		auto properties = file.audioProperties();
		AddAudioPropertiesToDictionary(mMetadata, properties);

		// TODO: Is this too much information?
#if 0
		switch(properties->version()) {
			case TagLib::MPEG::Header::Version1:
				switch(properties->layer()) {
					case 1:		CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-1 Layer I"));		break;
					case 2:		CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-1 Layer II"));	break;
					case 3:		CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-1 Layer III"));	break;
				}
				break;
			case TagLib::MPEG::Header::Version2:
				switch(properties->layer()) {
					case 1:		CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2 Layer I"));		break;
					case 2:		CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2 Layer II"));	break;
					case 3:		CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2 Layer III"));	break;
				}
				break;
			case TagLib::MPEG::Header::Version2_5:
				switch(properties->layer()) {
					case 1:		CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2.5 Layer I"));	break;
					case 2:		CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2.5 Layer II"));	break;
					case 3:		CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("MPEG-2.5 Layer III"));	break;
				}
				break;
		}
#endif

		if(properties->xingHeader() && properties->xingHeader()->totalFrames())
			AddIntToDictionary(mMetadata, kPropertiesTotalFramesKey, properties->xingHeader()->totalFrames());
	}

	if(file.APETag()) {
		std::vector<AttachedPicture *> pictures;
		AddAPETagToDictionary(mMetadata, pictures, file.APETag());
		for(auto picture : pictures)
			AddSavedPicture(picture);
	}

	if(file.ID3v1Tag())
		AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag());

	if(file.ID3v2Tag()) {
		std::vector<AttachedPicture *> pictures;
		AddID3v2TagToDictionary(mMetadata, pictures, file.ID3v2Tag());
		for(auto picture : pictures)
			AddSavedPicture(picture);
	}

	return true;
}
bool SFB::Audio::AddXiphCommentToDictionary(CFMutableDictionaryRef dictionary, std::vector<std::shared_ptr<AttachedPicture>>& attachedPictures, const TagLib::Ogg::XiphComment *tag)
{
	if(nullptr == dictionary || nullptr == tag)
		return false;

	SFB::CFMutableDictionary additionalMetadata = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

	for(auto it : tag->fieldListMap()) {
		// According to the Xiph comment specification keys should only contain a limited subset of ASCII, but UTF-8 is a safer choice
		SFB::CFString key = CFStringCreateWithCString(kCFAllocatorDefault, it.first.toCString(true), kCFStringEncodingUTF8);
		
		// Vorbis allows multiple comments with the same key, but this isn't supported by AudioMetadata
		SFB::CFString value = CFStringCreateWithCString(kCFAllocatorDefault, it.second.front().toCString(true), kCFStringEncodingUTF8);
		
		if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUM"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kAlbumTitleKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTIST"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kArtistKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTIST"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kAlbumArtistKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSER"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kComposerKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GENRE"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kGenreKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DATE"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kReleaseDateKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DESCRIPTION"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kCommentKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLE"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kTitleKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKNUMBER"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKTOTAL"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kTrackTotalKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPILATION"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kCompilationKey, CFStringGetIntValue(value) ? kCFBooleanTrue : kCFBooleanFalse);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCNUMBER"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kDiscNumberKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCTOTAL"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kDiscTotalKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("LYRICS"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kLyricsKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("BPM"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kBPMKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("RATING"), kCFCompareCaseInsensitive))
			AddIntToDictionary(dictionary, Metadata::kRatingKey, CFStringGetIntValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ISRC"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kISRCKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MCN"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kMCNKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MUSICBRAINZ_ALBUMID"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kMusicBrainzReleaseIDKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MUSICBRAINZ_TRACKID"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kMusicBrainzRecordingIDKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLESORT"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kTitleSortOrderKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMTITLESORT"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kAlbumTitleSortOrderKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTISTSORT"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kArtistSortOrderKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTISTSORT"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kAlbumArtistSortOrderKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSERSORT"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kComposerSortOrderKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GROUPING"), kCFCompareCaseInsensitive))
			CFDictionarySetValue(dictionary, Metadata::kGroupingKey, value);
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_REFERENCE_LOUDNESS"), kCFCompareCaseInsensitive))
			AddDoubleToDictionary(dictionary, Metadata::kReferenceLoudnessKey, CFStringGetDoubleValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_GAIN"), kCFCompareCaseInsensitive))
			AddDoubleToDictionary(dictionary, Metadata::kTrackGainKey, CFStringGetDoubleValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_PEAK"), kCFCompareCaseInsensitive))
			AddDoubleToDictionary(dictionary, Metadata::kTrackPeakKey, CFStringGetDoubleValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_GAIN"), kCFCompareCaseInsensitive))
			AddDoubleToDictionary(dictionary, Metadata::kAlbumGainKey, CFStringGetDoubleValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_PEAK"), kCFCompareCaseInsensitive))
			AddDoubleToDictionary(dictionary, Metadata::kAlbumPeakKey, CFStringGetDoubleValue(value));
		else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("METADATA_BLOCK_PICTURE"), kCFCompareCaseInsensitive)) {
			// Handle embedded pictures
			for(auto blockIterator : it.second) {
				auto encodedBlock = blockIterator.data(TagLib::String::UTF8);

				// Decode the Base-64 encoded data
				auto decodedBlock = TagLib::DecodeBase64(encodedBlock);

				// Create the picture
				TagLib::FLAC::Picture picture;
				picture.parse(decodedBlock);
				
				SFB::CFData data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)picture.data().data(), (CFIndex)picture.data().size());

				SFB::CFString description;
				if(!picture.description().isEmpty())
					description = CFStringCreateWithCString(kCFAllocatorDefault, picture.description().toCString(true), kCFStringEncodingUTF8);

				attachedPictures.push_back(std::make_shared<AttachedPicture>(data, (AttachedPicture::Type)picture.type(), description));
			}
		}
		// Put all unknown tags into the additional metadata
		else
			CFDictionarySetValue(additionalMetadata, key, value);
	}
	
	if(CFDictionaryGetCount(additionalMetadata))
		CFDictionarySetValue(dictionary, Metadata::kAdditionalMetadataKey, additionalMetadata);

	return true;
}