CFErrorRef SFB::CreateErrorForURL(CFStringRef domain, CFIndex code, CFStringRef descriptionFormatStringForURL, CFURLRef url, CFStringRef failureReason, CFStringRef recoverySuggestion) { if(nullptr == domain) return nullptr; SFB::CFMutableDictionary errorDictionary(0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if(!errorDictionary) return nullptr; if(descriptionFormatStringForURL && url) { CFDictionarySetValue(errorDictionary, kCFErrorURLKey, url); SFB::CFString displayName(CreateDisplayNameForURL(url)); if(displayName) { SFB::CFString description(nullptr, descriptionFormatStringForURL, displayName.Object()); if(description) CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, description); } } if(failureReason) CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, failureReason); if(recoverySuggestion) CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, recoverySuggestion); return CFErrorCreate(kCFAllocatorDefault, domain, code, errorDictionary); }
bool MonkeysAudioMetadata::ReadMetadata(CFErrorRef *error) { // Start from scratch CFDictionaryRemoveAllValues(mMetadata); CFDictionaryRemoveAllValues(mChangedMetadata); UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; TagLib::IOStream *stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf), true); TagLib::APE::File file(stream); if(!file.isValid()) { if(NULL != error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Monkey's Audio file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a Monkey's Audio 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("Monkey's Audio")); if(file.audioProperties()) { AddAudioPropertiesToDictionary(mMetadata, file.audioProperties()); if(0 != file.audioProperties()->bitsPerSample()) { int value = file.audioProperties()->bitsPerSample(); CFNumberRef bitsPerChannel = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value); CFDictionarySetValue(mMetadata, kPropertiesBitsPerChannelKey, bitsPerChannel); CFRelease(bitsPerChannel), bitsPerChannel = NULL; } } if(file.ID3v1Tag()) AddID3v1TagToDictionary(mMetadata, file.ID3v1Tag()); if(file.APETag()) AddAPETagToDictionary(mMetadata, file.APETag()); return true; }
bool MonkeysAudioMetadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; TagLib::IOStream *stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf)); TagLib::APE::File file(stream, false); if(!file.isValid()) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Mpnkey's Audio file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a Mpnkey's Audio 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } // Although both ID3v1 and APE tags are read, only APE tags are written if(file.APETag()) SetAPETagFromMetadata(*this, file.APETag()); if(!file.save()) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid Mpnkey's Audio 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; } return false; } MergeChangedMetadataIntoMetadata(); return true; }
bool OggSpeexDecoder::Open(CFErrorRef *error) { if(IsOpen()) { LOGGER_WARNING("org.sbooth.AudioEngine.AudioDecoder.OggSpeex", "Open() called on an AudioDecoder that is already open"); return true; } // Ensure the input source is open if(!mInputSource->IsOpen() && !mInputSource->Open(error)) return false; // Initialize Ogg data struct ogg_sync_init(&mOggSyncState); // Get the ogg buffer for writing char *data = ogg_sync_buffer(&mOggSyncState, READ_SIZE_BYTES); // Read bitstream from input file ssize_t bytesRead = GetInputSource()->Read(data, READ_SIZE_BYTES); if(-1 == bytesRead) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” could not be read."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Read error"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("Unable to read from the input file."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } ogg_sync_destroy(&mOggSyncState); return false; } // Tell the sync layer how many bytes were written to its internal buffer int result = ogg_sync_wrote(&mOggSyncState, bytesRead); if(-1 == result) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” does not appear to be an Ogg file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an Ogg 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderFileFormatNotRecognizedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } ogg_sync_destroy(&mOggSyncState); return false; } // Turn the data we wrote into an ogg page result = ogg_sync_pageout(&mOggSyncState, &mOggPage); if(1 != result) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” does not appear to be an Ogg file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an Ogg 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderFileFormatNotRecognizedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } ogg_sync_destroy(&mOggSyncState); return false; } // Initialize the stream and grab the serial number ogg_stream_init(&mOggStreamState, ogg_page_serialno(&mOggPage)); // Get the first Ogg page result = ogg_stream_pagein(&mOggStreamState, &mOggPage); if(0 != result) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” does not appear to be an Ogg file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an Ogg 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderFileFormatNotRecognizedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } ogg_sync_destroy(&mOggSyncState); return false; } // Get the first packet (should be the header) from the page ogg_packet op; result = ogg_stream_packetout(&mOggStreamState, &op); if(1 != result) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” does not appear to be an Ogg file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an Ogg 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderFileFormatNotRecognizedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } ogg_sync_destroy(&mOggSyncState); return false; } if(op.bytes >= 5 && !memcmp(op.packet, "Speex", 5)) mSpeexSerialNumber = mOggStreamState.serialno; ++mOggPacketCount; // Convert the packet to the Speex header SpeexHeader *header = speex_packet_to_header((char *)op.packet, static_cast<int>(op.bytes)); if(NULL == header) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” does not appear to be an Ogg Speex file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an Ogg Speex 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderFileFormatNotRecognizedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } ogg_sync_destroy(&mOggSyncState); return false; } else if(SPEEX_NB_MODES <= header->mode) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The Speex mode in the file “%@” is not supported."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Unsupported Ogg Speex file mode"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("This file may have been encoded with a newer version of Speex."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderFileFormatNotSupportedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } speex_header_free(header), header = NULL; ogg_sync_destroy(&mOggSyncState); return false; } const SpeexMode *mode = speex_lib_get_mode(header->mode); if(mode->bitstream_version != header->mode_bitstream_version) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The Speex version in the file “%@” is not supported."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Unsupported Ogg Speex file version"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("This file was encoded with a different version of Speex."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderFileFormatNotSupportedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } speex_header_free(header), header = NULL; ogg_sync_destroy(&mOggSyncState); return false; } // Initialize the decoder mSpeexDecoder = speex_decoder_init(mode); if(NULL== mSpeexDecoder) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, CFCopyLocalizedString(CFSTR("Unable to initialize the Speex decoder."), "")); // CFDictionarySetValue(errorDictionary, // kCFErrorLocalizedFailureReasonKey, // CFCopyLocalizedString(CFSTR("Unsupported Ogg Speex file version"), "")); // CFDictionarySetValue(errorDictionary, // kCFErrorLocalizedRecoverySuggestionKey, // CFCopyLocalizedString(CFSTR("This file was encoded with a different version of Speex."), "")); *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } speex_header_free(header), header = NULL; ogg_sync_destroy(&mOggSyncState); return false; } speex_decoder_ctl(mSpeexDecoder, SPEEX_SET_SAMPLING_RATE, &header->rate); mSpeexFramesPerOggPacket = (0 == header->frames_per_packet ? 1 : header->frames_per_packet); mExtraSpeexHeaderCount = header->extra_headers; // Initialize the speex bit-packing data structure speex_bits_init(&mSpeexBits); // Initialize the stereo mode mSpeexStereoState = speex_stereo_state_init(); if(2 == header->nb_channels) { SpeexCallback callback; callback.callback_id = SPEEX_INBAND_STEREO; callback.func = speex_std_stereo_request_handler; callback.data = mSpeexStereoState; speex_decoder_ctl(mSpeexDecoder, SPEEX_SET_HANDLER, &callback); } // Canonical Core Audio format mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; mFormat.mBitsPerChannel = 8 * sizeof(float); mFormat.mSampleRate = header->rate; mFormat.mChannelsPerFrame = header->nb_channels; mFormat.mBytesPerPacket = (mFormat.mBitsPerChannel / 8); mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; // Set up the source format mSourceFormat.mFormatID = 'SPEE'; mSourceFormat.mSampleRate = header->rate; mSourceFormat.mChannelsPerFrame = header->nb_channels; switch(header->nb_channels) { case 1: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Mono); break; case 2: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Stereo); break; } speex_header_free(header), header = NULL; // Allocate the buffer list spx_int32_t speexFrameSize = 0; speex_decoder_ctl(mSpeexDecoder, SPEEX_GET_FRAME_SIZE, &speexFrameSize); mBufferList = AllocateABL(mFormat, speexFrameSize); if(NULL == mBufferList) { if(error) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, NULL); speex_header_free(header), header = NULL; speex_stereo_state_destroy(mSpeexStereoState), mSpeexStereoState = NULL; speex_decoder_destroy(mSpeexDecoder), mSpeexDecoder = NULL; speex_bits_destroy(&mSpeexBits); ogg_sync_destroy(&mOggSyncState); return false; } for(UInt32 i = 0; i < mBufferList->mNumberBuffers; ++i) mBufferList->mBuffers[i].mDataByteSize = 0; mIsOpen = true; return true; }
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; }
bool MODDecoder::Open(CFErrorRef *error) { if(IsOpen()) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioDecoder.MOD"); LOG4CXX_WARN(logger, "Open() called on an AudioDecoder that is already open"); return true; } // Ensure the input source is open if(!mInputSource->IsOpen() && !mInputSource->Open(error)) return false; dfs.open = NULL; dfs.skip = skip_callback; dfs.getc = getc_callback; dfs.getnc = getnc_callback; dfs.close = close_callback; df = dumbfile_open_ex(this, &dfs); if(NULL == df) { return false; } CFStringRef fileSystemPath = CFURLCopyFileSystemPath(GetURL(), kCFURLPOSIXPathStyle); CFStringRef extension = NULL; CFRange range; if(CFStringFindWithOptionsAndLocale(fileSystemPath, CFSTR("."), CFRangeMake(0, CFStringGetLength(fileSystemPath)), kCFCompareBackwards, CFLocaleGetSystem(), &range)) { extension = CFStringCreateWithSubstring(kCFAllocatorDefault, fileSystemPath, CFRangeMake(range.location + 1, CFStringGetLength(fileSystemPath) - range.location - 1)); } CFRelease(fileSystemPath), fileSystemPath = NULL; if(NULL == extension) { return false; } // Attempt to create the appropriate decoder based on the file's extension if(kCFCompareEqualTo == CFStringCompare(extension, CFSTR("it"), kCFCompareCaseInsensitive)) duh = dumb_read_it(df); else if(kCFCompareEqualTo == CFStringCompare(extension, CFSTR("xm"), kCFCompareCaseInsensitive)) duh = dumb_read_xm(df); else if(kCFCompareEqualTo == CFStringCompare(extension, CFSTR("s3m"), kCFCompareCaseInsensitive)) duh = dumb_read_s3m(df); else if(kCFCompareEqualTo == CFStringCompare(extension, CFSTR("mod"), kCFCompareCaseInsensitive)) duh = dumb_read_mod(df); CFRelease(extension), extension = NULL; if(NULL == duh) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MOD file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a MOD 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } if(df) dumbfile_close(df), df = NULL; return false; } mTotalFrames = duh_get_length(duh); dsr = duh_start_sigrenderer(duh, 0, 2, 0); if(NULL == dsr) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid MOD file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a MOD 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } if(df) dumbfile_close(df), df = NULL; if(duh) unload_duh(duh), duh = NULL; return false; } // Generate interleaved 2 channel 44.1 16-bit output mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; mFormat.mSampleRate = DUMB_SAMPLE_RATE; mFormat.mChannelsPerFrame = DUMB_CHANNELS; mFormat.mBitsPerChannel = DUMB_BIT_DEPTH; mFormat.mBytesPerPacket = (mFormat.mBitsPerChannel / 8) * mFormat.mChannelsPerFrame; mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; // Set up the source format mSourceFormat.mFormatID = 'MOD '; mSourceFormat.mSampleRate = DUMB_SAMPLE_RATE; mSourceFormat.mChannelsPerFrame = DUMB_CHANNELS; // Setup the channel layout mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Stereo); mIsOpen = true; return true; }
bool MP4Metadata::ReadMetadata(CFErrorRef *error) { // Start from scratch CFDictionaryRemoveAllValues(mMetadata); CFDictionaryRemoveAllValues(mChangedMetadata); UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, FALSE, buf, PATH_MAX)) return false; // Open the file for reading MP4FileHandle file = MP4Read(reinterpret_cast<const char *>(buf)); if(MP4_INVALID_FILE_HANDLE == file) { if(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 MPEG-4 file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an MPEG-4 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataFileFormatNotRecognizedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } // Read the properties if(0 < MP4GetNumberOfTracks(file)) { // Should be type 'soun', media data name'mp4a' MP4TrackId trackID = MP4FindTrackId(file, 0); // Verify this is an MPEG-4 audio file if(MP4_INVALID_TRACK_ID == trackID || strncmp("soun", MP4GetTrackType(file, trackID), 4)) { MP4Close(file), file = NULL; if(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 MPEG-4 file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an MPEG-4 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataFileFormatNotSupportedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } MP4Duration mp4Duration = MP4GetTrackDuration(file, trackID); uint32_t mp4TimeScale = MP4GetTrackTimeScale(file, trackID); CFNumberRef totalFrames = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &mp4Duration); CFDictionarySetValue(mMetadata, kPropertiesTotalFramesKey, totalFrames); CFRelease(totalFrames), totalFrames = NULL; CFNumberRef sampleRate = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &mp4TimeScale); CFDictionarySetValue(mMetadata, kPropertiesSampleRateKey, sampleRate); CFRelease(sampleRate), sampleRate = NULL; double length = static_cast<double>(mp4Duration / mp4TimeScale); CFNumberRef duration = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &length); CFDictionarySetValue(mMetadata, kPropertiesDurationKey, duration); CFRelease(duration), duration = NULL; // "mdia.minf.stbl.stsd.*[0].channels" int channels = MP4GetTrackAudioChannels(file, trackID); CFNumberRef channelsPerFrame = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &channels); CFDictionaryAddValue(mMetadata, kPropertiesChannelsPerFrameKey, channelsPerFrame); CFRelease(channelsPerFrame), channelsPerFrame = NULL; // ALAC files if(MP4HaveTrackAtom(file, trackID, "mdia.minf.stbl.stsd.alac")) { CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("Apple Lossless")); uint64_t sampleSize; uint8_t *decoderConfig; uint32_t decoderConfigSize; if(MP4GetTrackBytesProperty(file, trackID, "mdia.minf.stbl.stsd.alac.alac.decoderConfig", &decoderConfig, &decoderConfigSize) && 28 <= decoderConfigSize) { // The ALAC magic cookie seems to have the following layout (28 bytes, BE): // Byte 10: Sample size // Bytes 25-28: Sample rate CFNumberRef bitsPerChannel = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, decoderConfig + 9); CFDictionaryAddValue(mMetadata, kPropertiesBitsPerChannelKey, bitsPerChannel); CFRelease(bitsPerChannel), bitsPerChannel = NULL; double losslessBitrate = static_cast<double>(mp4TimeScale * channels * sampleSize) / 1000; CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &losslessBitrate); CFDictionarySetValue(mMetadata, kPropertiesBitrateKey, bitrate); CFRelease(bitrate), bitrate = NULL; free(decoderConfig), decoderConfig = NULL; } else if(MP4GetTrackIntegerProperty(file, trackID, "mdia.minf.stbl.stsd.alac.sampleSize", &sampleSize)) { CFNumberRef bitsPerChannel = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &sampleSize); CFDictionaryAddValue(mMetadata, kPropertiesBitsPerChannelKey, bitsPerChannel); CFRelease(bitsPerChannel), bitsPerChannel = NULL; double losslessBitrate = static_cast<double>(mp4TimeScale * channels * sampleSize) / 1000; CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &losslessBitrate); CFDictionarySetValue(mMetadata, kPropertiesBitrateKey, bitrate); CFRelease(bitrate), bitrate = NULL; } } // AAC files if(MP4HaveTrackAtom(file, trackID, "mdia.minf.stbl.stsd.mp4a")) { CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("AAC")); // "mdia.minf.stbl.stsd.*.esds.decConfigDescr.avgBitrate" uint32_t trackBitrate = MP4GetTrackBitRate(file, trackID) / 1000; CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &trackBitrate); CFDictionaryAddValue(mMetadata, kPropertiesBitrateKey, bitrate); CFRelease(bitrate), bitrate = NULL; } } // No valid tracks in file else { MP4Close(file), file = NULL; if(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 MPEG-4 file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an MPEG-4 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataFileFormatNotSupportedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } // Read the tags const MP4Tags *tags = MP4TagsAlloc(); if(NULL == tags) { MP4Close(file), file = NULL; if(error) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, NULL); return false; } MP4TagsFetch(tags, file); // Album title if(tags->album) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->album, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataAlbumTitleKey, str); CFRelease(str), str = NULL; } // Artist if(tags->artist) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->artist, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataArtistKey, str); CFRelease(str), str = NULL; } // Album Artist if(tags->albumArtist) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->albumArtist, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataAlbumArtistKey, str); CFRelease(str), str = NULL; } // Genre if(tags->genre) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->genre, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataGenreKey, str); CFRelease(str), str = NULL; } // Release date if(tags->releaseDate) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->releaseDate, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataReleaseDateKey, str); CFRelease(str), str = NULL; } // Composer if(tags->composer) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->composer, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataComposerKey, str); CFRelease(str), str = NULL; } // Comment if(tags->comments) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->comments, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataCommentKey, str); CFRelease(str), str = NULL; } // Track title if(tags->name) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->name, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataTitleKey, str); CFRelease(str), str = NULL; } // Track number if(tags->track) { if(tags->track->index) { CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->track->index); CFDictionarySetValue(mMetadata, kMetadataTrackNumberKey, num); CFRelease(num), num = NULL; } if(tags->track->total) { CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->track->total); CFDictionarySetValue(mMetadata, kMetadataTrackTotalKey, num); CFRelease(num), num = NULL; } } // Disc number if(tags->disk) { if(tags->disk->index) { CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->disk->index); CFDictionarySetValue(mMetadata, kMetadataDiscNumberKey, num); CFRelease(num), num = NULL; } if(tags->disk->total) { CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &tags->disk->total); CFDictionarySetValue(mMetadata, kMetadataDiscTotalKey, num); CFRelease(num), num = NULL; } } // Compilation if(tags->compilation) CFDictionarySetValue(mMetadata, kMetadataCompilationKey, *(tags->compilation) ? kCFBooleanTrue : kCFBooleanFalse); // BPM if(tags->tempo) { } // Lyrics if(tags->lyrics) { CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, tags->lyrics, kCFStringEncodingUTF8); CFDictionarySetValue(mMetadata, kMetadataLyricsKey, str); CFRelease(str), str = NULL; } // Album art if(tags->artworkCount) { for(uint32_t i = 0; i < tags->artworkCount; ++i) { CFDataRef data = CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(tags->artwork[i].data), tags->artwork[i].size); CFDictionarySetValue(mMetadata, kAlbumArtFrontCoverKey, data); CFRelease(data), data = NULL; } } // ReplayGain // Reference loudness MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_reference_loudness"); if(NULL != items) { float referenceLoudnessValue; if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &referenceLoudnessValue)) { CFNumberRef referenceLoudness = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &referenceLoudnessValue); CFDictionaryAddValue(mMetadata, kReplayGainReferenceLoudnessKey, referenceLoudness); CFRelease(referenceLoudness), referenceLoudness = NULL; } MP4ItmfItemListFree(items), items = NULL; } // Track gain items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_gain"); if(NULL != items) { float trackGainValue; if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &trackGainValue)) { CFNumberRef trackGain = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &trackGainValue); CFDictionaryAddValue(mMetadata, kReplayGainTrackGainKey, trackGain); CFRelease(trackGain), trackGain = NULL; } MP4ItmfItemListFree(items), items = NULL; } // Track peak items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_peak"); if(NULL != items) { float trackPeakValue; if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &trackPeakValue)) { CFNumberRef trackPeak = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &trackPeakValue); CFDictionaryAddValue(mMetadata, kReplayGainTrackPeakKey, trackPeak); CFRelease(trackPeak), trackPeak = NULL; } MP4ItmfItemListFree(items), items = NULL; } // Album gain items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_gain"); if(NULL != items) { float albumGainValue; if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &albumGainValue)) { CFNumberRef albumGain = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &albumGainValue); CFDictionaryAddValue(mMetadata, kReplayGainAlbumGainKey, albumGain); CFRelease(albumGain), albumGain = NULL; } MP4ItmfItemListFree(items), items = NULL; } // Album peak items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_peak"); if(NULL != items) { float albumPeakValue; if(1 <= items->size && 1 <= items->elements[0].dataList.size && sscanf(reinterpret_cast<const char *>(items->elements[0].dataList.elements[0].value), "%f", &albumPeakValue)) { CFNumberRef albumPeak = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &albumPeakValue); CFDictionaryAddValue(mMetadata, kReplayGainAlbumPeakKey, albumPeak); CFRelease(albumPeak), albumPeak = NULL; } MP4ItmfItemListFree(items), items = NULL; } // Clean up MP4TagsFree(tags), tags = NULL; MP4Close(file), file = NULL; return true; }
bool MP4Metadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; // Open the file for modification MP4FileHandle file = MP4Modify(reinterpret_cast<const char *>(buf)); if(MP4_INVALID_FILE_HANDLE == file) { if(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 MPEG-4 file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not an MPEG 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } // Read the tags const MP4Tags *tags = MP4TagsAlloc(); if(NULL == tags) { MP4Close(file), file = NULL; if(error) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, NULL); return false; } MP4TagsFetch(tags, file); // Album Title CFStringRef str = GetAlbumTitle(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetAlbum(tags, cString); } else MP4TagsSetAlbum(tags, NULL); // Artist str = GetArtist(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetArtist(tags, cString); } else MP4TagsSetArtist(tags, NULL); // Album Artist str = GetAlbumArtist(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetAlbumArtist(tags, cString); } else MP4TagsSetAlbumArtist(tags, NULL); // Genre str = GetGenre(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetGenre(tags, cString); } else MP4TagsSetGenre(tags, NULL); // Release date str = GetReleaseDate(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetReleaseDate(tags, cString); } else MP4TagsSetReleaseDate(tags, NULL); // Composer str = GetComposer(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetComposer(tags, cString); } else MP4TagsSetComposer(tags, NULL); // Comment str = GetComment(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetComments(tags, cString); } else MP4TagsSetComments(tags, NULL); // Track title str = GetTitle(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetName(tags, cString); } else MP4TagsSetName(tags, NULL); // Track number and total MP4TagTrack trackInfo; memset(&trackInfo, 0, sizeof(MP4TagTrack)); if(GetTrackNumber()) CFNumberGetValue(GetTrackNumber(), kCFNumberSInt32Type, &trackInfo.index); if(GetTrackTotal()) CFNumberGetValue(GetTrackTotal(), kCFNumberSInt32Type, &trackInfo.total); MP4TagsSetTrack(tags, &trackInfo); // Disc number and total MP4TagDisk discInfo; memset(&discInfo, 0, sizeof(MP4TagDisk)); if(GetDiscNumber()) CFNumberGetValue(GetDiscNumber(), kCFNumberSInt32Type, &discInfo.index); if(GetDiscTotal()) CFNumberGetValue(GetDiscTotal(), kCFNumberSInt32Type, &discInfo.total); MP4TagsSetDisk(tags, &discInfo); // Compilation if(GetCompilation()) { uint8_t comp = CFBooleanGetValue(GetCompilation()); MP4TagsSetCompilation(tags, &comp); } else MP4TagsSetCompilation(tags, NULL); // Lyrics str = GetLyrics(); if(str) { CFIndex cStringSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); char cString [cStringSize + 1]; if(!CFStringGetCString(str, cString, cStringSize + 1, kCFStringEncodingUTF8)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } MP4TagsSetLyrics(tags, cString); } else MP4TagsSetLyrics(tags, NULL); // Album art CFDataRef artData = GetFrontCoverArt(); if(artData) { MP4TagArtwork artwork; artwork.data = reinterpret_cast<void *>(const_cast<UInt8 *>(CFDataGetBytePtr(artData))); artwork.size = static_cast<uint32_t>(CFDataGetLength(artData)); artwork.type = MP4_ART_UNDEFINED; MP4TagsAddArtwork(tags, &artwork); } // Save our changes MP4TagsStore(tags, file); // Replay Gain // Reference loudness if(GetReplayGainReferenceLoudness()) { float f; if(!CFNumberGetValue(GetReplayGainReferenceLoudness(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); return false; } char value [8]; snprintf(value, sizeof(value), "%2.1f dB", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_reference_loudness"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_reference_loudness"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Track gain if(GetReplayGainTrackGain()) { float f; if(!CFNumberGetValue(GetReplayGainTrackGain(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [10]; snprintf(value, sizeof(value), "%+2.2f dB", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_track_gain"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_gain"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Track peak if(GetReplayGainTrackPeak()) { float f; if(!CFNumberGetValue(GetReplayGainTrackPeak(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [12]; snprintf(value, sizeof(value), "%1.8f", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_track_peak"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_track_peak"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Album gain if(GetReplayGainAlbumGain()) { float f; if(!CFNumberGetValue(GetReplayGainAlbumGain(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [10]; snprintf(value, sizeof(value), "%+2.2f dB", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_album_gain"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_gain"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Album peak if(GetReplayGainAlbumPeak()) { float f; if(!CFNumberGetValue(GetReplayGainAlbumPeak(), kCFNumberFloatType, &f)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.MP4"); LOG4CXX_WARN(logger, "CFNumberGetValue() failed"); return false; } char value [12]; snprintf(value, sizeof(value), "%1.8f", f); MP4ItmfItem *item = MP4ItmfItemAlloc("----", 1); if(NULL != item) { item->mean = strdup("com.apple.iTunes"); item->name = strdup("replaygain_album_peak"); item->dataList.elements[0].typeCode = MP4_ITMF_BT_UTF8; item->dataList.elements[0].value = reinterpret_cast<uint8_t *>(strdup(value)); item->dataList.elements[0].valueSize = static_cast<uint32_t>(strlen(value)); } } else { MP4ItmfItemList *items = MP4ItmfGetItemsByMeaning(file, "com.apple.iTunes", "replaygain_album_peak"); if(items) { for(uint32_t i = 0; i < items->size; ++i) MP4ItmfRemoveItem(file, items->elements + i); } MP4ItmfItemListFree(items), items = NULL; } // Clean up MP4TagsFree(tags), tags = NULL; MP4Close(file), file = NULL; MergeChangedMetadataIntoMetadata(); return true; }
bool WavPackDecoder::Open(CFErrorRef *error) { if(IsOpen()) { LOGGER_WARNING("org.sbooth.AudioEngine.AudioDecoder.WavPack", "Open() called on an AudioDecoder that is already open"); return true; } // Ensure the input source is open if(!mInputSource->IsOpen() && !mInputSource->Open(error)) return false; mStreamReader.read_bytes = read_bytes_callback; mStreamReader.get_pos = get_pos_callback; mStreamReader.set_pos_abs = set_pos_abs_callback; mStreamReader.set_pos_rel = set_pos_rel_callback; mStreamReader.push_back_byte = push_back_byte_callback; mStreamReader.get_length = get_length_callback; mStreamReader.can_seek = can_seek_callback; char errorBuf [80]; // Setup converter mWPC = WavpackOpenFileInputEx(&mStreamReader, this, NULL, errorBuf, OPEN_WVC | OPEN_NORMALIZE, 0); if(NULL == mWPC) { if(error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mInputSource->GetURL()); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid WavPack file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a WavPack 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; *error = CFErrorCreate(kCFAllocatorDefault, AudioDecoderErrorDomain, AudioDecoderInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } return false; } // Floating-point and lossy files will be handed off in the canonical Core Audio format int mode = WavpackGetMode(mWPC); if(MODE_FLOAT & mode || !(MODE_LOSSLESS & mode)) { // Canonical Core Audio format mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; mFormat.mSampleRate = WavpackGetSampleRate(mWPC); mFormat.mChannelsPerFrame = WavpackGetNumChannels(mWPC); mFormat.mBitsPerChannel = 8 * sizeof(float); mFormat.mBytesPerPacket = (mFormat.mBitsPerChannel / 8); mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; } else { mFormat.mFormatID = kAudioFormatLinearPCM; mFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsAlignedHigh | kAudioFormatFlagIsNonInterleaved; mFormat.mSampleRate = WavpackGetSampleRate(mWPC); mFormat.mChannelsPerFrame = WavpackGetNumChannels(mWPC); mFormat.mBitsPerChannel = WavpackGetBitsPerSample(mWPC); mFormat.mBytesPerPacket = sizeof(int32_t); mFormat.mFramesPerPacket = 1; mFormat.mBytesPerFrame = mFormat.mBytesPerPacket * mFormat.mFramesPerPacket; mFormat.mReserved = 0; } mTotalFrames = WavpackGetNumSamples(mWPC); // Set up the source format mSourceFormat.mFormatID = 'WVPK'; mSourceFormat.mSampleRate = WavpackGetSampleRate(mWPC); mSourceFormat.mChannelsPerFrame = WavpackGetNumChannels(mWPC); mSourceFormat.mBitsPerChannel = WavpackGetBitsPerSample(mWPC); // Setup the channel layout switch(mFormat.mChannelsPerFrame) { case 1: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Mono); break; case 2: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Stereo); break; case 4: mChannelLayout = CreateChannelLayoutWithTag(kAudioChannelLayoutTag_Quadraphonic); break; } mBuffer = static_cast<int32_t *>(calloc(BUFFER_SIZE_FRAMES * mFormat.mChannelsPerFrame, sizeof(int32_t))); if(NULL == mBuffer) { if(error) *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainPOSIX, ENOMEM, NULL); return false; } mIsOpen = true; return true; }