mirror of
https://github.com/taglib/taglib.git
synced 2026-06-07 23:09:49 -04:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3de03501f | ||
|
|
1bd0d711ca | ||
|
|
7c85fcaa81 | ||
|
|
cbe54d2f40 | ||
|
|
c4ed590032 | ||
|
|
f3fb4d83a4 | ||
|
|
3d4428726e |
@@ -1,3 +1,12 @@
|
||||
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)
|
||||
==========================
|
||||
|
||||
|
||||
@@ -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 1)
|
||||
set(TAGLIB_SOVERSION_PATCH 2)
|
||||
|
||||
include(ConfigureChecks.cmake)
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 1
|
||||
#define TAGLIB_PATCH_VERSION 2
|
||||
|
||||
#if (defined(_MSC_VER) && _MSC_VER >= 1600)
|
||||
#define TAGLIB_CONSTRUCT_BITSET(x) static_cast<unsigned long long>(x)
|
||||
|
||||
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);
|
||||
|
||||
@@ -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