mirror of
https://github.com/taglib/taglib.git
synced 2025-06-04 01:28:21 -04:00
Merge branch 'xiph-picture' of https://github.com/gogglesmm/taglib into gogglesmm-xiph-picture
# Conflicts: # tests/test_bytevector.cpp # tests/test_ogg.cpp
This commit is contained in:
commit
1529af7a12
@ -26,17 +26,22 @@
|
||||
#include <tbytevector.h>
|
||||
#include <tdebug.h>
|
||||
|
||||
#include <flacpicture.h>
|
||||
#include <xiphcomment.h>
|
||||
#include <tpropertymap.h>
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
|
||||
typedef List<FLAC::Picture *> PictureList;
|
||||
|
||||
class Ogg::XiphComment::XiphCommentPrivate
|
||||
{
|
||||
public:
|
||||
FieldListMap fieldListMap;
|
||||
String vendorID;
|
||||
String commentField;
|
||||
PictureList pictureList;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -56,6 +61,7 @@ Ogg::XiphComment::XiphComment(const ByteVector &data) : TagLib::Tag()
|
||||
|
||||
Ogg::XiphComment::~XiphComment()
|
||||
{
|
||||
removePictures();
|
||||
delete d;
|
||||
}
|
||||
|
||||
@ -188,6 +194,8 @@ TagLib::uint Ogg::XiphComment::fieldCount() const
|
||||
for(; it != d->fieldListMap.end(); ++it)
|
||||
count += (*it).second.size();
|
||||
|
||||
count += d->pictureList.size();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -296,6 +304,36 @@ bool Ogg::XiphComment::contains(const String &key) const
|
||||
return !d->fieldListMap[key.upper()].isEmpty();
|
||||
}
|
||||
|
||||
void Ogg::XiphComment::removePicture(FLAC::Picture *picture, bool del)
|
||||
{
|
||||
PictureList::Iterator it = d->pictureList.find(picture);
|
||||
if(it != d->pictureList.end())
|
||||
d->pictureList.erase(it);
|
||||
|
||||
if(del)
|
||||
delete picture;
|
||||
}
|
||||
|
||||
void Ogg::XiphComment::removePictures()
|
||||
{
|
||||
PictureList newList;
|
||||
for(uint i = 0; i < d->pictureList.size(); i++) {
|
||||
delete d->pictureList[i];
|
||||
}
|
||||
d->pictureList = newList;
|
||||
}
|
||||
|
||||
void Ogg::XiphComment::addPicture(FLAC::Picture * picture)
|
||||
{
|
||||
d->pictureList.append(picture);
|
||||
}
|
||||
|
||||
|
||||
List<FLAC::Picture *> Ogg::XiphComment::pictureList()
|
||||
{
|
||||
return d->pictureList;
|
||||
}
|
||||
|
||||
ByteVector Ogg::XiphComment::render() const
|
||||
{
|
||||
return render(true);
|
||||
@ -342,6 +380,13 @@ ByteVector Ogg::XiphComment::render(bool addFramingBit) const
|
||||
}
|
||||
}
|
||||
|
||||
for(PictureList::ConstIterator it = d->pictureList.begin(); it != d->pictureList.end(); ++it) {
|
||||
ByteVector picture = (*it)->render().toBase64();
|
||||
data.append(ByteVector::fromUInt(picture.size() + 23, false));
|
||||
data.append("METADATA_BLOCK_PICTURE=");
|
||||
data.append(picture);
|
||||
}
|
||||
|
||||
// Append the "framing bit".
|
||||
|
||||
if(addFramingBit)
|
||||
@ -384,20 +429,85 @@ void Ogg::XiphComment::parse(const ByteVector &data)
|
||||
const uint commentLength = data.toUInt(pos, false);
|
||||
pos += 4;
|
||||
|
||||
String comment = String(data.mid(pos, commentLength), String::UTF8);
|
||||
ByteVector entry = data.mid(pos, commentLength);
|
||||
|
||||
pos += commentLength;
|
||||
if(pos > data.size()) {
|
||||
|
||||
// Don't go past data end
|
||||
if (pos > data.size())
|
||||
break;
|
||||
|
||||
// Handle Pictures separately
|
||||
if(entry.startsWith("METADATA_BLOCK_PICTURE=")) {
|
||||
|
||||
// We need base64 encoded data including padding
|
||||
if((entry.size() - 23) > 3 && ((entry.size() - 23) % 4) == 0) {
|
||||
|
||||
// Decode base64 picture data
|
||||
ByteVector picturedata = ByteVector::fromBase64(entry.mid(23));
|
||||
if (picturedata.size()) {
|
||||
|
||||
// Decode Flac Picture
|
||||
FLAC::Picture * picture = new FLAC::Picture();
|
||||
if(picture->parse(picturedata)) {
|
||||
|
||||
d->pictureList.append(picture);
|
||||
|
||||
// continue to next field
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
delete picture;
|
||||
debug("Failed to decode FlacPicture block");
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug("Failed to decode base64 encoded data");
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug("Invalid base64 encoded data");
|
||||
}
|
||||
}
|
||||
|
||||
int commentSeparatorPosition = comment.find("=");
|
||||
if(commentSeparatorPosition == -1) {
|
||||
break;
|
||||
// Handle old picture standard
|
||||
if (entry.startsWith("COVERART=")) {
|
||||
|
||||
if((entry.size() - 9) > 3 && ((entry.size() - 9) % 4) == 0) {
|
||||
|
||||
// Decode base64 picture data
|
||||
ByteVector picturedata = ByteVector::fromBase64(entry.mid(9));
|
||||
if (picturedata.size()) {
|
||||
|
||||
// Assume it's some type of image file
|
||||
FLAC::Picture * picture = new FLAC::Picture();
|
||||
picture->setData(picturedata);
|
||||
picture->setMimeType("image/");
|
||||
picture->setType(FLAC::Picture::Other);
|
||||
d->pictureList.append(picture);
|
||||
|
||||
// continue to next field
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
debug("Failed to decode base64 encoded data");
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug("Invalid base64 encoded data");
|
||||
}
|
||||
}
|
||||
|
||||
String key = comment.substr(0, commentSeparatorPosition);
|
||||
String value = comment.substr(commentSeparatorPosition + 1);
|
||||
// Check for field separator
|
||||
int sep = entry.find('=');
|
||||
if (sep < 1) {
|
||||
debug("Discarding invalid comment field.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse key and value
|
||||
String key = String(entry.mid(0, sep), String::UTF8);
|
||||
String value = String(entry.mid(sep + 1), String::UTF8);
|
||||
addField(key, value, false);
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "tstring.h"
|
||||
#include "tstringlist.h"
|
||||
#include "tbytevector.h"
|
||||
#include "flacpicture.h"
|
||||
#include "taglib_export.h"
|
||||
|
||||
namespace TagLib {
|
||||
@ -229,6 +230,31 @@ namespace TagLib {
|
||||
*/
|
||||
ByteVector render(bool addFramingBit) const;
|
||||
|
||||
|
||||
/*!
|
||||
* Returns a list of pictures attached to the xiph comment.
|
||||
*/
|
||||
List<FLAC::Picture *> pictureList();
|
||||
|
||||
/*!
|
||||
* Removes an picture. If \a del is true the picture's memory
|
||||
* will be freed; if it is false, it must be deleted by the user.
|
||||
*/
|
||||
void removePicture(FLAC::Picture *picture, bool del = true);
|
||||
|
||||
/*!
|
||||
* Remove all pictures.
|
||||
*/
|
||||
void removePictures();
|
||||
|
||||
/*!
|
||||
* Add a new picture to the comment block. The comment block takes ownership of the
|
||||
* picture and will handle freeing its memory.
|
||||
*
|
||||
* \note The file will be saved only after calling save().
|
||||
*/
|
||||
void addPicture(FLAC::Picture *picture);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Reads the tag from the file specified in the constructor and fills the
|
||||
|
@ -871,6 +871,123 @@ ByteVector ByteVector::toHex() const
|
||||
return encoded;
|
||||
}
|
||||
|
||||
ByteVector ByteVector::fromBase64(const ByteVector & input)
|
||||
{
|
||||
static const unsigned char base64[256] = {
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x3e,0x80,0x80,0x80,0x3f,
|
||||
0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,
|
||||
0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
|
||||
0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
|
||||
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80
|
||||
};
|
||||
|
||||
uint len = input.size();
|
||||
|
||||
ByteVector output(len);
|
||||
|
||||
const unsigned char * src = (const unsigned char*) input.data();
|
||||
unsigned char * dst = (unsigned char*) output.data();
|
||||
|
||||
while(4 <= len) {
|
||||
|
||||
// Check invalid character
|
||||
if(base64[src[0]] == 0x80)
|
||||
break;
|
||||
|
||||
// Check invalid character
|
||||
if(base64[src[1]] == 0x80)
|
||||
break;
|
||||
|
||||
// Decode first byte
|
||||
*dst++ = ((base64[src[0]] << 2) & 0xfc) | ((base64[src[1]] >> 4) & 0x03);
|
||||
|
||||
if(src[2] != '=') {
|
||||
|
||||
// Check invalid character
|
||||
if(base64[src[2]] == 0x80)
|
||||
break;
|
||||
|
||||
// Decode second byte
|
||||
*dst++ = ((base64[src[1]] & 0x0f) << 4) | ((base64[src[2]] >> 2) & 0x0f);
|
||||
|
||||
if(src[3] != '=') {
|
||||
|
||||
// Check invalid character
|
||||
if(base64[src[3]] == 0x80)
|
||||
break;
|
||||
|
||||
// Decode third byte
|
||||
*dst++ = ((base64[src[2]] & 0x03) << 6) | (base64[src[3]] & 0x3f);
|
||||
}
|
||||
else {
|
||||
// assume end of data
|
||||
len -= 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// assume end of data
|
||||
len -= 4;
|
||||
break;
|
||||
}
|
||||
src += 4;
|
||||
len -= 4;
|
||||
}
|
||||
|
||||
// Only return output if we processed all bytes
|
||||
if(len == 0) {
|
||||
output.resize(dst - (unsigned char*) output.data());
|
||||
return output;
|
||||
}
|
||||
return ByteVector();
|
||||
}
|
||||
|
||||
ByteVector ByteVector::toBase64() const
|
||||
{
|
||||
static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
if (size()) {
|
||||
uint len = size();
|
||||
ByteVector output(4 * ((len - 1) / 3 + 1)); // note roundup
|
||||
|
||||
const char * src = data();
|
||||
char * dst = output.data();
|
||||
while(3 <= len) {
|
||||
*dst++ = alphabet[(src[0] >> 2) & 0x3f];
|
||||
*dst++ = alphabet[((src[0] & 0x03) << 4) | ((src[1] >> 4) & 0x0f)];
|
||||
*dst++ = alphabet[((src[1] & 0x0f) << 2) | ((src[2] >> 6) & 0x03)];
|
||||
*dst++ = alphabet[src[2] & 0x3f];
|
||||
src += 3;
|
||||
len -= 3;
|
||||
}
|
||||
if(len) {
|
||||
*dst++ = alphabet[(src[0] >> 2) & 0x3f];
|
||||
if(len>1) {
|
||||
*dst++ = alphabet[((src[0] & 0x03) << 4) | ((src[1] >> 4) & 0x0f)];
|
||||
*dst++ = alphabet[((src[1] & 0x0f) << 2)];
|
||||
}
|
||||
else {
|
||||
*dst++ = alphabet[(src[0] & 0x03) << 4];
|
||||
*dst++ = '=';
|
||||
}
|
||||
*dst++ = '=';
|
||||
}
|
||||
return output;
|
||||
}
|
||||
return ByteVector();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// protected members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -594,6 +594,16 @@ namespace TagLib {
|
||||
*/
|
||||
ByteVector toHex() const;
|
||||
|
||||
/*!
|
||||
* Returns a base64 encoded copy of the byte vector
|
||||
*/
|
||||
ByteVector toBase64() const;
|
||||
|
||||
/*!
|
||||
* Decodes the base64 encoded byte vector.
|
||||
*/
|
||||
static ByteVector fromBase64(const ByteVector &);
|
||||
|
||||
protected:
|
||||
/*
|
||||
* If this ByteVector is being shared via implicit sharing, do a deep copy
|
||||
|
@ -44,6 +44,7 @@ class TestByteVector : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testIterator);
|
||||
CPPUNIT_TEST(testResize);
|
||||
CPPUNIT_TEST(testAppend);
|
||||
CPPUNIT_TEST(testBase64);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@ -402,6 +403,111 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("taglib"), v2);
|
||||
}
|
||||
|
||||
void testBase64()
|
||||
{
|
||||
ByteVector sempty;
|
||||
ByteVector t0("a"); // test 1 byte
|
||||
ByteVector t1("any carnal pleasure.");
|
||||
ByteVector t2("any carnal pleasure");
|
||||
ByteVector t3("any carnal pleasur");
|
||||
ByteVector s0("a"); // test 1 byte
|
||||
ByteVector s1("any carnal pleasure.");
|
||||
ByteVector s2("any carnal pleasure");
|
||||
ByteVector s3("any carnal pleasur");
|
||||
ByteVector eempty;
|
||||
ByteVector e0("YQ==");
|
||||
ByteVector e1("YW55IGNhcm5hbCBwbGVhc3VyZS4=");
|
||||
ByteVector e2("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
|
||||
ByteVector e3("YW55IGNhcm5hbCBwbGVhc3Vy");
|
||||
|
||||
// Encode
|
||||
CPPUNIT_ASSERT_EQUAL(eempty, sempty.toBase64());
|
||||
CPPUNIT_ASSERT_EQUAL(e0, s0.toBase64());
|
||||
CPPUNIT_ASSERT_EQUAL(e1, s1.toBase64());
|
||||
CPPUNIT_ASSERT_EQUAL(e2, s2.toBase64());
|
||||
CPPUNIT_ASSERT_EQUAL(e3, s3.toBase64());
|
||||
|
||||
// Decode
|
||||
CPPUNIT_ASSERT_EQUAL(sempty, eempty.toBase64());
|
||||
CPPUNIT_ASSERT_EQUAL(s0, ByteVector::fromBase64(e0));
|
||||
CPPUNIT_ASSERT_EQUAL(s1, ByteVector::fromBase64(e1));
|
||||
CPPUNIT_ASSERT_EQUAL(s2, ByteVector::fromBase64(e2));
|
||||
CPPUNIT_ASSERT_EQUAL(s3, ByteVector::fromBase64(e3));
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(t0, ByteVector::fromBase64(s0.toBase64()));
|
||||
CPPUNIT_ASSERT_EQUAL(t1, ByteVector::fromBase64(s1.toBase64()));
|
||||
CPPUNIT_ASSERT_EQUAL(t2, ByteVector::fromBase64(s2.toBase64()));
|
||||
CPPUNIT_ASSERT_EQUAL(t3, ByteVector::fromBase64(s3.toBase64()));
|
||||
|
||||
ByteVector all((uint)256);
|
||||
|
||||
// in order
|
||||
{
|
||||
for(int i = 0; i < 256; i++){
|
||||
all[i]=(unsigned char)i;
|
||||
}
|
||||
ByteVector b64 = all.toBase64();
|
||||
ByteVector original = ByteVector::fromBase64(b64);
|
||||
CPPUNIT_ASSERT_EQUAL(all,original);
|
||||
}
|
||||
|
||||
// reverse
|
||||
{
|
||||
for(int i = 0; i < 256; i++){
|
||||
all[i]=(unsigned char)255-i;
|
||||
}
|
||||
ByteVector b64 = all.toBase64();
|
||||
ByteVector original = ByteVector::fromBase64(b64);
|
||||
CPPUNIT_ASSERT_EQUAL(all,original);
|
||||
}
|
||||
|
||||
// all zeroes
|
||||
{
|
||||
for(int i = 0; i < 256; i++){
|
||||
all[i]=0;
|
||||
}
|
||||
ByteVector b64 = all.toBase64();
|
||||
ByteVector original = ByteVector::fromBase64(b64);
|
||||
CPPUNIT_ASSERT_EQUAL(all,original);
|
||||
}
|
||||
|
||||
// all ones
|
||||
{
|
||||
for(int i = 0; i < 256; i++){
|
||||
all[i]=(unsigned char)0xff;
|
||||
}
|
||||
ByteVector b64 = all.toBase64();
|
||||
ByteVector original = ByteVector::fromBase64(b64);
|
||||
CPPUNIT_ASSERT_EQUAL(all,original);
|
||||
}
|
||||
|
||||
// Missing end bytes
|
||||
{
|
||||
// No missing bytes
|
||||
ByteVector m0("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
|
||||
CPPUNIT_ASSERT_EQUAL(s2,ByteVector::fromBase64(m0));
|
||||
|
||||
// 1 missing byte
|
||||
ByteVector m1("YW55IGNhcm5hbCBwbGVhc3VyZQ=");
|
||||
CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64(m1));
|
||||
|
||||
// 2 missing bytes
|
||||
ByteVector m2("YW55IGNhcm5hbCBwbGVhc3VyZQ");
|
||||
CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64(m2));
|
||||
|
||||
// 3 missing bytes
|
||||
ByteVector m3("YW55IGNhcm5hbCBwbGVhc3VyZ");
|
||||
CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64(m3));
|
||||
}
|
||||
|
||||
// Grok invalid characters
|
||||
{
|
||||
ByteVector invalid("abd\x00\x01\x02\x03\x04");
|
||||
CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64(invalid));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestByteVector);
|
||||
|
@ -22,6 +22,7 @@ class TestOGG : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testDictInterface2);
|
||||
CPPUNIT_TEST(testAudioProperties);
|
||||
CPPUNIT_TEST(testPageChecksum);
|
||||
CPPUNIT_TEST(testPicture);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
@ -172,6 +173,39 @@ public:
|
||||
|
||||
}
|
||||
|
||||
void testPicture()
|
||||
{
|
||||
ScopedFileCopy copy("empty", ".ogg");
|
||||
string newname = copy.fileName();
|
||||
|
||||
Vorbis::File *f = new Vorbis::File(newname.c_str());
|
||||
FLAC::Picture *newpic = new FLAC::Picture();
|
||||
newpic->setType(FLAC::Picture::BackCover);
|
||||
newpic->setWidth(5);
|
||||
newpic->setHeight(6);
|
||||
newpic->setColorDepth(16);
|
||||
newpic->setNumColors(7);
|
||||
newpic->setMimeType("image/jpeg");
|
||||
newpic->setDescription("new image");
|
||||
newpic->setData("JPEG data");
|
||||
f->tag()->addPicture(newpic);
|
||||
f->save();
|
||||
delete f;
|
||||
|
||||
f = new Vorbis::File(newname.c_str());
|
||||
List<FLAC::Picture *> lst = f->tag()->pictureList();
|
||||
CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), lst.size());
|
||||
CPPUNIT_ASSERT_EQUAL(int(5), lst[0]->width());
|
||||
CPPUNIT_ASSERT_EQUAL(int(6), lst[0]->height());
|
||||
CPPUNIT_ASSERT_EQUAL(int(16), lst[0]->colorDepth());
|
||||
CPPUNIT_ASSERT_EQUAL(int(7), lst[0]->numColors());
|
||||
CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), lst[0]->mimeType());
|
||||
CPPUNIT_ASSERT_EQUAL(String("new image"), lst[0]->description());
|
||||
CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), lst[0]->data());
|
||||
|
||||
delete f;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestOGG);
|
||||
|
Loading…
x
Reference in New Issue
Block a user