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:
Tsuda Kageyu 2015-12-02 14:59:50 +09:00
commit 1529af7a12
6 changed files with 410 additions and 7 deletions

View File

@ -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);
}
}

View File

@ -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

View File

@ -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
////////////////////////////////////////////////////////////////////////////////

View File

@ -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

View File

@ -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);

View File

@ -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);