C bindings: taglib_file_new_iostream() to access file from memory (#987)

A ByteVectorStream can be created/freed from C using
taglib_memory_iostream_new()/taglib_iostream_free().
This commit is contained in:
Urs Fleisch 2023-10-22 13:38:57 +02:00
parent 135c0eb647
commit a7c0b53f7a
3 changed files with 160 additions and 21 deletions

View File

@ -31,6 +31,8 @@
#endif
#include "tstringlist.h"
#include "tbytevectorlist.h"
#include "tbytevectorstream.h"
#include "tiostream.h"
#include "tfile.h"
#include "tpropertymap.h"
#include "fileref.h"
@ -91,6 +93,21 @@ void taglib_free(void* pointer)
free(pointer);
}
////////////////////////////////////////////////////////////////////////////////
// TagLib::IOStream wrapper
////////////////////////////////////////////////////////////////////////////////
TagLib_IOStream *taglib_memory_iostream_new(const char *data, unsigned int size)
{
return reinterpret_cast<TagLib_IOStream *>(
new ByteVectorStream(ByteVector(data, size)));
}
void taglib_iostream_free(TagLib_IOStream *stream)
{
delete reinterpret_cast<IOStream *>(stream);
}
////////////////////////////////////////////////////////////////////////////////
// TagLib::FileRef wrapper
////////////////////////////////////////////////////////////////////////////////
@ -140,6 +157,12 @@ TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type)
return file ? reinterpret_cast<TagLib_File *>(new FileRef(file)) : NULL;
}
TagLib_File *taglib_file_new_iostream(TagLib_IOStream *stream)
{
return reinterpret_cast<TagLib_File *>(
new FileRef(reinterpret_cast<IOStream *>(stream)));
}
void taglib_file_free(TagLib_File *file)
{
delete reinterpret_cast<FileRef *>(file);

View File

@ -68,6 +68,7 @@ extern "C" {
typedef struct { int dummy; } TagLib_File;
typedef struct { int dummy; } TagLib_Tag;
typedef struct { int dummy; } TagLib_AudioProperties;
typedef struct { int dummy; } TagLib_IOStream;
/*!
* By default all strings coming into or out of TagLib's C API are in UTF8.
@ -89,6 +90,22 @@ TAGLIB_C_EXPORT void taglib_set_string_management_enabled(BOOL management);
*/
TAGLIB_C_EXPORT void taglib_free(void* pointer);
/*******************************************************************************
* Stream API
******************************************************************************/
/*!
* Creates a byte vector stream from \a size bytes of \a data.
* Such a stream can be used with taglib_file_new_iostream() to create a file
* from memory.
*/
TAGLIB_C_EXPORT TagLib_IOStream *taglib_memory_iostream_new(const char *data, unsigned int size);
/*!
* Frees and closes the stream.
*/
TAGLIB_C_EXPORT void taglib_iostream_free(TagLib_IOStream *stream);
/*******************************************************************************
* File API
******************************************************************************/
@ -121,6 +138,16 @@ TAGLIB_C_EXPORT TagLib_File *taglib_file_new(const char *filename);
*/
TAGLIB_C_EXPORT TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type);
/*!
* Creates a TagLib file from a \a stream.
* A byte vector stream can be used to read a file from memory and write to
* memory, e.g. when fetching network data.
* The stream has to be created using taglib_memory_iostream_new() and shall be
* freed using taglib_iostream_free() \e after this file is freed with
* taglib_file_free().
*/
TAGLIB_C_EXPORT TagLib_File *taglib_file_new_iostream(TagLib_IOStream *stream);
/*!
* Frees and closes the file.
*/

View File

@ -30,15 +30,54 @@
#include "tag_c.h"
#include "tbytevector.h"
#include "tstring.h"
#include "plainfile.h"
#include <cppunit/extensions/HelperMacros.h>
#include "utils.h"
using namespace TagLib;
namespace {
void propertiesToMap(
TagLib_File *file,
std::unordered_map<std::string, std::list<std::string>> &propertyMap)
{
if(char **keys = taglib_property_keys(file)) {
char **keyPtr = keys;
while(*keyPtr) {
char **values = taglib_property_get(file, *keyPtr);
char **valuePtr = values;
std::list<std::string> valueList;
while(*valuePtr) {
valueList.push_back(*valuePtr++);
}
taglib_property_free(values);
propertyMap[*keyPtr++] = valueList;
}
taglib_property_free(keys);
}
}
void complexPropertyKeysToList(
TagLib_File *file,
std::list<std::string> &keyList)
{
if(char **complexKeys = taglib_complex_property_keys(file)) {
char **complexKeyPtr = complexKeys;
while(*complexKeyPtr) {
keyList.push_back(*complexKeyPtr++);
}
taglib_complex_property_free_keys(complexKeys);
}
}
} // namespace
class TestTagC : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(TestTagC);
CPPUNIT_TEST(testMp3);
CPPUNIT_TEST(testStream);
CPPUNIT_TEST_SUITE_END();
public:
@ -89,21 +128,8 @@ public:
CPPUNIT_ASSERT_EQUAL(2U, taglib_tag_track(tag));
CPPUNIT_ASSERT_EQUAL(2023U, taglib_tag_year(tag));
char **keys = taglib_property_keys(file);
CPPUNIT_ASSERT(keys);
std::unordered_map<std::string, std::list<std::string>> propertyMap;
char **keyPtr = keys;
while(*keyPtr) {
char **values = taglib_property_get(file, *keyPtr);
char **valuePtr = values;
std::list<std::string> valueList;
while(*valuePtr) {
valueList.push_back(*valuePtr++);
}
taglib_property_free(values);
propertyMap[*keyPtr++] = valueList;
}
taglib_property_free(keys);
propertiesToMap(file, propertyMap);
const std::unordered_map<std::string, std::list<std::string>> expected {
{"TRACKNUMBER"s, {"2"s}},
{"TITLE"s, {"Title"s}},
@ -117,14 +143,8 @@ public:
};
CPPUNIT_ASSERT(expected == propertyMap);
char **complexKeys = taglib_complex_property_keys(file);
CPPUNIT_ASSERT(complexKeys);
std::list<std::string> keyList;
char **complexKeyPtr = complexKeys;
while(*complexKeyPtr) {
keyList.push_back(*complexKeyPtr++);
}
taglib_complex_property_free_keys(complexKeys);
complexPropertyKeysToList(file, keyList);
CPPUNIT_ASSERT(std::list{"PICTURE"s} == keyList);
TagLib_Complex_Property_Attribute*** properties =
@ -143,6 +163,75 @@ public:
taglib_tag_free_strings();
}
void testStream()
{
// Only fetch the beginning of a FLAC file
const ByteVector data = PlainFile(TEST_FILE_PATH_C("silence-44-s.flac"))
.readBlock(4200);
{
TagLib_IOStream *stream = taglib_memory_iostream_new(data.data(), data.size());
TagLib_File *file = taglib_file_new_iostream(stream);
CPPUNIT_ASSERT(taglib_file_is_valid(file));
const TagLib_AudioProperties *audioProperties = taglib_file_audioproperties(file);
CPPUNIT_ASSERT_EQUAL(2, taglib_audioproperties_channels(audioProperties));
CPPUNIT_ASSERT_EQUAL(3, taglib_audioproperties_length(audioProperties));
CPPUNIT_ASSERT_EQUAL(44100, taglib_audioproperties_samplerate(audioProperties));
TagLib_Tag *tag = taglib_file_tag(file);
CPPUNIT_ASSERT_EQUAL("Quod Libet Test Data"s, std::string(taglib_tag_album(tag)));
CPPUNIT_ASSERT_EQUAL("piman jzig"s, std::string(taglib_tag_artist(tag)));
CPPUNIT_ASSERT_EQUAL("Silence"s, std::string(taglib_tag_genre(tag)));
CPPUNIT_ASSERT_EQUAL(""s, std::string(taglib_tag_comment(tag)));
CPPUNIT_ASSERT_EQUAL("Silence"s, std::string(taglib_tag_title(tag)));
CPPUNIT_ASSERT_EQUAL(2U, taglib_tag_track(tag));
CPPUNIT_ASSERT_EQUAL(2004U, taglib_tag_year(tag));
std::unordered_map<std::string, std::list<std::string>> propertyMap;
propertiesToMap(file, propertyMap);
const std::unordered_map<std::string, std::list<std::string>> expected {
{"TRACKNUMBER"s, {"02/10"s}},
{"TITLE"s, {"Silence"s}},
{"GENRE"s, {"Silence"s}},
{"DATE"s, {"2004"s}},
{"ARTIST"s, {"piman"s, "jzig"s}},
{"ALBUM"s, {"Quod Libet Test Data"s}}
};
CPPUNIT_ASSERT(expected == propertyMap);
std::list<std::string> keyList;
complexPropertyKeysToList(file, keyList);
CPPUNIT_ASSERT(std::list{"PICTURE"s} == keyList);
TagLib_Complex_Property_Attribute*** properties =
taglib_complex_property_get(file, "PICTURE");
TagLib_Complex_Property_Picture_Data picture;
taglib_picture_from_complex_property(properties, &picture);
ByteVector expectedData(
"\x89PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR"
"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53"
"\xde\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b"
"\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd6"
"\x0b\x1c\x0a\x36\x06\x08\x44\x3d\x32\x00\x00\x00\x1dtEXt"
"Comment\0Created"
" with The GIMP\xef\x64"
"\x25\x6e\x00\x00\x00\x0cIDAT\x08\xd7\x63\xf8\xff\xff"
"\x3f\x00\x05\xfe\x02\xfe\xdc\xcc\x59\xe7\x00\x00\x00\x00IEND"
"\xae\x42\x60\x82", 150);
CPPUNIT_ASSERT_EQUAL("image/png"s, std::string(picture.mimeType));
CPPUNIT_ASSERT_EQUAL("A pixel."s, std::string(picture.description));
CPPUNIT_ASSERT_EQUAL("Front Cover"s, std::string(picture.pictureType));
CPPUNIT_ASSERT_EQUAL(expectedData, ByteVector(picture.data, 150));
CPPUNIT_ASSERT_EQUAL(150U, picture.size);
taglib_complex_property_free(properties);
taglib_file_free(file);
taglib_iostream_free(stream);
}
taglib_tag_free_strings();
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestTagC);