19 Commits
v2.0 ... v2.0.2

Author SHA1 Message Date
Urs Fleisch
e3de03501f Version 2.0.2 2024-08-24 06:40:41 +02:00
Urs Fleisch
1bd0d711ca Support older utfcpp versions with utf8cpp CMake target (#1243) (#1244)
This affects for example openSUSE Leap 15.6, which installs
utfcpp 3.2.1 in its own folder.
Now not only utf8::cpp, but also utf8cpp is supported as a CMake
target.
2024-08-15 12:44:17 +02:00
nekiwo
7c85fcaa81 Remove 'using namespace std' to avoid potential conflicts in example files (#1241)
Co-authored-by: nekiwo <nekiwo@users.noreply.github.com>
2024-08-05 21:54:33 +02:00
Urs Fleisch
cbe54d2f40 Support free form tags with MP4 properties (#1239) (#1240) 2024-07-29 20:24:33 +02:00
Urs Fleisch
c4ed590032 tagwriter option -p not working properly (#1236) (#1237)
The -p option of tagwriter sample does not work.
This is because the picture file is open in text mode instead of binary.
Also, the isFile function does not work on Windows in 32 bit mode with
large files. Using _stat64 instead of stat solves the problem.
2024-07-19 12:25:46 +02:00
Stephen Booth
f3fb4d83a4 Skip unknown MP4 boxes (#1231) 2024-05-18 06:45:10 +02:00
Urs Fleisch
3d4428726e Fix parsing of ID3v2.2 frames (#1228) 2024-05-18 06:43:00 +02:00
Urs Fleisch
ebf4c5bbb1 Version 2.0.1 2024-04-09 19:55:08 +02:00
Urs Fleisch
20cec27ac0 C bindings: Support UTF-8 for property values 2024-04-01 08:45:52 +02:00
Urs Fleisch
99bc87ccff Fix WASM build by inverting wchar_t size check
When building WASM with emscripten

cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/Emscripten.cmake ...

all SIZEOF_ variables which should be defined in ConfigureChecks.cmake
are empty and the wchar_t check fails with "LESS" "2", the other
checks seem to pass since they start with NOT. Instead of explicitly
skipping the check for "if(NOT EMSCRIPTEN)" as is done in vcpkg's
disable-wchar-t-check-emscripten.patch, the check is inverted to
start with NOT, so the build still has a chance to run for compilers
which behave like emscripten.
2024-03-25 20:14:19 +01:00
Urs Fleisch
7951f572b5 Avoid offset_t conflict on Illumos
Taken from NetBSD patch-taglib_toolkit_taglib.h.
2024-03-25 20:14:19 +01:00
Urs Fleisch
59ff35772e Fix building with -DBUILD_TESTING=ON -DBUILD_BINDINGS=OFF 2024-03-25 20:14:19 +01:00
Urs Fleisch
3784628155 Provide equal operator for MP4::Item
This is needed to generate MP4::ItemMap bindings with SWIG.
2024-03-25 20:13:55 +01:00
Urs Fleisch
e60df53152 Add virtual to abstract overridden destructor
This is redundant, but required by SWIG.
2024-03-25 20:13:55 +01:00
Urs Fleisch
1ae8e18db5 Windows: Suppress yet another MSVC C4251 warning 2024-03-25 20:13:55 +01:00
Urs Fleisch
0896fb9092 Detect utf8cpp by header if cmake config is not found (#1217) 2024-02-03 06:28:40 +01:00
Jonas Kvinge
920d97606b FileStream: Fix opening long paths on Windows (#1216)
To make sure paths longer than MAX_PATH (260) can be opened, prefix local
paths with `\\?\`, and UNC paths with `\\?\UNC\`.

I've tested on Windows 10 22H2 (Build 19045.3930), even when setting
LongPathsEnabled to 1 in the registry, it still won't open files with long
paths without prefixing them.

For more information see:
https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
2024-01-28 16:22:14 +01:00
Urs Fleisch
0d2c31b102 Clarify 2.0 source compatibility, remove obsolete URL (#1214) 2024-01-28 07:17:05 +01:00
Urs Fleisch
c8c4e5faec Fix 'get() != pointer()' assertion copying ByteVectorList/StringList (#1211)
This reverts dfef09f13 but keeps the assignments as a comment so these
functions do not look like they can be defaulted even though they cannot.
2024-01-27 10:56:31 +01:00
28 changed files with 487 additions and 119 deletions

View File

@@ -1,8 +1,30 @@
TagLib 2.0.2 (Aug 24, 2024)
===========================
* Fix parsing of ID3v2.2 frames.
* Tolerate MP4 files with unknown atom types as generated by Android tools.
* Support setting properties with arbitrary names in MP4 tags.
* Windows: Fix "-p" option in tagwriter example.
* Support building with older utfcpp versions.
TagLib 2.0.1 (Apr 9, 2024)
==========================
* Fix aborting when _GLIBCXX_ASSERTIONS are enabled.
* Fall back to utf8cpp header detection in the case that its CMake
configuration is removed.
* Improve compatibility with the SWIG interface compiler.
* Build system fixes for testing without bindings, Emscripten and Illumos.
* C bindings: Fix setting UTF-8 encoded property values.
* Windows: Fix opening long paths.
TagLib 2.0 (Jan 24, 2024)
=========================
* New major version, binary incompatible, but source-compatible with the
latest 1.x release if no deprecated features are used.
* New major version, binary incompatible, but mostly source-compatible
with the latest 1.x release if no deprecated features are used.
Simple applications should build without changes, more complex
applications (e.g. extending classes of TagLib) will have to be adapted.
* Requires a C++17 compiler and uses features of C++17.
* Major code cleanup, fixed warnings issued by compilers and static analyzers.
* Made methods virtual which should have been virtual but could not be

View File

@@ -93,7 +93,7 @@ endif()
# Patch version: increase it for bug fix releases.
set(TAGLIB_SOVERSION_MAJOR 2)
set(TAGLIB_SOVERSION_MINOR 0)
set(TAGLIB_SOVERSION_PATCH 0)
set(TAGLIB_SOVERSION_PATCH 2)
include(ConfigureChecks.cmake)
@@ -149,18 +149,33 @@ if(TRACE_IN_RELEASE)
endif()
find_package(utf8cpp QUIET)
if(NOT utf8cpp_FOUND)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/utfcpp/CMakeLists.txt)
add_subdirectory("3rdparty/utfcpp")
message(STATUS "Using utfcpp from ${utf8cpp_SOURCE_DIR}")
else()
message(FATAL_ERROR
"utfcpp not found. Either install package (probably utfcpp, utf8cpp, or libutfcpp-dev) "
"or fetch the git submodule using\n"
"git submodule update --init")
endif()
else()
if(utf8cpp_FOUND)
message(STATUS "Using utfcpp ${utf8cpp_VERSION} from ${utf8cpp_CONFIG}")
else()
find_path(utf8cpp_INCLUDE_DIR NAMES utf8.h PATH_SUFFIXES utf8cpp
DOC "utf8cpp include directory")
mark_as_advanced(utf8cpp_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(utf8cpp REQUIRED_VARS utf8cpp_INCLUDE_DIR)
if(utf8cpp_FOUND)
set(utf8cpp_INCLUDE_DIRS "${utf8cpp_INCLUDE_DIR}")
if(NOT TARGET utf8::cpp)
add_library(utf8::cpp INTERFACE IMPORTED)
set_target_properties(utf8::cpp PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${utf8cpp_INCLUDE_DIR}")
endif()
message(STATUS "Using utfcpp from ${utf8cpp_INCLUDE_DIR}")
else()
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/utfcpp/CMakeLists.txt)
add_subdirectory("3rdparty/utfcpp")
message(STATUS "Using utfcpp from ${utf8cpp_SOURCE_DIR}")
else()
message(FATAL_ERROR
"utfcpp not found. Either install package (probably utfcpp, utf8cpp, or libutfcpp-dev) "
"or fetch the git submodule using\n"
"git submodule update --init")
endif()
endif()
endif()
add_subdirectory(taglib)

View File

@@ -20,7 +20,7 @@ if(NOT ${SIZEOF_LONGLONG} EQUAL 8)
endif()
check_type_size("wchar_t" SIZEOF_WCHAR_T)
if(${SIZEOF_WCHAR_T} LESS 2)
if(NOT ${SIZEOF_WCHAR_T} GREATER 1)
message(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.")
endif()

View File

@@ -7,10 +7,10 @@
https://taglib.org/
TagLib is a library for reading and editing the metadata of several
popular audio formats. Currently, it supports both ID3v1 and [ID3v2][]
popular audio formats. Currently, it supports both ID3v1 and ID3v2
for MP3 files, [Ogg Vorbis][] comments and ID3 tags
in [FLAC][], MPC, Speex, WavPack, TrueAudio, WAV, AIFF, MP4, APE,
and ASF files.
in [FLAC][], MPC, Speex, WavPack, TrueAudio, WAV, AIFF, MP4, APE, ASF,
DSF, DFF and AAC files.
TagLib is distributed under the [GNU Lesser General Public License][]
(LGPL) and [Mozilla Public License][] (MPL). Essentially that means that
@@ -18,7 +18,6 @@ it may be used in proprietary applications, but if changes are made to
TagLib they must be contributed back to the project. Please review the
licenses if you are considering using TagLib in your project.
[ID3v2]: https://id3.org/
[Ogg Vorbis]: https://xiph.org/vorbis/
[FLAC]: https://xiph.org/flac/
[GNU Lesser General Public License]: https://www.gnu.org/licenses/lgpl.html

View File

@@ -410,14 +410,14 @@ void _taglib_property_set(TagLib_File *file, const char* prop, const char* value
if(value) {
auto property = map.find(prop);
if(property == map.end()) {
map.insert(prop, StringList(value));
map.insert(prop, StringList(charArrayToString(value)));
}
else {
if(append) {
property->second.append(value);
property->second.append(charArrayToString(value));
}
else {
property->second = StringList(value);
property->second = StringList(charArrayToString(value));
}
}
}
@@ -542,14 +542,14 @@ bool _taglib_complex_property_set(
map.insert(attrKey, attr->value.value.doubleValue);
break;
case TagLib_Variant_String:
map.insert(attrKey, attr->value.value.stringValue);
map.insert(attrKey, charArrayToString(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++);
strs.append(charArrayToString(*s++));
}
}
map.insert(attrKey, strs);

View File

@@ -34,7 +34,6 @@
#include "id3v1tag.h"
#include "apetag.h"
using namespace std;
using namespace TagLib;
int main(int argc, char *argv[])
@@ -44,7 +43,7 @@ int main(int argc, char *argv[])
for(int i = 1; i < argc; i++) {
cout << "******************** \"" << argv[i] << "\"********************" << endl;
std::cout << "******************** \"" << argv[i] << "\"********************" << std::endl;
MPEG::File f(argv[i]);
@@ -52,62 +51,62 @@ int main(int argc, char *argv[])
if(id3v2tag) {
cout << "ID3v2."
std::cout << "ID3v2."
<< id3v2tag->header()->majorVersion()
<< "."
<< id3v2tag->header()->revisionNumber()
<< ", "
<< id3v2tag->header()->tagSize()
<< " bytes in tag"
<< endl;
<< std::endl;
const auto &frames = id3v2tag->frameList();
for(auto it = frames.begin(); it != frames.end(); it++) {
cout << (*it)->frameID();
std::cout << (*it)->frameID();
if(auto comment = dynamic_cast<ID3v2::CommentsFrame *>(*it))
if(!comment->description().isEmpty())
cout << " [" << comment->description() << "]";
std::cout << " [" << comment->description() << "]";
cout << " - \"" << (*it)->toString() << "\"" << endl;
std::cout << " - \"" << (*it)->toString() << "\"" << std::endl;
}
}
else
cout << "file does not have a valid id3v2 tag" << endl;
std::cout << "file does not have a valid id3v2 tag" << std::endl;
cout << endl << "ID3v1" << endl;
std::cout << std::endl << "ID3v1" << std::endl;
ID3v1::Tag *id3v1tag = f.ID3v1Tag();
if(id3v1tag) {
cout << "title - \"" << id3v1tag->title() << "\"" << endl;
cout << "artist - \"" << id3v1tag->artist() << "\"" << endl;
cout << "album - \"" << id3v1tag->album() << "\"" << endl;
cout << "year - \"" << id3v1tag->year() << "\"" << endl;
cout << "comment - \"" << id3v1tag->comment() << "\"" << endl;
cout << "track - \"" << id3v1tag->track() << "\"" << endl;
cout << "genre - \"" << id3v1tag->genre() << "\"" << endl;
std::cout << "title - \"" << id3v1tag->title() << "\"" << std::endl;
std::cout << "artist - \"" << id3v1tag->artist() << "\"" << std::endl;
std::cout << "album - \"" << id3v1tag->album() << "\"" << std::endl;
std::cout << "year - \"" << id3v1tag->year() << "\"" << std::endl;
std::cout << "comment - \"" << id3v1tag->comment() << "\"" << std::endl;
std::cout << "track - \"" << id3v1tag->track() << "\"" << std::endl;
std::cout << "genre - \"" << id3v1tag->genre() << "\"" << std::endl;
}
else
cout << "file does not have a valid id3v1 tag" << endl;
std::cout << "file does not have a valid id3v1 tag" << std::endl;
APE::Tag *ape = f.APETag();
cout << endl << "APE" << endl;
std::cout << std::endl << "APE" << std::endl;
if(ape) {
const auto &items = ape->itemListMap();
for(auto it = items.begin(); it != items.end(); ++it)
{
if((*it).second.type() != APE::Item::Binary)
cout << (*it).first << " - \"" << (*it).second.toString() << "\"" << endl;
std::cout << (*it).first << " - \"" << (*it).second.toString() << "\"" << std::endl;
else
cout << (*it).first << " - Binary data (" << (*it).second.binaryData().size() << " bytes)" << endl;
std::cout << (*it).first << " - Binary data (" << (*it).second.binaryData().size() << " bytes)" << std::endl;
}
}
else
cout << "file does not have a valid APE tag" << endl;
std::cout << "file does not have a valid APE tag" << std::endl;
cout << endl;
std::cout << std::endl;
}
}

View File

@@ -32,13 +32,11 @@
#include "fileref.h"
#include "tag.h"
using namespace std;
int main(int argc, char *argv[])
{
for(int i = 1; i < argc; i++) {
cout << "******************** \"" << argv[i] << "\" ********************" << endl;
std::cout << "******************** \"" << argv[i] << "\" ********************" << std::endl;
TagLib::FileRef f(argv[i]);
@@ -46,14 +44,14 @@ int main(int argc, char *argv[])
TagLib::Tag *tag = f.tag();
cout << "-- TAG (basic) --" << endl;
cout << "title - \"" << tag->title() << "\"" << endl;
cout << "artist - \"" << tag->artist() << "\"" << endl;
cout << "album - \"" << tag->album() << "\"" << endl;
cout << "year - \"" << tag->year() << "\"" << endl;
cout << "comment - \"" << tag->comment() << "\"" << endl;
cout << "track - \"" << tag->track() << "\"" << endl;
cout << "genre - \"" << tag->genre() << "\"" << endl;
std::cout << "-- TAG (basic) --" << std::endl;
std::cout << "title - \"" << tag->title() << "\"" << std::endl;
std::cout << "artist - \"" << tag->artist() << "\"" << std::endl;
std::cout << "album - \"" << tag->album() << "\"" << std::endl;
std::cout << "year - \"" << tag->year() << "\"" << std::endl;
std::cout << "comment - \"" << tag->comment() << "\"" << std::endl;
std::cout << "track - \"" << tag->track() << "\"" << std::endl;
std::cout << "genre - \"" << tag->genre() << "\"" << std::endl;
TagLib::PropertyMap tags = f.properties();
if(!tags.isEmpty()) {
@@ -64,10 +62,10 @@ int main(int argc, char *argv[])
}
}
cout << "-- TAG (properties) --" << endl;
std::cout << "-- TAG (properties) --" << std::endl;
for(auto j = tags.cbegin(); j != tags.cend(); ++j) {
for(auto k = j->second.begin(); k != j->second.end(); ++k) {
cout << left << std::setfill(' ') << std::setw(longest) << j->first << " - " << '"' << *k << '"' << endl;
std::cout << std::left << std::setfill(' ') << std::setw(longest) << j->first << " - " << '"' << *k << '"' << std::endl;
}
}
}
@@ -76,13 +74,13 @@ int main(int argc, char *argv[])
for(const auto &name : names) {
const auto& properties = f.complexProperties(name);
for(const auto &property : properties) {
cout << name << ":" << endl;
std::cout << name << ":" << std::endl;
for(const auto &[key, value] : property) {
cout << " " << left << std::setfill(' ') << std::setw(11) << key << " - ";
std::cout << " " << std::left << std::setfill(' ') << std::setw(11) << key << " - ";
if(value.type() == TagLib::Variant::ByteVector) {
cout << "(" << value.value<TagLib::ByteVector>().size() << " bytes)" << endl;
std::cout << "(" << value.value<TagLib::ByteVector>().size() << " bytes)" << std::endl;
/* The picture could be extracted using:
ofstream picture;
std::ofstream picture;
TagLib::String fn(argv[i]);
int slashPos = fn.rfind('/');
int dotPos = fn.rfind('.');
@@ -90,13 +88,13 @@ int main(int argc, char *argv[])
fn = fn.substr(slashPos + 1, dotPos - slashPos - 1);
}
fn += ".jpg";
picture.open(fn.toCString(), ios_base::out | ios_base::binary);
picture.open(fn.toCString(), std::ios_base::out | std::ios_base::binary);
picture << value.value<TagLib::ByteVector>();
picture.close();
*/
}
else {
cout << value << endl;
std::cout << value << std::endl;
}
}
}
@@ -110,12 +108,13 @@ int main(int argc, char *argv[])
int seconds = properties->lengthInSeconds() % 60;
int minutes = (properties->lengthInSeconds() - seconds) / 60;
cout << "-- AUDIO --" << endl;
cout << "bitrate - " << properties->bitrate() << endl;
cout << "sample rate - " << properties->sampleRate() << endl;
cout << "channels - " << properties->channels() << endl;
cout << "length - " << minutes << ":" << setfill('0') << setw(2) << right << seconds << endl;
std::cout << "-- AUDIO --" << std::endl;
std::cout << "bitrate - " << properties->bitrate() << std::endl;
std::cout << "sample rate - " << properties->sampleRate() << std::endl;
std::cout << "channels - " << properties->channels() << std::endl;
std::cout << "length - " << minutes << ":" << std::setfill('0') << std::setw(2) << std::right << seconds << std::endl;
}
}
return 0;
}

View File

@@ -39,8 +39,6 @@
#include "fileref.h"
#include "tag.h"
using namespace std;
bool isArgument(const char *s)
{
return strlen(s) == 2 && s[0] == '-';
@@ -48,32 +46,33 @@ bool isArgument(const char *s)
bool isFile(const char *s)
{
struct stat st;
#ifdef _WIN32
return ::stat(s, &st) == 0 && (st.st_mode & (S_IFREG));
struct _stat64 st;
return ::_stat64(s, &st) == 0 && (st.st_mode & S_IFREG);
#else
struct stat st;
return ::stat(s, &st) == 0 && (st.st_mode & (S_IFREG | S_IFLNK));
#endif
}
void usage()
{
cout << endl;
cout << "Usage: tagwriter <fields> <files>" << endl;
cout << endl;
cout << "Where the valid fields are:" << endl;
cout << " -t <title>" << endl;
cout << " -a <artist>" << endl;
cout << " -A <album>" << endl;
cout << " -c <comment>" << endl;
cout << " -g <genre>" << endl;
cout << " -y <year>" << endl;
cout << " -T <track>" << endl;
cout << " -R <tagname> <tagvalue>" << endl;
cout << " -I <tagname> <tagvalue>" << endl;
cout << " -D <tagname>" << endl;
cout << " -p <picturefile> <description> (\"\" \"\" to remove)" << endl;
cout << endl;
std::cout << std::endl;
std::cout << "Usage: tagwriter <fields> <files>" << std::endl;
std::cout << std::endl;
std::cout << "Where the valid fields are:" << std::endl;
std::cout << " -t <title>" << std::endl;
std::cout << " -a <artist>" << std::endl;
std::cout << " -A <album>" << std::endl;
std::cout << " -c <comment>" << std::endl;
std::cout << " -g <genre>" << std::endl;
std::cout << " -y <year>" << std::endl;
std::cout << " -T <track>" << std::endl;
std::cout << " -R <tagname> <tagvalue>" << std::endl;
std::cout << " -I <tagname> <tagvalue>" << std::endl;
std::cout << " -D <tagname>" << std::endl;
std::cout << " -p <picturefile> <description> (\"\" \"\" to remove)" << std::endl;
std::cout << std::endl;
exit(1);
}
@@ -87,10 +86,10 @@ void checkForRejectedProperties(const TagLib::PropertyMap &tags)
longest = i->first.size();
}
}
cout << "-- rejected TAGs (properties) --" << endl;
std::cout << "-- rejected TAGs (properties) --" << std::endl;
for(auto i = tags.begin(); i != tags.end(); ++i) {
for(auto j = i->second.begin(); j != i->second.end(); ++j) {
cout << left << std::setw(longest) << i->first << " - " << '"' << *j << '"' << endl;
std::cout << std::left << std::setw(longest) << i->first << " - " << '"' << *j << '"' << std::endl;
}
}
}
@@ -176,12 +175,12 @@ int main(int argc, char *argv[])
numArgsConsumed = 3;
if(!value.isEmpty()) {
if(!isFile(value.toCString())) {
cout << value.toCString() << " not found." << endl;
std::cout << value.toCString() << " not found." << std::endl;
return 1;
}
ifstream picture;
picture.open(value.toCString());
stringstream buffer;
std::ifstream picture;
picture.open(value.toCString(), std::ios::in | std::ios::binary);
std::stringstream buffer;
buffer << picture.rdbuf();
picture.close();
TagLib::String buf(buffer.str());

View File

@@ -347,7 +347,7 @@ target_include_directories(tag INTERFACE
)
target_link_libraries(tag
PRIVATE $<$<TARGET_EXISTS:utf8::cpp>:utf8::cpp>
PRIVATE $<IF:$<TARGET_EXISTS:utf8::cpp>,utf8::cpp,$<$<TARGET_EXISTS:utf8cpp>:utf8cpp>>
$<$<TARGET_EXISTS:ZLIB::ZLIB>:ZLIB::ZLIB>
)

View File

@@ -143,7 +143,7 @@ namespace TagLib {
/*!
* Destroys this StreamTypeResolver instance.
*/
~StreamTypeResolver() override = 0;
virtual ~StreamTypeResolver() override = 0; // virtual is needed by SWIG
StreamTypeResolver(const StreamTypeResolver &) = delete;
StreamTypeResolver &operator=(const StreamTypeResolver &) = delete;

View File

@@ -95,13 +95,6 @@ MP4::Atom::Atom(File *file)
}
d->name = header.mid(4, 4);
for(int i = 0; i < 4; ++i) {
if(const char ch = d->name.at(i); (ch < ' ' || ch > '~') && ch != '\251') {
debug("MP4: Invalid atom type");
d->length = 0;
file->seek(0, File::End);
}
}
for(auto c : containers) {
if(d->name == c) {

View File

@@ -69,3 +69,13 @@ MP4::CoverArt::data() const
{
return d->data;
}
bool MP4::CoverArt::operator==(const CoverArt &other) const
{
return format() == other.format() && data() == other.data();
}
bool MP4::CoverArt::operator!=(const CoverArt &other) const
{
return !(*this == other);
}

View File

@@ -69,6 +69,17 @@ namespace TagLib {
//! The image data
ByteVector data() const;
/*!
* Returns \c true if the CoverArt and \a other are of the same format and
* contain the same data.
*/
bool operator==(const CoverArt &other) const;
/*!
* Returns \c true if the CoverArt and \a other differ in format or data.
*/
bool operator!=(const CoverArt &other) const;
private:
class CoverArtPrivate;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE

View File

@@ -30,6 +30,7 @@ using namespace TagLib;
class MP4::Item::ItemPrivate
{
public:
Type type;
bool valid { true };
AtomDataType atomDataType { TypeUndefined };
union {
@@ -48,6 +49,7 @@ public:
MP4::Item::Item() :
d(std::make_shared<ItemPrivate>())
{
d->type = Type::Void;
d->valid = false;
}
@@ -67,36 +69,42 @@ MP4::Item::~Item() = default;
MP4::Item::Item(bool value) :
d(std::make_shared<ItemPrivate>())
{
d->type = Type::Bool;
d->m_bool = value;
}
MP4::Item::Item(int value) :
d(std::make_shared<ItemPrivate>())
{
d->type = Type::Int;
d->m_int = value;
}
MP4::Item::Item(unsigned char value) :
d(std::make_shared<ItemPrivate>())
{
d->type = Type::Byte;
d->m_byte = value;
}
MP4::Item::Item(unsigned int value) :
d(std::make_shared<ItemPrivate>())
{
d->type = Type::UInt;
d->m_uint = value;
}
MP4::Item::Item(long long value) :
d(std::make_shared<ItemPrivate>())
{
d->type = Type::LongLong;
d->m_longlong = value;
}
MP4::Item::Item(int value1, int value2) :
d(std::make_shared<ItemPrivate>())
{
d->type = Type::IntPair;
d->m_intPair.first = value1;
d->m_intPair.second = value2;
}
@@ -104,18 +112,21 @@ MP4::Item::Item(int value1, int value2) :
MP4::Item::Item(const ByteVectorList &value) :
d(std::make_shared<ItemPrivate>())
{
d->type = Type::ByteVectorList;
d->m_byteVectorList = value;
}
MP4::Item::Item(const StringList &value) :
d(std::make_shared<ItemPrivate>())
{
d->type = Type::StringList;
d->m_stringList = value;
}
MP4::Item::Item(const MP4::CoverArtList &value) :
d(std::make_shared<ItemPrivate>())
{
d->type = Type::CoverArtList;
d->m_coverArtList = value;
}
@@ -188,3 +199,47 @@ MP4::Item::isValid() const
{
return d->valid;
}
MP4::Item::Type MP4::Item::type() const
{
return d->type;
}
bool MP4::Item::operator==(const Item &other) const
{
if(isValid() && other.isValid() &&
type() == other.type() &&
atomDataType() == other.atomDataType()) {
switch(type()) {
case Type::Void:
return true;
case Type::Bool:
return toBool() == other.toBool();
case Type::Int:
return toInt() == other.toInt();
case Type::IntPair: {
const auto lhs = toIntPair();
const auto rhs = other.toIntPair();
return lhs.first == rhs.first && lhs.second == rhs.second;
}
case Type::Byte:
return toByte() == other.toByte();
case Type::UInt:
return toUInt() == other.toUInt();
case Type::LongLong:
return toLongLong() == other.toLongLong();
case Type::StringList:
return toStringList() == other.toStringList();
case Type::ByteVectorList:
return toByteVectorList() == other.toByteVectorList();
case Type::CoverArtList:
return toCoverArtList() == other.toCoverArtList();
}
}
return false;
}
bool MP4::Item::operator!=(const Item &other) const
{
return !(*this == other);
}

View File

@@ -36,6 +36,22 @@ namespace TagLib {
class TAGLIB_EXPORT Item
{
public:
/*!
* The data type stored in the item.
*/
enum class Type : unsigned char {
Void,
Bool,
Int,
IntPair,
Byte,
UInt,
LongLong,
StringList,
ByteVectorList,
CoverArtList
};
struct IntPair {
int first, second;
};
@@ -80,6 +96,19 @@ namespace TagLib {
bool isValid() const;
Type type() const;
/*!
* Returns \c true if the Item and \a other are of the same type and
* contain the same value.
*/
bool operator==(const Item &other) const;
/*!
* Returns \c true if the Item and \a other differ in type or value.
*/
bool operator!=(const Item &other) const;
private:
class ItemPrivate;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE

View File

@@ -35,6 +35,12 @@
using namespace TagLib;
using namespace MP4;
namespace {
constexpr char freeFormPrefix[] = "----:com.apple.iTunes:";
} // namespace
class ItemFactory::ItemFactoryPrivate
{
public:
@@ -231,7 +237,11 @@ String ItemFactory::propertyKeyForName(const ByteVector &name) const
if(d->propertyKeyForName.isEmpty()) {
d->propertyKeyForName = namePropertyMap();
}
return d->propertyKeyForName.value(name);
String key = d->propertyKeyForName.value(name);
if(key.isEmpty() && name.startsWith(freeFormPrefix)) {
key = name.mid(std::size(freeFormPrefix) - 1);
}
return key;
}
ByteVector ItemFactory::nameForPropertyKey(const String &key) const
@@ -244,7 +254,14 @@ ByteVector ItemFactory::nameForPropertyKey(const String &key) const
d->nameForPropertyKey[t] = k;
}
}
return d->nameForPropertyKey.value(key);
ByteVector name = d->nameForPropertyKey.value(key);
if(name.isEmpty() && !key.isEmpty()) {
const auto &firstChar = key[0];
if(firstChar >= 'A' && firstChar <= 'Z') {
name = (freeFormPrefix + key).data(String::UTF8);
}
}
return name;
}
////////////////////////////////////////////////////////////////////////////////

View File

@@ -843,8 +843,10 @@ void ID3v2::Tag::parse(const ByteVector &origData)
break;
}
Frame *frame = d->factory->createFrame(data.mid(frameDataPosition),
&d->header);
const ByteVector origData = data.mid(frameDataPosition);
const Header *tagHeader = &d->header;
unsigned int headerVersion = tagHeader->majorVersion();
Frame *frame = d->factory->createFrame(origData, tagHeader);
if(!frame)
return;
@@ -856,7 +858,15 @@ void ID3v2::Tag::parse(const ByteVector &origData)
return;
}
frameDataPosition += frame->size() + frame->headerSize();
if(frame->header()->version() == headerVersion) {
frameDataPosition += frame->size() + frame->headerSize();
} else {
// The frame was converted to another version, e.g. from 2.2 to 2.4.
// We must advance the frame data position according to the original
// frame, not the converted frame because its header size might differ.
Frame::Header origHeader(origData, headerVersion);
frameDataPosition += origHeader.frameSize() + origHeader.size();
}
addFrame(frame);
}

View File

@@ -28,7 +28,7 @@
#define TAGLIB_MAJOR_VERSION 2
#define TAGLIB_MINOR_VERSION 0
#define TAGLIB_PATCH_VERSION 0
#define TAGLIB_PATCH_VERSION 2
#if (defined(_MSC_VER) && _MSC_VER >= 1600)
#define TAGLIB_CONSTRUCT_BITSET(x) static_cast<unsigned long long>(x)
@@ -60,7 +60,7 @@ namespace TagLib {
// In Win32, always 64bit. Otherwise, equivalent to off_t.
#ifdef _WIN32
using offset_t = long long;
#else
#elif !defined(__illumos__)
using offset_t = off_t;
#endif

View File

@@ -70,7 +70,8 @@ ByteVectorList::~ByteVectorList() = default;
ByteVectorList::ByteVectorList(const ByteVectorList &l) :
List<ByteVector>(l)
{
*d = *l.d;
// Uncomment if d is used, d.get() is nullptr and *d behavior undefined
// *d = *l.d;
}
ByteVectorList::ByteVectorList(std::initializer_list<ByteVector> init) :
@@ -84,7 +85,8 @@ ByteVectorList &ByteVectorList::operator=(const ByteVectorList &l)
return *this;
List<ByteVector>::operator=(l);
*d = *l.d;
// Uncomment if d is used, d.get() is nullptr and *d behavior undefined
// *d = *l.d;
return *this;
}

View File

@@ -56,7 +56,21 @@ namespace
#if defined (PLATFORM_WINRT)
return CreateFile2(path.wstr().c_str(), access, FILE_SHARE_READ, OPEN_EXISTING, nullptr);
#else
return CreateFileW(path.wstr().c_str(), access, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
constexpr wchar_t LongLocalPathPrefix[] = L"\\\\?\\";
constexpr wchar_t UNCPathPrefix[] = L"\\\\";
constexpr wchar_t LongUNCPathPrefix[] = L"\\\\?\\UNC\\";
std::wstring pathWStr = path.wstr();
if(pathWStr.length() > MAX_PATH &&
pathWStr.compare(0, std::size(LongLocalPathPrefix) - 1, LongLocalPathPrefix) != 0 &&
pathWStr.compare(0, std::size(LongUNCPathPrefix) - 1, LongUNCPathPrefix) != 0) {
if(pathWStr.compare(0, std::size(UNCPathPrefix) - 1, UNCPathPrefix) == 0) {
pathWStr = LongUNCPathPrefix + pathWStr.substr(2);
}
else {
pathWStr = LongLocalPathPrefix + pathWStr;
}
}
return CreateFileW(pathWStr.c_str(), access, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
#endif
}

View File

@@ -31,6 +31,7 @@
#ifdef _MSC_VER
// Explained at end of tpropertymap.cpp
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
extern template class TagLib::Map<TagLib::String, TagLib::StringList>;
#endif

View File

@@ -59,7 +59,8 @@ StringList::StringList() = default;
StringList::StringList(const StringList &l) :
List<String>(l)
{
*d = *l.d;
// Uncomment if d is used, d.get() is nullptr and *d behavior undefined
// *d = *l.d;
}
StringList::StringList(std::initializer_list<String> init) :
@@ -73,7 +74,8 @@ StringList &StringList::operator=(const StringList &l)
return *this;
List<String>::operator=(l);
*d = *l.d;
// Uncomment if d is used, d.get() is nullptr and *d behavior undefined
// *d = *l.d;
return *this;
}

View File

@@ -76,13 +76,20 @@ SET(test_runner_SRCS
test_dsdiff.cpp
test_sizes.cpp
test_versionnumber.cpp
test_tag_c.cpp
)
IF(BUILD_BINDINGS)
SET(test_runner_SRCS ${test_runner_SRCS}
test_tag_c.cpp
)
ENDIF()
INCLUDE_DIRECTORIES(${CPPUNIT_INCLUDE_DIR})
ADD_EXECUTABLE(test_runner ${test_runner_SRCS})
TARGET_LINK_LIBRARIES(test_runner tag tag_c ${CPPUNIT_LIBRARIES})
TARGET_LINK_LIBRARIES(test_runner tag ${CPPUNIT_LIBRARIES})
IF(BUILD_BINDINGS)
TARGET_LINK_LIBRARIES(test_runner tag_c)
ENDIF()
ADD_TEST(test_runner test_runner)
ADD_CUSTOM_TARGET(check COMMAND ${CMAKE_CTEST_COMMAND} -V

BIN
tests/data/itunes10.mp3 Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -101,6 +101,7 @@ class TestMP4 : public CppUnit::TestFixture
CPPUNIT_TEST(testRemoveMetadata);
CPPUNIT_TEST(testNonFullMetaAtom);
CPPUNIT_TEST(testItemFactory);
CPPUNIT_TEST(testNonPrintableAtom);
CPPUNIT_TEST_SUITE_END();
public:
@@ -441,6 +442,7 @@ public:
tags["BPM"] = StringList("123");
tags["ARTIST"] = StringList("Foo Bar");
tags["COMPILATION"] = StringList("1");
tags["REMIXEDBY"] = StringList("Remixed by");
f.setProperties(tags);
tags = f.properties();
@@ -467,6 +469,11 @@ public:
CPPUNIT_ASSERT_EQUAL(true, f.tag()->item("cpil").toBool());
CPPUNIT_ASSERT_EQUAL(StringList("1"), tags["COMPILATION"]);
CPPUNIT_ASSERT(f.tag()->contains("----:com.apple.iTunes:REMIXEDBY"));
CPPUNIT_ASSERT_EQUAL(StringList("Remixed by"),
f.tag()->item("----:com.apple.iTunes:REMIXEDBY").toStringList());
CPPUNIT_ASSERT_EQUAL(StringList("Remixed by"), tags["REMIXEDBY"]);
tags["COMPILATION"] = StringList("0");
f.setProperties(tags);
@@ -847,6 +854,25 @@ public:
CPPUNIT_ASSERT_EQUAL(StringList("456"), properties.value("TESTINTEGER"));
}
}
void testNonPrintableAtom()
{
ScopedFileCopy copy("nonprintable-atom-type", ".m4a");
{
MP4::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(f.isValid());
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(32000, f.audioProperties()->sampleRate());
f.tag()->setTitle("TITLE");
f.save();
}
{
MP4::File f(copy.fileName().c_str());
CPPUNIT_ASSERT(f.isValid());
CPPUNIT_ASSERT(f.hasMP4Tag());
CPPUNIT_ASSERT_EQUAL(String("TITLE"), f.tag()->title());
}
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4);

View File

@@ -39,6 +39,7 @@ class TestMP4Item : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(TestMP4Item);
CPPUNIT_TEST(testCoverArtList);
CPPUNIT_TEST(testItemOperations);
CPPUNIT_TEST_SUITE_END();
public:
@@ -58,6 +59,112 @@ public:
CPPUNIT_ASSERT_EQUAL(ByteVector("bar"), l[1].data());
}
void testItemOperations()
{
MP4::Item e;
MP4::Item i1(1);
MP4::Item i2(1);
MP4::Item i3(-1);
MP4::Item c1(static_cast<unsigned char>('A'));
MP4::Item c2(static_cast<unsigned char>('A'));
MP4::Item c3(static_cast<unsigned char>('Z'));
MP4::Item u1(2U);
MP4::Item u2(2U);
MP4::Item u3(0U);
MP4::Item l1(3LL);
MP4::Item l2(3LL);
MP4::Item l3(-7LL);
MP4::Item b1(true);
MP4::Item b2(true);
MP4::Item b3(false);
MP4::Item p1(4, 5);
MP4::Item p2(4, 5);
MP4::Item p3(-4, -5);
MP4::Item s1(StringList{"abc", "de"});
MP4::Item s2(StringList{"abc", "de"});
MP4::Item s3(StringList{"abc"});
MP4::Item v1(ByteVectorList{"f", "gh"});
MP4::Item v2(ByteVectorList{"f", "gh"});
MP4::Item v3(ByteVectorList{});
MP4::Item a1(MP4::CoverArtList{
MP4::CoverArt(MP4::CoverArt::PNG, "foo"),
MP4::CoverArt(MP4::CoverArt::JPEG, "bar")
});
MP4::Item a2(MP4::CoverArtList{
MP4::CoverArt(MP4::CoverArt::PNG, "foo"),
MP4::CoverArt(MP4::CoverArt::JPEG, "bar")
});
MP4::Item a3(MP4::CoverArtList{
MP4::CoverArt(MP4::CoverArt::JPEG, "bar")
});
CPPUNIT_ASSERT(i1 == i2);
CPPUNIT_ASSERT(i2 != i3);
CPPUNIT_ASSERT(i3 != c1);
CPPUNIT_ASSERT(c1 == c1);
CPPUNIT_ASSERT(c1 == c2);
CPPUNIT_ASSERT(c2 != c3);
CPPUNIT_ASSERT(c3 != u1);
CPPUNIT_ASSERT(u1 == u2);
CPPUNIT_ASSERT(u2 != u3);
CPPUNIT_ASSERT(u3 != l1);
CPPUNIT_ASSERT(l1 == l2);
CPPUNIT_ASSERT(l2 != l3);
CPPUNIT_ASSERT(l3 != b1);
CPPUNIT_ASSERT(b1 == b2);
CPPUNIT_ASSERT(b2 != b3);
CPPUNIT_ASSERT(b3 != p1);
CPPUNIT_ASSERT(p1 == p2);
CPPUNIT_ASSERT(p2 != p3);
CPPUNIT_ASSERT(p3 != s1);
CPPUNIT_ASSERT(s1 == s2);
CPPUNIT_ASSERT(s2 != s3);
CPPUNIT_ASSERT(s3 != v1);
CPPUNIT_ASSERT(v1 == v2);
CPPUNIT_ASSERT(v2 != v3);
CPPUNIT_ASSERT(v3 != a1);
CPPUNIT_ASSERT(a1 == a2);
CPPUNIT_ASSERT(a2 != a3);
CPPUNIT_ASSERT(a3 != e);
CPPUNIT_ASSERT(!e.isValid());
CPPUNIT_ASSERT(i1.isValid());
CPPUNIT_ASSERT_EQUAL(MP4::Item::Type::Void, e.type());
CPPUNIT_ASSERT_EQUAL(MP4::Item::Type::Int, i1.type());
CPPUNIT_ASSERT_EQUAL(MP4::Item::Type::Byte, c1.type());
CPPUNIT_ASSERT_EQUAL(MP4::Item::Type::UInt, u1.type());
CPPUNIT_ASSERT_EQUAL(MP4::Item::Type::LongLong, l1.type());
CPPUNIT_ASSERT_EQUAL(MP4::Item::Type::Bool, b1.type());
CPPUNIT_ASSERT_EQUAL(MP4::Item::Type::IntPair, p1.type());
CPPUNIT_ASSERT_EQUAL(MP4::Item::Type::StringList, s1.type());
CPPUNIT_ASSERT_EQUAL(MP4::Item::Type::ByteVectorList, v1.type());
CPPUNIT_ASSERT_EQUAL(MP4::Item::Type::CoverArtList, a1.type());
CPPUNIT_ASSERT_EQUAL(1, i1.toInt());
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>('A'), c1.toByte());
CPPUNIT_ASSERT_EQUAL(2U, u1.toUInt());
CPPUNIT_ASSERT_EQUAL(3LL, l1.toLongLong());
CPPUNIT_ASSERT_EQUAL(true, b1.toBool());
CPPUNIT_ASSERT_EQUAL(4, p1.toIntPair().first);
CPPUNIT_ASSERT_EQUAL((StringList{"abc", "de"}), s1.toStringList());
CPPUNIT_ASSERT_EQUAL((ByteVectorList{"f", "gh"}), v1.toByteVectorList());
CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, a1.toCoverArtList().front().format());
s3.swap(s1);
CPPUNIT_ASSERT_EQUAL((StringList{"abc"}), s1.toStringList());
CPPUNIT_ASSERT_EQUAL((StringList{"abc", "de"}), s3.toStringList());
CPPUNIT_ASSERT_EQUAL(MP4::AtomDataType::TypeUndefined, s1.atomDataType());
s1.setAtomDataType(MP4::AtomDataType::TypeUTF8);
CPPUNIT_ASSERT_EQUAL(MP4::AtomDataType::TypeUTF8, s1.atomDataType());
s1 = s3;
CPPUNIT_ASSERT_EQUAL((StringList{"abc", "de"}), s1.toStringList());
MP4::ItemMap m1{{"key1", i1}, {"key2", p1}};
MP4::ItemMap m2{{"key1", i2}, {"key2", p2}};
MP4::ItemMap m3{{"key1", i2}, {"key2", p3}};
CPPUNIT_ASSERT(m1 == m2);
CPPUNIT_ASSERT(m1 != m3);
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4Item);

View File

@@ -71,6 +71,7 @@ class TestMPEG : public CppUnit::TestFixture
CPPUNIT_TEST(testIgnoreGarbage);
CPPUNIT_TEST(testExtendedHeader);
CPPUNIT_TEST(testReadStyleFast);
CPPUNIT_TEST(testID3v22Properties);
CPPUNIT_TEST_SUITE_END();
public:
@@ -618,6 +619,56 @@ public:
}
}
void testID3v22Properties()
{
ScopedFileCopy copy("itunes10", ".mp3");
MPEG::File f(copy.fileName().c_str());
PropertyMap expectedProperties(SimplePropertyMap{
{"ALBUM", {"Album"}},
{"ALBUMARTIST", {"Album Artist"}},
{"ALBUMARTISTSORT", {"Sort Album Artist"}},
{"ALBUMSORT", {"Sort Album"}},
{"ARTIST", {"Artist"}},
{"ARTISTSORT", {"Sort Artist"}},
{"BPM", {"180"}},
{"COMMENT", {"Comments"}},
{"COMMENT:ITUNPGAP", {"1"}},
{"COMPILATION", {"1"}},
{"COMPOSER", {"Composer"}},
{"COMPOSERSORT", {"Sort Composer"}},
{"DATE", {"2011"}},
{"DISCNUMBER", {"1/2"}},
{"GENRE", {"Heavy Metal"}},
{"LYRICS", {"Lyrics"}},
{"SUBTITLE", {"Description"}},
{"TITLE", {"iTunes10MP3"}},
{"TITLESORT", {"Sort Name"}},
{"TRACKNUMBER", {"1/10"}},
{"WORK", {"Grouping"}}
});
expectedProperties.addUnsupportedData("APIC");
expectedProperties.addUnsupportedData("UNKNOWN/RVA");
PropertyMap properties = f.properties();
if (expectedProperties != properties) {
CPPUNIT_ASSERT_EQUAL(expectedProperties.toString(), properties.toString());
}
CPPUNIT_ASSERT(expectedProperties == properties);
const String PICTURE_KEY("PICTURE");
CPPUNIT_ASSERT_EQUAL(StringList(PICTURE_KEY), f.complexPropertyKeys());
auto pictures = f.complexProperties(PICTURE_KEY);
CPPUNIT_ASSERT_EQUAL(1U, pictures.size());
auto picture = pictures.front();
CPPUNIT_ASSERT_EQUAL(String("image/png"), picture.value("mimeType").toString());
CPPUNIT_ASSERT(picture.value("description").toString().isEmpty());
CPPUNIT_ASSERT_EQUAL(String("Other"), picture.value("pictureType").toString());
auto data = picture.value("data").toByteVector();
CPPUNIT_ASSERT(data.startsWith("\x89PNG\x0d\x0a\x1a\x0a"));
CPPUNIT_ASSERT_EQUAL(2315U, data.size());
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestMPEG);