From a3ad2d0aaad14992ec1ab8cfe444a7ad5a818608 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 15 Oct 2023 19:38:15 +0200 Subject: [PATCH] C bindings for complex properties like pictures (#953) Provides a unified API for complex properties in the same way as the Properties API in the C bindings. Since constructing and traversing complex properties in C is a bit complicated, there are convenience functions for the most common use case of getting or setting a single picture. TAGLIB_COMPLEX_PROPERTY_PICTURE(props, data, size, "Written by TagLib", "image/jpeg", "Front Cover"); taglib_complex_property_set(file, "PICTURE", props); and TagLib_Complex_Property_Attribute*** properties = taglib_complex_property_get(file, "PICTURE"); TagLib_Complex_Property_Picture_Data picture; taglib_picture_from_complex_property(properties, &picture); --- bindings/c/tag_c.cpp | 318 ++++++++++++++++++++++++++++++++++++++++- bindings/c/tag_c.h | 234 +++++++++++++++++++++++++++++- examples/tagreader_c.c | 69 +++++++++ 3 files changed, 619 insertions(+), 2 deletions(-) diff --git a/bindings/c/tag_c.cpp b/bindings/c/tag_c.cpp index f5adbd51..88069232 100644 --- a/bindings/c/tag_c.cpp +++ b/bindings/c/tag_c.cpp @@ -23,11 +23,14 @@ #include #include +#include #include #ifdef HAVE_CONFIG_H # include "config.h" #endif +#include "tstringlist.h" +#include "tbytevectorlist.h" #include "tfile.h" #include "tpropertymap.h" #include "fileref.h" @@ -412,7 +415,320 @@ void taglib_property_free(char **props) char **p = props; while(*p) { - free(*p++); + free(*p++); + } + free(props); +} + + +/****************************************************************************** + * Complex Properties API + ******************************************************************************/ + +namespace { + +bool _taglib_complex_property_set( + TagLib_File *file, const char *key, + const TagLib_Complex_Property_Attribute **value, bool append) +{ + if(file == NULL || key == NULL) + return false; + + auto tfile = reinterpret_cast(file); + + if(value == NULL) { + return tfile->setComplexProperties(key, {}); + } + + VariantMap map; + const TagLib_Complex_Property_Attribute** attrPtr = value; + while(*attrPtr) { + const TagLib_Complex_Property_Attribute *attr = *attrPtr; + String attrKey(attr->key); + TagLib_Variant_Type type = attr->value.type; + switch(type) { + case TagLib_Variant_Void: + map.insert(attrKey, Variant()); + break; + case TagLib_Variant_Bool: + map.insert(attrKey, attr->value.value.boolValue != 0); + break; + case TagLib_Variant_Int: + map.insert(attrKey, attr->value.value.intValue); + break; + case TagLib_Variant_UInt: + map.insert(attrKey, attr->value.value.uIntValue); + break; + case TagLib_Variant_LongLong: + map.insert(attrKey, attr->value.value.longLongValue); + break; + case TagLib_Variant_ULongLong: + map.insert(attrKey, attr->value.value.uLongLongValue); + break; + case TagLib_Variant_Double: + map.insert(attrKey, attr->value.value.doubleValue); + break; + case TagLib_Variant_String: + map.insert(attrKey, attr->value.value.stringValue); + break; + case TagLib_Variant_StringList: { + StringList strs; + if(attr->value.value.stringListValue) { + char **s = attr->value.value.stringListValue;; + while(*s) { + strs.append(*s++); + } + } + map.insert(attrKey, strs); + break; + } + case TagLib_Variant_ByteVector: + map.insert(attrKey, ByteVector(attr->value.value.byteVectorValue, + attr->value.size)); + break; + } + ++attrPtr; + } + + return append ? tfile->setComplexProperties(key, tfile->complexProperties(key).append(map)) + : tfile->setComplexProperties(key, {map}); +} + +} // namespace + +BOOL taglib_complex_property_set( + TagLib_File *file, const char *key, + const TagLib_Complex_Property_Attribute **value) +{ + return _taglib_complex_property_set(file, key, value, false); +} + +BOOL taglib_complex_property_set_append( + TagLib_File *file, const char *key, + const TagLib_Complex_Property_Attribute **value) +{ + return _taglib_complex_property_set(file, key, value, true); +} + +char** taglib_complex_property_keys(TagLib_File *file) +{ + if(file == NULL) { + return NULL; + } + + const StringList strs = reinterpret_cast(file)->complexPropertyKeys(); + if(strs.isEmpty()) { + return NULL; + } + + auto keys = static_cast(malloc(sizeof(char *) * (strs.size() + 1))); + char **keyPtr = keys; + + for(const auto &str : strs) { + *keyPtr++ = stringToCharArray(str); + } + *keyPtr = NULL; + + return keys; +} + +TagLib_Complex_Property_Attribute*** taglib_complex_property_get( + TagLib_File *file, const char *key) +{ + if(file == NULL || key == NULL) { + return NULL; + } + + const auto variantMaps = reinterpret_cast(file)->complexProperties(key); + if(variantMaps.isEmpty()) { + return NULL; + } + + TagLib_Complex_Property_Attribute ***props = static_cast( + malloc(sizeof(TagLib_Complex_Property_Attribute **) * (variantMaps.size() + 1))); + TagLib_Complex_Property_Attribute ***propPtr = props; + + for(const auto &variantMap : variantMaps) { + TagLib_Complex_Property_Attribute **attrs = static_cast( + malloc(sizeof(TagLib_Complex_Property_Attribute *) * (variantMap.size() + 1))); + TagLib_Complex_Property_Attribute *attr = static_cast( + malloc(sizeof(TagLib_Complex_Property_Attribute) * variantMap.size())); + TagLib_Complex_Property_Attribute **attrPtr = attrs; + for (const auto &[k, v] : variantMap) { + attr->key = stringToCharArray(k); + attr->value.size = 0; + switch(v.type()) { + case Variant::Void: + attr->value.type = TagLib_Variant_Void; + attr->value.value.stringValue = NULL; + break; + case Variant::Bool: + attr->value.type = TagLib_Variant_Bool; + attr->value.value.boolValue = v.value(); + break; + case Variant::Int: + attr->value.type = TagLib_Variant_Int; + attr->value.value.intValue = v.value(); + break; + case Variant::UInt: + attr->value.type = TagLib_Variant_UInt; + attr->value.value.uIntValue = v.value(); + break; + case Variant::LongLong: + attr->value.type = TagLib_Variant_LongLong; + attr->value.value.longLongValue = v.value(); + break; + case Variant::ULongLong: + attr->value.type = TagLib_Variant_ULongLong; + attr->value.value.uLongLongValue = v.value(); + break; + case Variant::Double: + attr->value.type = TagLib_Variant_Double; + attr->value.value.doubleValue = v.value(); + break; + case Variant::String: { + attr->value.type = TagLib_Variant_String; + auto str = v.value(); + attr->value.value.stringValue = stringToCharArray(str); + attr->value.size = str.size(); + break; + } + case Variant::StringList: { + attr->value.type = TagLib_Variant_StringList; + StringList strs = v.value(); + auto strPtr = static_cast(malloc(sizeof(char *) * (strs.size() + 1))); + attr->value.value.stringListValue = strPtr; + attr->value.size = strs.size(); + for(const auto &str : strs) { + *strPtr++ = stringToCharArray(str); + } + *strPtr = NULL; + break; + } + case Variant::ByteVector: { + attr->value.type = TagLib_Variant_ByteVector; + const ByteVector data = v.value(); + auto bytePtr = static_cast(malloc(data.size())); + attr->value.value.byteVectorValue = bytePtr; + attr->value.size = data.size(); + ::memcpy(bytePtr, data.data(), data.size()); + break; + } + case Variant::ByteVectorList: + case Variant::VariantList: + case Variant::VariantMap: { + attr->value.type = TagLib_Variant_String; + std::stringstream ss; + ss << v; + attr->value.value.stringValue = stringToCharArray(ss.str()); + break; + } + } + *attrPtr++ = attr++; + } + *attrPtr++ = NULL; + *propPtr++ = attrs; + } + *propPtr = NULL; + return props; +} + +void taglib_picture_from_complex_property( + TagLib_Complex_Property_Attribute*** properties, + TagLib_Complex_Property_Picture_Data *picture) +{ + if(!properties || !picture) { + return; + } + std::memset(picture, 0, sizeof(*picture)); + TagLib_Complex_Property_Attribute*** propPtr = properties; + while(!picture->data && *propPtr) { + TagLib_Complex_Property_Attribute** attrPtr = *propPtr; + while(*attrPtr) { + TagLib_Complex_Property_Attribute *attr = *attrPtr; + TagLib_Variant_Type type = attr->value.type; + switch(type) { + case TagLib_Variant_String: + if(strcmp("mimeType", attr->key) == 0) { + picture->mimeType = attr->value.value.stringValue; + } + else if(strcmp("description", attr->key) == 0) { + picture->description = attr->value.value.stringValue; + } + else if(strcmp("pictureType", attr->key) == 0) { + picture->pictureType = attr->value.value.stringValue; + } + break; + case TagLib_Variant_ByteVector: + if(strcmp("data", attr->key) == 0) { + picture->data = attr->value.value.byteVectorValue; + picture->size = attr->value.size; + } + break; + default: + break; + } + ++attrPtr; + } + ++propPtr; + } +} + +void taglib_complex_property_free_keys(char **keys) +{ + if(keys == NULL) { + return; + } + + char **k = keys; + while(*k) { + free(*k++); + } + free(keys); +} + +void taglib_complex_property_free( + TagLib_Complex_Property_Attribute ***props) +{ + if(props == NULL) { + return; + } + TagLib_Complex_Property_Attribute*** propPtr = props; + while(*propPtr) { + TagLib_Complex_Property_Attribute** attrPtr = *propPtr; + while(*attrPtr) { + TagLib_Complex_Property_Attribute *attr = *attrPtr; + TagLib_Variant_Type type = attr->value.type; + switch(type) { + case TagLib_Variant_String: + free(attr->value.value.stringValue); + break; + case TagLib_Variant_StringList: + if(attr->value.value.stringListValue) { + char **s = attr->value.value.stringListValue; + while(*s) { + free(*s++); + } + free(attr->value.value.stringListValue); + } + break; + case TagLib_Variant_ByteVector: + free(attr->value.value.byteVectorValue); + break; + case TagLib_Variant_Void: + case TagLib_Variant_Bool: + case TagLib_Variant_Int: + case TagLib_Variant_UInt: + case TagLib_Variant_LongLong: + case TagLib_Variant_ULongLong: + case TagLib_Variant_Double: + break; + } + free(attr->key); + ++attrPtr; + } + free(**propPtr); + free(*propPtr++); } free(props); } diff --git a/bindings/c/tag_c.h b/bindings/c/tag_c.h index f72e1f79..a88d6e99 100644 --- a/bindings/c/tag_c.h +++ b/bindings/c/tag_c.h @@ -320,7 +320,7 @@ TAGLIB_C_EXPORT char** taglib_property_keys(TagLib_File *file); * Get value(s) of property \a prop. * * \return NULL terminated array of C-strings (char *), only NULL if empty. - It must be freed by the client using taglib_property_free(). + * It must be freed by the client using taglib_property_free(). */ TAGLIB_C_EXPORT char** taglib_property_get(TagLib_File *file, const char *prop); @@ -329,6 +329,238 @@ TAGLIB_C_EXPORT char** taglib_property_get(TagLib_File *file, const char *prop); */ TAGLIB_C_EXPORT void taglib_property_free(char **props); +/****************************************************************************** + * Complex Properties API + ******************************************************************************/ + +/*! + * Types which can be stored in a TagLib_Variant. + * + * \related TagLib::Variant::Type + * These correspond to TagLib::Variant::Type, but ByteVectorList, VariantList, + * VariantMap are not supported and will be returned as their string + * representation. + */ +typedef enum { + TagLib_Variant_Void, + TagLib_Variant_Bool, + TagLib_Variant_Int, + TagLib_Variant_UInt, + TagLib_Variant_LongLong, + TagLib_Variant_ULongLong, + TagLib_Variant_Double, + TagLib_Variant_String, + TagLib_Variant_StringList, + TagLib_Variant_ByteVector +} TagLib_Variant_Type; + +/*! + * Discriminated union used in complex property attributes. + * + * \e type must be set according to the \e value union used. + * \e size is only required for TagLib_Variant_ByteVector and must contain + * the number of bytes. + * + * \related TagLib::Variant. + */ +typedef struct { + TagLib_Variant_Type type; + unsigned int size; + union { + char *stringValue; + char *byteVectorValue; + char **stringListValue; + BOOL boolValue; + int intValue; + unsigned int uIntValue; + long long longLongValue; + unsigned long long uLongLongValue; + double doubleValue; + } value; +} TagLib_Variant; + +/*! + * Attribute of a complex property. + * Complex properties consist of a NULL-terminated array of pointers to + * this structure with \e key and \e value. + */ +typedef struct { + char *key; + TagLib_Variant value; +} TagLib_Complex_Property_Attribute; + +/*! + * Picture data extracted from a complex property by the convenience function + * taglib_picture_from_complex_property(). + */ +typedef struct { + char *mimeType; + char *description; + char *pictureType; + char *data; + unsigned int size; +} TagLib_Complex_Property_Picture_Data; + +/*! + * Declare complex property attributes to set a picture. + * Can be used to define a variable \a var which can be used with + * taglib_complex_property_set() and a "PICTURE" key to set an + * embedded picture with the picture data \a dat of size \a siz + * and description \a desc, mime type \a mime and picture type + * \a typ (size is unsigned int, the other input parameters char *). + */ +#define TAGLIB_COMPLEX_PROPERTY_PICTURE(var, dat, siz, desc, mime, typ) \ +const TagLib_Complex_Property_Attribute \ +var##Attrs[] = { \ + { \ + (char *)"data", \ + { \ + TagLib_Variant_ByteVector, \ + (siz), \ + { \ + (char *)(dat) \ + } \ + } \ + }, \ + { \ + (char *)"mimeType", \ + { \ + TagLib_Variant_String, \ + 0U, \ + { \ + (char *)(mime) \ + } \ + } \ + }, \ + { \ + (char *)"description", \ + { \ + TagLib_Variant_String, \ + 0U, \ + { \ + (char *)(desc) \ + } \ + } \ + }, \ + { \ + (char *)"pictureType", \ + { \ + TagLib_Variant_String, \ + 0U, \ + { \ + (char *)(typ) \ + } \ + } \ + } \ +}; \ +const TagLib_Complex_Property_Attribute *var[] = { \ + &var##Attrs[0], &var##Attrs[1], &var##Attrs[2], \ + &var##Attrs[3], NULL \ +} + +/*! + * Sets the complex property \a key with \a value. Use \a value = NULL to + * remove the property, otherwise it will be replaced with the NULL + * terminated array of attributes in \a value. + * + * A picture can be set with the TAGLIB_COMPLEX_PROPERTY_PICTURE macro: + * + * \code {.c} + * TagLib_File *file = taglib_file_new("myfile.mp3"); + * FILE *fh = fopen("mypicture.jpg", "rb"); + * if(fh) { + * fseek(fh, 0L, SEEK_END); + * long size = ftell(fh); + * fseek(fh, 0L, SEEK_SET); + * char *data = (char *)malloc(size); + * fread(data, size, 1, fh); + * TAGLIB_COMPLEX_PROPERTY_PICTURE(props, data, size, "Written by TagLib", + * "image/jpeg", "Front Cover"); + * taglib_complex_property_set(file, "PICTURE", props); + * taglib_file_save(file); + * free(data); + * fclose(fh); + * } + * \endcode + */ +TAGLIB_C_EXPORT BOOL taglib_complex_property_set( + TagLib_File *file, const char *key, + const TagLib_Complex_Property_Attribute **value); + +/*! + * Appends \a value to the complex property \a key (sets it if non-existing). + * Use \a value = NULL to remove all values associated with the \a key. + */ +TAGLIB_C_EXPORT BOOL taglib_complex_property_set_append( + TagLib_File *file, const char *key, + const TagLib_Complex_Property_Attribute **value); + +/*! + * Get the keys of the complex properties. + * + * \return NULL terminated array of C-strings (char *), only NULL if empty. + * It must be freed by the client using taglib_complex_property_free_keys(). + */ +TAGLIB_C_EXPORT char** taglib_complex_property_keys(TagLib_File *file); + +/*! + * Get value(s) of complex property \a key. + * + * \return NULL terminated array of property values, which are themselves an + * array of property attributes, only NULL if empty. + * It must be freed by the client using taglib_complex_property_free(). + */ +TAGLIB_C_EXPORT TagLib_Complex_Property_Attribute*** taglib_complex_property_get( + TagLib_File *file, const char *key); + +/*! + * Extract the complex property values of a picture. + * + * This function can be used to get the data from a "PICTURE" complex property + * without having to traverse the whole variant map. A picture can be + * retrieved like this: + * + * \code {.c} + * TagLib_File *file = taglib_file_new("myfile.mp3"); + * TagLib_Complex_Property_Attribute*** properties = + * taglib_complex_property_get(file, "PICTURE"); + * TagLib_Complex_Property_Picture_Data picture; + * taglib_picture_from_complex_property(properties, &picture); + * // Do something with picture.mimeType, picture.description, + * // picture.pictureType, picture.data, picture.size, e.g. extract it. + * FILE *fh = fopen("mypicture.jpg", "wb"); + * if(fh) { + * fwrite(picture.data, picture.size, 1, fh); + * fclose(fh); + * } + * taglib_complex_property_free(properties); + * \endcode + * + * Note that the data in \a picture contains pointers to data in \a properties, + * i.e. it only lives as long as the properties, until they are freed with + * taglib_complex_property_free(). + * If you want to access multiple pictures or additional properties of FLAC + * pictures ("width", "height", "numColors", "colorDepth" int values), you + * have to traverse the \a properties yourself. + */ +TAGLIB_C_EXPORT void taglib_picture_from_complex_property( + TagLib_Complex_Property_Attribute*** properties, + TagLib_Complex_Property_Picture_Data *picture); + +/*! + * Frees the NULL terminated array \a keys (as returned by + * taglib_complex_property_keys()) and the C-strings it contains. + */ +TAGLIB_C_EXPORT void taglib_complex_property_free_keys(char **keys); + +/*! + * Frees the NULL terminated array \a props of property attribute arrays + * (as returned by taglib_complex_property_get()) and the data such as + * C-strings and byte vectors contained in these attributes. + */ +TAGLIB_C_EXPORT void taglib_complex_property_free( + TagLib_Complex_Property_Attribute ***props); + #ifdef __cplusplus } #endif diff --git a/examples/tagreader_c.c b/examples/tagreader_c.c index 668de994..fc55eb35 100644 --- a/examples/tagreader_c.c +++ b/examples/tagreader_c.c @@ -40,6 +40,7 @@ int main(int argc, char *argv[]) TagLib_Tag *tag; const TagLib_AudioProperties *properties; char **propertiesMap; + char **complexKeys; taglib_set_strings_unicode(1); @@ -54,6 +55,7 @@ int main(int argc, char *argv[]) tag = taglib_file_tag(file); properties = taglib_file_audioproperties(file); propertiesMap = taglib_property_keys(file); + complexKeys = taglib_complex_property_keys(file); if(tag != NULL) { printf("-- TAG (basic) --\n"); @@ -91,6 +93,73 @@ int main(int argc, char *argv[]) } } + if(complexKeys != NULL) { + char **keyPtr = complexKeys; + while(*keyPtr) { + TagLib_Complex_Property_Attribute*** properties = + taglib_complex_property_get(file, *keyPtr); + printf("%s:\n", *keyPtr); + if(properties != NULL) { + TagLib_Complex_Property_Attribute*** propPtr = properties; + while(*propPtr) { + TagLib_Complex_Property_Attribute** attrPtr = *propPtr; + while(*attrPtr) { + TagLib_Complex_Property_Attribute *attr = *attrPtr; + TagLib_Variant_Type type = attr->value.type; + printf(" %-11s - ", attr->key); + switch(type) { + case TagLib_Variant_Void: + printf("null\n"); + break; + case TagLib_Variant_Bool: + printf("%s\n", attr->value.value.boolValue ? "true" : "false"); + break; + case TagLib_Variant_Int: + printf("%d\n", attr->value.value.intValue); + break; + case TagLib_Variant_UInt: + printf("%u\n", attr->value.value.uIntValue); + break; + case TagLib_Variant_LongLong: + printf("%lld\n", attr->value.value.longLongValue); + break; + case TagLib_Variant_ULongLong: + printf("%llu\n", attr->value.value.uLongLongValue); + break; + case TagLib_Variant_Double: + printf("%f\n", attr->value.value.doubleValue); + break; + case TagLib_Variant_String: + printf("%s\n", attr->value.value.stringValue); + break; + case TagLib_Variant_StringList: + if(attr->value.value.stringListValue) { + char **strs = attr->value.value.stringListValue; + char **s = strs; + while(*s) { + if(s != strs) { + printf(" "); + } + printf("%s", *s++); + } + } + printf("\n"); + break; + case TagLib_Variant_ByteVector: + printf("(%u bytes)\n", attr->value.size); + break; + } + ++attrPtr; + } + ++propPtr; + } + taglib_complex_property_free(properties); + } + ++keyPtr; + } + taglib_complex_property_free_keys(complexKeys); + } + if(properties != NULL) { seconds = taglib_audioproperties_length(properties) % 60; minutes = (taglib_audioproperties_length(properties) - seconds) / 60;