double UserObjectImp::toNumber(ExecState *exec) const { double result = 0; JSUserObject* jsObjPtr = KJSValueToJSObject(toObject(exec), exec); CFTypeRef cfValue = jsObjPtr ? jsObjPtr->CopyCFValue() : 0; if (cfValue) { CFTypeID cfType = CFGetTypeID(cfValue); if (cfValue == GetCFNull()) { // } else if (cfType == CFBooleanGetTypeID()) { if (cfValue == kCFBooleanTrue) { result = 1; } } else if (cfType == CFStringGetTypeID()) { result = CFStringGetDoubleValue((CFStringRef)cfValue); } else if (cfType == CFNumberGetTypeID()) { CFNumberGetValue((CFNumberRef)cfValue, kCFNumberDoubleType, &result); } } ReleaseCFType(cfValue); if (jsObjPtr) jsObjPtr->Release(); return result; }
static void asynchttp_complete(asynchttp_t *http) { secdebug("http", "http: %p", http); /* Shutdown streams and timer, we're about to invoke our client callback. */ if (http->stream) { CFReadStreamSetClient(http->stream, kCFStreamEventNone, NULL, NULL); CFReadStreamSetDispatchQueue(http->stream, NULL); CFReadStreamClose(http->stream); CFReleaseNull(http->stream); } if (http->timer) { dispatch_source_cancel(http->timer); dispatch_release_null(http->timer); } if (http->completed) { /* This should probably move to our clients. */ CFTimeInterval maxAge = NULL_TIME; if (http->response) { CFStringRef cacheControl = CFHTTPMessageCopyHeaderFieldValue( http->response, CFSTR("cache-control")); if (cacheControl) { CFStringRef maxAgeValue = copyParseMaxAge(cacheControl); CFRelease(cacheControl); if (maxAgeValue) { secdebug("http", "http header max-age: %@", maxAgeValue); maxAge = CFStringGetDoubleValue(maxAgeValue); CFRelease(maxAgeValue); } } } http->completed(http, maxAge); } }
//-------------------------------------------------------------------------------- OSStatus SubSynth::GetProperty(AudioUnitPropertyID inPropertyID, AudioUnitScope inScope, AudioUnitElement inElement, void * outData) { if (inScope == kAudioUnitScope_Global) { switch (inPropertyID) { case kAudioUnitProperty_ParameterValueFromString: { AudioUnitParameterValueFromString * name = (AudioUnitParameterValueFromString*)outData; if (name->inString == NULL) return kAudioUnitErr_InvalidPropertyValue; double paramValue_literal = CFStringGetDoubleValue(name->inString); switch (name->inParamID) { case kParam_Tune: if (paramValue_literal <= 0.0) name->outValue = 0.0; // XXX avoid log10(0) or log10(-X) else name->outValue = (log10(paramValue_literal / (0.0726 * GetSampleRate())) + 2.5) / 1.5; break; case kParam_Release: return kAudioUnitErr_PropertyNotInUse; // XXX I can't figure out how to invert this one default: return kAudioUnitErr_InvalidParameter; } return noErr; } case kAudioUnitProperty_ParameterStringFromValue: { AudioUnitParameterStringFromValue * name = (AudioUnitParameterStringFromValue*)outData; double paramValue = (name->inValue == NULL) ? GetParameter(name->inParamID) : *(name->inValue); int precision = 0; switch (name->inParamID) { case kParam_Tune: paramValue = 0.0726 * GetSampleRate() * pow(10.0, -2.5 + (1.5 * paramValue)); precision = 3; break; case kParam_Release: paramValue = GetReleaseTimeForParamValue(paramValue); precision = 1; break; default: return kAudioUnitErr_InvalidParameter; } name->outString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%.*f"), precision, paramValue); if (name->outString == NULL) return coreFoundationUnknownErr; return noErr; } } } return AUEffectBase::GetProperty(inPropertyID, inScope, inElement, outData); }
/* ------------------------------------------------------------------------------------------------------ SetNumberValueFromHeader: read FITS header and set number value to attribute name. ------------------------------------------------------------------------------------------------------ */ void SetNumberValueFromFITSHeader(const char* filename, CFMutableDictionaryRef attributes, const char* importerAttrName, char* keyword, Boolean isCoordinates) { CFStringRef headerValue = getCleanedHeaderValue(filename, keyword); if (headerValue != NULL) { double number; CFRange r = CFStringFind(headerValue, CFSTR(":"), 0); if ((isCoordinates == true) && (r.location != kCFNotFound)) { double sign = 1.; CFArrayRef dmsArray = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, headerValue, CFSTR(":")); if (CFArrayGetCount(dmsArray) == 3) { if (CFStringGetDoubleValue(CFArrayGetValueAtIndex(dmsArray, 0)) < 0) { sign = -1; } // Work for either DMS or HMS, since the input format is kept. It is not a transformation, just a flattening. number = sign * ( fabs(CFStringGetDoubleValue(CFArrayGetValueAtIndex(dmsArray, 0))) + CFStringGetDoubleValue(CFArrayGetValueAtIndex(dmsArray, 1)) / 60 + CFStringGetDoubleValue(CFArrayGetValueAtIndex(dmsArray, 2)) / 3600 ); } CFRelease(dmsArray); } else { number = CFStringGetDoubleValue(headerValue); } CFNumberRef value = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &number); CFStringRef cfImporterAttrName = CFStringCreateWithCString(kCFAllocatorDefault, importerAttrName, kCFStringEncodingUTF8); CFDictionaryAddValue(attributes, cfImporterAttrName, value); CFRelease(value); CFRelease(cfImporterAttrName); CFRelease(headerValue); } }
float NumericControl::getFloatValue( WindowRef window ) { CFStringRef stringRef = getStringRef( window ); if( stringRef ) { mValue = CFStringGetDoubleValue( stringRef ); return mValue; } return 0.0f; }
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; FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); // ENOMEM sux if(NULL == chain) return false; if(!FLAC__metadata_chain_read(chain, reinterpret_cast<const char *>(buf))) { // Attempt to provide a descriptive error message if(NULL != error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); switch(FLAC__metadata_chain_status(chain)) { case FLAC__METADATA_CHAIN_STATUS_NOT_A_FLAC_FILE: { CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a FLAC 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; break; } case FLAC__METADATA_CHAIN_STATUS_BAD_METADATA: { CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a FLAC file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file contains bad metadata."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; break; } default: { CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a FLAC 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; break; } } *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataFileFormatNotRecognizedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } FLAC__metadata_chain_delete(chain), chain = NULL; return false; } FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(NULL == iterator) { FLAC__metadata_chain_delete(chain), chain = NULL; return false; } CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("FLAC")); FLAC__metadata_iterator_init(iterator, chain); FLAC__StreamMetadata *block = NULL; CFMutableDictionaryRef additionalMetadata = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); do { block = FLAC__metadata_iterator_get_block(iterator); if(NULL == block) break; switch(block->type) { case FLAC__METADATA_TYPE_VORBIS_COMMENT: for(unsigned i = 0; i < block->data.vorbis_comment.num_comments; ++i) { char *fieldName = NULL; char *fieldValue = NULL; // Let FLAC parse the comment for us if(!FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(block->data.vorbis_comment.comments[i], &fieldName, &fieldValue)) { // Ignore malformed comments continue; } CFStringRef key = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(fieldName), strlen(fieldName), kCFStringEncodingASCII, false, kCFAllocatorMalloc); CFStringRef value = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(fieldValue), strlen(fieldValue), kCFStringEncodingUTF8, false, kCFAllocatorMalloc); if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUM"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataAlbumTitleKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTIST"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataArtistKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTIST"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataAlbumArtistKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSER"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataComposerKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GENRE"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataGenreKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DATE"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataReleaseDateKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DESCRIPTION"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataCommentKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLE"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataTitleKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKNUMBER"), kCFCompareCaseInsensitive)) { int num = CFStringGetIntValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num); CFDictionarySetValue(mMetadata, kMetadataTrackNumberKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKTOTAL"), kCFCompareCaseInsensitive)) { int num = CFStringGetIntValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num); CFDictionarySetValue(mMetadata, kMetadataTrackTotalKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPILATION"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataCompilationKey, CFStringGetIntValue(value) ? kCFBooleanTrue : kCFBooleanFalse); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCNUMBER"), kCFCompareCaseInsensitive)) { int num = CFStringGetIntValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num); CFDictionarySetValue(mMetadata, kMetadataDiscNumberKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCTOTAL"), kCFCompareCaseInsensitive)) { int num = CFStringGetIntValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num); CFDictionarySetValue(mMetadata, kMetadataDiscTotalKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ISRC"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataISRCKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MCN"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataMCNKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_REFERENCE_LOUDNESS"), kCFCompareCaseInsensitive)) { double num = CFStringGetDoubleValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num); CFDictionarySetValue(mMetadata, kReplayGainReferenceLoudnessKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_GAIN"), kCFCompareCaseInsensitive)) { double num = CFStringGetDoubleValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num); CFDictionarySetValue(mMetadata, kReplayGainTrackGainKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_PEAK"), kCFCompareCaseInsensitive)) { double num = CFStringGetDoubleValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num); CFDictionarySetValue(mMetadata, kReplayGainTrackPeakKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_GAIN"), kCFCompareCaseInsensitive)) { double num = CFStringGetDoubleValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num); CFDictionarySetValue(mMetadata, kReplayGainAlbumGainKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_PEAK"), kCFCompareCaseInsensitive)) { double num = CFStringGetDoubleValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num); CFDictionarySetValue(mMetadata, kReplayGainAlbumPeakKey, number); CFRelease(number), number = NULL; } // Put all unknown tags into the additional metadata else CFDictionarySetValue(additionalMetadata, key, value); CFRelease(key), key = NULL; CFRelease(value), value = NULL; fieldName = NULL; fieldValue = NULL; } break; case FLAC__METADATA_TYPE_PICTURE: { CFDataRef data = CFDataCreate(kCFAllocatorDefault, block->data.picture.data, block->data.picture.data_length); CFDictionarySetValue(mMetadata, kAlbumArtFrontCoverKey, data); CFRelease(data), data = NULL; } break; case FLAC__METADATA_TYPE_STREAMINFO: { CFNumberRef sampleRate = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &block->data.stream_info.sample_rate); CFDictionarySetValue(mMetadata, kPropertiesSampleRateKey, sampleRate); CFRelease(sampleRate), sampleRate = NULL; CFNumberRef channelsPerFrame = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &block->data.stream_info.channels); CFDictionarySetValue(mMetadata, kPropertiesChannelsPerFrameKey, channelsPerFrame); CFRelease(channelsPerFrame), channelsPerFrame = NULL; CFNumberRef bitsPerChannel = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &block->data.stream_info.bits_per_sample); CFDictionarySetValue(mMetadata, kPropertiesBitsPerChannelKey, bitsPerChannel); CFRelease(bitsPerChannel), bitsPerChannel = NULL; CFNumberRef totalFrames = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &block->data.stream_info.total_samples); CFDictionarySetValue(mMetadata, kPropertiesTotalFramesKey, totalFrames); CFRelease(totalFrames), totalFrames = NULL; double length = static_cast<double>(block->data.stream_info.total_samples / block->data.stream_info.sample_rate); CFNumberRef duration = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &length); CFDictionarySetValue(mMetadata, kPropertiesDurationKey, duration); CFRelease(duration), duration = NULL; double losslessBitrate = static_cast<double>(block->data.stream_info.sample_rate * block->data.stream_info.channels * block->data.stream_info.bits_per_sample) / 1000; CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &losslessBitrate); CFDictionarySetValue(mMetadata, kPropertiesBitrateKey, bitrate); CFRelease(bitrate), bitrate = NULL; } break; case FLAC__METADATA_TYPE_PADDING: break; case FLAC__METADATA_TYPE_APPLICATION: break; case FLAC__METADATA_TYPE_SEEKTABLE: break; case FLAC__METADATA_TYPE_CUESHEET: break; case FLAC__METADATA_TYPE_UNDEFINED: break; default: break; } } while(FLAC__metadata_iterator_next(iterator)); if(CFDictionaryGetCount(additionalMetadata)) SetAdditionalMetadata(additionalMetadata); CFRelease(additionalMetadata), additionalMetadata = NULL; FLAC__metadata_iterator_delete(iterator), iterator = NULL; FLAC__metadata_chain_delete(chain), chain = NULL; 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; }
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; }
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; }
int main(int argc, char** argv) { if(argc < 4) { PrintUsage(stdout); return 0; } /* <argv parsing> */ /* Parse argument: mountpoint (CFString) */ /*CFStringRef mountpoint = CFStringCreateWithCString(kCFAllocatorDefault, argv[1], kCFStringEncodingUTF8); */ /* Parse argument: mountpointRaw (char*) */ char *mountpointRaw = argv[1]; /* Parse argument: timeout (double) */ double timeout; { CFStringRef timeoutString = CFStringCreateWithCString(kCFAllocatorDefault, argv[2], kCFStringEncodingUTF8); timeout = CFStringGetDoubleValue(timeoutString); CFRelease(timeoutString); if(timeout == 0.0) { fprintf(stdout, "Invalid argument: timeout (\"%s\"", argv[2]); return -1; } } /* Parse argument: mount_command */ const char *mount_command = argv[3]; /* </argv parsing> */ CFNotificationCenterRef centerRef; CFStringRef notificationObjectName = CFSTR(FUSE_LISTEN_OBJECT); CFStringRef notificationName = CFSTR(FUSE_MOUNT_NOTIFICATION_NAME); CFRunLoopRef crl; if(DEBUGMODE) { DEBUG("Testing NotificationCallback...\n"); NotificationCallback(NULL, NULL, notificationObjectName, NULL, NULL); DEBUG("Test completed. Adding observer...\n"); } centerRef = CFNotificationCenterGetDistributedCenter(); crl = CFRunLoopGetCurrent(); /* Think. Will the child process also be an observer? I don't think so... */ CFNotificationCenterAddObserver(centerRef, NULL, NotificationCallback, notificationName, notificationObjectName, CFNotificationSuspensionBehaviorDrop); int forkRetval = fork(); if(forkRetval == -1) { fprintf(stderr, "Could not fork!\n"); return -1; } else if(forkRetval != 0) { // Parent process int childProcessPID = forkRetval; int waitpid_status = 0; DEBUG("Waiting for PID %d...\n", childProcessPID); int waitpidres = waitpid(childProcessPID, &waitpid_status, 0); if(waitpidres == childProcessPID) { if(!WIFEXITED(waitpid_status)) { DEBUG("Child process did not exit cleanly! Returning -1."); return -1; } int retval = WEXITSTATUS(waitpid_status); DEBUG("PID %d returned with exit code: %d Exiting fuse_wait with this exit code...\n", childProcessPID, retval); if(retval != 0) { DEBUG("Exit value indicates an error while executing mount command. Returning without\n"); DEBUG("waiting for notification.\n"); DEBUG("Returning retval: %d\n", retval); return retval; } } else { DEBUG("Abnormal termination of process %d. :( waitpid returned: %d\n", childProcessPID, waitpidres); return -1; } DEBUG("Running run loop a long time...\n"); CFStringRef mountPathSnapshot = NULL; while(mountPathSnapshot == NULL) { int crlrimRetval = CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true); DEBUG("Exited from run loop. Let's find out why... crlrimRetval: %d (handled: %d)\n", crlrimRetval, kCFRunLoopRunHandledSource); mountPathSnapshot = mountPath; // Might have been modified during run loop. if(crlrimRetval != kCFRunLoopRunHandledSource) { fprintf(stderr, "Did not receive a signal within %f seconds. Exiting...\n", timeout); break; } else if(mountPathSnapshot != NULL) { DEBUG("mountPathSnapshot: %X\n", (int)mountPathSnapshot); int mountPathUTF8Length = CFStringGetLength(mountPath) + 1; // null terminator char *mountPathUTF8 = malloc(mountPathUTF8Length); memset(mountPathUTF8, 0, mountPathUTF8Length); GetCorruptedMacFUSEStringAsUTF8(mountPath, mountPathUTF8, mountPathUTF8Length); char *canonicalMountPath = malloc(PATH_MAX); char *canonicalMountpoint = malloc(PATH_MAX); memset(canonicalMountPath, 0, PATH_MAX); memset(canonicalMountpoint, 0, PATH_MAX); realpath(mountPathUTF8, canonicalMountPath); realpath(mountpointRaw, canonicalMountpoint); int cmpres = strncmp(canonicalMountPath, canonicalMountpoint, PATH_MAX); if(cmpres != 0) { if(DEBUGMODE) { DEBUG("Strings NOT equal. cmpres=%d\n", cmpres); DEBUG("mountPath (UTF-8): \"%s\"\n", canonicalMountPath); DEBUG("mountpoint (UTF-8): \"%s\"\n", canonicalMountpoint); } mountPathSnapshot = NULL; } else DEBUG("Mounter has signaled! Great success!\n"); free(mountPathUTF8); free(canonicalMountPath); free(canonicalMountpoint); } //CFRunLoopRun(); } DEBUG("Run loop done.\n"); if(mountPath != NULL) CFRelease(mountPath); return 0; // We have previously checked that the return value from the child process is 0. We can't get here if it isn't. } else { // forkRetval == 0 // Child process const int childargc = argc-3; char *childargv[childargc+1]; childargv[childargc] = NULL; // Null terminated int i; for(i = 0; i < childargc; ++i) childargv[i] = argv[i+(argc-childargc)]; if(DEBUGMODE) { DEBUG("Contents of argv:\n"); for(i = 0; i < argc; ++i) DEBUG(" argv[%i]: \"%s\"\n", i, argv[i]); DEBUG("Contents of childargv:\n"); for(i = 0; i < childargc; ++i) DEBUG(" childargv[%i]: \"%s\"\n", i, childargv[i]); } execvp(mount_command, childargv); fprintf(stderr, "Could not execute %s!\n", argv[3]); return -1; } }
/* ------------------------------------------------------------------------------------------------------ SetDateValueFromFITSHeader: read FITS header and set striped string value to attribute name. ------------------------------------------------------------------------------------------------------ */ void SetDateValueFromFITSHeader(const char* filename, CFMutableDictionaryRef attributes, const char* importerAttrName, char* mainKeyword, char* secondaryKeyword1, char* secondaryKeyword2) { CFMutableStringRef headerValue = CFStringCreateMutable(kCFAllocatorDefault, (CFIndex)80); CFStringRef value1 = getCleanedHeaderValue(filename, mainKeyword); if (value1 != NULL) { CFIndex valueLength1 = CFStringGetLength(value1); if ((valueLength1 >= 19) || (valueLength1 == 10)) { CFStringAppend(headerValue, value1); // value of mainKeyword looks OK. } CFRelease(value1); } CFStringRef value2 = getCleanedHeaderValue(filename, secondaryKeyword1); if (value2 != NULL) { CFIndex valueLength2 = CFStringGetLength(value2); CFIndex headerValueLength = CFStringGetLength(headerValue); if (headerValueLength == 0 && valueLength2 >= 10) { CFStringAppend(headerValueLength, value2); // Append value of secondaryKeyword1 } else if (headerValueLength < 19 && valueLength2 >= 19) { CFRange range = CFRangeMake(0, headerValueLength); CFStringDelete(headerValue, range); CFStringAppend(headerValue, value2); // Replace value of current headerValue by value of secondaryKeyword1 } CFRelease(value2); } CFStringRef value3 = getCleanedHeaderValue(filename, secondaryKeyword2); if (value3 != NULL) { CFRange r = CFStringFind(value3, CFSTR(":"), 0); CFIndex headerValueLength = CFStringGetLength(headerValue); if (headerValueLength == 10 && r.location != kCFNotFound) { CFStringAppend(headerValue, CFSTR("T")); CFStringAppend(headerValue, value3); } CFRelease(value3); } CFIndex headerValueLength = CFStringGetLength(headerValue); if (headerValueLength > 0) { CFArrayRef tArray = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, headerValue, CFSTR("T")); CFArrayRef ymdArray = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, CFArrayGetValueAtIndex(tArray, 0), CFSTR("-")); CFGregorianDate gregDate; gregDate.year = 0; gregDate.month = 0; gregDate.day = 0.; if (CFStringGetLength(CFArrayGetValueAtIndex(ymdArray, 0)) > 0) { gregDate.year = CFStringGetIntValue(CFArrayGetValueAtIndex(ymdArray, 0)); } if (CFStringGetLength(CFArrayGetValueAtIndex(ymdArray, 1)) > 0) { gregDate.month = CFStringGetIntValue(CFArrayGetValueAtIndex(ymdArray, 1)); } if (CFStringGetLength(CFArrayGetValueAtIndex(ymdArray, 2)) > 0) { gregDate.day = CFStringGetIntValue(CFArrayGetValueAtIndex(ymdArray, 2)); } CFRelease(ymdArray); gregDate.hour = 0; gregDate.minute = 0; gregDate.second = 0.; CFIndex arraySize = CFArrayGetCount(tArray); if (arraySize == 2) { CFArrayRef hmsArray = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, CFArrayGetValueAtIndex(tArray, 1), CFSTR(":")); if (CFStringGetLength(CFArrayGetValueAtIndex(hmsArray, 0)) > 0) { gregDate.hour = CFStringGetIntValue(CFArrayGetValueAtIndex(hmsArray, 0)); } if (CFStringGetLength(CFArrayGetValueAtIndex(hmsArray, 1)) > 0) { gregDate.minute = CFStringGetIntValue(CFArrayGetValueAtIndex(hmsArray, 1)); } if (CFStringGetLength(CFArrayGetValueAtIndex(hmsArray, 2)) > 0) { gregDate.second = CFStringGetDoubleValue(CFArrayGetValueAtIndex(hmsArray, 2)); } CFRelease(hmsArray); } CFRelease(tArray); // printf("%i %i %i %i %i %f\n\n", gregDate.year, gregDate.month, gregDate.day, gregDate.hour, gregDate.minute, gregDate.second); if ((gregDate.year > 0) && (gregDate.month > 0) && (gregDate.day > 0)) { CFTimeZoneRef timeZone = CFTimeZoneCreateWithName(kCFAllocatorDefault, CFSTR("GMT"), true); CFAbsoluteTime absTime = CFGregorianDateGetAbsoluteTime(gregDate, timeZone); CFDateRef date = CFDateCreate(kCFAllocatorDefault, absTime); if (timeZone != NULL) { CFRelease(timeZone); } CFStringRef cfImporterAttrName = CFStringCreateWithCString(kCFAllocatorDefault, importerAttrName, kCFStringEncodingUTF8); CFDictionaryAddValue(attributes, cfImporterAttrName, date); CFRelease(cfImporterAttrName); CFRelease(date); } } CFRelease(headerValue); }
int main(int argc, char** argv) { LOG_DEBUG("[Debug messages enabled]\n"); if(argc < 4) { PrintUsage(stdout); return 0; } /* <argv parsing> */ /* Parse argument: mountpoint (CFString) */ /*CFStringRef mountpoint = CFStringCreateWithCString(kCFAllocatorDefault, argv[1], kCFStringEncodingUTF8); */ /* Parse argument: mountpointRaw (char*) */ char *mountpointRaw = argv[1]; /* Parse argument: timeout (double) */ double timeout; { CFStringRef timeoutString = CFStringCreateWithCString(kCFAllocatorDefault, argv[2], kCFStringEncodingUTF8); timeout = CFStringGetDoubleValue(timeoutString); CFRelease(timeoutString); if(timeout == 0.0) { fprintf(stdout, "Invalid argument: timeout (\"%s\")\n", argv[2]); return -1; } } /* Parse argument: mount_command */ const char *mount_command = argv[3]; /* </argv parsing> */ CFNotificationCenterRef centerRef; CFStringRef notificationObjectName = CFSTR(FUSE_LISTEN_OBJECT); CFStringRef notificationName = CFSTR(FUSE_MOUNT_NOTIFICATION_NAME); //CFRunLoopRef runLoop; if(DEBUGMODE) { LOG_DEBUG("Testing NotificationCallback...\n"); NotificationCallback(NULL, NULL, notificationObjectName, NULL, NULL); LOG_DEBUG("Test completed. Adding observer...\n"); } centerRef = CFNotificationCenterGetDistributedCenter(); //runLoop = CFRunLoopGetCurrent(); /* * We need to add ourselves as an observer before the fork, in case the forked * process outruns us. * * Will the child process also be an observer? Possibly, but even if that is * the case, it shouldn't seem to make any practical difference (we never * enter a CFRunLoop in the child process, at least not before execvp). */ CFNotificationCenterAddObserver(centerRef, NULL, NotificationCallback, notificationName, notificationObjectName, CFNotificationSuspensionBehaviorDrop); int forkRetval = fork(); if(forkRetval == -1) { fprintf(stderr, "Could not fork!\n"); return -1; } else if(forkRetval != 0) { // Parent process int childProcessPID = forkRetval; int waitpid_status = 0; LOG_DEBUG("Waiting for PID %d...\n", childProcessPID); int waitpidres = waitpid(childProcessPID, &waitpid_status, 0); if(waitpidres == childProcessPID) { if(!WIFEXITED(waitpid_status)) { LOG_DEBUG("Child process did not exit cleanly! Returning -1."); return -1; } int retval = WEXITSTATUS(waitpid_status); LOG_DEBUG("PID %d returned with exit code: %d Exiting fuse_wait with this exit code...\n", childProcessPID, retval); if(retval != 0) { LOG_DEBUG("Exit value indicates an error while executing mount command. Returning without\n"); LOG_DEBUG("waiting for notification.\n"); LOG_DEBUG("Returning retval: %d\n", retval); return retval; } } else { LOG_DEBUG("Abnormal termination of process %d. waitpid returned: %d\n", childProcessPID, waitpidres); return -1; } LOG_DEBUG("Running run loop a long time...\n"); CFStringRef mountPathSnapshot = NULL; while(mountPathSnapshot == NULL) { #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 int crlrimRetval = CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true); LOG_DEBUG("Exited from run loop. Let's find out why... crlrimRetval: %d " "(handled: %d)\n", crlrimRetval, kCFRunLoopRunHandledSource); #else CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true); LOG_DEBUG("Exited from run loop. Let's find out why.\n"); #endif mountPathSnapshot = mountPath; // Might have been modified during RunLoop. #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 if(crlrimRetval != kCFRunLoopRunHandledSource) { fprintf(stderr, "Did not receive a signal within %f seconds. " "Exiting...\n", timeout); return -2; } #endif if(mountPathSnapshot != NULL) { LOG_DEBUG("mountPathSnapshot: %p\n", mountPathSnapshot); /* * Convert the CFString that we got back from the CFNotificationCenter * into UTF-8 data. (We assume that UTF-8 is the encoding used by the * system, which is true in Mac OS X.) */ CFDataRef utf8Data = CFStringCreateExternalRepresentation(NULL, mountPath, kCFStringEncodingUTF8, 0); if(utf8Data != NULL) { /* * Put the UTF-8 data in a NULL-terminated C-string. */ size_t mountPathUTF8Length = CFDataGetLength(utf8Data) + 1; char *mountPathUTF8 = calloc(1, mountPathUTF8Length); CFDataGetBytes(utf8Data, CFRangeMake(0, mountPathUTF8Length - 1), (unsigned char*) mountPathUTF8); //int mountPathUTF8Length = CFStringGetLength(mountPath) + 1; // null terminator //char *mountPathUTF8 = malloc(mountPathUTF8Length); //memset(mountPathUTF8, 0, mountPathUTF8Length); //GetCorruptedMacFUSEStringAsUTF8(mountPath, mountPathUTF8, mountPathUTF8Length); /* * Canonicalize the two paths, in case one of the pathnames contain * symbolic links, but not the other. */ char *canonicalMountPath = malloc(PATH_MAX); char *canonicalMountpoint = malloc(PATH_MAX); memset(canonicalMountPath, 0, PATH_MAX); memset(canonicalMountpoint, 0, PATH_MAX); realpath(mountPathUTF8, canonicalMountPath); realpath(mountpointRaw, canonicalMountpoint); /* * Finally, compare the two paths for equality. */ int cmpres = strncmp(canonicalMountPath, canonicalMountpoint, PATH_MAX); if(cmpres != 0) { LOG_DEBUG("Strings NOT equal. cmpres=%d\n", cmpres); LOG_DEBUG("mountPath (UTF-8): \"%s\"\n", canonicalMountPath); LOG_DEBUG("mountpoint (UTF-8): \"%s\"\n", canonicalMountpoint); mountPathSnapshot = NULL; } else LOG_DEBUG("Recieved a signal for the mount point.\n"); free(mountPathUTF8); free(canonicalMountPath); free(canonicalMountpoint); } CFRelease(utf8Data); } } LOG_DEBUG("Run loop done.\n"); if(mountPath != NULL) CFRelease(mountPath); return 0; // We have previously checked that the return value from the child process is 0. We can't get here if it isn't. } else { // forkRetval == 0 // Child process const int childargc = argc-3; char *childargv[childargc+1]; childargv[childargc] = NULL; // Null terminated int i; for(i = 0; i < childargc; ++i) childargv[i] = argv[i+(argc-childargc)]; LOG_DEBUG("Contents of argv:\n"); for(i = 0; i < argc; ++i) LOG_DEBUG(" argv[%i]: \"%s\"\n", i, argv[i]); LOG_DEBUG("Contents of childargv:\n"); for(i = 0; i < childargc; ++i) LOG_DEBUG(" childargv[%i]: \"%s\"\n", i, childargv[i]); execvp(mount_command, childargv); fprintf(stderr, "Could not execute %s!\n", argv[3]); return -1; } }