FLAC__bool do_major_operation_on_file(const char *filename, const CommandLineOptions *options) { FLAC__bool ok = true, needs_write = false, is_ogg = false; FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); if(0 == chain) die("out of memory allocating chain"); /*@@@@ lame way of guessing the file type */ if(strlen(filename) >= 4 && 0 == strcmp(filename+strlen(filename)-4, ".ogg")) is_ogg = true; if(! (is_ogg? FLAC__metadata_chain_read_ogg(chain, filename) : FLAC__metadata_chain_read(chain, filename)) ) { print_error_with_chain_status(chain, "%s: ERROR: reading metadata", filename); FLAC__metadata_chain_delete(chain); return false; } switch(options->ops.operations[0].type) { case OP__LIST: ok = do_major_operation__list(options->prefix_with_filename? filename : 0, chain, options); break; case OP__APPEND: ok = do_major_operation__append(chain, options); needs_write = true; break; case OP__REMOVE: ok = do_major_operation__remove(chain, options); needs_write = true; break; case OP__REMOVE_ALL: ok = do_major_operation__remove_all(chain, options); needs_write = true; break; case OP__MERGE_PADDING: FLAC__metadata_chain_merge_padding(chain); needs_write = true; break; case OP__SORT_PADDING: FLAC__metadata_chain_sort_padding(chain); needs_write = true; break; default: FLAC__ASSERT(0); return false; } if(ok && needs_write) { if(options->use_padding) FLAC__metadata_chain_sort_padding(chain); ok = FLAC__metadata_chain_write(chain, options->use_padding, options->preserve_modtime); if(!ok) print_error_with_chain_status(chain, "%s: ERROR: writing FLAC file", filename); } FLAC__metadata_chain_delete(chain); return ok; }
static const char *store_to_file_pre_(const char *filename, FLAC__Metadata_Chain **chain, FLAC__StreamMetadata **block) { FLAC__Metadata_Iterator *iterator; const char *error; FLAC__bool found_vc_block = false; if(0 == (*chain = FLAC__metadata_chain_new())) return "memory allocation error"; if(!FLAC__metadata_chain_read(*chain, filename)) { error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; FLAC__metadata_chain_delete(*chain); return error; } if(0 == (iterator = FLAC__metadata_iterator_new())) { FLAC__metadata_chain_delete(*chain); return "memory allocation error"; } FLAC__metadata_iterator_init(iterator, *chain); do { *block = FLAC__metadata_iterator_get_block(iterator); if((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) found_vc_block = true; } while(!found_vc_block && FLAC__metadata_iterator_next(iterator)); if(!found_vc_block) { /* create a new block */ *block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); if(0 == *block) { FLAC__metadata_chain_delete(*chain); FLAC__metadata_iterator_delete(iterator); return "memory allocation error"; } while(FLAC__metadata_iterator_next(iterator)) ; if(!FLAC__metadata_iterator_insert_block_after(iterator, *block)) { error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; FLAC__metadata_chain_delete(*chain); FLAC__metadata_iterator_delete(iterator); return error; } /* iterator is left pointing to new block */ FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == *block); } FLAC__metadata_iterator_delete(iterator); FLAC__ASSERT(0 != *block); FLAC__ASSERT((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); return 0; }
FLAC__bool do_shorthand_operations_on_file(const char *filename, const CommandLineOptions *options) { unsigned i; FLAC__bool ok = true, needs_write = false, use_padding = options->use_padding; FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); if(0 == chain) die("out of memory allocating chain"); if(!FLAC__metadata_chain_read(chain, filename)) { print_error_with_chain_status(chain, "%s: ERROR: reading metadata", filename); return false; } for(i = 0; i < options->ops.num_operations && ok; i++) { /* * Do OP__ADD_SEEKPOINT last to avoid decoding twice if both * --add-seekpoint and --import-cuesheet-from are used. */ if(options->ops.operations[i].type != OP__ADD_SEEKPOINT) ok &= do_shorthand_operation(filename, options->prefix_with_filename, chain, &options->ops.operations[i], &needs_write, options->utf8_convert); /* The following seems counterintuitive but the meaning * of 'use_padding' is 'try to keep the overall metadata * to its original size, adding or truncating extra * padding if necessary' which is why we need to turn it * off in this case. If we don't, the extra padding block * will just be truncated. */ if(options->ops.operations[i].type == OP__ADD_PADDING) use_padding = false; } /* * Do OP__ADD_SEEKPOINT last to avoid decoding twice if both * --add-seekpoint and --import-cuesheet-from are used. */ for(i = 0; i < options->ops.num_operations && ok; i++) { if(options->ops.operations[i].type == OP__ADD_SEEKPOINT) ok &= do_shorthand_operation(filename, options->prefix_with_filename, chain, &options->ops.operations[i], &needs_write, options->utf8_convert); } if(ok && needs_write) { if(use_padding) FLAC__metadata_chain_sort_padding(chain); ok = FLAC__metadata_chain_write(chain, use_padding, options->preserve_modtime); if(!ok) print_error_with_chain_status(chain, "%s: ERROR: writing FLAC file", filename); } FLAC__metadata_chain_delete(chain); return ok; }
bool FLACMetadata::WriteMetadata(CFErrorRef *error) { 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_chain_sort_padding(chain); FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(NULL == iterator) { FLAC__metadata_chain_delete(chain), chain = NULL; return false; } FLAC__metadata_iterator_init(iterator, chain); // Seek to the vorbis comment block if it exists while(FLAC__METADATA_TYPE_VORBIS_COMMENT != FLAC__metadata_iterator_get_block_type(iterator)) { if(!FLAC__metadata_iterator_next(iterator)) break; // Already at end } FLAC__StreamMetadata *block = NULL; // If there isn't a vorbis comment block add one if(FLAC__METADATA_TYPE_VORBIS_COMMENT != FLAC__metadata_iterator_get_block_type(iterator)) { // The padding block will be the last block if it exists; add the comment block before it if(FLAC__METADATA_TYPE_PADDING == FLAC__metadata_iterator_get_block_type(iterator)) FLAC__metadata_iterator_prev(iterator); block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); if(NULL == block) { FLAC__metadata_chain_delete(chain), chain = NULL; FLAC__metadata_iterator_delete(iterator), iterator = NULL; return false; } // Add our metadata if(!FLAC__metadata_iterator_insert_block_after(iterator, block)) { if(NULL != error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Unable to write metadata"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } FLAC__metadata_chain_delete(chain), chain = NULL; FLAC__metadata_iterator_delete(iterator), iterator = NULL; return false; } } else block = FLAC__metadata_iterator_get_block(iterator); // Standard tags SetVorbisComment(block, "ALBUM", GetAlbumTitle()); SetVorbisComment(block, "ARTIST", GetArtist()); SetVorbisComment(block, "ALBUMARTIST", GetAlbumArtist()); SetVorbisComment(block, "COMPOSER", GetComposer()); SetVorbisComment(block, "GENRE", GetGenre()); SetVorbisComment(block, "DATE", GetReleaseDate()); SetVorbisComment(block, "DESCRIPTION", GetComment()); SetVorbisComment(block, "TITLE", GetTitle()); SetVorbisCommentNumber(block, "TRACKNUMBER", GetTrackNumber()); SetVorbisCommentNumber(block, "TRACKTOTAL", GetTrackTotal()); SetVorbisCommentBoolean(block, "COMPILATION", GetCompilation()); SetVorbisCommentNumber(block, "DISCNUMBER", GetDiscNumber()); SetVorbisCommentNumber(block, "DISCTOTAL", GetDiscTotal()); SetVorbisComment(block, "ISRC", GetISRC()); SetVorbisComment(block, "MCN", GetMCN()); // Additional metadata CFDictionaryRef additionalMetadata = GetAdditionalMetadata(); if(NULL != additionalMetadata) { CFIndex count = CFDictionaryGetCount(additionalMetadata); const void * keys [count]; const void * values [count]; CFDictionaryGetKeysAndValues(additionalMetadata, reinterpret_cast<const void **>(keys), reinterpret_cast<const void **>(values)); for(CFIndex i = 0; i < count; ++i) { CFIndex keySize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(reinterpret_cast<CFStringRef>(keys[i])), kCFStringEncodingASCII); char key [keySize + 1]; if(!CFStringGetCString(reinterpret_cast<CFStringRef>(keys[i]), key, keySize + 1, kCFStringEncodingASCII)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.FLAC"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); continue; } SetVorbisComment(block, key, reinterpret_cast<CFStringRef>(values[i])); } } // ReplayGain info SetVorbisCommentDouble(block, "REPLAYGAIN_REFERENCE_LOUDNESS", GetReplayGainReferenceLoudness(), CFSTR("%2.1f dB")); SetVorbisCommentDouble(block, "REPLAYGAIN_TRACK_GAIN", GetReplayGainReferenceLoudness(), CFSTR("%+2.2f dB")); SetVorbisCommentDouble(block, "REPLAYGAIN_TRACK_PEAK", GetReplayGainTrackGain(), CFSTR("%1.8f")); SetVorbisCommentDouble(block, "REPLAYGAIN_ALBUM_GAIN", GetReplayGainAlbumGain(), CFSTR("%+2.2f dB")); SetVorbisCommentDouble(block, "REPLAYGAIN_ALBUM_PEAK", GetReplayGainAlbumPeak(), CFSTR("%1.8f")); // Write the new metadata to the file if(!FLAC__metadata_chain_write(chain, true, false)) { if(NULL != error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Unable to write metadata"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } FLAC__metadata_chain_delete(chain), chain = NULL; FLAC__metadata_iterator_delete(iterator), iterator = NULL; return false; } FLAC__metadata_chain_delete(chain), chain = NULL; FLAC__metadata_iterator_delete(iterator), iterator = NULL; MergeChangedMetadataIntoMetadata(); return true; }
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; }
/* * Write Flac tag, using the level 2 flac interface */ gboolean Flac_Tag_Write_File_Tag (ET_File *ETFile) { File_Tag *FileTag; gchar *filename_utf8, *filename; gchar *basename_utf8; FLAC__Metadata_Chain *chain; FLAC__Metadata_Iterator *iter; FLAC__StreamMetadata_VorbisComment_Entry vce_field_vendor_string; // To save vendor string gboolean vce_field_vendor_string_found = FALSE; if (!ETFile || !ETFile->FileTag) return FALSE; FileTag = (File_Tag *)ETFile->FileTag->data; filename = ((File_Name *)ETFile->FileNameCur->data)->value; filename_utf8 = ((File_Name *)ETFile->FileNameCur->data)->value_utf8; flac_error_msg = NULL; /* libFLAC is able to detect (and skip) ID3v2 tags by itself */ // Create a new chain instance to get all blocks in one time chain = FLAC__metadata_chain_new(); if (chain == NULL || !FLAC__metadata_chain_read(chain,filename)) { if (chain == NULL) { // Error with "FLAC__metadata_chain_new" flac_error_msg = FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR]; } else { // Error with "FLAC__metadata_chain_read" FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status(chain); flac_error_msg = FLAC__Metadata_ChainStatusString[status]; FLAC__metadata_chain_delete(chain); } Log_Print(LOG_ERROR,_("ERROR while opening file: '%s' as FLAC (%s)."),filename_utf8,flac_error_msg); return FALSE; } // Create a new iterator instance for the chain iter = FLAC__metadata_iterator_new(); if (iter == NULL) { flac_error_msg = FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR]; Log_Print(LOG_ERROR,_("ERROR while opening file: '%s' as FLAC (%s)."),filename_utf8,flac_error_msg); return FALSE; } // Initialize the iterator to point to the first metadata block in the given chain. FLAC__metadata_iterator_init(iter,chain); while (FLAC__metadata_iterator_next(iter)) { //g_print("Write: %d %s -> block type: %d\n",j++,g_path_get_basename(filename),FLAC__metadata_iterator_get_block_type(iter)); // Action to do according the type switch ( FLAC__metadata_iterator_get_block_type(iter) ) { // // Delete the VORBIS_COMMENT block and convert to padding. But before, save the original vendor string. // case FLAC__METADATA_TYPE_VORBIS_COMMENT: { // Get block data FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iter); FLAC__StreamMetadata_VorbisComment *vc = &block->data.vorbis_comment; if (vc->vendor_string.entry != NULL) { // Get initial vendor string, to don't alterate it by FLAC__VENDOR_STRING when saving file vce_field_vendor_string.entry = (FLAC__byte *)g_strdup((gchar *)vc->vendor_string.entry); vce_field_vendor_string.length = strlen((gchar *)vce_field_vendor_string.entry); vce_field_vendor_string_found = TRUE; } // Free block data FLAC__metadata_iterator_delete_block(iter,true); break; } // // Delete all the PICTURE blocks, and convert to padding // #ifndef LEGACY_FLAC // For FLAC >= 1.1.3 case FLAC__METADATA_TYPE_PICTURE: { FLAC__metadata_iterator_delete_block(iter,true); break; } #endif default: break; } } // // Create and insert a new VORBISCOMMENT block // { FLAC__StreamMetadata *vc_block; // For vorbis comments FLAC__StreamMetadata_VorbisComment_Entry field; gchar *string; GList *list; // Allocate a block for Vorbis comments vc_block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); // Set the original vendor string, else will be use the version of library if (vce_field_vendor_string_found) { // must set 'copy' param to false, because the API will reuse the pointer of an empty // string (yet still return 'true', indicating it was copied); the string is free'd during // metadata_chain_delete routine FLAC__metadata_object_vorbiscomment_set_vendor_string(vc_block, vce_field_vendor_string, /*copy=*/false); } /********* * Title * *********/ if ( FileTag->title ) { Flac_Write_Delimetered_Tag(vc_block,"TITLE=",FileTag->title); } /********** * Artist * **********/ if ( FileTag->artist ) { Flac_Write_Delimetered_Tag(vc_block,"ARTIST=",FileTag->artist); } /********* * Album * *********/ if ( FileTag->album ) { Flac_Write_Delimetered_Tag(vc_block,"ALBUM=",FileTag->album); } /*************** * Disc Number * ***************/ if ( FileTag->disc_number ) { string = g_strconcat("DISCNUMBER=",FileTag->disc_number,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /******** * Year * ********/ if ( FileTag->year ) { string = g_strconcat("DATE=",FileTag->year,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /************************* * Track and Total Track * *************************/ if ( FileTag->track ) { string = g_strconcat("TRACKNUMBER=",FileTag->track,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } if ( FileTag->track_total /*&& strlen(FileTag->track_total)>0*/ ) { string = g_strconcat("TRACKTOTAL=",FileTag->track_total,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /********* * Genre * *********/ if ( FileTag->genre ) { Flac_Write_Delimetered_Tag(vc_block,"GENRE=",FileTag->genre); } /*********** * Comment * ***********/ // We write the comment using the "both" format if ( FileTag->comment ) { Flac_Write_Delimetered_Tag(vc_block,"DESCRIPTION=",FileTag->comment); Flac_Write_Delimetered_Tag(vc_block,"COMMENT=",FileTag->comment); } /************ * Composer * ************/ if ( FileTag->composer ) { Flac_Write_Delimetered_Tag(vc_block,"COMPOSER=",FileTag->composer); } /******************* * Original artist * *******************/ if ( FileTag->orig_artist ) { Flac_Write_Delimetered_Tag(vc_block,"PERFORMER=",FileTag->orig_artist); } /************* * Copyright * *************/ if ( FileTag->copyright ) { string = g_strconcat("COPYRIGHT=",FileTag->copyright,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /******* * URL * *******/ if ( FileTag->url ) { string = g_strconcat("LICENSE=",FileTag->url,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /************** * Encoded by * **************/ if ( FileTag->encoded_by ) { string = g_strconcat("ENCODED-BY=",FileTag->encoded_by,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /************************** * Set unsupported fields * **************************/ list = FileTag->other; while (list) { if (list->data) { string = (gchar*)list->data; field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); } list = list->next; } // Add the block to the the chain (so we don't need to free the block) FLAC__metadata_iterator_insert_block_after(iter, vc_block); } // // Create and insert PICTURE blocks // /*********** * Picture * ***********/ // For FLAC >= 1.1.3 #ifndef LEGACY_FLAC { Picture *pic = FileTag->picture; while (pic) { if (pic->data) { const gchar *violation; FLAC__StreamMetadata *picture_block; // For picture data Picture_Format format; // Allocate block for picture data picture_block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE); // Type picture_block->data.picture.type = pic->type; // Mime type format = Picture_Format_From_Data(pic); FLAC__metadata_object_picture_set_mime_type(picture_block, (gchar *)Picture_Mime_Type_String(format), TRUE); // Description if (pic->description) { FLAC__metadata_object_picture_set_description(picture_block, (FLAC__byte *)pic->description, TRUE); } // Resolution picture_block->data.picture.width = pic->width; picture_block->data.picture.height = pic->height; picture_block->data.picture.depth = 0; // Picture data FLAC__metadata_object_picture_set_data(picture_block, (FLAC__byte *)pic->data, (FLAC__uint32) pic->size, TRUE); if (!FLAC__metadata_object_picture_is_legal(picture_block, &violation)) { Log_Print(LOG_ERROR,_("Picture block isn't valid: '%s'"),violation); FLAC__metadata_object_delete(picture_block); } else { // Add the block to the the chain (so we don't need to free the block) FLAC__metadata_iterator_insert_block_after(iter, picture_block); } } pic = pic->next; } } #endif // Free iter FLAC__metadata_iterator_delete(iter); // // Prepare for writing tag // // Move all PADDING blocks to the end on the metadata, and merge them into a single block. FLAC__metadata_chain_sort_padding(chain); // Write tag if ( !FLAC__metadata_chain_write(chain, /*padding*/TRUE, PRESERVE_MODIFICATION_TIME) ) { // Error with "FLAC__metadata_chain_write" FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status(chain); flac_error_msg = FLAC__Metadata_ChainStatusString[status]; FLAC__metadata_chain_delete(chain); Log_Print(LOG_ERROR,_("ERROR: Failed to write comments to file '%s' (%s)."),filename_utf8,flac_error_msg); return FALSE; } else { basename_utf8 = g_path_get_basename(filename_utf8); Log_Print(LOG_OK,_("Written tag of '%s'"),basename_utf8); g_free(basename_utf8); } FLAC__metadata_chain_delete(chain); #ifdef ENABLE_MP3 /* * Write also the ID3 tags (ID3v1 and/or ID3v2) if wanted (as needed by some players) */ if (WRITE_ID3_TAGS_IN_FLAC_FILE) { Id3tag_Write_File_Tag(ETFile); } else { // Delete the ID3 tags (create a dummy ETFile for the Id3tag_... function) ET_File *ETFile_tmp = ET_File_Item_New(); File_Name *FileName_tmp = ET_File_Name_Item_New(); File_Tag *FileTag_tmp = ET_File_Tag_Item_New(); // Same file... FileName_tmp->value = g_strdup(filename); FileName_tmp->value_utf8 = g_strdup(filename_utf8); // Not necessary to fill 'value_ck' ETFile_tmp->FileNameList = g_list_append(NULL,FileName_tmp); ETFile_tmp->FileNameCur = ETFile_tmp->FileNameList; // With empty tag... ETFile_tmp->FileTagList = g_list_append(NULL,FileTag_tmp); ETFile_tmp->FileTag = ETFile_tmp->FileTagList; Id3tag_Write_File_Tag(ETFile_tmp); ET_Free_File_List_Item(ETFile_tmp); } #endif return TRUE; }