diff --git a/bindings/c/tag_c.cpp b/bindings/c/tag_c.cpp index 595b6273..c51766a3 100644 --- a/bindings/c/tag_c.cpp +++ b/bindings/c/tag_c.cpp @@ -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( + new ByteVectorStream(ByteVector(data, size))); +} + +void taglib_iostream_free(TagLib_IOStream *stream) +{ + delete reinterpret_cast(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(new FileRef(file)) : NULL; } +TagLib_File *taglib_file_new_iostream(TagLib_IOStream *stream) +{ + return reinterpret_cast( + new FileRef(reinterpret_cast(stream))); +} + void taglib_file_free(TagLib_File *file) { delete reinterpret_cast(file); diff --git a/bindings/c/tag_c.h b/bindings/c/tag_c.h index 6e7d85ea..88b76eda 100644 --- a/bindings/c/tag_c.h +++ b/bindings/c/tag_c.h @@ -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. */ diff --git a/tests/test_tag_c.cpp b/tests/test_tag_c.cpp index 46200c01..0a8b37ad 100644 --- a/tests/test_tag_c.cpp +++ b/tests/test_tag_c.cpp @@ -30,15 +30,54 @@ #include "tag_c.h" #include "tbytevector.h" #include "tstring.h" +#include "plainfile.h" #include #include "utils.h" using namespace TagLib; +namespace { + +void propertiesToMap( + TagLib_File *file, + std::unordered_map> &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 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 &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> propertyMap; - char **keyPtr = keys; - while(*keyPtr) { - char **values = taglib_property_get(file, *keyPtr); - char **valuePtr = values; - std::list 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> 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 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> propertyMap; + propertiesToMap(file, propertyMap); + const std::unordered_map> 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 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);