mirror of
https://github.com/taglib/taglib.git
synced 2025-05-27 13:10:26 -04:00
Unified interface for complex properties like pictures (#94)
Provides a dynamic interface for properties which cannot be represented with simple strings, e.g. pictures. The keys of such properties can be queried using `complexPropertyKeys()`, which could return for example ["PICTURE"]. The property can then be read using `complexProperties("PICTURE")`, which will return a list of variant maps containing the picture data and attributes. Adding a picture is as easy as t->setComplexProperties("PICTURE", { { {"data", data}, {"pictureType", "Front Cover"}, {"mimeType", "image/jpeg"} } });
This commit is contained in:
parent
75d4252480
commit
6be03b7ae1
@ -27,6 +27,8 @@
|
||||
#include <cstdio>
|
||||
|
||||
#include "tpropertymap.h"
|
||||
#include "tstringlist.h"
|
||||
#include "tvariant.h"
|
||||
#include "fileref.h"
|
||||
#include "tag.h"
|
||||
|
||||
@ -65,10 +67,39 @@ int main(int argc, char *argv[])
|
||||
cout << "-- TAG (properties) --" << endl;
|
||||
for(auto i = tags.cbegin(); i != tags.cend(); ++i) {
|
||||
for(auto j = i->second.begin(); j != i->second.end(); ++j) {
|
||||
cout << left << std::setw(longest) << i->first << " - " << '"' << *j << '"' << endl;
|
||||
cout << left << std::setfill(' ') << std::setw(longest) << i->first << " - " << '"' << *j << '"' << endl;
|
||||
}
|
||||
}
|
||||
|
||||
TagLib::StringList names = f.file()->complexPropertyKeys();
|
||||
for(const auto &name : names) {
|
||||
const auto& properties = f.file()->complexProperties(name);
|
||||
for(const auto &property : properties) {
|
||||
cout << name << ":" << endl;
|
||||
for(const auto &[key, value] : property) {
|
||||
cout << " " << left << std::setfill(' ') << std::setw(11) << key << " - ";
|
||||
if(value.type() == TagLib::Variant::ByteVector) {
|
||||
cout << "(" << value.value<TagLib::ByteVector>().size() << " bytes)" << endl;
|
||||
/* The picture could be extracted using:
|
||||
ofstream picture;
|
||||
TagLib::String fn(argv[i]);
|
||||
int slashPos = fn.rfind('/');
|
||||
int dotPos = fn.rfind('.');
|
||||
if(slashPos >= 0 && dotPos > slashPos) {
|
||||
fn = fn.substr(slashPos + 1, dotPos - slashPos - 1);
|
||||
}
|
||||
fn += ".jpg";
|
||||
picture.open(fn.toCString(), ios_base::out | ios_base::binary);
|
||||
picture << value.value<TagLib::ByteVector>();
|
||||
picture.close();
|
||||
*/
|
||||
}
|
||||
else {
|
||||
cout << value << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!f.isNull() && f.audioProperties()) {
|
||||
|
@ -24,6 +24,8 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@ -33,6 +35,7 @@
|
||||
#include "tlist.h"
|
||||
#include "tfile.h"
|
||||
#include "tpropertymap.h"
|
||||
#include "tvariant.h"
|
||||
#include "fileref.h"
|
||||
#include "tag.h"
|
||||
|
||||
@ -69,6 +72,7 @@ void usage()
|
||||
cout << " -R <tagname> <tagvalue>" << endl;
|
||||
cout << " -I <tagname> <tagvalue>" << endl;
|
||||
cout << " -D <tagname>" << endl;
|
||||
cout << " -p <picturefile> <description> (\"\" \"\" to remove)" << endl;
|
||||
cout << endl;
|
||||
|
||||
exit(1);
|
||||
@ -109,12 +113,14 @@ int main(int argc, char *argv[])
|
||||
if(fileList.isEmpty())
|
||||
usage();
|
||||
|
||||
for(int i = 1; i < argc - 1; i += 2) {
|
||||
int i = 1;
|
||||
while(i < argc - 1) {
|
||||
|
||||
if(isArgument(argv[i]) && i + 1 < argc && !isArgument(argv[i + 1])) {
|
||||
|
||||
char field = argv[i][1];
|
||||
TagLib::String value = argv[i + 1];
|
||||
int numArgsConsumed = 2;
|
||||
|
||||
TagLib::List<TagLib::FileRef>::ConstIterator it;
|
||||
for(it = fileList.cbegin(); it != fileList.cend(); ++it) {
|
||||
@ -153,7 +159,7 @@ int main(int argc, char *argv[])
|
||||
else {
|
||||
map.insert(value, TagLib::String(argv[i + 2]));
|
||||
}
|
||||
++i;
|
||||
numArgsConsumed = 3;
|
||||
checkForRejectedProperties((*it).file()->setProperties(map));
|
||||
}
|
||||
else {
|
||||
@ -166,11 +172,49 @@ int main(int argc, char *argv[])
|
||||
checkForRejectedProperties((*it).file()->setProperties(map));
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
if(i + 2 < argc) {
|
||||
numArgsConsumed = 3;
|
||||
if(!value.isEmpty()) {
|
||||
if(!isFile(value.toCString())) {
|
||||
cout << value.toCString() << " not found." << endl;
|
||||
return 1;
|
||||
}
|
||||
ifstream picture;
|
||||
picture.open(value.toCString());
|
||||
stringstream buffer;
|
||||
buffer << picture.rdbuf();
|
||||
picture.close();
|
||||
TagLib::String buf(buffer.str());
|
||||
TagLib::ByteVector data(buf.data(TagLib::String::Latin1));
|
||||
TagLib::String mimeType = data.startsWith("\x89PNG\x0d\x0a\x1a\x0a")
|
||||
? "image/png" : "image/jpeg";
|
||||
TagLib::String description(argv[i + 2]);
|
||||
it->file()->setComplexProperties("PICTURE", {
|
||||
{
|
||||
{"data", data},
|
||||
{"pictureType", "Front Cover"},
|
||||
{"mimeType", mimeType},
|
||||
{"description", description}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
// empty value, remove pictures
|
||||
it->file()->setComplexProperties("PICTURE", {});
|
||||
}
|
||||
}
|
||||
else {
|
||||
usage();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
i += numArgsConsumed;
|
||||
}
|
||||
else
|
||||
usage();
|
||||
|
@ -47,6 +47,7 @@ set(tag_HDRS
|
||||
toolkit/tfilestream.h
|
||||
toolkit/tmap.h
|
||||
toolkit/tmap.tcc
|
||||
toolkit/tpicturetype.h
|
||||
toolkit/tpropertymap.h
|
||||
toolkit/tdebuglistener.h
|
||||
toolkit/tversionnumber.h
|
||||
@ -307,6 +308,7 @@ set(toolkit_SRCS
|
||||
toolkit/tfile.cpp
|
||||
toolkit/tfilestream.cpp
|
||||
toolkit/tdebug.cpp
|
||||
toolkit/tpicturetype.cpp
|
||||
toolkit/tpropertymap.cpp
|
||||
toolkit/tdebuglistener.cpp
|
||||
toolkit/tzlib.cpp
|
||||
|
@ -50,6 +50,9 @@ namespace
|
||||
const unsigned int MinKeyLength = 2;
|
||||
const unsigned int MaxKeyLength = 255;
|
||||
|
||||
const String FRONT_COVER("COVER ART (FRONT)");
|
||||
const String BACK_COVER("COVER ART (BACK)");
|
||||
|
||||
bool isKeyValid(const ByteVector &key)
|
||||
{
|
||||
static constexpr std::array invalidKeys { "ID3", "TAG", "OGGS", "MP+" };
|
||||
@ -265,6 +268,96 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
|
||||
return invalid;
|
||||
}
|
||||
|
||||
StringList APE::Tag::complexPropertyKeys() const
|
||||
{
|
||||
StringList keys;
|
||||
if(d->itemListMap.contains(FRONT_COVER) ||
|
||||
d->itemListMap.contains(BACK_COVER)) {
|
||||
keys.append("PICTURE");
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
List<VariantMap> APE::Tag::complexProperties(const String &key) const
|
||||
{
|
||||
List<VariantMap> properties;
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
const StringList itemNames = StringList(FRONT_COVER).append(BACK_COVER);
|
||||
for(const auto &itemName: itemNames) {
|
||||
if(d->itemListMap.contains(itemName)) {
|
||||
Item picture = d->itemListMap.value(itemName);
|
||||
if(picture.type() == Item::Binary) {
|
||||
ByteVector data = picture.binaryData();
|
||||
// Do not search for a description if the first byte could start JPG or PNG
|
||||
// data.
|
||||
int index = data.isEmpty() || data.at(0) == '\xff' || data.at(0) == '\x89'
|
||||
? -1 : data.find('\0');
|
||||
String description;
|
||||
if(index >= 0) {
|
||||
description = String(data.mid(0, index), String::UTF8);
|
||||
data = data.mid(index + 1);
|
||||
}
|
||||
|
||||
VariantMap property;
|
||||
property.insert("data", data);
|
||||
if(!description.isEmpty()) {
|
||||
property.insert("description", description);
|
||||
}
|
||||
property.insert("pictureType",
|
||||
itemName == BACK_COVER ? "Back Cover" : "Front Cover");
|
||||
properties.append(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool APE::Tag::setComplexProperties(const String &key, const List<VariantMap> &value)
|
||||
{
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
removeItem(FRONT_COVER);
|
||||
removeItem(BACK_COVER);
|
||||
|
||||
auto frontItems = List<Item>();
|
||||
auto backItems = List<Item>();
|
||||
for(auto property : value) {
|
||||
ByteVector data = property.value("description").value<String>().data(String::UTF8)
|
||||
.append('\0')
|
||||
.append(property.value("data").value<ByteVector>());
|
||||
String pictureType = property.value("pictureType").value<String>();
|
||||
Item item;
|
||||
item.setType(Item::Binary);
|
||||
item.setBinaryData(data);
|
||||
if(pictureType == "Back Cover") {
|
||||
item.setKey(BACK_COVER);
|
||||
backItems.append(item);
|
||||
}
|
||||
else if(pictureType == "Front Cover") {
|
||||
item.setKey(FRONT_COVER);
|
||||
// prioritize pictures with correct type
|
||||
frontItems.prepend(item);
|
||||
}
|
||||
else {
|
||||
item.setKey(FRONT_COVER);
|
||||
frontItems.append(item);
|
||||
}
|
||||
}
|
||||
if(!frontItems.isEmpty()) {
|
||||
setItem(FRONT_COVER, frontItems.front());
|
||||
}
|
||||
if(!backItems.isEmpty()) {
|
||||
setItem(BACK_COVER, backItems.front());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool APE::Tag::checkKey(const String &key)
|
||||
{
|
||||
if(key.size() < MinKeyLength || key.size() > MaxKeyLength)
|
||||
|
@ -130,6 +130,10 @@ namespace TagLib {
|
||||
*/
|
||||
PropertyMap setProperties(const PropertyMap &) override;
|
||||
|
||||
StringList complexPropertyKeys() const override;
|
||||
List<VariantMap> complexProperties(const String &key) const override;
|
||||
bool setComplexProperties(const String &key, const List<VariantMap> &value) override;
|
||||
|
||||
/*!
|
||||
* Check if the given String is a valid APE tag key.
|
||||
*/
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
#include "tstring.h"
|
||||
#include "tbytevector.h"
|
||||
#include "tpicturetype.h"
|
||||
#include "taglib_export.h"
|
||||
#include "attachedpictureframe.h"
|
||||
|
||||
@ -52,50 +53,7 @@ namespace TagLib
|
||||
/*!
|
||||
* This describes the function or content of the picture.
|
||||
*/
|
||||
enum Type {
|
||||
//! A type not enumerated below
|
||||
Other = 0x00,
|
||||
//! 32x32 PNG image that should be used as the file icon
|
||||
FileIcon = 0x01,
|
||||
//! File icon of a different size or format
|
||||
OtherFileIcon = 0x02,
|
||||
//! Front cover image of the album
|
||||
FrontCover = 0x03,
|
||||
//! Back cover image of the album
|
||||
BackCover = 0x04,
|
||||
//! Inside leaflet page of the album
|
||||
LeafletPage = 0x05,
|
||||
//! Image from the album itself
|
||||
Media = 0x06,
|
||||
//! Picture of the lead artist or soloist
|
||||
LeadArtist = 0x07,
|
||||
//! Picture of the artist or performer
|
||||
Artist = 0x08,
|
||||
//! Picture of the conductor
|
||||
Conductor = 0x09,
|
||||
//! Picture of the band or orchestra
|
||||
Band = 0x0A,
|
||||
//! Picture of the composer
|
||||
Composer = 0x0B,
|
||||
//! Picture of the lyricist or text writer
|
||||
Lyricist = 0x0C,
|
||||
//! Picture of the recording location or studio
|
||||
RecordingLocation = 0x0D,
|
||||
//! Picture of the artists during recording
|
||||
DuringRecording = 0x0E,
|
||||
//! Picture of the artists during performance
|
||||
DuringPerformance = 0x0F,
|
||||
//! Picture from a movie or video related to the track
|
||||
MovieScreenCapture = 0x10,
|
||||
//! Picture of a large, coloured fish
|
||||
ColouredFish = 0x11,
|
||||
//! Illustration related to the track
|
||||
Illustration = 0x12,
|
||||
//! Logo of the band or performer
|
||||
BandLogo = 0x13,
|
||||
//! Logo of the publisher (record company)
|
||||
PublisherLogo = 0x14
|
||||
};
|
||||
DECLARE_PICTURE_TYPE_ENUM(Type)
|
||||
|
||||
/*!
|
||||
* Constructs an empty picture.
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include <utility>
|
||||
|
||||
#include "tpropertymap.h"
|
||||
#include "asfattribute.h"
|
||||
#include "asfpicture.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
@ -373,3 +375,54 @@ PropertyMap ASF::Tag::setProperties(const PropertyMap &props)
|
||||
|
||||
return ignoredProps;
|
||||
}
|
||||
|
||||
StringList ASF::Tag::complexPropertyKeys() const
|
||||
{
|
||||
StringList keys;
|
||||
if(d->attributeListMap.contains("WM/Picture")) {
|
||||
keys.append("PICTURE");
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
List<VariantMap> ASF::Tag::complexProperties(const String &key) const
|
||||
{
|
||||
List<VariantMap> properties;
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
const AttributeList pictures = d->attributeListMap.value("WM/Picture");
|
||||
for(const Attribute &attribute : pictures) {
|
||||
ASF::Picture picture = attribute.toPicture();
|
||||
VariantMap property;
|
||||
property.insert("data", picture.picture());
|
||||
property.insert("mimeType", picture.mimeType());
|
||||
property.insert("description", picture.description());
|
||||
property.insert("pictureType",
|
||||
ASF::Picture::typeToString(picture.type()));
|
||||
properties.append(property);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool ASF::Tag::setComplexProperties(const String &key, const List<VariantMap> &value)
|
||||
{
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
removeItem("WM/Picture");;
|
||||
|
||||
for(auto property : value) {
|
||||
ASF::Picture picture;
|
||||
picture.setPicture(property.value("data").value<ByteVector>());
|
||||
picture.setMimeType(property.value("mimeType").value<String>());
|
||||
picture.setDescription(property.value("description").value<String>());
|
||||
picture.setType(ASF::Picture::typeFromString(
|
||||
property.value("pictureType").value<String>()));
|
||||
addAttribute("WM/Picture", Attribute(picture));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -204,6 +204,10 @@ namespace TagLib {
|
||||
void removeUnsupportedProperties(const StringList &props) override;
|
||||
PropertyMap setProperties(const PropertyMap &props) override;
|
||||
|
||||
StringList complexPropertyKeys() const override;
|
||||
List<VariantMap> complexProperties(const String &key) const override;
|
||||
bool setComplexProperties(const String &key, const List<VariantMap> &value) override;
|
||||
|
||||
private:
|
||||
|
||||
class TagPrivate;
|
||||
|
@ -140,6 +140,71 @@ PropertyMap FLAC::File::setProperties(const PropertyMap &properties)
|
||||
return xiphComment(true)->setProperties(properties);
|
||||
}
|
||||
|
||||
StringList FLAC::File::complexPropertyKeys() const
|
||||
{
|
||||
StringList keys = TagLib::File::complexPropertyKeys();
|
||||
if(!keys.contains("PICTURE")) {
|
||||
for(const auto &block : std::as_const(d->blocks)) {
|
||||
if(dynamic_cast<Picture *>(block) != nullptr) {
|
||||
keys.append("PICTURE");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
List<VariantMap> FLAC::File::complexProperties(const String &key) const
|
||||
{
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
List<VariantMap> properties;
|
||||
for(const auto &block : std::as_const(d->blocks)) {
|
||||
if(auto picture = dynamic_cast<Picture *>(block)) {
|
||||
VariantMap property;
|
||||
property.insert("data", picture->data());
|
||||
property.insert("mimeType", picture->mimeType());
|
||||
property.insert("description", picture->description());
|
||||
property.insert("pictureType",
|
||||
FLAC::Picture::typeToString(picture->type()));
|
||||
property.insert("width", picture->width());
|
||||
property.insert("height", picture->height());
|
||||
property.insert("numColors", picture->numColors());
|
||||
property.insert("colorDepth", picture->colorDepth());
|
||||
properties.append(property);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
return TagLib::File::complexProperties(key);
|
||||
}
|
||||
|
||||
bool FLAC::File::setComplexProperties(const String &key, const List<VariantMap> &value)
|
||||
{
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
removePictures();
|
||||
|
||||
for(auto property : value) {
|
||||
FLAC::Picture *picture = new FLAC::Picture;
|
||||
picture->setData(property.value("data").value<ByteVector>());
|
||||
picture->setMimeType(property.value("mimeType").value<String>());
|
||||
picture->setDescription(property.value("description").value<String>());
|
||||
picture->setType(FLAC::Picture::typeFromString(
|
||||
property.value("pictureType").value<String>()));
|
||||
picture->setWidth(property.value("width").value<int>());
|
||||
picture->setHeight(property.value("height").value<int>());
|
||||
picture->setNumColors(property.value("numColors").value<int>());
|
||||
picture->setColorDepth(property.value("colorDepth").value<int>());
|
||||
addPicture(picture);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return TagLib::File::setComplexProperties(key, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FLAC::Properties *FLAC::File::audioProperties() const
|
||||
{
|
||||
return d->properties.get();
|
||||
|
@ -161,6 +161,23 @@ namespace TagLib {
|
||||
*/
|
||||
PropertyMap setProperties(const PropertyMap &) override;
|
||||
|
||||
/*!
|
||||
* Returns ["PICTURE"] if any picture is stored in METADATA_BLOCK_PICTURE.
|
||||
*/
|
||||
StringList complexPropertyKeys() const override;
|
||||
|
||||
/*!
|
||||
* Get the pictures stored in METADATA_BLOCK_PICTURE as complex properties
|
||||
* for \a key "PICTURE".
|
||||
*/
|
||||
List<VariantMap> complexProperties(const String &key) const override;
|
||||
|
||||
/*!
|
||||
* Set the complex properties \a value as pictures in METADATA_BLOCK_PICTURE
|
||||
* for \a key "PICTURE".
|
||||
*/
|
||||
bool setComplexProperties(const String &key, const List<VariantMap> &value) override;
|
||||
|
||||
/*!
|
||||
* Returns the FLAC::Properties for this file. If no audio properties
|
||||
* were read then this will return a null pointer.
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "tlist.h"
|
||||
#include "tstring.h"
|
||||
#include "tbytevector.h"
|
||||
#include "tpicturetype.h"
|
||||
#include "taglib_export.h"
|
||||
#include "flacmetadatablock.h"
|
||||
|
||||
@ -41,50 +42,7 @@ namespace TagLib {
|
||||
/*!
|
||||
* This describes the function or content of the picture.
|
||||
*/
|
||||
enum Type {
|
||||
//! A type not enumerated below
|
||||
Other = 0x00,
|
||||
//! 32x32 PNG image that should be used as the file icon
|
||||
FileIcon = 0x01,
|
||||
//! File icon of a different size or format
|
||||
OtherFileIcon = 0x02,
|
||||
//! Front cover image of the album
|
||||
FrontCover = 0x03,
|
||||
//! Back cover image of the album
|
||||
BackCover = 0x04,
|
||||
//! Inside leaflet page of the album
|
||||
LeafletPage = 0x05,
|
||||
//! Image from the album itself
|
||||
Media = 0x06,
|
||||
//! Picture of the lead artist or soloist
|
||||
LeadArtist = 0x07,
|
||||
//! Picture of the artist or performer
|
||||
Artist = 0x08,
|
||||
//! Picture of the conductor
|
||||
Conductor = 0x09,
|
||||
//! Picture of the band or orchestra
|
||||
Band = 0x0A,
|
||||
//! Picture of the composer
|
||||
Composer = 0x0B,
|
||||
//! Picture of the lyricist or text writer
|
||||
Lyricist = 0x0C,
|
||||
//! Picture of the recording location or studio
|
||||
RecordingLocation = 0x0D,
|
||||
//! Picture of the artists during recording
|
||||
DuringRecording = 0x0E,
|
||||
//! Picture of the artists during performance
|
||||
DuringPerformance = 0x0F,
|
||||
//! Picture from a movie or video related to the track
|
||||
MovieScreenCapture = 0x10,
|
||||
//! Picture of a large, coloured fish
|
||||
ColouredFish = 0x11,
|
||||
//! Illustration related to the track
|
||||
Illustration = 0x12,
|
||||
//! Logo of the band or performer
|
||||
BandLogo = 0x13,
|
||||
//! Logo of the publisher (record company)
|
||||
PublisherLogo = 0x14
|
||||
};
|
||||
DECLARE_PICTURE_TYPE_ENUM(Type)
|
||||
|
||||
Picture();
|
||||
Picture(const ByteVector &data);
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "tpropertymap.h"
|
||||
#include "id3v1genres.h"
|
||||
#include "mp4atom.h"
|
||||
#include "mp4coverart.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
@ -1066,6 +1067,78 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
|
||||
return ignoredProps;
|
||||
}
|
||||
|
||||
StringList MP4::Tag::complexPropertyKeys() const
|
||||
{
|
||||
StringList keys;
|
||||
if(d->items.contains("covr")) {
|
||||
keys.append("PICTURE");
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
List<VariantMap> MP4::Tag::complexProperties(const String &key) const
|
||||
{
|
||||
List<VariantMap> properties;
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
const CoverArtList pictures = d->items.value("covr").toCoverArtList();
|
||||
for(const CoverArt &picture : pictures) {
|
||||
String mimeType = "image/";
|
||||
switch(picture.format()) {
|
||||
case CoverArt::BMP:
|
||||
mimeType.append("bmp");
|
||||
break;
|
||||
case CoverArt::JPEG:
|
||||
mimeType.append("jpeg");
|
||||
break;
|
||||
case CoverArt::GIF:
|
||||
mimeType.append("gif");
|
||||
break;
|
||||
case CoverArt::PNG:
|
||||
mimeType.append("png");
|
||||
break;
|
||||
case CoverArt::Unknown:
|
||||
break;
|
||||
}
|
||||
|
||||
VariantMap property;
|
||||
property.insert("data", picture.data());
|
||||
property.insert("mimeType", mimeType);
|
||||
properties.append(property);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool MP4::Tag::setComplexProperties(const String &key, const List<VariantMap> &value)
|
||||
{
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
CoverArtList pictures;
|
||||
for(auto property : value) {
|
||||
String mimeType = property.value("mimeType").value<String>();
|
||||
CoverArt::Format format;
|
||||
if(mimeType == "image/bmp") {
|
||||
format = CoverArt::BMP;
|
||||
} else if(mimeType == "image/png") {
|
||||
format = CoverArt::PNG;
|
||||
} else if(mimeType == "image/gif") {
|
||||
format = CoverArt::GIF;
|
||||
} else if(mimeType == "image/jpeg") {
|
||||
format = CoverArt::JPEG;
|
||||
} else {
|
||||
format = CoverArt::Unknown;
|
||||
}
|
||||
pictures.append(CoverArt(format, property.value("data").value<ByteVector>()));
|
||||
}
|
||||
d->items["covr"] = pictures;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MP4::Tag::addItem(const String &name, const Item &value)
|
||||
{
|
||||
if(!d->items.contains(name)) {
|
||||
|
@ -102,6 +102,10 @@ namespace TagLib {
|
||||
void removeUnsupportedProperties(const StringList &props) override;
|
||||
PropertyMap setProperties(const PropertyMap &props) override;
|
||||
|
||||
StringList complexPropertyKeys() const override;
|
||||
List<VariantMap> complexProperties(const String &key) const override;
|
||||
bool setComplexProperties(const String &key, const List<VariantMap> &value) override;
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Sets the value of \a key to \a value, overwriting any previous value.
|
||||
|
@ -27,6 +27,7 @@
|
||||
#define TAGLIB_ATTACHEDPICTUREFRAME_H
|
||||
|
||||
#include "taglib_export.h"
|
||||
#include "tpicturetype.h"
|
||||
#include "id3v2frame.h"
|
||||
#include "id3v2header.h"
|
||||
|
||||
@ -52,50 +53,7 @@ namespace TagLib {
|
||||
/*!
|
||||
* This describes the function or content of the picture.
|
||||
*/
|
||||
enum Type {
|
||||
//! A type not enumerated below
|
||||
Other = 0x00,
|
||||
//! 32x32 PNG image that should be used as the file icon
|
||||
FileIcon = 0x01,
|
||||
//! File icon of a different size or format
|
||||
OtherFileIcon = 0x02,
|
||||
//! Front cover image of the album
|
||||
FrontCover = 0x03,
|
||||
//! Back cover image of the album
|
||||
BackCover = 0x04,
|
||||
//! Inside leaflet page of the album
|
||||
LeafletPage = 0x05,
|
||||
//! Image from the album itself
|
||||
Media = 0x06,
|
||||
//! Picture of the lead artist or soloist
|
||||
LeadArtist = 0x07,
|
||||
//! Picture of the artist or performer
|
||||
Artist = 0x08,
|
||||
//! Picture of the conductor
|
||||
Conductor = 0x09,
|
||||
//! Picture of the band or orchestra
|
||||
Band = 0x0A,
|
||||
//! Picture of the composer
|
||||
Composer = 0x0B,
|
||||
//! Picture of the lyricist or text writer
|
||||
Lyricist = 0x0C,
|
||||
//! Picture of the recording location or studio
|
||||
RecordingLocation = 0x0D,
|
||||
//! Picture of the artists during recording
|
||||
DuringRecording = 0x0E,
|
||||
//! Picture of the artists during performance
|
||||
DuringPerformance = 0x0F,
|
||||
//! Picture from a movie or video related to the track
|
||||
MovieScreenCapture = 0x10,
|
||||
//! Picture of a large, coloured fish
|
||||
ColouredFish = 0x11,
|
||||
//! Illustration related to the track
|
||||
Illustration = 0x12,
|
||||
//! Logo of the band or performer
|
||||
BandLogo = 0x13,
|
||||
//! Logo of the publisher (record company)
|
||||
PublisherLogo = 0x14
|
||||
};
|
||||
DECLARE_PICTURE_TYPE_ENUM(Type)
|
||||
|
||||
/*!
|
||||
* Constructs an empty picture frame. The description, content and text
|
||||
|
@ -37,6 +37,8 @@
|
||||
#include "id3v2footer.h"
|
||||
#include "id3v2synchdata.h"
|
||||
#include "id3v1genres.h"
|
||||
#include "frames/attachedpictureframe.h"
|
||||
#include "frames/generalencapsulatedobjectframe.h"
|
||||
#include "frames/textidentificationframe.h"
|
||||
#include "frames/commentsframe.h"
|
||||
#include "frames/urllinkframe.h"
|
||||
@ -451,6 +453,84 @@ PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps)
|
||||
return PropertyMap(); // ID3 implements the complete PropertyMap interface, so an empty map is returned
|
||||
}
|
||||
|
||||
StringList ID3v2::Tag::complexPropertyKeys() const
|
||||
{
|
||||
StringList keys;
|
||||
if(d->frameListMap.contains("APIC")) {
|
||||
keys.append("PICTURE");
|
||||
}
|
||||
if(d->frameListMap.contains("GEOB")) {
|
||||
keys.append("GENERALOBJECT");
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
List<VariantMap> ID3v2::Tag::complexProperties(const String &key) const
|
||||
{
|
||||
List<VariantMap> properties;
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
const FrameList pictures = d->frameListMap.value("APIC");
|
||||
for(const Frame *frame : pictures) {
|
||||
auto picture = static_cast<const AttachedPictureFrame *>(frame);
|
||||
VariantMap property;
|
||||
property.insert("data", picture->picture());
|
||||
property.insert("mimeType", picture->mimeType());
|
||||
property.insert("description", picture->description());
|
||||
property.insert("pictureType",
|
||||
AttachedPictureFrame::typeToString(picture->type()));
|
||||
properties.append(property);
|
||||
}
|
||||
}
|
||||
else if(uppercaseKey == "GENERALOBJECT") {
|
||||
const FrameList geobs = d->frameListMap.value("GEOB");
|
||||
for(const Frame *frame : geobs) {
|
||||
auto geob = static_cast<const GeneralEncapsulatedObjectFrame *>(frame);
|
||||
VariantMap property;
|
||||
property.insert("data", geob->object());
|
||||
property.insert("mimeType", geob->mimeType());
|
||||
property.insert("description", geob->description());
|
||||
property.insert("fileName", geob->fileName());
|
||||
properties.append(property);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool ID3v2::Tag::setComplexProperties(const String &key, const List<VariantMap> &value)
|
||||
{
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
removeFrames("APIC");
|
||||
|
||||
for(auto property : value) {
|
||||
auto picture = new AttachedPictureFrame;
|
||||
picture->setPicture(property.value("data").value<ByteVector>());
|
||||
picture->setMimeType(property.value("mimeType").value<String>());
|
||||
picture->setDescription(property.value("description").value<String>());
|
||||
picture->setType(AttachedPictureFrame::typeFromString(
|
||||
property.value("pictureType").value<String>()));
|
||||
addFrame(picture);
|
||||
}
|
||||
}
|
||||
else if(uppercaseKey == "GENERALOBJECT") {
|
||||
removeFrames("GEOB");
|
||||
|
||||
for(auto property : value) {
|
||||
auto geob = new GeneralEncapsulatedObjectFrame;
|
||||
geob->setObject(property.value("data").value<ByteVector>());
|
||||
geob->setMimeType(property.value("mimeType").value<String>());
|
||||
geob->setDescription(property.value("description").value<String>());
|
||||
geob->setFileName(property.value("fileName").value<String>());
|
||||
addFrame(geob);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ByteVector ID3v2::Tag::render() const
|
||||
{
|
||||
return render(ID3v2::v4);
|
||||
|
@ -329,6 +329,10 @@ namespace TagLib {
|
||||
*/
|
||||
PropertyMap setProperties(const PropertyMap &) override;
|
||||
|
||||
StringList complexPropertyKeys() const override;
|
||||
List<VariantMap> complexProperties(const String &key) const override;
|
||||
bool setComplexProperties(const String &key, const List<VariantMap> &value) override;
|
||||
|
||||
/*!
|
||||
* Render the tag back to binary data, suitable to be written to disk.
|
||||
*/
|
||||
|
@ -235,6 +235,63 @@ PropertyMap Ogg::XiphComment::setProperties(const PropertyMap &properties)
|
||||
return invalid;
|
||||
}
|
||||
|
||||
StringList Ogg::XiphComment::complexPropertyKeys() const
|
||||
{
|
||||
StringList keys;
|
||||
if(!d->pictureList.isEmpty()) {
|
||||
keys.append("PICTURE");
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
List<VariantMap> Ogg::XiphComment::complexProperties(const String &key) const
|
||||
{
|
||||
List<VariantMap> properties;
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
for(const FLAC::Picture *picture : std::as_const(d->pictureList)) {
|
||||
VariantMap property;
|
||||
property.insert("data", picture->data());
|
||||
property.insert("mimeType", picture->mimeType());
|
||||
property.insert("description", picture->description());
|
||||
property.insert("pictureType",
|
||||
FLAC::Picture::typeToString(picture->type()));
|
||||
property.insert("width", picture->width());
|
||||
property.insert("height", picture->height());
|
||||
property.insert("numColors", picture->numColors());
|
||||
property.insert("colorDepth", picture->colorDepth());
|
||||
properties.append(property);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool Ogg::XiphComment::setComplexProperties(const String &key, const List<VariantMap> &value)
|
||||
{
|
||||
const String uppercaseKey = key.upper();
|
||||
if(uppercaseKey == "PICTURE") {
|
||||
removeAllPictures();
|
||||
|
||||
for(auto property : value) {
|
||||
FLAC::Picture *picture = new FLAC::Picture;
|
||||
picture->setData(property.value("data").value<ByteVector>());
|
||||
picture->setMimeType(property.value("mimeType").value<String>());
|
||||
picture->setDescription(property.value("description").value<String>());
|
||||
picture->setType(FLAC::Picture::typeFromString(
|
||||
property.value("pictureType").value<String>()));
|
||||
picture->setWidth(property.value("width").value<int>());
|
||||
picture->setHeight(property.value("height").value<int>());
|
||||
picture->setNumColors(property.value("numColors").value<int>());
|
||||
picture->setColorDepth(property.value("colorDepth").value<int>());
|
||||
addPicture(picture);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ogg::XiphComment::checkKey(const String &key)
|
||||
{
|
||||
if(key.size() < 1)
|
||||
|
@ -167,6 +167,10 @@ namespace TagLib {
|
||||
*/
|
||||
PropertyMap setProperties(const PropertyMap&) override;
|
||||
|
||||
StringList complexPropertyKeys() const override;
|
||||
List<VariantMap> complexProperties(const String &key) const override;
|
||||
bool setComplexProperties(const String &key, const List<VariantMap> &value) override;
|
||||
|
||||
/*!
|
||||
* Check if the given String is a valid Xiph comment key.
|
||||
*/
|
||||
|
@ -146,6 +146,21 @@ PropertyMap Tag::setProperties(const PropertyMap &origProps)
|
||||
return properties;
|
||||
}
|
||||
|
||||
StringList Tag::complexPropertyKeys() const
|
||||
{
|
||||
return StringList();
|
||||
}
|
||||
|
||||
List<VariantMap> Tag::complexProperties(const String &) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Tag::setComplexProperties(const String &, const List<VariantMap> &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Tag::duplicate(const Tag *source, Tag *target, bool overwrite) // static
|
||||
{
|
||||
if(overwrite) {
|
||||
|
45
taglib/tag.h
45
taglib/tag.h
@ -28,6 +28,8 @@
|
||||
|
||||
#include "taglib_export.h"
|
||||
#include "tstring.h"
|
||||
#include "tlist.h"
|
||||
#include "tvariant.h"
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
@ -78,6 +80,49 @@ namespace TagLib {
|
||||
*/
|
||||
virtual PropertyMap setProperties(const PropertyMap &origProps);
|
||||
|
||||
/*!
|
||||
* Get the keys of complex properties, i.e. properties which cannot be
|
||||
* represented simply by a string.
|
||||
* Because such properties might be expensive to fetch, there are separate
|
||||
* operations to get the available keys - which is expected to be cheap -
|
||||
* and getting and setting the property values.
|
||||
* The default implementation returns only an empty list. Reimplementations
|
||||
* should provide "PICTURE" if embedded cover art is present, and optionally
|
||||
* support other properties.
|
||||
*/
|
||||
virtual StringList complexPropertyKeys() const;
|
||||
|
||||
/*!
|
||||
* Get the complex properties for a given \a key.
|
||||
* In order to be flexible for different metadata formats, the properties
|
||||
* are represented as variant maps. Despite this dynamic nature, some
|
||||
* degree of standardization should be achieved between formats:
|
||||
*
|
||||
* - PICTURE
|
||||
* - data: ByteVector with picture data
|
||||
* - description: String with description
|
||||
* - pictureType: String with type as specified for ID3v2,
|
||||
* e.g. "Front Cover", "Back Cover", "Band"
|
||||
* - mimeType: String with image format, e.g. "image/jpeg"
|
||||
* - optionally more information found in the tag, such as
|
||||
* "width", "height", "numColors", "colorDepth" int values
|
||||
* in FLAC pictures
|
||||
* - GENERALOBJECT
|
||||
* - data: ByteVector with object data
|
||||
* - description: String with description
|
||||
* - fileName: String with file name
|
||||
* - mimeType: String with MIME type
|
||||
* - this is currently only implemented for ID3v2 GEOB frames
|
||||
*/
|
||||
virtual List<VariantMap> complexProperties(const String &key) const;
|
||||
|
||||
/*!
|
||||
* Set all complex properties for a given \a key using variant maps as
|
||||
* \a value with the same format as returned by complexProperties().
|
||||
* An empty list as \a value to removes all complex properties for \a key.
|
||||
*/
|
||||
virtual bool setComplexProperties(const String &key, const List<VariantMap> &value);
|
||||
|
||||
/*!
|
||||
* Returns the track name; if no track name is present in the tag
|
||||
* String::null will be returned.
|
||||
|
@ -124,6 +124,45 @@ void TagUnion::removeUnsupportedProperties(const StringList &unsupported)
|
||||
}
|
||||
}
|
||||
|
||||
StringList TagUnion::complexPropertyKeys() const
|
||||
{
|
||||
for(const auto &tag : d->tags) {
|
||||
if(tag) {
|
||||
const StringList keys = tag->complexPropertyKeys();
|
||||
if(!keys.isEmpty()) {
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
return StringList();
|
||||
}
|
||||
|
||||
List<VariantMap> TagUnion::complexProperties(const String &key) const
|
||||
{
|
||||
for(const auto &tag : d->tags) {
|
||||
if(tag) {
|
||||
const List<VariantMap> props = tag->complexProperties(key);
|
||||
if(!props.isEmpty()) {
|
||||
return props;
|
||||
}
|
||||
}
|
||||
}
|
||||
return List<VariantMap>();
|
||||
}
|
||||
|
||||
bool TagUnion::setComplexProperties(const String &key, const List<VariantMap> &value)
|
||||
{
|
||||
bool combinedResult = false;
|
||||
for(const auto &tag : d->tags) {
|
||||
if(tag) {
|
||||
if(tag->setComplexProperties(key, value)) {
|
||||
combinedResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return combinedResult;
|
||||
}
|
||||
|
||||
String TagUnion::title() const
|
||||
{
|
||||
stringUnion(title);
|
||||
|
@ -62,6 +62,10 @@ namespace TagLib {
|
||||
PropertyMap properties() const override;
|
||||
void removeUnsupportedProperties(const StringList &unsupported) override;
|
||||
|
||||
StringList complexPropertyKeys() const override;
|
||||
List<VariantMap> complexProperties(const String &key) const override;
|
||||
bool setComplexProperties(const String &key, const List<VariantMap> &value) override;
|
||||
|
||||
String title() const override;
|
||||
String artist() const override;
|
||||
String album() const override;
|
||||
|
@ -106,6 +106,21 @@ PropertyMap File::setProperties(const PropertyMap &properties)
|
||||
return tag()->setProperties(properties);
|
||||
}
|
||||
|
||||
StringList File::complexPropertyKeys() const
|
||||
{
|
||||
return tag()->complexPropertyKeys();
|
||||
}
|
||||
|
||||
List<VariantMap> File::complexProperties(const String &key) const
|
||||
{
|
||||
return tag()->complexProperties(key);
|
||||
}
|
||||
|
||||
bool File::setComplexProperties(const String &key, const List<VariantMap> &value)
|
||||
{
|
||||
return tag()->setComplexProperties(key, value);
|
||||
}
|
||||
|
||||
ByteVector File::readBlock(size_t length)
|
||||
{
|
||||
return d->stream->readBlock(length);
|
||||
|
@ -133,6 +133,28 @@ namespace TagLib {
|
||||
*/
|
||||
virtual PropertyMap setProperties(const PropertyMap &properties);
|
||||
|
||||
/*!
|
||||
* Get the keys of complex properties, i.e. properties which cannot be
|
||||
* represented simply by a string.
|
||||
* The default implementation calls Tag::complexPropertyKeys().
|
||||
* \see Tag::complexPropertyKeys()
|
||||
*/
|
||||
virtual StringList complexPropertyKeys() const;
|
||||
|
||||
/*!
|
||||
* Get the complex properties for a given \a key.
|
||||
* The default implementation calls Tag::complexProperties().
|
||||
* \see Tag::complexProperties()
|
||||
*/
|
||||
virtual List<VariantMap> complexProperties(const String &key) const;
|
||||
|
||||
/*!
|
||||
* Set all complex properties for \a key using the variant maps \a value.
|
||||
* The default implementation calls Tag::setComplexProperties().
|
||||
* \see Tag::setComplexProperties()
|
||||
*/
|
||||
virtual bool setComplexProperties(const String &key, const List<VariantMap> &value);
|
||||
|
||||
/*!
|
||||
* Returns a pointer to this file's audio properties. This should be
|
||||
* reimplemented in the concrete subclasses. If no audio properties were
|
||||
|
76
taglib/toolkit/tpicturetype.cpp
Normal file
76
taglib/toolkit/tpicturetype.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2023 by Urs Fleisch
|
||||
email : ufleisch@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include "tpicturetype.h"
|
||||
|
||||
#include "tstring.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
namespace {
|
||||
|
||||
static const char *const typeStrs[] = {
|
||||
"Other",
|
||||
"File Icon",
|
||||
"Other File Icon",
|
||||
"Front Cover",
|
||||
"Back Cover",
|
||||
"Leaflet Page",
|
||||
"Media",
|
||||
"Lead Artist",
|
||||
"Artist",
|
||||
"Conductor",
|
||||
"Band",
|
||||
"Composer",
|
||||
"Lyricist",
|
||||
"Recording Location",
|
||||
"During Recording",
|
||||
"During Performance",
|
||||
"Movie Screen Capture",
|
||||
"Coloured Fish",
|
||||
"Illustration",
|
||||
"Band Logo",
|
||||
"Publisher Logo"
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
String Utils::pictureTypeToString(int type)
|
||||
{
|
||||
if(type >= 0 && type < static_cast<int>(std::size(typeStrs))) {
|
||||
return typeStrs[type];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
int Utils::pictureTypeFromString(String str)
|
||||
{
|
||||
for(int i = 0; i < static_cast<int>(std::size(typeStrs)); ++i) {
|
||||
if(str == typeStrs[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
122
taglib/toolkit/tpicturetype.h
Normal file
122
taglib/toolkit/tpicturetype.h
Normal file
@ -0,0 +1,122 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2023 by Urs Fleisch
|
||||
email : ufleisch@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef TAGLIB_PICTURETYPE_H
|
||||
#define TAGLIB_PICTURETYPE_H
|
||||
|
||||
// THIS FILE IS NOT A PART OF THE TAGLIB API
|
||||
|
||||
#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header
|
||||
|
||||
#include "taglib_export.h"
|
||||
|
||||
/*!
|
||||
* Declare a picture type \a name enumeration inside a class.
|
||||
* Declares a picture type enum according to the ID3v2 specification and
|
||||
* adds methods \c typeToString() and \c typeFromString().
|
||||
*
|
||||
* \code {.cpp}
|
||||
* class MyClass {
|
||||
* public:
|
||||
* DECLARE_PICTURE_TYPE_ENUM(Type)
|
||||
* (..)
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
#define DECLARE_PICTURE_TYPE_ENUM(name) \
|
||||
enum name { \
|
||||
/*! A type not enumerated below */ \
|
||||
Other = 0x00, \
|
||||
/*! 32x32 PNG image that should be used as the file icon */ \
|
||||
FileIcon = 0x01, \
|
||||
/*! File icon of a different size or format */ \
|
||||
OtherFileIcon = 0x02, \
|
||||
/*! Front cover image of the album */ \
|
||||
FrontCover = 0x03, \
|
||||
/*! Back cover image of the album */ \
|
||||
BackCover = 0x04, \
|
||||
/*! Inside leaflet page of the album */ \
|
||||
LeafletPage = 0x05, \
|
||||
/*! Image from the album itself */ \
|
||||
Media = 0x06, \
|
||||
/*! Picture of the lead artist or soloist */ \
|
||||
LeadArtist = 0x07, \
|
||||
/*! Picture of the artist or performer */ \
|
||||
Artist = 0x08, \
|
||||
/*! Picture of the conductor */ \
|
||||
Conductor = 0x09, \
|
||||
/*! Picture of the band or orchestra */ \
|
||||
Band = 0x0A, \
|
||||
/*! Picture of the composer */ \
|
||||
Composer = 0x0B, \
|
||||
/*! Picture of the lyricist or text writer */ \
|
||||
Lyricist = 0x0C, \
|
||||
/*! Picture of the recording location or studio */ \
|
||||
RecordingLocation = 0x0D, \
|
||||
/*! Picture of the artists during recording */ \
|
||||
DuringRecording = 0x0E, \
|
||||
/*! Picture of the artists during performance */ \
|
||||
DuringPerformance = 0x0F, \
|
||||
/*! Picture from a movie or video related to the track */ \
|
||||
MovieScreenCapture = 0x10, \
|
||||
/*! Picture of a large, coloured fish */ \
|
||||
ColouredFish = 0x11, \
|
||||
/*! Illustration related to the track */ \
|
||||
Illustration = 0x12, \
|
||||
/*! Logo of the band or performer */ \
|
||||
BandLogo = 0x13, \
|
||||
/*! Logo of the publisher (record company) */ \
|
||||
PublisherLogo = 0x14 \
|
||||
}; \
|
||||
static TagLib::String typeToString(name type) { \
|
||||
return TagLib::Utils::pictureTypeToString(type); \
|
||||
} \
|
||||
static name typeFromString(TagLib::String str) { \
|
||||
return static_cast<name>( \
|
||||
TagLib::Utils::pictureTypeFromString(str)); \
|
||||
}
|
||||
|
||||
namespace TagLib {
|
||||
|
||||
class String;
|
||||
|
||||
namespace Utils {
|
||||
|
||||
/*!
|
||||
* Get string representation of picture type.
|
||||
*/
|
||||
String TAGLIB_EXPORT pictureTypeToString(int type);
|
||||
|
||||
/*!
|
||||
* Get picture type from string representation.
|
||||
*/
|
||||
int TAGLIB_EXPORT pictureTypeFromString(String str);
|
||||
|
||||
} // namespace Utils
|
||||
} // namespace TagLib
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
@ -40,6 +40,7 @@ SET(test_runner_SRCS
|
||||
test_string.cpp
|
||||
test_propertymap.cpp
|
||||
test_variant.cpp
|
||||
test_complexproperties.cpp
|
||||
test_file.cpp
|
||||
test_fileref.cpp
|
||||
test_id3v1.cpp
|
||||
|
417
tests/test_complexproperties.cpp
Normal file
417
tests/test_complexproperties.cpp
Normal file
@ -0,0 +1,417 @@
|
||||
/***************************************************************************
|
||||
copyright : (C) 2023 by Urs Fleisch
|
||||
email : ufleisch@users.sourceforge.net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* This library is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License version *
|
||||
* 2.1 as published by the Free Software Foundation. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; if not, write to the Free Software *
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
|
||||
* 02110-1301 USA *
|
||||
* *
|
||||
* Alternatively, this file is available under the Mozilla Public *
|
||||
* License Version 1.1. You may obtain a copy of the License at *
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include "asfpicture.h"
|
||||
#include "flacpicture.h"
|
||||
#include "flacfile.h"
|
||||
#include "tbytevector.h"
|
||||
#include "tvariant.h"
|
||||
#include "tzlib.h"
|
||||
#include "fileref.h"
|
||||
#include "apetag.h"
|
||||
#include "asftag.h"
|
||||
#include "mp4tag.h"
|
||||
#include "xiphcomment.h"
|
||||
#include "id3v1tag.h"
|
||||
#include "id3v2tag.h"
|
||||
#include "attachedpictureframe.h"
|
||||
#include "generalencapsulatedobjectframe.h"
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
namespace {
|
||||
|
||||
const String GEOB_KEY("GENERALOBJECT");
|
||||
const String PICTURE_KEY("PICTURE");
|
||||
|
||||
const VariantMap TEST_PICTURE {
|
||||
{"data", ByteVector(
|
||||
"\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48"
|
||||
"\x00\x00\xff\xdb\x00\x43\x00\x03\x02\x02\x02\x02\x02\x03\x02\x02\x02\x03"
|
||||
"\x03\x03\x03\x04\x06\x04\x04\x04\x04\x04\x08\x06\x06\x05\x06\x09\x08\x0a"
|
||||
"\x0a\x09\x08\x09\x09\x0a\x0c\x0f\x0c\x0a\x0b\x0e\x0b\x09\x09\x0d\x11\x0d"
|
||||
"\x0e\x0f\x10\x10\x11\x10\x0a\x0c\x12\x13\x12\x10\x13\x0f\x10\x10\x10\xff"
|
||||
"\xc9\x00\x0b\x08\x00\x01\x00\x01\x01\x01\x11\x00\xff\xcc\x00\x06\x00\x10"
|
||||
"\x10\x05\xff\xda\x00\x08\x01\x01\x00\x00\x3f\x00\xd2\xcf\x20\xff\xd9",
|
||||
125)},
|
||||
{"mimeType", "image/jpeg"},
|
||||
{"description", "Embedded cover"},
|
||||
{"pictureType", "Front Cover"}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class TestComplexProperties : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TestComplexProperties);
|
||||
CPPUNIT_TEST(testReadMp3Picture);
|
||||
CPPUNIT_TEST(testReadM4aPicture);
|
||||
CPPUNIT_TEST(testReadOggPicture);
|
||||
CPPUNIT_TEST(testReadWriteFlacPicture);
|
||||
CPPUNIT_TEST(testReadWriteMultipleProperties);
|
||||
CPPUNIT_TEST(testSetGetId3Geob);
|
||||
CPPUNIT_TEST(testSetGetId3Picture);
|
||||
CPPUNIT_TEST(testSetGetApePicture);
|
||||
CPPUNIT_TEST(testSetGetAsfPicture);
|
||||
CPPUNIT_TEST(testSetGetMp4Picture);
|
||||
CPPUNIT_TEST(testSetGetXiphPicture);
|
||||
CPPUNIT_TEST(testNonExistent);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void testReadMp3Picture()
|
||||
{
|
||||
if(zlib::isAvailable()) {
|
||||
FileRef f(TEST_FILE_PATH_C("compressed_id3_frame.mp3"), false);
|
||||
CPPUNIT_ASSERT_EQUAL(StringList(PICTURE_KEY),
|
||||
f.file()->complexPropertyKeys());
|
||||
auto pictures = f.file()->complexProperties(PICTURE_KEY);
|
||||
CPPUNIT_ASSERT_EQUAL(1U, pictures.size());
|
||||
auto picture = pictures.front();
|
||||
CPPUNIT_ASSERT_EQUAL(86414U,
|
||||
picture.value("data").value<ByteVector>().size());
|
||||
CPPUNIT_ASSERT_EQUAL(String(""),
|
||||
picture.value("description").value<String>());
|
||||
CPPUNIT_ASSERT_EQUAL(String("image/bmp"),
|
||||
picture.value("mimeType").value<String>());
|
||||
CPPUNIT_ASSERT_EQUAL(String("Other"),
|
||||
picture.value("pictureType").value<String>());
|
||||
}
|
||||
}
|
||||
|
||||
void testReadM4aPicture()
|
||||
{
|
||||
const ByteVector expectedData1(
|
||||
"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00"
|
||||
"\x00\x02\x00\x00\x00\x02\x08\x02\x00\x00\x00\xfd\xd4\x9a\x73\x00\x00\x00"
|
||||
"\x16\x49\x44\x41\x54\x78\x9c\x63\x7c\x9f\xca\xc0\xc0\xc0\xc0\xc4\xc0\xc0"
|
||||
"\xc0\xc0\xc0\x00\x00\x11\x09\x01\x58\xab\x88\xdb\x6f\x00\x00\x00\x00\x49"
|
||||
"\x45\x4e\x44\xae\x42\x60\x82", 79);
|
||||
const ByteVector expectedData2(
|
||||
"\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x64\x00\x64"
|
||||
"\x00\x00\xff\xdb\x00\x43\x00\x09\x06\x07\x08\x07\x06\x09\x08\x08\x08\x0a"
|
||||
"\x0a\x09\x0b\x0e\x17\x0f\x0e\x0d\x0d\x0e\x1c\x14\x15\x11\x17\x22\x1e\x23"
|
||||
"\x23\x21\x1e\x20\x20\x25\x2a\x35\x2d\x25\x27\x32\x28\x20\x20\x2e\x3f\x2f"
|
||||
"\x32\x37\x39\x3c\x3c\x3c\x24\x2d\x42\x46\x41\x3a\x46\x35\x3b\x3c\x39\xff"
|
||||
"\xdb\x00\x43\x01\x0a\x0a\x0a\x0e\x0c\x0e\x1b\x0f\x0f\x1b\x39\x26\x20\x26"
|
||||
"\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39"
|
||||
"\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39"
|
||||
"\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\x39\xff\xc0\x00\x11"
|
||||
"\x08\x00\x02\x00\x02\x03\x01\x22\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00"
|
||||
"\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x00\x07\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
"\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x15\x01\x01\x01\x00\x00\x00\x00\x00"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x06\xff\xc4\x00\x14\x11\x01\x00"
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00"
|
||||
"\x0c\x03\x01\x00\x02\x11\x03\x11\x00\x3f\x00\x8d\x80\xb8\x19\xff\xd9", 287);
|
||||
|
||||
FileRef f(TEST_FILE_PATH_C("has-tags.m4a"), false);
|
||||
CPPUNIT_ASSERT_EQUAL(StringList(PICTURE_KEY),
|
||||
f.file()->complexPropertyKeys());
|
||||
auto pictures = f.file()->complexProperties(PICTURE_KEY);
|
||||
CPPUNIT_ASSERT_EQUAL(2U, pictures.size());
|
||||
auto picture = pictures.front();
|
||||
CPPUNIT_ASSERT_EQUAL(expectedData1,
|
||||
picture.value("data").value<ByteVector>());
|
||||
CPPUNIT_ASSERT_EQUAL(String("image/png"),
|
||||
picture.value("mimeType").value<String>());
|
||||
picture = pictures.back();
|
||||
CPPUNIT_ASSERT_EQUAL(expectedData2,
|
||||
picture.value("data").value<ByteVector>());
|
||||
CPPUNIT_ASSERT_EQUAL(String("image/jpeg"),
|
||||
picture.value("mimeType").value<String>());
|
||||
}
|
||||
|
||||
void testReadOggPicture()
|
||||
{
|
||||
FileRef f(TEST_FILE_PATH_C("lowercase-fields.ogg"), false);
|
||||
CPPUNIT_ASSERT_EQUAL(StringList(PICTURE_KEY),
|
||||
f.file()->complexPropertyKeys());
|
||||
auto pictures = f.file()->complexProperties(PICTURE_KEY);
|
||||
CPPUNIT_ASSERT_EQUAL(1U, pictures.size());
|
||||
auto picture = pictures.front();
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"),
|
||||
picture.value("data").value<ByteVector>());
|
||||
CPPUNIT_ASSERT_EQUAL(String("image/jpeg"),
|
||||
picture.value("mimeType").value<String>());
|
||||
CPPUNIT_ASSERT_EQUAL(String("Back Cover"),
|
||||
picture.value("pictureType").value<String>());
|
||||
CPPUNIT_ASSERT_EQUAL(String("new image"),
|
||||
picture.value("description").value<String>());
|
||||
CPPUNIT_ASSERT_EQUAL(16, picture.value("colorDepth").value<int>());
|
||||
CPPUNIT_ASSERT_EQUAL(7, picture.value("numColors").value<int>());
|
||||
CPPUNIT_ASSERT_EQUAL(5, picture.value("width").value<int>());
|
||||
CPPUNIT_ASSERT_EQUAL(6, picture.value("height").value<int>());
|
||||
}
|
||||
|
||||
void testReadWriteFlacPicture()
|
||||
{
|
||||
VariantMap picture(TEST_PICTURE);
|
||||
picture.insert("colorDepth", 8);
|
||||
picture.insert("numColors", 1);
|
||||
picture.insert("width", 1);
|
||||
picture.insert("height", 1);
|
||||
|
||||
ScopedFileCopy copy("no-tags", ".flac");
|
||||
|
||||
{
|
||||
FLAC::File f(copy.fileName().c_str(), false);
|
||||
CPPUNIT_ASSERT(f.complexPropertyKeys().isEmpty());
|
||||
CPPUNIT_ASSERT(f.pictureList().isEmpty());
|
||||
CPPUNIT_ASSERT(f.setComplexProperties(PICTURE_KEY, {picture}));
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
FLAC::File f(copy.fileName().c_str(), false);
|
||||
CPPUNIT_ASSERT_EQUAL(StringList(PICTURE_KEY), f.complexPropertyKeys());
|
||||
CPPUNIT_ASSERT_EQUAL(picture, f.complexProperties(PICTURE_KEY).front());
|
||||
auto flacPictures = f.pictureList();
|
||||
CPPUNIT_ASSERT_EQUAL(1U, flacPictures.size());
|
||||
auto flacPicture = flacPictures.front();
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("data").value<ByteVector>(),
|
||||
flacPicture->data());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("mimeType").value<String>(),
|
||||
flacPicture->mimeType());
|
||||
CPPUNIT_ASSERT_EQUAL(FLAC::Picture::FrontCover, flacPicture->type());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("description").value<String>(),
|
||||
flacPicture->description());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("colorDepth").value<int>(),
|
||||
flacPicture->colorDepth());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("numColors").value<int>(),
|
||||
flacPicture->numColors());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("width").value<int>(),
|
||||
flacPicture->width());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("height").value<int>(),
|
||||
flacPicture->height());
|
||||
|
||||
CPPUNIT_ASSERT(f.setComplexProperties(PICTURE_KEY, {}));
|
||||
f.save();
|
||||
}
|
||||
{
|
||||
FLAC::File f(copy.fileName().c_str(), false);
|
||||
CPPUNIT_ASSERT(f.complexPropertyKeys().isEmpty());
|
||||
CPPUNIT_ASSERT(f.pictureList().isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
void testReadWriteMultipleProperties()
|
||||
{
|
||||
const VariantMap picture2 {
|
||||
{"data", ByteVector("PNG data")},
|
||||
{"mimeType", "image/png"},
|
||||
{"description", ""},
|
||||
{"pictureType", "Back Cover"}
|
||||
};
|
||||
const VariantMap geob1 {
|
||||
{"data", ByteVector("First")},
|
||||
{"mimeType", "text/plain"},
|
||||
{"description", "Object 1"},
|
||||
{"fileName", "test1.txt"}
|
||||
};
|
||||
const VariantMap geob2 {
|
||||
{"data", ByteVector("Second")},
|
||||
{"mimeType", "text/plain"},
|
||||
{"description", "Object 2"},
|
||||
{"fileName", "test2.txt"}
|
||||
};
|
||||
|
||||
ScopedFileCopy copy("xing", ".mp3");
|
||||
|
||||
{
|
||||
FileRef f(copy.fileName().c_str(), false);
|
||||
CPPUNIT_ASSERT(f.file()->complexPropertyKeys().isEmpty());
|
||||
f.file()->setComplexProperties(PICTURE_KEY, {TEST_PICTURE, picture2});
|
||||
f.file()->setComplexProperties(GEOB_KEY, {geob1, geob2});
|
||||
f.file()->save();
|
||||
}
|
||||
{
|
||||
FileRef f(copy.fileName().c_str(), false);
|
||||
CPPUNIT_ASSERT_EQUAL(StringList({PICTURE_KEY, GEOB_KEY}),
|
||||
f.file()->complexPropertyKeys());
|
||||
CPPUNIT_ASSERT(List<VariantMap>({TEST_PICTURE, picture2}) ==
|
||||
f.file()->complexProperties(PICTURE_KEY));
|
||||
CPPUNIT_ASSERT(List<VariantMap>({geob1, geob2}) ==
|
||||
f.file()->complexProperties(GEOB_KEY));
|
||||
}
|
||||
}
|
||||
|
||||
void testSetGetId3Geob()
|
||||
{
|
||||
const VariantMap geob {
|
||||
{"data", ByteVector("Just a test")},
|
||||
{"mimeType", "text/plain"},
|
||||
{"description", "Embedded object"},
|
||||
{"fileName", "test.txt"}
|
||||
};
|
||||
ID3v2::Tag tag;
|
||||
CPPUNIT_ASSERT(!tag.frameListMap().contains("GEOB"));
|
||||
CPPUNIT_ASSERT(tag.complexPropertyKeys().isEmpty());
|
||||
CPPUNIT_ASSERT(tag.complexProperties(GEOB_KEY).isEmpty());
|
||||
CPPUNIT_ASSERT(tag.setComplexProperties(GEOB_KEY, {geob}));
|
||||
CPPUNIT_ASSERT_EQUAL(StringList(GEOB_KEY), tag.complexPropertyKeys());
|
||||
CPPUNIT_ASSERT_EQUAL(geob, tag.complexProperties(GEOB_KEY).front());
|
||||
auto frames = tag.frameListMap().value("GEOB");
|
||||
CPPUNIT_ASSERT_EQUAL(1U, frames.size());
|
||||
auto frame =
|
||||
dynamic_cast<ID3v2::GeneralEncapsulatedObjectFrame *>(frames.front());
|
||||
CPPUNIT_ASSERT(frame);
|
||||
CPPUNIT_ASSERT_EQUAL(geob.value("data").value<ByteVector>(), frame->object());
|
||||
CPPUNIT_ASSERT_EQUAL(geob.value("mimeType").value<String>(), frame->mimeType());
|
||||
CPPUNIT_ASSERT_EQUAL(geob.value("description").value<String>(), frame->description());
|
||||
CPPUNIT_ASSERT_EQUAL(geob.value("fileName").value<String>(), frame->fileName());
|
||||
}
|
||||
|
||||
void tagSetGetPicture(Tag &tag, const VariantMap &picture)
|
||||
{
|
||||
CPPUNIT_ASSERT(tag.complexPropertyKeys().isEmpty());
|
||||
CPPUNIT_ASSERT(tag.complexProperties(PICTURE_KEY).isEmpty());
|
||||
CPPUNIT_ASSERT(tag.setComplexProperties(PICTURE_KEY, {picture}));
|
||||
CPPUNIT_ASSERT_EQUAL(StringList(PICTURE_KEY), tag.complexPropertyKeys());
|
||||
CPPUNIT_ASSERT_EQUAL(picture, tag.complexProperties(PICTURE_KEY).front());
|
||||
}
|
||||
|
||||
void testSetGetId3Picture()
|
||||
{
|
||||
const VariantMap picture(TEST_PICTURE);
|
||||
ID3v2::Tag tag;
|
||||
CPPUNIT_ASSERT(!tag.frameListMap().contains("APIC"));
|
||||
tagSetGetPicture(tag, picture);
|
||||
auto frames = tag.frameListMap().value("APIC");
|
||||
CPPUNIT_ASSERT_EQUAL(1U, frames.size());
|
||||
auto frame =
|
||||
dynamic_cast<ID3v2::AttachedPictureFrame *>(frames.front());
|
||||
CPPUNIT_ASSERT(frame);
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("data").value<ByteVector>(), frame->picture());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("mimeType").value<String>(), frame->mimeType());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("description").value<String>(), frame->description());
|
||||
CPPUNIT_ASSERT_EQUAL(ID3v2::AttachedPictureFrame::FrontCover, frame->type());
|
||||
}
|
||||
|
||||
void testSetGetApePicture()
|
||||
{
|
||||
const String FRONT_COVER("COVER ART (FRONT)");
|
||||
VariantMap picture(TEST_PICTURE);
|
||||
picture.erase("mimeType");
|
||||
APE::Tag tag;
|
||||
CPPUNIT_ASSERT(!tag.itemListMap().contains(FRONT_COVER));
|
||||
tagSetGetPicture(tag, picture);
|
||||
auto item = tag.itemListMap().value(FRONT_COVER);
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
picture.value("description").value<String>().data(String::UTF8)
|
||||
.append('\0')
|
||||
.append(picture.value("data").value<ByteVector>()),
|
||||
item.binaryData());
|
||||
}
|
||||
|
||||
void testSetGetAsfPicture()
|
||||
{
|
||||
VariantMap picture(TEST_PICTURE);
|
||||
ASF::Tag tag;
|
||||
CPPUNIT_ASSERT(!tag.attributeListMap().contains("WM/Picture"));
|
||||
tagSetGetPicture(tag, picture);
|
||||
auto attributes = tag.attribute("WM/Picture");
|
||||
CPPUNIT_ASSERT_EQUAL(1U, attributes.size());
|
||||
auto asfPicture = attributes.front().toPicture();
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("data").value<ByteVector>(),
|
||||
asfPicture.picture());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("mimeType").value<String>(),
|
||||
asfPicture.mimeType());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("description").value<String>(),
|
||||
asfPicture.description());
|
||||
CPPUNIT_ASSERT_EQUAL(ASF::Picture::FrontCover, asfPicture.type());
|
||||
}
|
||||
|
||||
void testSetGetMp4Picture()
|
||||
{
|
||||
VariantMap picture(TEST_PICTURE);
|
||||
picture.erase("description");
|
||||
picture.erase("pictureType");
|
||||
MP4::Tag tag;
|
||||
CPPUNIT_ASSERT(!tag.itemMap().contains("covr"));
|
||||
tagSetGetPicture(tag, picture);
|
||||
auto covrs = tag.item("covr").toCoverArtList();
|
||||
CPPUNIT_ASSERT_EQUAL(1U, covrs.size());
|
||||
auto covr = covrs.front();
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("data").value<ByteVector>(),
|
||||
covr.data());
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, covr.format());
|
||||
}
|
||||
|
||||
void testSetGetXiphPicture()
|
||||
{
|
||||
VariantMap picture(TEST_PICTURE);
|
||||
picture.insert("colorDepth", 8);
|
||||
picture.insert("numColors", 1);
|
||||
picture.insert("width", 1);
|
||||
picture.insert("height", 1);
|
||||
Ogg::XiphComment tag;
|
||||
CPPUNIT_ASSERT(tag.pictureList().isEmpty());
|
||||
tagSetGetPicture(tag, picture);
|
||||
auto pics = tag.pictureList();
|
||||
CPPUNIT_ASSERT_EQUAL(1U, pics.size());
|
||||
auto pic = pics.front();
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("data").value<ByteVector>(),
|
||||
pic->data());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("mimeType").value<String>(),
|
||||
pic->mimeType());
|
||||
CPPUNIT_ASSERT_EQUAL(picture.value("description").value<String>(),
|
||||
pic->description());
|
||||
CPPUNIT_ASSERT_EQUAL(FLAC::Picture::FrontCover, pic->type());
|
||||
CPPUNIT_ASSERT_EQUAL(8, pic->colorDepth());
|
||||
CPPUNIT_ASSERT_EQUAL(1, pic->numColors());
|
||||
CPPUNIT_ASSERT_EQUAL(1, pic->width());
|
||||
CPPUNIT_ASSERT_EQUAL(1, pic->height());
|
||||
}
|
||||
|
||||
void testNonExistent()
|
||||
{
|
||||
{
|
||||
ID3v2::Tag tag;
|
||||
CPPUNIT_ASSERT(tag.complexPropertyKeys().isEmpty());
|
||||
CPPUNIT_ASSERT(tag.complexProperties(PICTURE_KEY).isEmpty());
|
||||
CPPUNIT_ASSERT(tag.complexProperties(GEOB_KEY).isEmpty());
|
||||
CPPUNIT_ASSERT(tag.complexProperties("NONEXISTENT").isEmpty());
|
||||
CPPUNIT_ASSERT(!tag.setComplexProperties("NONEXISTENT", {{{"description", "test"}}}));
|
||||
CPPUNIT_ASSERT(tag.complexProperties("NONEXISTENT").isEmpty());
|
||||
CPPUNIT_ASSERT(tag.setComplexProperties(PICTURE_KEY, {TEST_PICTURE}));
|
||||
CPPUNIT_ASSERT(!tag.complexProperties(PICTURE_KEY).isEmpty());
|
||||
}
|
||||
{
|
||||
ID3v1::Tag tag;
|
||||
CPPUNIT_ASSERT(tag.complexPropertyKeys().isEmpty());
|
||||
CPPUNIT_ASSERT(tag.complexProperties(PICTURE_KEY).isEmpty());
|
||||
CPPUNIT_ASSERT(tag.complexProperties(GEOB_KEY).isEmpty());
|
||||
CPPUNIT_ASSERT(tag.complexProperties("NONEXISTENT").isEmpty());
|
||||
CPPUNIT_ASSERT(!tag.setComplexProperties("NONEXISTENT", {{{"description", "test"}}}));
|
||||
CPPUNIT_ASSERT(tag.complexProperties("NONEXISTENT").isEmpty());
|
||||
CPPUNIT_ASSERT(!tag.setComplexProperties(PICTURE_KEY, {TEST_PICTURE}));
|
||||
CPPUNIT_ASSERT(tag.complexProperties(PICTURE_KEY).isEmpty());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestComplexProperties);
|
Loading…
Reference in New Issue
Block a user