mirror of
https://github.com/taglib/taglib.git
synced 2026-06-13 17:59:24 -04:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3de03501f | ||
|
|
1bd0d711ca | ||
|
|
7c85fcaa81 | ||
|
|
cbe54d2f40 | ||
|
|
c4ed590032 | ||
|
|
f3fb4d83a4 | ||
|
|
3d4428726e | ||
|
|
ebf4c5bbb1 | ||
|
|
20cec27ac0 | ||
|
|
99bc87ccff | ||
|
|
7951f572b5 | ||
|
|
59ff35772e | ||
|
|
3784628155 | ||
|
|
e60df53152 | ||
|
|
1ae8e18db5 | ||
|
|
0896fb9092 | ||
|
|
920d97606b | ||
|
|
0d2c31b102 | ||
|
|
c8c4e5faec |
26
CHANGELOG.md
26
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
BIN
tests/data/itunes10.mp3
Normal file
Binary file not shown.
BIN
tests/data/nonprintable-atom-type.m4a
Normal file
BIN
tests/data/nonprintable-atom-type.m4a
Normal file
Binary file not shown.
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user