void GL::Image::load(const unsigned char *buf, size_t bufSize) { CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8*)buf, (CFIndex)bufSize, kCFAllocatorNull); if (data != NULL) { CGImageSourceRef imageSource = CGImageSourceCreateWithData(data, NULL); if (imageSource != NULL) { CGImageRef img = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL); if (img != NULL) { width_ = (int)CGImageGetWidth(img); height_ = (int)CGImageGetHeight(img); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); if (colorSpace != NULL) { char *texData = (char*)calloc(width_ * height_ * 4, sizeof(char)); CGContextRef ctx = CGBitmapContextCreate(texData, width_, height_, 8, width_ * 4, colorSpace, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); if (ctx != NULL) { CGContextDrawImage(ctx, CGRectMake(0.0, 0.0, width_, height_), img); CGContextRelease(ctx); loadTextureData_(texData); } free(texData); CGColorSpaceRelease(colorSpace); } CGImageRelease(img); } CFRelease(imageSource); } CFRelease(data); } }
int DFGetImageDimensions(const void *data, size_t len, const char *ext, unsigned int *width, unsigned int *height, char **errmsg) { // FIXME: Should use ext here to determine the UTI, and pass that in the options directory // (the second parameter to CGImageSourceCreateWithData) CFDataRef cfdata = CFDataCreate(NULL,data,len); CGImageSourceRef imageSrc = CGImageSourceCreateWithData(cfdata,NULL); CFRelease(cfdata); if (imageSrc == NULL) return 0; CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSrc,0,NULL); if (properties != NULL) { CFNumberRef widthNum = CFDictionaryGetValue(properties,kCGImagePropertyPixelWidth); CFNumberRef heightNum = CFDictionaryGetValue(properties,kCGImagePropertyPixelHeight); if ((widthNum != NULL) && (heightNum != NULL)) { CFIndex widthValue = 0; CFIndex heightValue = 0; CFNumberGetValue(widthNum,kCFNumberCFIndexType,&widthValue); CFNumberGetValue(heightNum,kCFNumberCFIndexType,&heightValue); CFRelease(properties); *width = (unsigned int)widthValue; *height = (unsigned int)heightValue; return 1; } CFRelease(properties); } return 0; }
bool wxBitmapDataObject::SetData( size_t nSize, const void *pBuf ) { Clear(); if ((pBuf == NULL) || (nSize == 0)) return false; Handle picHandle = NewHandle( nSize ); memcpy( *picHandle, pBuf, nSize ); m_pictHandle = picHandle; CGImageRef cgImageRef = 0; CFDataRef data = CFDataCreateWithBytesNoCopy( kCFAllocatorDefault, (const UInt8*) pBuf, nSize, kCFAllocatorNull); CGImageSourceRef source = CGImageSourceCreateWithData( data, NULL ); if ( source ) { cgImageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL); CFRelease( source ); } CFRelease( data ); if ( cgImageRef ) { m_bitmap.Create( CGImageGetWidth(cgImageRef) , CGImageGetHeight(cgImageRef) ); CGRect r = CGRectMake( 0 , 0 , CGImageGetWidth(cgImageRef) , CGImageGetHeight(cgImageRef) ); // since our context is upside down we dont use CGContextDrawImage wxMacDrawCGImage( (CGContextRef) m_bitmap.GetHBITMAP() , &r, cgImageRef ) ; CGImageRelease(cgImageRef); cgImageRef = NULL; } return m_bitmap.IsOk(); }
bool wxBitmapDataObject::SetData( size_t nSize, const void *pBuf ) { Clear(); if ((pBuf == NULL) || (nSize == 0)) return false; Handle picHandle = NewHandle( nSize ); memcpy( *picHandle, pBuf, nSize ); m_pictHandle = picHandle; CGImageRef cgImageRef = 0; CFDataRef data = CFDataCreateWithBytesNoCopy( kCFAllocatorDefault, (const UInt8*) pBuf, nSize, kCFAllocatorNull); CGImageSourceRef source = CGImageSourceCreateWithData( data, NULL ); if ( source ) { cgImageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL); CFRelease( source ); } CFRelease( data ); if ( cgImageRef ) { m_bitmap.Create( cgImageRef ); CGImageRelease(cgImageRef); cgImageRef = NULL; } return m_bitmap.IsOk(); }
QVariant QMacPasteboardMimeTiff::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) { if(data.count() > 1) qWarning("QMacPasteboardMimeTiff: Cannot handle multiple member data"); QVariant ret; if (!canConvert(mime, flav)) return ret; const QByteArray &a = data.first(); QCFType<CGImageRef> image; #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { QCFType<CFDataRef> data = CFDataCreateWithBytesNoCopy(0, reinterpret_cast<const UInt8 *>(a.constData()), a.size(), kCFAllocatorNull); QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithData(data, 0); image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0); } else #endif { #ifdef Q_WS_MAC32 if (resolveMimeQuickTimeSymbols()) { Handle tiff = NewHandle(a.size()); memcpy(*tiff, a.constData(), a.size()); GraphicsImportComponent graphicsImporter; ComponentResult result = OpenADefaultComponent(GraphicsImporterComponentType, kQTFileTypeTIFF, &graphicsImporter); if (!result) result = ptrGraphicsImportSetDataHandle(graphicsImporter, tiff); if (!result) result = ptrGraphicsImportCreateCGImage(graphicsImporter, &image, kGraphicsImportCreateCGImageUsingCurrentSettings); CloseComponent(graphicsImporter); DisposeHandle(tiff); } #endif } if (image != 0) ret = QVariant(QPixmap::fromMacCGImageRef(image).toImage()); return ret; }
//----------------------------------------------------------------------------- IPlatformBitmap* IPlatformBitmap::createFromMemory (const void* ptr, uint32_t memSize) { CGBitmap* bitmap = 0; CFDataRef data = CFDataCreate (0, (const UInt8*)ptr, memSize); if (data) { CGImageSourceRef source = CGImageSourceCreateWithData (data, NULL); if (source) { bitmap = new CGBitmap (); bool result = bitmap->loadFromImageSource (source); if (result == false) { bitmap->forget (); bitmap = 0; } CFRelease (source); } CFRelease (data); } return bitmap; }
bool CBaseTexture::LoadFromFile(const CStdString& texturePath, unsigned int maxWidth, unsigned int maxHeight, bool autoRotate, unsigned int *originalWidth, unsigned int *originalHeight) { if (URIUtils::GetExtension(texturePath).Equals(".dds")) { // special case for DDS images CDDSImage image; if (image.ReadFile(texturePath)) { Update(image.GetWidth(), image.GetHeight(), 0, image.GetFormat(), image.GetData(), false); return true; } return false; } #if defined(__APPLE__) && defined(__arm__) XFILE::CFile file; UInt8 *imageBuff = NULL; int64_t imageBuffSize = 0; //open path and read data to buffer //this handles advancedsettings.xml pathsubstitution //and resulting networking if (file.Open(texturePath, 0)) { imageBuffSize =file.GetLength(); imageBuff = new UInt8[imageBuffSize]; imageBuffSize = file.Read(imageBuff, imageBuffSize); file.Close(); } else { CLog::Log(LOGERROR, "Texture manager unable to open file %s", texturePath.c_str()); return false; } if (imageBuffSize <= 0) { CLog::Log(LOGERROR, "Texture manager read texture file failed."); delete [] imageBuff; return false; } // create the image from buffer; CGImageSourceRef imageSource; // create a CFDataRef using CFDataCreateWithBytesNoCopy and kCFAllocatorNull for deallocator. // this allows us to do a nocopy reference and we handle the free of imageBuff CFDataRef cfdata = CFDataCreateWithBytesNoCopy(NULL, imageBuff, imageBuffSize, kCFAllocatorNull); imageSource = CGImageSourceCreateWithData(cfdata, NULL); if (imageSource == nil) { CLog::Log(LOGERROR, "Texture manager unable to load file: %s", CSpecialProtocol::TranslatePath(texturePath).c_str()); CFRelease(cfdata); delete [] imageBuff; return false; } CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL); int rotate = 0; if (autoRotate) { // get the orientation of the image for displaying it correctly CFDictionaryRef imagePropertiesDictionary = CGImageSourceCopyPropertiesAtIndex(imageSource,0, NULL); if (imagePropertiesDictionary != nil) { CFNumberRef orientation = (CFNumberRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyOrientation); if (orientation != nil) { int value = 0; CFNumberGetValue(orientation, kCFNumberIntType, &value); if (value) rotate = value - 1; } CFRelease(imagePropertiesDictionary); } } CFRelease(imageSource); unsigned int width = CGImageGetWidth(image); unsigned int height = CGImageGetHeight(image); m_hasAlpha = (CGImageGetAlphaInfo(image) != kCGImageAlphaNone); if (originalWidth) *originalWidth = width; if (originalHeight) *originalHeight = height; // check texture size limits and limit to screen size - preserving aspectratio of image if ( width > g_Windowing.GetMaxTextureSize() || height > g_Windowing.GetMaxTextureSize() ) { float aspect; if ( width > height ) { aspect = (float)width / (float)height; width = g_Windowing.GetWidth(); height = (float)width / (float)aspect; } else { aspect = (float)height / (float)width; height = g_Windowing.GetHeight(); width = (float)height / (float)aspect; } CLog::Log(LOGDEBUG, "Texture manager texture clamp:new texture size: %i x %i", width, height); } // use RGBA to skip swizzling Allocate(width, height, XB_FMT_RGBA8); m_orientation = rotate; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // hw convert jmpeg to RGBA CGContextRef context = CGBitmapContextCreate(m_pixels, width, height, 8, GetPitch(), colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGColorSpaceRelease(colorSpace); // Flip so that it isn't upside-down //CGContextTranslateCTM(context, 0, height); //CGContextScaleCTM(context, 1.0f, -1.0f); #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5 CGContextClearRect(context, CGRectMake(0, 0, width, height)); #else #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 // (just a way of checking whether we're running in 10.5 or later) if (CGContextDrawLinearGradient == 0) CGContextClearRect(context, CGRectMake(0, 0, width, height)); else #endif CGContextSetBlendMode(context, kCGBlendModeCopy); #endif //CGContextSetBlendMode(context, kCGBlendModeCopy); CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); CGContextRelease(context); CGImageRelease(image); CFRelease(cfdata); delete [] imageBuff; #else DllImageLib dll; if (!dll.Load()) return false; ImageInfo image; memset(&image, 0, sizeof(image)); unsigned int width = maxWidth ? std::min(maxWidth, g_Windowing.GetMaxTextureSize()) : g_Windowing.GetMaxTextureSize(); unsigned int height = maxHeight ? std::min(maxHeight, g_Windowing.GetMaxTextureSize()) : g_Windowing.GetMaxTextureSize(); if(!dll.LoadImage(texturePath.c_str(), width, height, &image)) { CLog::Log(LOGERROR, "Texture manager unable to load file: %s", texturePath.c_str()); return false; } m_hasAlpha = NULL != image.alpha; Allocate(image.width, image.height, XB_FMT_A8R8G8B8); if (autoRotate && image.exifInfo.Orientation) m_orientation = image.exifInfo.Orientation - 1; if (originalWidth) *originalWidth = image.originalwidth; if (originalHeight) *originalHeight = image.originalheight; unsigned int dstPitch = GetPitch(); unsigned int srcPitch = ((image.width + 1)* 3 / 4) * 4; // bitmap row length is aligned to 4 bytes unsigned char *dst = m_pixels; unsigned char *src = image.texture + (m_imageHeight - 1) * srcPitch; for (unsigned int y = 0; y < m_imageHeight; y++) { unsigned char *dst2 = dst; unsigned char *src2 = src; for (unsigned int x = 0; x < m_imageWidth; x++, dst2 += 4, src2 += 3) { dst2[0] = src2[0]; dst2[1] = src2[1]; dst2[2] = src2[2]; dst2[3] = 0xff; } src -= srcPitch; dst += dstPitch; } if(image.alpha) { dst = m_pixels + 3; src = image.alpha + (m_imageHeight - 1) * m_imageWidth; for (unsigned int y = 0; y < m_imageHeight; y++) { unsigned char *dst2 = dst; unsigned char *src2 = src; for (unsigned int x = 0; x < m_imageWidth; x++, dst2+=4, src2++) *dst2 = *src2; src -= m_imageWidth; dst += dstPitch; } } dll.ReleaseImage(&image); #endif ClampToEdge(); return true; }
bool wxBitmapDataObject::SetData( const wxDataFormat& format, size_t nSize, const void *pBuf ) { Clear(); if ((pBuf == NULL) || (nSize == 0)) return false; #if wxMAC_USE_CORE_GRAPHICS Handle picHandle = NULL ; m_pictHandle = NewHandle( nSize ); memcpy( *(Handle) m_pictHandle, pBuf, nSize ); if ( format == s_pict ) { // pict for IO expects a 512 byte header picHandle = NewHandle( nSize + 512 ); memset( *picHandle , 0 , 512 ); memcpy( *picHandle+512, pBuf, nSize ); } else { picHandle = (Handle) m_pictHandle; } CGImageRef cgImageRef = 0; #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 if ( UMAGetSystemVersion() >= 0x1040 ) { CFDataRef data = NULL; HLock( picHandle ); data = CFDataCreateWithBytesNoCopy( kCFAllocatorDefault, (const UInt8*) *picHandle, GetHandleSize(picHandle), kCFAllocatorNull); CGImageSourceRef source = CGImageSourceCreateWithData( data, NULL ); if ( source ) { cgImageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL); } CFRelease( source ); CFRelease( data ); HUnlock( picHandle ); } else #endif #ifndef __LP64__ { // import from TIFF GraphicsImportComponent importer = 0; OSStatus err = OpenADefaultComponent(GraphicsImporterComponentType, s_pict == format ? kQTFileTypePicture : kQTFileTypeTIFF, &importer); if (noErr == err) { if ( picHandle ) { ComponentResult result = GraphicsImportSetDataHandle(importer, picHandle); if ( result == noErr ) { Rect frame; GraphicsImportGetNaturalBounds( importer, &frame ); GraphicsImportCreateCGImage( importer, &cgImageRef, kGraphicsImportCreateCGImageUsingCurrentSettings ); } } CloseComponent( importer ); } } #endif if ( format == s_pict ) { DisposeHandle( picHandle ); } if ( cgImageRef ) { m_bitmap.Create( CGImageGetWidth(cgImageRef) , CGImageGetHeight(cgImageRef) ); CGRect r = CGRectMake( 0 , 0 , CGImageGetWidth(cgImageRef) , CGImageGetHeight(cgImageRef) ); // since our context is upside down we dont use CGContextDrawImage HIViewDrawCGImage( (CGContextRef) m_bitmap.GetHBITMAP() , &r, cgImageRef ) ; CGImageRelease(cgImageRef); cgImageRef = NULL; } #else PicHandle picHandle = (PicHandle)NewHandle( nSize ); memcpy( *picHandle, pBuf, nSize ); m_pictHandle = picHandle; // ownership is transferred to the bitmap m_pictCreated = false; #ifndef __LP64__ Rect frame; wxMacGetPictureBounds( picHandle, &frame ); #if wxUSE_METAFILE wxMetafile mf; mf.SetHMETAFILE( (WXHMETAFILE)m_pictHandle ); #endif wxMemoryDC mdc; m_bitmap.Create( frame.right - frame.left, frame.bottom - frame.top ); mdc.SelectObject( m_bitmap ); #if wxUSE_METAFILE mf.Play( &mdc ); #endif mdc.SelectObject( wxNullBitmap ); #endif #endif return m_bitmap.Ok(); }
bool FLACMetadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; auto stream = new TagLib::FileStream(reinterpret_cast<const char *>(buf)); TagLib::FLAC::File file(stream, TagLib::ID3v2::FrameFactory::instance(), false); if(!file.isValid()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Not a FLAC file"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } SetXiphCommentFromMetadata(*this, file.xiphComment(), false); // Remove existing cover art file.removePictures(); // Add album art for(auto attachedPicture : GetAttachedPictures()) { CGImageSourceRef imageSource = CGImageSourceCreateWithData(attachedPicture->GetData(), nullptr); if(nullptr == imageSource) { LOGGER_ERR("org.sbooth.AudioEngine.AudioMetadata.FLAC", "Skipping album art (unable to create image)"); continue; } TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture; picture->setData(TagLib::ByteVector((const char *)CFDataGetBytePtr(attachedPicture->GetData()), (TagLib::uint)CFDataGetLength(attachedPicture->GetData()))); picture->setType(static_cast<TagLib::FLAC::Picture::Type>(attachedPicture->GetType())); if(attachedPicture->GetDescription()) picture->setDescription(TagLib::StringFromCFString(attachedPicture->GetDescription())); // Convert the image's UTI into a MIME type CFStringRef mimeType = UTTypeCopyPreferredTagWithClass(CGImageSourceGetType(imageSource), kUTTagClassMIMEType); if(mimeType) { picture->setMimeType(TagLib::StringFromCFString(mimeType)); CFRelease(mimeType), mimeType = nullptr; } // Flesh out the height, width, and depth CFDictionaryRef imagePropertiesDictionary = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nullptr); if(imagePropertiesDictionary) { CFNumberRef imageWidth = (CFNumberRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyPixelWidth); CFNumberRef imageHeight = (CFNumberRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyPixelHeight); CFNumberRef imageDepth = (CFNumberRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyDepth); int height, width, depth; // Ignore numeric conversion errors CFNumberGetValue(imageWidth, kCFNumberIntType, &width); CFNumberGetValue(imageHeight, kCFNumberIntType, &height); CFNumberGetValue(imageDepth, kCFNumberIntType, &depth); picture->setHeight(height); picture->setWidth(width); picture->setColorDepth(depth); CFRelease(imagePropertiesDictionary), imagePropertiesDictionary = nullptr; } file.addPicture(picture); CFRelease(imageSource), imageSource = nullptr; } if(!file.save()) { if(error) { CFStringRef description = CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""); CFStringRef failureReason = CFCopyLocalizedString(CFSTR("Unable to write metadata"), ""); CFStringRef recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(AudioMetadataErrorDomain, AudioMetadataInputOutputError, description, mURL, failureReason, recoverySuggestion); CFRelease(description), description = nullptr; CFRelease(failureReason), failureReason = nullptr; CFRelease(recoverySuggestion), recoverySuggestion = nullptr; } return false; } MergeChangedMetadataIntoMetadata(); return true; }
bool SFB::Audio::SetID3v2TagFromMetadata(const Metadata& metadata, TagLib::ID3v2::Tag *tag, bool setAlbumArt) { if(nullptr == tag) return false; // Use UTF-8 as the default encoding (TagLib::ID3v2::FrameFactory::instance())->setDefaultTextEncoding(TagLib::String::UTF8); // Album title tag->setAlbum(TagLib::StringFromCFString(metadata.GetAlbumTitle())); // Artist tag->setArtist(TagLib::StringFromCFString(metadata.GetArtist())); // Composer tag->removeFrames("TCOM"); if(metadata.GetComposer()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TCOM", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(metadata.GetComposer())); tag->addFrame(frame); } // Genre tag->setGenre(TagLib::StringFromCFString(metadata.GetGenre())); // Date #if 1 int year = 0; if(metadata.GetReleaseDate()) year = CFStringGetIntValue(metadata.GetReleaseDate()); tag->setYear((TagLib::uint)year); #else // TODO: Parse the release date into components and set the frame appropriately tag->removeFrames("TDRC"); if(metadata.GetReleaseDate()) { /* The timestamp fields are based on a subset of ISO 8601. When being as precise as possible the format of a time string is yyyy-MM-ddTHH:mm:ss (year, "-", month, "-", day, "T", hour (out of 24), ":", minutes, ":", seconds), but the precision may be reduced by removing as many time indicators as wanted. Hence valid timestamps are yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use the slash character as described in 8601, and for multiple non- contiguous dates, use multiple strings, if allowed by the frame definition. */ // year = CFStringGetIntValue(metadata.GetReleaseDate()); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TDRC", TagLib::String::Latin1); frame->setText(""); tag->addFrame(frame); } #endif // Comment tag->setComment(TagLib::StringFromCFString(metadata.GetComment())); // Album artist tag->removeFrames("TPE2"); if(metadata.GetAlbumArtist()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPE2", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(metadata.GetAlbumArtist())); tag->addFrame(frame); } // Track title tag->setTitle(TagLib::StringFromCFString(metadata.GetTitle())); // BPM tag->removeFrames("TBPM"); if(metadata.GetBPM()) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@"), metadata.GetBPM()); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TBPM", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } // Rating tag->removeFrames("POPM"); CFNumberRef rating = metadata.GetRating(); if(rating) { TagLib::ID3v2::PopularimeterFrame *frame = new TagLib::ID3v2::PopularimeterFrame(); int i; if(CFNumberGetValue(rating, kCFNumberIntType, &i)) { frame->setRating(i); tag->addFrame(frame); } } // Track number and total tracks tag->removeFrames("TRCK"); CFNumberRef trackNumber = metadata.GetTrackNumber(); CFNumberRef trackTotal = metadata.GetTrackTotal(); if(trackNumber && trackTotal) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@/%@"), trackNumber, trackTotal); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TRCK", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } else if(trackNumber) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@"), trackNumber); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TRCK", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } else if(trackTotal) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("/%@"), trackTotal); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TRCK", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } // Compilation // iTunes uses the TCMP frame for this, which isn't in the standard, but we'll use it for compatibility tag->removeFrames("TCMP"); if(metadata.GetCompilation()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TCMP", TagLib::String::Latin1); frame->setText(CFBooleanGetValue(metadata.GetCompilation()) ? "1" : "0"); tag->addFrame(frame); } // Disc number and total discs tag->removeFrames("TPOS"); CFNumberRef discNumber = metadata.GetDiscNumber(); CFNumberRef discTotal = metadata.GetDiscTotal(); if(discNumber && discTotal) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@/%@"), discNumber, discTotal); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPOS", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } else if(discNumber) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@"), discNumber); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPOS", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } else if(discTotal) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("/%@"), discTotal); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPOS", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } // Lyrics tag->removeFrames("USLT"); if(metadata.GetLyrics()) { auto frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetLyrics())); tag->addFrame(frame); } tag->removeFrames("TSRC"); if(metadata.GetISRC()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSRC", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(metadata.GetISRC())); tag->addFrame(frame); } // MusicBrainz auto musicBrainzReleaseIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "MusicBrainz Album Id"); if(nullptr != musicBrainzReleaseIDFrame) tag->removeFrame(musicBrainzReleaseIDFrame); CFStringRef musicBrainzReleaseID = metadata.GetMusicBrainzReleaseID(); if(musicBrainzReleaseID) { auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("MusicBrainz Album Id"); frame->setText(TagLib::StringFromCFString(musicBrainzReleaseID)); tag->addFrame(frame); } auto musicBrainzRecordingIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "MusicBrainz Track Id"); if(nullptr != musicBrainzRecordingIDFrame) tag->removeFrame(musicBrainzRecordingIDFrame); CFStringRef musicBrainzRecordingID = metadata.GetMusicBrainzRecordingID(); if(musicBrainzRecordingID) { auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("MusicBrainz Track Id"); frame->setText(TagLib::StringFromCFString(musicBrainzRecordingID)); tag->addFrame(frame); } // Sorting and grouping tag->removeFrames("TSOT"); if(metadata.GetTitleSortOrder()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOT", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetTitleSortOrder())); tag->addFrame(frame); } tag->removeFrames("TSOA"); if(metadata.GetAlbumTitleSortOrder()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOA", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetAlbumTitleSortOrder())); tag->addFrame(frame); } tag->removeFrames("TSOP"); if(metadata.GetArtistSortOrder()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOP", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetArtistSortOrder())); tag->addFrame(frame); } tag->removeFrames("TSO2"); if(metadata.GetAlbumArtistSortOrder()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSO2", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetAlbumArtistSortOrder())); tag->addFrame(frame); } tag->removeFrames("TSOC"); if(metadata.GetComposerSortOrder()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOC", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetComposerSortOrder())); tag->addFrame(frame); } tag->removeFrames("TIT1"); if(metadata.GetGrouping()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TIT1", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetGrouping())); tag->addFrame(frame); } // ReplayGain CFNumberRef trackGain = metadata.GetReplayGainTrackGain(); CFNumberRef trackPeak = metadata.GetReplayGainTrackPeak(); CFNumberRef albumGain = metadata.GetReplayGainAlbumGain(); CFNumberRef albumPeak = metadata.GetReplayGainAlbumPeak(); // Write TXXX frames auto trackGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_track_gain"); auto trackPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_track_peak"); auto albumGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_album_gain"); auto albumPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_album_peak"); if(nullptr != trackGainFrame) tag->removeFrame(trackGainFrame); if(nullptr != trackPeakFrame) tag->removeFrame(trackPeakFrame); if(nullptr != albumGainFrame) tag->removeFrame(albumGainFrame); if(nullptr != albumPeakFrame) tag->removeFrame(albumPeakFrame); if(trackGain) { SFB::CFString str = CreateStringFromNumberWithFormat(trackGain, kCFNumberDoubleType, CFSTR("%+2.2f dB")); auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("replaygain_track_gain"); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } if(trackPeak) { SFB::CFString str = CreateStringFromNumberWithFormat(trackPeak, kCFNumberDoubleType, CFSTR("%1.8f dB")); auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("replaygain_track_peak"); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } if(albumGain) { SFB::CFString str = CreateStringFromNumberWithFormat(albumGain, kCFNumberDoubleType, CFSTR("%+2.2f dB")); auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("replaygain_album_gain"); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } if(albumPeak) { SFB::CFString str = CreateStringFromNumberWithFormat(albumPeak, kCFNumberDoubleType, CFSTR("%1.8f dB")); auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("replaygain_album_peak"); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } // Also write the RVA2 frames tag->removeFrames("RVA2"); if(trackGain) { auto relativeVolume = new TagLib::ID3v2::RelativeVolumeFrame(); float f; CFNumberGetValue(trackGain, kCFNumberFloatType, &f); relativeVolume->setIdentification(TagLib::String("track", TagLib::String::Latin1)); relativeVolume->setVolumeAdjustment(f, TagLib::ID3v2::RelativeVolumeFrame::MasterVolume); tag->addFrame(relativeVolume); } if(albumGain) { auto relativeVolume = new TagLib::ID3v2::RelativeVolumeFrame(); float f; CFNumberGetValue(albumGain, kCFNumberFloatType, &f); relativeVolume->setIdentification(TagLib::String("album", TagLib::String::Latin1)); relativeVolume->setVolumeAdjustment(f, TagLib::ID3v2::RelativeVolumeFrame::MasterVolume); tag->addFrame(relativeVolume); } // Album art if(setAlbumArt) { tag->removeFrames("APIC"); for(auto attachedPicture : metadata.GetAttachedPictures()) { SFB::CGImageSource imageSource = CGImageSourceCreateWithData(attachedPicture->GetData(), nullptr); if(!imageSource) continue; TagLib::ID3v2::AttachedPictureFrame *frame = new TagLib::ID3v2::AttachedPictureFrame; // Convert the image's UTI into a MIME type SFB::CFString mimeType = UTTypeCopyPreferredTagWithClass(CGImageSourceGetType(imageSource), kUTTagClassMIMEType); if(mimeType) frame->setMimeType(TagLib::StringFromCFString(mimeType)); frame->setPicture(TagLib::ByteVector((const char *)CFDataGetBytePtr(attachedPicture->GetData()), (TagLib::uint)CFDataGetLength(attachedPicture->GetData()))); frame->setType((TagLib::ID3v2::AttachedPictureFrame::Type)attachedPicture->GetType()); if(attachedPicture->GetDescription()) frame->setDescription(TagLib::StringFromCFString(attachedPicture->GetDescription())); tag->addFrame(frame); } } return true; }
bool SetAPETagFromMetadata(const AudioMetadata& metadata, TagLib::APE::Tag *tag, bool setAlbumArt) { if(nullptr == tag) return false; // Standard tags SetAPETag(tag, "ALBUM", metadata.GetAlbumTitle()); SetAPETag(tag, "ARTIST", metadata.GetArtist()); SetAPETag(tag, "ALBUMARTIST", metadata.GetAlbumArtist()); SetAPETag(tag, "COMPOSER", metadata.GetComposer()); SetAPETag(tag, "GENRE", metadata.GetGenre()); SetAPETag(tag, "DATE", metadata.GetReleaseDate()); SetAPETag(tag, "DESCRIPTION", metadata.GetComment()); SetAPETag(tag, "TITLE", metadata.GetTitle()); SetAPETagNumber(tag, "TRACKNUMBER", metadata.GetTrackNumber()); SetAPETagNumber(tag, "TRACKTOTAL", metadata.GetTrackTotal()); SetAPETagBoolean(tag, "COMPILATION", metadata.GetCompilation()); SetAPETagNumber(tag, "DISCNUMBER", metadata.GetDiscNumber()); SetAPETagNumber(tag, "DISCTOTAL", metadata.GetDiscTotal()); SetAPETagNumber(tag, "BPM", metadata.GetBPM()); SetAPETagNumber(tag, "RATING", metadata.GetRating()); SetAPETag(tag, "ISRC", metadata.GetISRC()); SetAPETag(tag, "MCN", metadata.GetMCN()); SetAPETag(tag, "TITLESORT", metadata.GetTitleSortOrder()); SetAPETag(tag, "ALBUMTITLESORT", metadata.GetAlbumTitleSortOrder()); SetAPETag(tag, "ARTISTSORT", metadata.GetArtistSortOrder()); SetAPETag(tag, "ALBUMARTISTSORT", metadata.GetAlbumArtistSortOrder()); SetAPETag(tag, "COMPOSERSORT", metadata.GetComposerSortOrder()); SetAPETag(tag, "GROUPING", metadata.GetGrouping()); // Additional metadata CFDictionaryRef additionalMetadata = metadata.GetAdditionalMetadata(); if(nullptr != 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)) { LOGGER_ERR("org.sbooth.AudioEngine", "CFStringGetCString failed"); continue; } SetAPETag(tag, key, reinterpret_cast<CFStringRef>(values[i])); } } // ReplayGain info SetAPETagDouble(tag, "REPLAYGAIN_REFERENCE_LOUDNESS", metadata.GetReplayGainReferenceLoudness(), CFSTR("%2.1f dB")); SetAPETagDouble(tag, "REPLAYGAIN_TRACK_GAIN", metadata.GetReplayGainTrackGain(), CFSTR("%+2.2f dB")); SetAPETagDouble(tag, "REPLAYGAIN_TRACK_PEAK", metadata.GetReplayGainTrackPeak(), CFSTR("%1.8f")); SetAPETagDouble(tag, "REPLAYGAIN_ALBUM_GAIN", metadata.GetReplayGainAlbumGain(), CFSTR("%+2.2f dB")); SetAPETagDouble(tag, "REPLAYGAIN_ALBUM_PEAK", metadata.GetReplayGainAlbumPeak(), CFSTR("%1.8f")); // Album art if(setAlbumArt) { tag->removeItem("Cover Art (Front)"); tag->removeItem("Cover Art (Back)"); #if 0 tag->removeItem("METADATA_BLOCK_PICTURE"); #endif for(auto attachedPicture : metadata.GetAttachedPictures()) { // APE can handle front and back covers natively if(AttachedPicture::Type::FrontCover == attachedPicture->GetType() || AttachedPicture::Type::FrontCover == attachedPicture->GetType()) { TagLib::ByteVector data; if(attachedPicture->GetDescription()) data.append(TagLib::StringFromCFString(attachedPicture->GetDescription()).data(TagLib::String::UTF8)); data.append('\0'); data.append(TagLib::ByteVector((const char *)CFDataGetBytePtr(attachedPicture->GetData()), (TagLib::uint)CFDataGetLength(attachedPicture->GetData()))); if(AttachedPicture::Type::FrontCover == attachedPicture->GetType()) tag->setData("Cover Art (Front)", data); else if(AttachedPicture::Type::BackCover == attachedPicture->GetType()) tag->setData("Cover Art (Back)", data); } #if 0 else { CGImageSourceRef imageSource = CGImageSourceCreateWithData(attachedPicture->GetData(), nullptr); if(nullptr == imageSource) return false; TagLib::FLAC::Picture picture; picture.setData(TagLib::ByteVector((const char *)CFDataGetBytePtr(attachedPicture->GetData()), (TagLib::uint)CFDataGetLength(attachedPicture->GetData()))); picture.setType(static_cast<TagLib::FLAC::Picture::Type>(attachedPicture->GetType())); if(attachedPicture->GetDescription()) picture.setDescription(TagLib::StringFromCFString(attachedPicture->GetDescription())); // Convert the image's UTI into a MIME type CFStringRef mimeType = UTTypeCopyPreferredTagWithClass(CGImageSourceGetType(imageSource), kUTTagClassMIMEType); if(mimeType) { picture.setMimeType(TagLib::StringFromCFString(mimeType)); CFRelease(mimeType), mimeType = nullptr; } // Flesh out the height, width, and depth CFDictionaryRef imagePropertiesDictionary = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nullptr); if(imagePropertiesDictionary) { CFNumberRef imageWidth = (CFNumberRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyPixelWidth); CFNumberRef imageHeight = (CFNumberRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyPixelHeight); CFNumberRef imageDepth = (CFNumberRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyDepth); int height, width, depth; // Ignore numeric conversion errors CFNumberGetValue(imageWidth, kCFNumberIntType, &width); CFNumberGetValue(imageHeight, kCFNumberIntType, &height); CFNumberGetValue(imageDepth, kCFNumberIntType, &depth); picture.setHeight(height); picture.setWidth(width); picture.setColorDepth(depth); CFRelease(imagePropertiesDictionary), imagePropertiesDictionary = nullptr; } TagLib::ByteVector encodedBlock = TagLib::EncodeBase64(picture.render()); tag->addValue("METADATA_BLOCK_PICTURE", TagLib::String(encodedBlock, TagLib::String::UTF8), false); CFRelease(imageSource), imageSource = nullptr; } #endif } } return true; }