Beispiel #1
0
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;
}