Tuple *flac_probe_for_tuple(const char *filename, VFSFile *fd) { AUDDBG("Probe for tuple.\n"); Tuple *tuple = NULL; FLAC__Metadata_Iterator *iter; FLAC__Metadata_Chain *chain; FLAC__StreamMetadata *metadata = NULL; FLAC__Metadata_ChainStatus status; FLAC__StreamMetadata_VorbisComment_Entry *entry; char *key; char *value; tuple = tuple_new_from_filename(filename); tuple_set_str(tuple, FIELD_CODEC, NULL, "Free Lossless Audio Codec (FLAC)"); tuple_set_str(tuple, FIELD_QUALITY, NULL, _("lossless")); chain = FLAC__metadata_chain_new(); if (!FLAC__metadata_chain_read_with_callbacks(chain, fd, io_callbacks)) goto ERR; iter = FLAC__metadata_iterator_new(); FLAC__metadata_iterator_init(iter, chain); do { switch (FLAC__metadata_iterator_get_block_type(iter)) { case FLAC__METADATA_TYPE_VORBIS_COMMENT: if (FLAC__metadata_iterator_get_block_type(iter) == FLAC__METADATA_TYPE_VORBIS_COMMENT) { metadata = FLAC__metadata_iterator_get_block(iter); AUDDBG("Vorbis comment contains %d fields\n", metadata->data.vorbis_comment.num_comments); AUDDBG("Vendor string: %s\n", metadata->data.vorbis_comment.vendor_string.entry); entry = metadata->data.vorbis_comment.comments; for (int i = 0; i < metadata->data.vorbis_comment.num_comments; i++, entry++) { if (FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(*entry, &key, &value) == false) AUDDBG("Could not parse comment\n"); else { parse_comment(tuple, key, value); free(key); free(value); } } } break; case FLAC__METADATA_TYPE_STREAMINFO: metadata = FLAC__metadata_iterator_get_block(iter); /* Calculate the stream length (milliseconds) */ if (metadata->data.stream_info.sample_rate == 0) { FLACNG_ERROR("Invalid sample rate for stream!\n"); tuple_set_int(tuple, FIELD_LENGTH, NULL, -1); } else { tuple_set_int(tuple, FIELD_LENGTH, NULL, (metadata->data.stream_info.total_samples / metadata->data.stream_info.sample_rate) * 1000); AUDDBG("Stream length: %d seconds\n", tuple_get_int(tuple, FIELD_LENGTH, NULL)); } int64_t size = vfs_fsize(fd); if (size == -1 || metadata->data.stream_info.total_samples == 0) tuple_set_int(tuple, FIELD_BITRATE, NULL, 0); else { int bitrate = 8 * size * (int64_t) metadata->data.stream_info.sample_rate / metadata->data.stream_info.total_samples; tuple_set_int(tuple, FIELD_BITRATE, NULL, (bitrate + 500) / 1000); } break; default: ; } } while (FLAC__metadata_iterator_next(iter)); FLAC__metadata_iterator_delete(iter); FLAC__metadata_chain_delete(chain); return tuple; ERR: status = FLAC__metadata_chain_status(chain); FLAC__metadata_chain_delete(chain); FLACNG_ERROR("An error occured: %s\n", FLAC__Metadata_ChainStatusString[status]); return tuple; }
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; }