From ea968ae3be2805e3add9f62905e8ba7d4b75d21c Mon Sep 17 00:00:00 2001 From: Sebastian Rachuj Date: Fri, 22 Mar 2013 20:58:47 +0100 Subject: [PATCH 1/7] Added a rough matroska implementation. --- taglib/ebml/ebmlconstants.h | 63 +++ taglib/ebml/ebmlelement.cpp | 496 +++++++++++++++++ taglib/ebml/ebmlelement.h | 276 ++++++++++ taglib/ebml/ebmlfile.cpp | 102 ++++ taglib/ebml/ebmlfile.h | 86 +++ taglib/ebml/matroska/ebmlmatroskaaudio.cpp | 119 ++++ taglib/ebml/matroska/ebmlmatroskaaudio.h | 88 +++ taglib/ebml/matroska/ebmlmatroskaconstants.h | 140 +++++ taglib/ebml/matroska/ebmlmatroskafile.cpp | 549 +++++++++++++++++++ taglib/ebml/matroska/ebmlmatroskafile.h | 201 +++++++ 10 files changed, 2120 insertions(+) create mode 100644 taglib/ebml/ebmlconstants.h create mode 100644 taglib/ebml/ebmlelement.cpp create mode 100644 taglib/ebml/ebmlelement.h create mode 100644 taglib/ebml/ebmlfile.cpp create mode 100644 taglib/ebml/ebmlfile.h create mode 100644 taglib/ebml/matroska/ebmlmatroskaaudio.cpp create mode 100644 taglib/ebml/matroska/ebmlmatroskaaudio.h create mode 100644 taglib/ebml/matroska/ebmlmatroskaconstants.h create mode 100644 taglib/ebml/matroska/ebmlmatroskafile.cpp create mode 100644 taglib/ebml/matroska/ebmlmatroskafile.h diff --git a/taglib/ebml/ebmlconstants.h b/taglib/ebml/ebmlconstants.h new file mode 100644 index 00000000..d0c7c7de --- /dev/null +++ b/taglib/ebml/ebmlconstants.h @@ -0,0 +1,63 @@ +/*************************************************************************** + copyright : (C) 2013 by Sebastian Rachuj + email : rachus@web.de + ***************************************************************************/ + +/*************************************************************************** + * 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_EBML_CONSTANTS +#define TAGLIB_EBML_CONSTANTS + +#ifndef NDEBUG +#include +#include "tdebug.h" +#endif + +namespace TagLib { + + namespace EBML { + //! Shorter representation of the type. + typedef unsigned long long int ulli; + + //! The id of an EBML Void element that is just a placeholder. + const ulli Void = 0xecL; + + //! The id of an EBML CRC32 element that contains a crc32 value. + const ulli CRC32 = 0xc3L; + + //! A namespace containing the ids of the EBML header's elements. + namespace Header { + const ulli EBML = 0x1a45dfa3L; + const ulli EBMLVersion = 0x4286L; + const ulli EBMLReadVersion = 0x42f7L; + const ulli EBMLMaxIDWidth = 0x42f2L; + const ulli EBMLMaxSizeWidth = 0x42f3L; + const ulli DocType = 0x4282L; + const ulli DocTypeVersion = 0x4287L; + const ulli DocTypeReadVersion = 0x4285L; + } + + } + +} + + +#endif diff --git a/taglib/ebml/ebmlelement.cpp b/taglib/ebml/ebmlelement.cpp new file mode 100644 index 00000000..a49964cd --- /dev/null +++ b/taglib/ebml/ebmlelement.cpp @@ -0,0 +1,496 @@ +/*************************************************************************** + copyright : (C) 2013 by Sebastian Rachuj + email : rachus@web.de + ***************************************************************************/ + +/*************************************************************************** + * 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 "ebmlelement.h" + +using namespace TagLib; + +class EBML::Element::ElementPrivate +{ +public: + // The id of this element. + ulli id; + + // The position of the element, where the header begins. + offset_t position; + + // The size of the element as read from the header. Note: Actually an ulli but + // due to the variable integer size limited, thus offset_t is ok. + offset_t size; + + // The position of the element's data. + offset_t data; + + // The element's children. + List children; + + // True: Treated this element as container and read children. + bool populated; + + // The parent element. If NULL (0) this is the document root. + Element *parent; + + // The file used to read and write. + File *document; + + // Destructor: Clean up all children. + ~ElementPrivate() + { + for(List::Iterator i = children.begin(); i != children.end(); ++i) { + delete *i; + } + } + + // Reads a variable length integer from the file at the given position + // and saves its value to result. If cutOne is true the first one of the + // binary representation of the result is removed (required for size). If + // cutOne is false the one will remain in the result (required for id). + // This method returns the position directly after the read integer. + offset_t readVInt(offset_t position, ulli *result, bool cutOne = true) + { + document->seek(position); + + // Determine the length of the integer + char firstByte = document->readBlock(1)[0]; + uint byteSize = 1; + for(uint i = 0; i < 8 && ((firstByte << i) & (1 << 7)) == 0; ++i) + ++byteSize; + + // Load the integer + document->seek(position); + ByteVector vint = document->readBlock(byteSize); + + // Cut the one if requested + if(cutOne) + vint[0] = (vint[0] & (~(1 << (8 - byteSize)))); + + // Store the result and return the current position + if(result) + *result = static_cast(vint.toLongLong()); + return position + byteSize; + } + + // Returns a BytVector containing the given number in the variable integer + // format. Truncates numbers > 2^56 (^ means potency in this case). + // If addOne is true, the ByteVector will remain the One to determine the + // integer's length. + // If shortest is true, the ByteVector will be as short as possible (required + // for the id) + ByteVector createVInt(ulli number, bool addOne = true, bool shortest = true) + { + ByteVector vint = ByteVector::fromLongLong(static_cast(number)); + + // Do we actually need to calculate the length of the variable length + // integer? If not, then prepend the 0b0000 0001 if necessary and return the + // vint. + if(!shortest) { + if(addOne) + vint[0] = 1; + return vint; + } + + // Calculate the minimal length of the variable length integer + uint byteSize = vint.size(); + for(uint i = 0; byteSize > 0 && vint[i] == 0; ++i) + --byteSize; + + if(!addOne) + return ByteVector(vint.data() + vint.size() - byteSize, byteSize); + + ulli firstByte = (1 << (vint.size() - byteSize)); + // The most significant byte loses #bytSize bits for storing information. + // Therefore, we might need to increase byteSize. + if(number >= (firstByte << (8 * (byteSize - 1))) && byteSize < vint.size()) + ++byteSize; + // Add the one at the correct position + uint firstBytePosition = vint.size() - byteSize; + vint[firstBytePosition] |= (1 << firstBytePosition); + return ByteVector(vint.data() + firstBytePosition, byteSize); + } + + // Returns a void element within this element which is at least "least" in + // size. Uses best fit method. Returns a null pointer if no suitable element + // was found. + Element *searchVoid(offset_t least = 0L) + { + Element *currentBest = 0; + for(List::Iterator i = children.begin(); i != children.end(); ++i) { + if((*i)->d->id == Void && + // We need room for the header if we don't remove the element. + ((((*i)->d->size + (*i)->d->data - (*i)->d->position) == least || ((*i)->d->size >= least)) && + // best fit + (!currentBest || (*i)->d->size < currentBest->d->size)) + ) { + currentBest = *i; + } + } + return currentBest; + } + + // Replaces this element by a Void element. Returns true on success and false + // on error. + bool makeVoid() + { + ulli realSize = size + data - position; + ByteVector header(createVInt(Void, false)); + ulli leftSize = realSize - (header.size() + sizeof(ulli)); + // Does not make sense to create a Void element + if (leftSize > realSize) + return false; + header.append(createVInt(leftSize, true, false)); + // Write to file + document->seek(position); + document->writeBlock(header); + // Update data + data = position + header.size(); + size = leftSize; + return true; + + // XXX: We actually should merge Voids, if possible. + } + + // Reading constructor: Reads all unknown information from the file. + ElementPrivate(File *p_document, Element *p_parent = 0, offset_t p_position = 0) : + id(0), + position(p_position), + data(0), + populated(false), + parent(p_parent), + document(p_document) + { + if(parent) { + ulli ssize; + data = readVInt(readVInt(position, &id, false), &ssize); + size = static_cast(ssize); + } + else { + document->seek(0, File::End); + size = document->tell(); + } + } + + // Writing constructor: Takes given information, calculates missing information + // and writes everything to the file. + // Tries to use void elements if available in the parent. + ElementPrivate(ulli p_id, File *p_document, Element *p_parent, + offset_t p_position, offset_t p_size) : + id(p_id), + position(p_position), + size(p_size), + populated(true), // It is a new element so we know, there are no children. + parent(p_parent), + document(p_document) + { + // header + ByteVector content(createVInt(id, false).append(createVInt(size, true, false))); + data = position + content.size(); + // space for children + content.resize(data - position + size); + + Element *freeSpace; + if (!(freeSpace = searchVoid(content.size()))) { + // We have to make room + document->insert(content, position); + // Update parents + for(Element *current = parent; current->d->parent; current = current->d->parent) { + current->d->size += content.size(); + // Create new header and write it. + ByteVector parentHeader(createVInt(current->d->id, false).append(createVInt(current->d->size, true, false))); + uint oldHeaderSize = current->d->data - current->d->position; + if(oldHeaderSize < parentHeader.size()) { + ByteVector secondHeader(createVInt(current->d->id, false).append(createVInt(current->d->size))); + if(oldHeaderSize == secondHeader.size()) { + // Write the header where the old one was. + document->seek(current->d->position); + document->writeBlock(secondHeader); + continue; // Very important here! + } + } + // Insert the new header + document->insert(parentHeader, current->d->position, oldHeaderSize); + current->d->data = current->d->position + parentHeader.size(); + } + } + else { + document->seek(freeSpace->d->position); + if((freeSpace->d->size + freeSpace->d->data - freeSpace->d->position) + == content.size()) { + // Write to file + document->writeBlock(content); + // Update parent + for(List::Iterator i = parent->d->children.begin(); + i != parent->d->children.end(); ++i) { + if(freeSpace == *i) + parent->d->children.erase(i); + } + delete freeSpace; + } + else { + ulli newSize = freeSpace->d->size - content.size(); + ByteVector newVoid(createVInt(Void, false).append(createVInt(newSize, true, false))); + + // Check if the original size of the size field was really 8 byte + if (newVoid.size() != (freeSpace->d->data - freeSpace->d->position)) + newVoid = createVInt(Void, false).append(createVInt(newSize)); + // Update freeSpace + freeSpace->d->size = newSize; + freeSpace->d->data = freeSpace->d->position + newVoid.size(); + // Write to file + document->writeBlock(newVoid.resize(newVoid.size() + newSize).append(content)); + } + } + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +EBML::Element::~Element() +{ + delete d; +} + +EBML::Element::Element(EBML::File *document) + : d(new EBML::Element::ElementPrivate(document)) +{ +} + +EBML::Element *EBML::Element::getChild(EBML::ulli id) +{ + populate(); + for(List::Iterator i = d->children.begin(); i != d->children.end(); + ++i) { + if ((*i)->d->id == id) + return *i; + } + return 0; +} + +List EBML::Element::getChildren(EBML::ulli id) +{ + populate(); + List result; + for(List::Iterator i = d->children.begin(); i != d->children.end(); + ++i) { + if ((*i)->d->id == id) + result.append(*i); + } + return result; +} + +List EBML::Element::getChildren() +{ + populate(); + return d->children; +} + +EBML::Element *EBML::Element::getParent() +{ + return d->parent; +} + +ByteVector EBML::Element::getAsBinary() +{ + d->document->seek(d->data); + return d->document->readBlock(d->size); +} + +String EBML::Element::getAsString() +{ + return String(getAsBinary(), String::UTF8); +} + +signed long long EBML::Element::getAsInt() +{ + // The debug note about returning 0 because of empty data is irrelevant. The + // behavior is as expected. + return getAsBinary().toLongLong(); +} + +EBML::ulli EBML::Element::getAsUnsigned() +{ + // The debug note about returning 0 because of empty data is irrelevant. The + // behavior is as expected. + return static_cast(getAsBinary().toLongLong()); +} + +long double EBML::Element::getAsFloat() +{ + // Very dirty implementation! + ByteVector bin = getAsBinary(); + uint size = bin.size(); + ulli sum = 0.0L; + + // For 0 byte floats and any float that is not defined in the ebml spec. + if (size != 4 && size != 8 /*&& size() != 10*/) // XXX: Currently no support for 10 bit floats. + return sum; + + // From toNumber; Might not be portable, since it requires IEEE floats. + uint last = size - 1; + for(uint i = 0; i <= last; i++) + sum |= (ulli) uchar(bin[i]) << ((last - i) * 8); + + if (size == 4) { + float result = *reinterpret_cast(&sum); + return result; + } + else { + double result = *reinterpret_cast(&sum); + return result; + } +} + +EBML::Element *EBML::Element::addElement(EBML::ulli id) +{ + Element *elem = new Element( + new ElementPrivate(id, d->document, this, d->data + d->size, 0) + ); + d->children.append(elem); + return elem; +} + +EBML::Element *EBML::Element::addElement(EBML::ulli id, const ByteVector &binary) +{ + Element *elem = new Element( + new ElementPrivate(id, d->document, this, d->data + d->size, binary.size()) + ); + d->document->seek(elem->d->data); + d->document->writeBlock(binary); + d->children.append(elem); + return elem; +} + +EBML::Element *EBML::Element::addElement(EBML::ulli id, const String &string) +{ + return addElement(id, string.data(String::UTF8)); +} + +EBML::Element *EBML::Element::addElement(EBML::ulli id, signed long long number) +{ + return addElement(id, ByteVector::fromLongLong(number)); +} + +EBML::Element *EBML::Element::addElement(EBML::ulli id, EBML::ulli number) +{ + return addElement(id, ByteVector::fromLongLong(static_cast(number))); +} + +EBML::Element *EBML::Element::addElement(EBML::ulli id, long double number) +{ + // Probably, we will never need this method. + return 0; +} + +bool EBML::Element::removeChildren(EBML::ulli id, bool useVoid) +{ + bool result = false; + for(List::Iterator i = d->children.begin(); i != d->children.end(); ++i) + if((*i)->d->id == id) { + removeChild(*i, useVoid); + result = true; + } + return result; +} + +bool EBML::Element::removeChildren(bool useVoid) +{ + // Maybe a better implementation, because we probably create a lot of voids + // in a row where a huge Void would be more appropriate. + if (d->children.isEmpty()) + return false; + + for(List::Iterator i = d->children.begin(); i != d->children.end(); ++i) + removeChild(*i, useVoid); + return true; +} + +bool EBML::Element::removeChild(Element *element, bool useVoid) +{ + if (!d->children.contains(element)) + return false; + + if(!useVoid || !element->d->makeVoid()) { + d->document->removeBlock(element->d->position, element->d->size); + // Update parents + for(Element* current = this; current; current = current->d->parent) + current->d->size -= element->d->size; + // Update this element + for(List::Iterator i = d->children.begin(); i != d->children.end(); ++i) + if(element == *i) + d->children.erase(i); + delete element; + } + return true; +} + +void EBML::Element::setAsBinary(const ByteVector &binary) +{ + // Maybe: Search for void element after this one + d->document->insert(binary, d->data, d->size); +} + +void EBML::Element::setAsString(const String &string) +{ + setAsBinary(string.data(String::UTF8)); +} + +void EBML::Element::setAsInt(signed long long number) +{ + setAsBinary(ByteVector::fromLongLong(number)); +} + +void EBML::Element::setAsUnsigned(EBML::ulli number) +{ + setAsBinary(ByteVector::fromLongLong(static_cast(number))); +} + +void EBML::Element::setAsFloat(long double) +{ + // Probably, we will never need this method. +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void EBML::Element::populate() +{ + if(!d->populated) { + d->populated = true; + offset_t end = d->data + d->size; + + for(offset_t i = d->data; i < end;) { + Element *elem = new Element( + new ElementPrivate(d->document, this, i) + ); + d->children.append(elem); + i = elem->d->data + elem->d->size; + } + } +} + +EBML::Element::Element(EBML::Element::ElementPrivate *pe) : d(pe) +{} diff --git a/taglib/ebml/ebmlelement.h b/taglib/ebml/ebmlelement.h new file mode 100644 index 00000000..98d61f06 --- /dev/null +++ b/taglib/ebml/ebmlelement.h @@ -0,0 +1,276 @@ +/*************************************************************************** + copyright : (C) 2013 by Sebastian Rachuj + email : rachus@web.de + ***************************************************************************/ + +/*************************************************************************** + * 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_EBMLELEMENT_H +#define TAGLIB_EBMLELEMENT_H + +#include "tlist.h" +#include "tbytevector.h" +#include "tstring.h" + +#include "ebmlfile.h" + +namespace TagLib { + + namespace EBML { + + /*! + * Represents an element of the EBML. The only instance of this child, that + * is directly used is the root element. Every other element is accessed + * via pointers to the elements within the root element. + * + * Just create one root instance per file to prevent race conditions. + * + * Changes of the document tree will be directly written back to the file. + * Invalid values (exceeding the maximal value defined in the RFC) will be + * truncated. + * + * This class should not be used by library users since the proper file + * class should handle the internals. + * + * NOTE: Currently does not adjust CRC32 values. + */ + class TAGLIB_EXPORT Element + { + public: + //! Destroys the instance of the element. + ~Element(); + + /*! + * Creates an root element using document. + */ + Element(File *document); + + /*! + * Returns the first found child element with the given id. Returns a null + * pointer if the child does not exist. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + */ + Element *getChild(const ulli id); + + /*! + * Returns a list of all child elements with the given id. Returns an + * empty list if no such element exists. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + */ + List getChildren(const ulli id); + + /*! + * Returns a list of every child elements available. Returns an empty list + * if there are no children. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + */ + List getChildren(); + + /*! + * Returns the parent element or null if no such element exists. + */ + Element *getParent(); + + /*! + * Returns the raw content of the element. + */ + ByteVector getAsBinary(); + + /*! + * Returns the content of this element interpreted as a string. + */ + String getAsString(); + + /*! + * Returns the content of this element interpreted as an signed integer. + * + * Do not call this method if *this element is not an INT element (see + * corresponding DTD) + */ + signed long long getAsInt(); + + /*! + * Returns the content of this element interpreted as an unsigned integer. + * + * Do not call this method if *this element is not an UINT element (see + * corresponding DTD) + */ + ulli getAsUnsigned(); + + /*! + * Returns the content of this element interpreted as a floating point + * type. + * + * Do not call this method if *this element is not an FLOAT element (see + * corresponding DTD) + * + * NOTE: There are 10 byte floats defined, therefore we might need a long + * double to store the value. + */ + long double getAsFloat(); + + /*! + * Adds an empty element with given id to this element. Returns a pointer + * to the new element. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + */ + Element *addElement(ulli id); + + /*! + * Adds a new element, containing the given binary, to this element. + * Returns a pointer to the new element. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + */ + Element *addElement(ulli id, const ByteVector &binary); + + /*! + * Adds a new element, containing the given string, to this element. + * Returns a pointer to the new element. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + */ + Element *addElement(ulli id, const String &string); + + /*! + * Adds a new element, containing the given integer, to this element. + * Returns a pointer to the new element. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + */ + Element *addElement(ulli id, signed long long number); + + /*! + * Adds a new element, containing the given unsigned integer, to this element. + * Returns a pointer to the new element. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + */ + Element *addElement(ulli id, ulli number); + + /*! + * Adds a new element, containing the given floating point value, to this element. + * Returns a pointer to the new element. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + * + * This method is not implemented! + */ + Element *addElement(ulli id, long double number); + + /*! + * Removes all children with the given id. Returns false if there was no + * such element. + * If useVoid is true, the element will be changed to a void element. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + * + * Every pointer to a removed element is invalidated. + */ + bool removeChildren(ulli id, bool useVoid = true); + + /*! + * Removes all children. Returns false if this element had no children. + * If useVoid ist rue, the element will be changed to a void element. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + * + * Every pointer to a removed element is invalidated. + */ + bool removeChildren(bool useVoid = true); + + /*! + * Removes the given element. + * If useVoid is true, the element will be changed to a void element. + * + * Do not call this method if *this element is not a container element (see + * corresponding DTD) + * + * The pointer to the given element is invalidated. + */ + bool removeChild(Element *element, bool useVoid = true); + + /*! + * Writes the given binary to this element. + */ + void setAsBinary(const ByteVector &binary); + + /*! + * Writes the given string to this element. + */ + void setAsString(const String &string); + + /*! + * Writes the given integer to this element. + */ + void setAsInt(signed long long number); + + /*! + * Writes the given unsigned integer to this element. + */ + void setAsUnsigned(ulli number); + + /*! + * Writes the given floating point variable to this element. + * + * This method is not implemented! + */ + void setAsFloat(long double number); + + private: + //! Non-copyable + Element(const Element &); + //! Non-copyable + Element &operator=(const File &); + + //! Lazy parsing. This method will be triggered when trying to access + //! children. + void populate(); + + class ElementPrivate; + ElementPrivate *d; + + //! Creates a new Element from an ElementPrivate. (The constructor takes + //! ownership of the pointer and will delete it when the element is + //! destroyed. + Element(ElementPrivate *pe); + }; + + } +} + + +#endif diff --git a/taglib/ebml/ebmlfile.cpp b/taglib/ebml/ebmlfile.cpp new file mode 100644 index 00000000..0995a37c --- /dev/null +++ b/taglib/ebml/ebmlfile.cpp @@ -0,0 +1,102 @@ +/*************************************************************************** + copyright : (C) 2013 by Sebastian Rachuj + email : rachus@web.de + ***************************************************************************/ + +/*************************************************************************** + * 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 "ebmlelement.h" + +using namespace TagLib; + +class EBML::File::FilePrivate +{ +public: + FilePrivate(File *document) : root(document) + { + } + + // Performs a few basic checks and creates the FilePrivate if they were + // successful. + static FilePrivate *checkAndCreate(File* document) + { + document->seek(0); + ByteVector magical = document->readBlock(4); + if(static_cast(magical.toLongLong()) != Header::EBML) + return 0; + FilePrivate *d = new FilePrivate(document); + Element *head = d->root.getChild(Header::EBML); + Element *p; + if(!head || + !((p = head->getChild(Header::EBMLVersion)) && p->getAsUnsigned() == 1L) || + !((p = head->getChild(Header::EBMLReadVersion)) && p->getAsUnsigned() == 1L) || + // Actually 4 is the current maximum of the EBML spec, but we support up to 8 + !((p = head->getChild(Header::EBMLMaxIDWidth)) && p->getAsUnsigned() <= 8) || + !((p = head->getChild(Header::EBMLMaxSizeWidth)) && p->getAsUnsigned() <= 8) + ) { + delete d; + return 0; + } + return d; + } + + Element root; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +EBML::File::~File() +{ + delete d; +} + +EBML::Element *EBML::File::getDocumentRoot() +{ + if(!d && isValid()) + d = new FilePrivate(this); + return &d->root; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +EBML::File::File(FileName file) : + TagLib::File(file) +{ + if(isOpen()) { + d = FilePrivate::checkAndCreate(this); + if(!d) + setValid(false); + } +} + +EBML::File::File(IOStream *stream) : + TagLib::File(stream) +{ + if(isOpen()) { + d = FilePrivate::checkAndCreate(this); + if(!d) + setValid(false); + } +} diff --git a/taglib/ebml/ebmlfile.h b/taglib/ebml/ebmlfile.h new file mode 100644 index 00000000..fc1204e8 --- /dev/null +++ b/taglib/ebml/ebmlfile.h @@ -0,0 +1,86 @@ +/*************************************************************************** + copyright : (C) 2013 by Sebastian Rachuj + email : rachus@web.de + ***************************************************************************/ + +/*************************************************************************** + * 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_EBMLFILE_H +#define TAGLIB_EBMLFILE_H + +#include "taglib_export.h" +#include "tfile.h" + +#include "ebmlconstants.h" + +namespace TagLib { + + //! A namespace for the classes used by EBML-based metadata files + namespace EBML { + + class Element; + + /*! + * Represents an EBML file. It offers access to the root element which can + * be used to obtain the necessary information and to change the file + * according to changes. + */ + class TAGLIB_EXPORT File : public TagLib::File + { + public: + //! Destroys the instance of the file. + virtual ~File(); + + /*! + * Returns a pointer to the document root element of the EBML file. + */ + Element *getDocumentRoot(); + + protected: + /*! + * Constructs an instance of an EBML file from \a file. + * + * This constructor is protected since an object should be created + * through a specific subclass. + */ + File(FileName file); + + /*! + * Constructs an instance of an EBML file from an IOStream. + * + * This constructor is protected since an object should be created + * through a specific subclass. + */ + File(IOStream *stream); + + private: + //! Non-copyable + File(const File&); + File &operator=(const File &); + + class FilePrivate; + FilePrivate *d; + }; + + } +} + +#endif diff --git a/taglib/ebml/matroska/ebmlmatroskaaudio.cpp b/taglib/ebml/matroska/ebmlmatroskaaudio.cpp new file mode 100644 index 00000000..3ae8b3d2 --- /dev/null +++ b/taglib/ebml/matroska/ebmlmatroskaaudio.cpp @@ -0,0 +1,119 @@ +/*************************************************************************** + copyright : (C) 2013 by Sebastian Rachuj + email : rachus@web.de + ***************************************************************************/ + +/*************************************************************************** + * 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 "ebmlmatroskaconstants.h" +#include "ebmlmatroskaaudio.h" + +using namespace TagLib; + +class EBML::Matroska::AudioProperties::AudioPropertiesPrivate +{ +public: + // Constructor + AudioPropertiesPrivate(File *p_document) : + document(p_document), + length(0), + bitrate(0), + channels(1), + samplerate(8000) + { + Element *elem = document->getDocumentRoot()->getChild(Constants::Segment); + Element *info = elem->getChild(Constants::SegmentInfo); + Element *value; + + if(info && (value = info->getChild(Constants::Duration))) { + length = static_cast(value->getAsFloat()); + if((value = info->getChild(Constants::TimecodeScale))){ + length /= (value->getAsUnsigned() / 1000);} + } + + info = elem->getChild(Constants::Tracks); + if(!info || !(info = info->getChild(Constants::TrackEntry)) || + !(info = info->getChild(Constants::Audio))) { + + return; + } + + // Dirty bitrate: + document->seek(0, File::End); + bitrate = ((8 * document->tell()) / length) / 1000; + + if((value = info->getChild(Constants::Channels))) + channels = value->getAsUnsigned(); + + if((value = info->getChild(Constants::SamplingFrequency))) + samplerate = static_cast(value->getAsFloat()); + } + + // The corresponding file + File *document; + + // The length of the file + int length; + + // The bitrate + int bitrate; + + // The amount of channels + int channels; + + // The sample rate + int samplerate; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +EBML::Matroska::AudioProperties::AudioProperties(File *document) : + TagLib::AudioProperties(TagLib::AudioProperties::Fast), + d(new AudioPropertiesPrivate(document)) +{ +} + +EBML::Matroska::AudioProperties::~AudioProperties() +{ + delete d; +} + +int EBML::Matroska::AudioProperties::length() const +{ + return d->length; +} + +int EBML::Matroska::AudioProperties::bitrate() const +{ + return d->bitrate; +} + +int EBML::Matroska::AudioProperties::channels() const +{ + return d->channels; +} + +int EBML::Matroska::AudioProperties::sampleRate() const +{ + return d->samplerate; +} diff --git a/taglib/ebml/matroska/ebmlmatroskaaudio.h b/taglib/ebml/matroska/ebmlmatroskaaudio.h new file mode 100644 index 00000000..de5ecb3e --- /dev/null +++ b/taglib/ebml/matroska/ebmlmatroskaaudio.h @@ -0,0 +1,88 @@ +/*************************************************************************** + copyright : (C) 2013 by Sebastian Rachuj + email : rachus@web.de + ***************************************************************************/ + +/*************************************************************************** + * 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_EBMLMATROSKAAUDIO_H +#define TAGLIB_EBMLMATROSKAAUDIO_H + +#include "ebmlmatroskafile.h" + +#include "audioproperties.h" + +namespace TagLib { + + namespace EBML { + + namespace Matroska { + + /*! + * This class represents the audio properties of a matroska file. + * Currently all information are read from the container format and + * could be inexact. + */ + class TAGLIB_EXPORT AudioProperties : public TagLib::AudioProperties + { + public: + //! Destructor + virtual ~AudioProperties(); + + /*! + * Constructs an instance from a file. + */ + AudioProperties(File *document); + + /*! + * Returns the length of the file. + */ + virtual int length() const; + + /*! + * Returns the bit rate of the file. Since the container format does not + * offer a proper value, it ist currently calculated by dividing the + * file size by the length. + */ + virtual int bitrate() const; + + /*! + * Returns the amount of channels of the file. + */ + virtual int channels() const; + + /*! + * Returns the sample rate of the file. + */ + virtual int sampleRate() const; + + private: + class AudioPropertiesPrivate; + AudioPropertiesPrivate *d; + }; + + } + + } + +} + +#endif diff --git a/taglib/ebml/matroska/ebmlmatroskaconstants.h b/taglib/ebml/matroska/ebmlmatroskaconstants.h new file mode 100644 index 00000000..150be658 --- /dev/null +++ b/taglib/ebml/matroska/ebmlmatroskaconstants.h @@ -0,0 +1,140 @@ +/*************************************************************************** + copyright : (C) 2013 by Sebastian Rachuj + email : rachus@web.de + ***************************************************************************/ + +/*************************************************************************** + * 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_EBMLMATROSKACONSTANTS_H +#define TAGLIB_EBMLMATROSKACONSTANTS_H + +#include "ebmlconstants.h" +#include "tstring.h" + +namespace TagLib { + + namespace EBML { + + namespace Matroska { + + namespace Constants { + + //! ID of an Matroska segment. + const ulli Segment = 0x18538067L; + + //! ID of the tags element. + const ulli Tags = 0x1254c367L; + + //! ID of the tag element. + const ulli Tag = 0x7373L; + + //! ID of the targets element. + const ulli Targets = 0x63c0L; + + //! ID of the target type value element. + const ulli TargetTypeValue = 0x68caL; + + //! ID of the target type element. + const ulli TargetType = 0x63caL; + + //! ID of a simple tag element. + const ulli SimpleTag = 0x67c8L; + + //! ID of the tag name. + const ulli TagName = 0x45a3L; + + //! ID of the tag content. + const ulli TagString = 0x4487L; + + //! The DocType of a matroska file. + const String DocTypeMatroska = "matroska"; + + //! The DocType of a WebM file. + const String DocTypeWebM = "webm"; + + + + //! The TITLE entry + const String TITLE = "TITLE"; + + //! The ARTIST entry + const String ARTIST = "ARTIST"; + + //! The COMMENT entry + const String COMMENT = "COMMENT"; + + //! The GENRE entry + const String GENRE = "GENRE"; + + //! The DATE_RELEASE entry + const String DATE_RELEASE = "DATE_RELEASE"; + + //! The PART_NUMBER entry + const String PART_NUMBER = "PART_NUMBER"; + + //! The TargetTypeValue of the most common grouping level (e.g. album) + const ulli MostCommonGroupingValue = 50; + + //! The TargetTypeValue of the most common parts of a group (e.g. track) + const ulli MostCommonPartValue = 30; + + //! Name of the TargetType of an album. + const String ALBUM = "ALBUM"; + + //! Name of the TargetType of a track. + const String TRACK = "TRACK"; + + + + // For AudioProperties + + //! ID of the Info block within the Segment. + const ulli SegmentInfo = 0x1549a966L; + + //! ID of the duration element. + const ulli Duration = 0x4489L; + + //! ID of TimecodeScale element. + const ulli TimecodeScale = 0x2ad7b1L; + + //! ID of the Tracks container + const ulli Tracks = 0x1654ae6bL; + + //! ID of a TrackEntry element. + const ulli TrackEntry = 0xaeL; + + //! ID of the Audio container. + const ulli Audio = 0xe1L; + + //! ID of the SamplingFrequency element. + const ulli SamplingFrequency = 0xb5L; + + //! ID of the Channels element. + const ulli Channels = 0x9fL; + + //! ID of the BitDepth element. + const ulli BitDepth = 0x6264L; + } + } + } +} + +#endif diff --git a/taglib/ebml/matroska/ebmlmatroskafile.cpp b/taglib/ebml/matroska/ebmlmatroskafile.cpp new file mode 100644 index 00000000..a43a9783 --- /dev/null +++ b/taglib/ebml/matroska/ebmlmatroskafile.cpp @@ -0,0 +1,549 @@ +/*************************************************************************** + copyright : (C) 2013 by Sebastian Rachuj + email : rachus@web.de + ***************************************************************************/ + +/*************************************************************************** + * 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 "ebmlmatroskaconstants.h" +#include "ebmlmatroskaaudio.h" + +#include "tpropertymap.h" + +using namespace TagLib; + +class EBML::Matroska::File::FilePrivate +{ +public: + // Returns true if simpleTag has a TagName and TagString child and writes + // their contents into name and value. + bool extractContent(Element *simpleTag, String &name, String &value) + { + Element *n = simpleTag->getChild(Constants::TagName); + Element *v = simpleTag->getChild(Constants::TagString); + if(!n || !v) + return false; + + name = n->getAsString(); + value = v->getAsString(); + return true; + } + + FilePrivate(File *p_document) : tag(0), document(p_document) + { + // Just get the first segment, because "Typically a Matroska file is + // composed of 1 segment." + Element* elem = document->getDocumentRoot()->getChild(Constants::Segment); + + // We take the first tags element (there shouldn't be more), if there is + // non such element, consider the file as not compatible. + if(!elem || !(elem = elem->getChild(Constants::Tags))) { + document->setValid(false); + return; + } + + // Load all Tag entries + List entries = elem->getChildren(Constants::Tag); + for(List::Iterator i = entries.begin(); i != entries.end(); ++i) { + Element *target = (*i)->getChild(Constants::Targets); + ulli ttvalue = 0; + if(target && (target = (*i)->getChild(Constants::TargetTypeValue))) + ttvalue = target->getAsUnsigned(); + + // Load all SimpleTags + PropertyMap tagEntries; + List simpleTags = (*i)->getChildren(Constants::SimpleTag); + for(List::Iterator j = simpleTags.begin(); j != simpleTags.end(); + ++j) { + String name, value; + if(!extractContent(*j, name, value)) + continue; + tagEntries.insert(name, StringList(value)); + } + + tags.append(std::pair >(tagEntries, std::pair(*i, ttvalue))); + } + } + + // Creates Tag and AudioProperties. Late creation because both require a fully + // functional FilePrivate (well AudioProperties doesn't...) + void lateCreate() + { + tag = new Tag(document); + audio = new AudioProperties(document); + } + + // Checks the EBML header and creates the FilePrivate. + static FilePrivate *checkAndCreate(File *document) + { + Element *elem = document->getDocumentRoot()->getChild(Header::EBML); + Element *child = elem->getChild(Header::DocType); + if(child) { + String dt = child->getAsString(); + if (dt == Constants::DocTypeMatroska || dt == Constants::DocTypeWebM) { + FilePrivate *fp = new FilePrivate(document); + return fp; + } + } + + return 0; + } + + // The tags with their Element and TargetTypeValue + List > > tags; + + // The tag + Tag *tag; + + // The audio properties + AudioProperties *audio; + + // The corresponding file. + File *document; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +EBML::Matroska::File::~File() +{ + delete d->tag; + delete d->audio; + delete d; +} + +EBML::Matroska::File::File(FileName file) : EBML::File(file), d(0) +{ + if(isValid() && isOpen()) { + d = FilePrivate::checkAndCreate(this); + if(!d) + setValid(false); + else + d->lateCreate(); + } +} + +EBML::Matroska::File::File(IOStream *stream) : EBML::File(stream), d(0) +{ + if(isValid() && isOpen()) { + d = FilePrivate::checkAndCreate(this); + if(!d) + setValid(false); + else + d->lateCreate(); + } +} + +Tag *EBML::Matroska::File::tag() const +{ + return d->tag; +} + +PropertyMap EBML::Matroska::File::properties() const +{ + List > >::Iterator best = d->tags.end(); + for(List > >::Iterator i = d->tags.begin(); + i != d->tags.end(); ++i) { + if(best == d->tags.end() || best->second.second > i->second.second) + best = i; + } + return best->first; +} + +PropertyMap EBML::Matroska::File::setProperties(const PropertyMap &properties) +{ + List > >::Iterator best = d->tags.end(); + for(List > >::Iterator i = d->tags.begin(); + i != d->tags.end(); ++i) { + if(best == d->tags.end() || best->second.second > i->second.second) + best = i; + } + + std::pair > replace(properties, best->second); + d->tags.erase(best); + d->tags.prepend(replace); + + return PropertyMap(); +} + +AudioProperties *EBML::Matroska::File::audioProperties() const +{ + return d->audio; +} + +bool EBML::Matroska::File::save() +{ + if(readOnly()) + return false; + + // C++11 features would be nice: for(auto &i : d->tags) { /* ... */ } + // Well, here we just iterate over each extracted element. + for(List > >::Iterator i = d->tags.begin(); + i != d->tags.end(); ++i) { + + for(PropertyMap::Iterator j = i->first.begin(); j != i->first.end(); ++j) { + + // No element? Create it! + if(!i->second.first) { + // Should be save, since we already checked, when creating the object. + Element *container = d->document->getDocumentRoot() + ->getChild(Constants::Segment)->getChild(Constants::Tags); + + // Create Targets container + i->second.first = container->addElement(Constants::Tag); + Element *target = i->second.first->addElement(Constants::Targets); + + if(i->second.second == Constants::MostCommonPartValue) + target->addElement(Constants::TargetType, Constants::TRACK); + else if(i->second.second == Constants::MostCommonGroupingValue) + target->addElement(Constants::TargetType, Constants::ALBUM); + + target->addElement(Constants::TargetTypeValue, i->second.second); + } + + // Find entries + List simpleTags = i->second.first->getChildren(Constants::SimpleTag); + StringList::Iterator str = j->second.begin(); + List::Iterator k = simpleTags.begin(); + for(; k != simpleTags.end(); ++k) { + + String name, value; + if(!d->extractContent(*k, name, value)) + continue; + + // Write entry from StringList + if(name == j->first) { + if(str == j->second.end()) { + // We have all StringList elements but still found another element + // with the same name? Let's delete it! + i->second.first->removeChild(*k); + } + else { + if(value != *str) { + // extractContent already checked for availability + (*k)->getChild(Constants::TagString)->setAsString(*str); + } + ++str; + } + } + } + + // If we didn't write the complete StringList, we have to write the rest. + for(; str != j->second.end(); ++str) { + Element *stag = i->second.first->addElement(Constants::SimpleTag); + stag->addElement(Constants::TagName, j->first); + stag->addElement(Constants::TagString, *str); + } + } + + // Finally, we have to find elements that are not in the PropertyMap and + // remove them. + List simpleTags = i->second.first->getChildren(Constants::SimpleTag); + for(List::Iterator j = simpleTags.begin(); j != simpleTags.end(); ++j) { + + String name, value; + if(!d->extractContent(*j, name, value)) + continue; + + if(i->first.find(name) == i->first.end()){ + i->second.first->removeChild(*j);} + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Tag +// +//////////////////////////////////////////////////////////////////////////////// + + +class EBML::Matroska::File::Tag::TagPrivate +{ +public: + // Creates a TagPrivate instance + TagPrivate(File *p_document) : + document(p_document), + title(document->d->tags.end()), + artist(document->d->tags.end()), + album(document->d->tags.end()), + comment(document->d->tags.end()), + genre(document->d->tags.end()), + year(document->d->tags.end()), + track(document->d->tags.end()) + { + + for(List > >::Iterator i = + document->d->tags.begin(); i != document->d->tags.end(); ++i) { + + // Just save it, if the title is more specific, or there is no title yet. + if(i->first.find(Constants::TITLE) != i->first.end() && + (title == document->d->tags.end() || + title->second.second > i->second.second || + i->second.second == Constants::MostCommonPartValue)) { + + title = i; + } + + // Same goes for artist. + if(i->first.find(Constants::ARTIST) != i->first.end() && + (artist == document->d->tags.end() || + artist->second.second > i->second.second || + i->second.second == Constants::MostCommonPartValue)) { + + artist = i; + } + + // Here, we also look for a title (the album title), but since we + // specified the granularity, we have to search for it exactly. + // Therefore it is possible, that title and album are the same (if only + // the title of the album is given). + if(i->first.find(Constants::TITLE) != i->first.end() && + i->second.second == Constants::MostCommonGroupingValue) { + + album = i; + } + + // Again the same as title and artist. + if(i->first.find(Constants::COMMENT) != i->first.end() && + (comment == document->d->tags.end() || + comment->second.second > i->second.second || + i->second.second == Constants::MostCommonPartValue)) { + + comment = i; + } + + // Same goes for genre. + if(i->first.find(Constants::GENRE) != i->first.end() && + (genre == document->d->tags.end() || + genre->second.second > i->second.second || + i->second.second == Constants::MostCommonPartValue)) { + + genre = i; + } + + // And year (in our case: DATE_REALEASE) + if(i->first.find(Constants::DATE_RELEASE) != i->first.end() && + (year == document->d->tags.end() || + year->second.second > i->second.second || + i->second.second == Constants::MostCommonPartValue)) { + + year = i; + } + + // And track (in our case: PART_NUMBER) + if(i->first.find(Constants::PART_NUMBER) != i->first.end() && + (track == document->d->tags.end() || + track->second.second > i->second.second || + i->second.second == Constants::MostCommonPartValue)) { + + track = i; + } + } + } + + // Searches for the Tag with given TargetTypeValue (returns the first one) + List > >::Iterator + find(ulli ttv) + { + for(List > >::Iterator i = + document->d->tags.begin(); i != document->d->tags.end(); ++i) { + + if(i->second.second == ttv) + return i; + } + } + + // Updates the given information + void update( + List > >::Iterator t, + const String &tagname, + const String &s + ) + { + t->first.find(tagname)->second.front() = s; + } + + // Inserts a tag with given information + void insert(const String &tagname, const ulli ttv, const String &s) + { + for(List > >::Iterator i = + document->d->tags.begin(); i != document->d->tags.end(); ++i) { + + if(i->second.second == ttv) { + i->first.insert(tagname, StringList(s)); + return; + } + } + + // Not found? Create new! + PropertyMap pm; + pm.insert(tagname, StringList(s)); + document->d->tags.append( + std::pair >(pm, + std::pair(0, ttv) + ) + ); + } + + // The PropertyMap from the Matroska::File + File *document; + + // Iterators to the tags. + List > >::Iterator title; + List > >::Iterator artist; + List > >::Iterator album; + List > >::Iterator comment; + List > >::Iterator genre; + List > >::Iterator year; + List > >::Iterator track; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +EBML::Matroska::File::Tag::~Tag() +{ + delete e; +} + +EBML::Matroska::File::Tag::Tag(EBML::Matroska::File *document) : + e(new EBML::Matroska::File::Tag::TagPrivate(document)) +{ +} + +String EBML::Matroska::File::Tag::title() const +{ + if(e->title != e->document->d->tags.end()) + return e->title->first.find(Constants::TITLE)->second.front(); + else + return String::null; +} + +String EBML::Matroska::File::Tag::artist() const +{ + if(e->artist != e->document->d->tags.end()) + return e->artist->first.find(Constants::ARTIST)->second.front(); + else + return String::null; +} + +String EBML::Matroska::File::Tag::album() const +{ + if(e->album != e->document->d->tags.end()) + return e->album->first.find(Constants::TITLE)->second.front(); + else + return String::null; +} + +String EBML::Matroska::File::Tag::comment() const +{ + if(e->comment != e->document->d->tags.end()) + return e->comment->first.find(Constants::COMMENT)->second.front(); + else + return String::null; +} + +String EBML::Matroska::File::Tag::genre() const +{ + if(e->genre != e->document->d->tags.end()) + return e->genre->first.find(Constants::GENRE)->second.front(); + else + return String::null; +} + +uint EBML::Matroska::File::Tag::year() const +{ + if(e->year != e->document->d->tags.end()) + return e->year->first.find(Constants::DATE_RELEASE)->second.front().toInt(); + else + return 0; +} + +uint EBML::Matroska::File::Tag::track() const +{ + if(e->track != e->document->d->tags.end()) + return e->track->first.find(Constants::PART_NUMBER)->second.front().toInt(); + else + return 0; +} + +void EBML::Matroska::File::Tag::setTitle(const String &s) +{ + if(e->title != e->document->d->tags.end()) + e->update(e->title, Constants::TITLE, s); + else + e->insert(Constants::TITLE, Constants::MostCommonPartValue, s); +} + +void EBML::Matroska::File::Tag::setArtist(const String &s) +{ + if(e->artist != e->document->d->tags.end()) + e->update(e->artist, Constants::ARTIST, s); + else + e->insert(Constants::ARTIST, Constants::MostCommonPartValue, s); +} + +void EBML::Matroska::File::Tag::setAlbum(const String &s) +{ + if(e->album != e->document->d->tags.end()) + e->update(e->album, Constants::TITLE, s); + else + e->insert(Constants::TITLE, Constants::MostCommonGroupingValue, s); +} + +void EBML::Matroska::File::Tag::setComment(const String &s) +{ + if(e->comment != e->document->d->tags.end()) + e->update(e->comment, Constants::COMMENT, s); + else + e->insert(Constants::COMMENT, Constants::MostCommonPartValue, s); +} + +void EBML::Matroska::File::Tag::setGenre(const String &s) +{ + if(e->genre != e->document->d->tags.end()) + e->update(e->genre, Constants::GENRE, s); + else + e->insert(Constants::GENRE, Constants::MostCommonPartValue, s); +} + +void EBML::Matroska::File::Tag::setYear(uint i) +{ + String s = String::number(i); + if(e->year != e->document->d->tags.end()) + e->update(e->year, Constants::DATE_RELEASE, s); + else + e->insert(Constants::DATE_RELEASE, Constants::MostCommonPartValue, s); +} + +void EBML::Matroska::File::Tag::setTrack(uint i) +{ + String s = String::number(i); + if(e->track != e->document->d->tags.end()) + e->update(e->track, Constants::PART_NUMBER, s); + else + e->insert(Constants::PART_NUMBER, Constants::MostCommonPartValue, s); +} diff --git a/taglib/ebml/matroska/ebmlmatroskafile.h b/taglib/ebml/matroska/ebmlmatroskafile.h new file mode 100644 index 00000000..5e57a751 --- /dev/null +++ b/taglib/ebml/matroska/ebmlmatroskafile.h @@ -0,0 +1,201 @@ +/*************************************************************************** + copyright : (C) 2013 by Sebastian Rachuj + email : rachus@web.de + ***************************************************************************/ + +/*************************************************************************** + * 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_EBMLMATROSKAFILE_H +#define TAGLIB_EBMLMATROSKAFILE_H + +#include "ebmlelement.h" + +namespace TagLib { + + namespace EBML { + + //! Implementation for reading Matroska tags. + namespace Matroska { + + /*! + * Implements the TagLib::File API and offers access to the tags of the + * matroska file. + */ + class TAGLIB_EXPORT File : public EBML::File + { + public: + //! Destroys the instance of the file. + virtual ~File(); + + /*! + * Constructs a Matroska File from a file name. + */ + File(FileName file); + + /*! + * Constructs a Matroska File from a stream. + */ + File(IOStream *stream); + + /*! + * Returns the pointer to a tag that allow access on common tags. + */ + virtual TagLib::Tag *tag() const; + + /*! + * Exports the tags to a PropertyMap. Due to the diversity of the + * Matroska format (e.g. multiple media streams in one file, each with + * its own tags), only the best fitting tags are taken into account. + * There are no unsupported tags. + */ + virtual PropertyMap properties() const; + + /*! + * Sets the tags of the file to those specified in properties. The + * returned PropertyMap is always empty. + * Note: Only the best fitting tags are taken into account. + */ + virtual PropertyMap setProperties(const PropertyMap &properties); + + /*! + * Returns a pointer to this file's audio properties. + * + * I'm glad about not having a setAudioProperties method ;) + */ + virtual AudioProperties *audioProperties() const; + + /*! + * Saves the file. Returns true on success. + */ + bool save(); + + /*! + * Offers access to a few common tag entries. + */ + class Tag : public TagLib::Tag + { + public: + //! Destroys the tag. + ~Tag(); + + /*! + * Creates a new Tag for Matroska files. The given properties are gained + * by the Matroska::File. + */ + Tag(File *document); + + /*! + * Returns the track name; if no track name is present in the tag + * String::null will be returned. + */ + virtual String title() const; + + /*! + * Returns the artist name; if no artist name is present in the tag + * String::null will be returned. + */ + virtual String artist() const; + + /*! + * Returns the album name; if no album name is present in the tag + * String::null will be returned. + */ + virtual String album() const; + + /*! + * Returns the track comment; if no comment is present in the tag + * String::null will be returned. + */ + virtual String comment() const; + + /*! + * Returns the genre name; if no genre is present in the tag String::null + * will be returned. + */ + virtual String genre() const; + + /*! + * Returns the year; if there is no year set, this will return 0. + */ + virtual uint year() const; + + /*! + * Returns the track number; if there is no track number set, this will + * return 0. + */ + virtual uint track() const; + + /*! + * Sets the title to s. If s is String::null then this value will be + * cleared. + */ + virtual void setTitle(const String &s); + + /*! + * Sets the artist to s. If s is String::null then this value will be + * cleared. + */ + virtual void setArtist(const String &s); + + /*! + * Sets the album to s. If s is String::null then this value will be + * cleared. + */ + virtual void setAlbum(const String &s); + + /*! + * Sets the comment to s. If s is String::null then this value will be + * cleared. + */ + virtual void setComment(const String &s); + + /*! + * Sets the genre to s. If s is String::null then this value will be + * cleared. + */ + virtual void setGenre(const String &s); + + /*! + * Sets the year to i. If s is 0 then this value will be cleared. + */ + virtual void setYear(uint i); + + /*! + * Sets the track to i. If s is 0 then this value will be cleared. + */ + virtual void setTrack(uint i); + + private: + class TagPrivate; + TagPrivate *e; + }; + + private: + class FilePrivate; + FilePrivate *d; + }; + + } + } +} + +#endif From 3d3c97b5ef0b369d4f92622a6b6f5f128d4b42f0 Mon Sep 17 00:00:00 2001 From: Sebastian Rachuj Date: Fri, 22 Mar 2013 20:59:57 +0100 Subject: [PATCH 2/7] CMakeLists adjusted for Matroska --- taglib/CMakeLists.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 218edf5b..e87ef027 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -24,6 +24,8 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/s3m ${CMAKE_CURRENT_SOURCE_DIR}/it ${CMAKE_CURRENT_SOURCE_DIR}/xm + ${CMAKE_CURRENT_SOURCE_DIR}/ebml + ${CMAKE_CURRENT_SOURCE_DIR}/ebml/matroska ) if(ZLIB_FOUND) @@ -131,6 +133,12 @@ set(tag_HDRS s3m/s3mproperties.h xm/xmfile.h xm/xmproperties.h + ebml/ebmlfile.h + ebml/ebmlelement.h + ebml/ebmlconstants.h + ebml/matroska/ebmlmatroskafile.h + ebml/matroska/ebmlmatroskaconstants.h + ebml/matroska/ebmlmatroskaaudio.h ) set(mpeg_SRCS @@ -297,11 +305,22 @@ set(toolkit_SRCS toolkit/unicode.cpp ) +set(ebml_SRCS + ebml/ebmlfile.cpp + ebml/ebmlelement.cpp +) + +set(matroska_SRCS + ebml/matroska/ebmlmatroskafile.cpp + ebml/matroska/ebmlmatroskaaudio.cpp +) + set(tag_LIB_SRCS ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} + ${ebml_SRCS} ${matroska_SRCS} tag.cpp tagunion.cpp fileref.cpp From 3dc37a482e787afd94fe720750bc4aaceeda29e1 Mon Sep 17 00:00:00 2001 From: Sebastian Rachuj Date: Fri, 19 Apr 2013 15:22:33 +0200 Subject: [PATCH 3/7] Changed to/from LongLong to to/from (U)Int64 --- taglib/ebml/ebmlelement.cpp | 20 ++++++++++---------- taglib/ebml/ebmlfile.cpp | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/taglib/ebml/ebmlelement.cpp b/taglib/ebml/ebmlelement.cpp index a49964cd..8c08ac71 100644 --- a/taglib/ebml/ebmlelement.cpp +++ b/taglib/ebml/ebmlelement.cpp @@ -88,7 +88,7 @@ public: // Store the result and return the current position if(result) - *result = static_cast(vint.toLongLong()); + *result = static_cast(vint.toInt64()); return position + byteSize; } @@ -100,7 +100,7 @@ public: // for the id) ByteVector createVInt(ulli number, bool addOne = true, bool shortest = true) { - ByteVector vint = ByteVector::fromLongLong(static_cast(number)); + ByteVector vint = ByteVector::fromUInt64(static_cast(number)); // Do we actually need to calculate the length of the variable length // integer? If not, then prepend the 0b0000 0001 if necessary and return the @@ -236,7 +236,7 @@ public: else { document->seek(freeSpace->d->position); if((freeSpace->d->size + freeSpace->d->data - freeSpace->d->position) - == content.size()) { + == static_cast(content.size())) { // Write to file document->writeBlock(content); // Update parent @@ -252,7 +252,7 @@ public: ByteVector newVoid(createVInt(Void, false).append(createVInt(newSize, true, false))); // Check if the original size of the size field was really 8 byte - if (newVoid.size() != (freeSpace->d->data - freeSpace->d->position)) + if (static_cast(newVoid.size()) != (freeSpace->d->data - freeSpace->d->position)) newVoid = createVInt(Void, false).append(createVInt(newSize)); // Update freeSpace freeSpace->d->size = newSize; @@ -327,14 +327,14 @@ signed long long EBML::Element::getAsInt() { // The debug note about returning 0 because of empty data is irrelevant. The // behavior is as expected. - return getAsBinary().toLongLong(); + return getAsBinary().toInt64(); } EBML::ulli EBML::Element::getAsUnsigned() { // The debug note about returning 0 because of empty data is irrelevant. The // behavior is as expected. - return static_cast(getAsBinary().toLongLong()); + return static_cast(getAsBinary().toInt64()); } long double EBML::Element::getAsFloat() @@ -390,12 +390,12 @@ EBML::Element *EBML::Element::addElement(EBML::ulli id, const String &string) EBML::Element *EBML::Element::addElement(EBML::ulli id, signed long long number) { - return addElement(id, ByteVector::fromLongLong(number)); + return addElement(id, ByteVector::fromUInt64(number)); } EBML::Element *EBML::Element::addElement(EBML::ulli id, EBML::ulli number) { - return addElement(id, ByteVector::fromLongLong(static_cast(number))); + return addElement(id, ByteVector::fromUInt64(static_cast(number))); } EBML::Element *EBML::Element::addElement(EBML::ulli id, long double number) @@ -459,12 +459,12 @@ void EBML::Element::setAsString(const String &string) void EBML::Element::setAsInt(signed long long number) { - setAsBinary(ByteVector::fromLongLong(number)); + setAsBinary(ByteVector::fromUInt64(number)); } void EBML::Element::setAsUnsigned(EBML::ulli number) { - setAsBinary(ByteVector::fromLongLong(static_cast(number))); + setAsBinary(ByteVector::fromUInt64(static_cast(number))); } void EBML::Element::setAsFloat(long double) diff --git a/taglib/ebml/ebmlfile.cpp b/taglib/ebml/ebmlfile.cpp index 0995a37c..7a330f82 100644 --- a/taglib/ebml/ebmlfile.cpp +++ b/taglib/ebml/ebmlfile.cpp @@ -40,7 +40,7 @@ public: { document->seek(0); ByteVector magical = document->readBlock(4); - if(static_cast(magical.toLongLong()) != Header::EBML) + if(static_cast(magical.toInt64()) != Header::EBML) return 0; FilePrivate *d = new FilePrivate(document); Element *head = d->root.getChild(Header::EBML); From fb71d7341a742eadad6b0797b12c2cbb591dae8a Mon Sep 17 00:00:00 2001 From: Sebastian Rachuj Date: Fri, 19 Apr 2013 15:24:52 +0200 Subject: [PATCH 4/7] Correct length calculation --- taglib/ebml/matroska/ebmlmatroskaaudio.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/taglib/ebml/matroska/ebmlmatroskaaudio.cpp b/taglib/ebml/matroska/ebmlmatroskaaudio.cpp index 3ae8b3d2..3ae9734d 100644 --- a/taglib/ebml/matroska/ebmlmatroskaaudio.cpp +++ b/taglib/ebml/matroska/ebmlmatroskaaudio.cpp @@ -46,7 +46,8 @@ public: if(info && (value = info->getChild(Constants::Duration))) { length = static_cast(value->getAsFloat()); if((value = info->getChild(Constants::TimecodeScale))){ - length /= (value->getAsUnsigned() / 1000);} + length /= (value->getAsUnsigned() * (1 / 1000000000)); + } } info = elem->getChild(Constants::Tracks); From 36ceaadfaa3eea2509916c6dfc089b24c4d88aec Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Mon, 29 Apr 2013 20:15:05 +0900 Subject: [PATCH 5/7] Improved reference-counted pointer --- ConfigureChecks.cmake | 115 ++++++++++++------ config-taglib.h.cmake | 15 ++- taglib/asf/asfattribute.h | 2 +- taglib/asf/asfpicture.h | 2 +- taglib/fileref.cpp | 2 +- taglib/fileref.h | 2 +- taglib/mp4/mp4coverart.h | 2 +- taglib/mp4/mp4item.h | 2 +- taglib/mpeg/mpegheader.h | 2 +- taglib/toolkit/tbytevector.cpp | 4 +- taglib/toolkit/tbytevector.h | 2 +- taglib/toolkit/tlist.h | 2 +- taglib/toolkit/tmap.h | 2 +- taglib/toolkit/trefcountptr.h | 208 +++++++++++++++++---------------- taglib/toolkit/tstring.h | 2 +- tests/CMakeLists.txt | 1 + tests/test_smartptr.cpp | 200 +++++++++++++++++++++++++++++++ 17 files changed, 409 insertions(+), 156 deletions(-) create mode 100644 tests/test_smartptr.cpp diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index b7bda179..c08f7377 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -6,60 +6,103 @@ include(CheckLibraryExists) include(CheckTypeSize) include(CheckCXXSourceCompiles) -# check for libz using the cmake supplied FindZLIB.cmake -find_package(ZLIB) -if(ZLIB_FOUND) - set(HAVE_ZLIB 1) -else() - set(HAVE_ZLIB 0) -endif() - # Determine whether or not your compiler supports move semantics. check_cxx_source_compiles(" - #ifdef __clang__ - # pragma clang diagnostic error \"-Wc++11-extensions\" - #endif - #include - int func(int &&x) { return x - 1; } - int main() { return func(std::move(1)); } + #ifdef __clang__ + # pragma clang diagnostic error \"-Wc++11-extensions\" + #endif + #include + int func(int &&x) { return x - 1; } + int main() { return func(std::move(1)); } " SUPPORT_MOVE_SEMANTICS) -# Determine whether or not your compiler supports template alias. -check_cxx_source_compiles(" - #ifdef __clang__ - # pragma clang diagnostic error \"-Wc++11-extensions\" - #endif - #include - template using myVector = std::vector; - int main() { return 0; } -" SUPPORT_TEMPLATE_ALIAS) - # Determine where shared_ptr is defined regardless of C++11 support. check_cxx_source_compiles(" - #include - int main() { std::tr1::shared_ptr x; return 0; } + #include + int main() { std::tr1::shared_ptr x; return 0; } " HAVE_STD_SHARED_PTR) -check_cxx_source_compiles(" +if(NOT HAVE_STD_SHARED_PTR) + check_cxx_source_compiles(" #include int main() { std::tr1::shared_ptr x; return 0; } -" HAVE_TR1_SHARED_PTR) + " HAVE_TR1_SHARED_PTR) + + if(NOT HAVE_TR1_SHARED_PTR) + check_cxx_source_compiles(" + #include + int main() { boost::shared_ptr x; return 0; } + " HAVE_BOOST_SHARED_PTR) + endif() +endif() + +# Determine which kind of atomic operations your compiler supports. +if(NOT HAVE_STD_SHARED_PTR AND NOT HAVE_TR1_SHARED_PTR AND NOT HAVE_BOOST_SHARED_PTR) + check_cxx_source_compiles(" + int main() { + volatile int x; + __sync_add_and_fetch(&x, 1); + int y = __sync_sub_and_fetch(&x, 1); + return 0; + } + " HAVE_GCC_ATOMIC) + + if(NOT HAVE_GCC_ATOMIC) + check_cxx_source_compiles(" + #include + int main() { + volatile int32_t x; + OSAtomicIncrement32Barrier(&x); + int32_t y = OSAtomicDecrement32Barrier(&x); + return 0; + } + " HAVE_MAC_ATOMIC) + + if(NOT HAVE_MAC_ATOMIC) + check_cxx_source_compiles(" + #include + int main() { + volatile LONG x; + InterlockedIncrement(&x); + LONG y = InterlockedDecrement(&x); + return 0; + } + " HAVE_WIN_ATOMIC) + + if(NOT HAVE_WIN_ATOMIC) + check_cxx_source_compiles(" + #include + int main() { + volatile int x; + __sync_add_and_fetch(&x, 1); + int y = __sync_sub_and_fetch(&x, 1); + return 0; + } + " HAVE_IA64_ATOMIC) + endif() + endif() + endif() +endif() -check_cxx_source_compiles(" - #include - int main() { boost::shared_ptr x; return 0; } -" HAVE_BOOST_SHARED_PTR) # Determine whether your compiler supports codecvt header. check_cxx_source_compiles(" - #include - int main() { std::codecvt_utf8_utf16 x; return 0; } +#include +int main() { std::codecvt_utf8_utf16 x; return 0; } " HAVE_CODECVT) +# check for libz using the cmake supplied FindZLIB.cmake +find_package(ZLIB) +if(ZLIB_FOUND) +set(HAVE_ZLIB 1) +else() +set(HAVE_ZLIB 0) +endif() + set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) find_package(CppUnit) if(NOT CppUnit_FOUND AND BUILD_TESTS) - message(STATUS "CppUnit not found, disabling tests.") - set(BUILD_TESTS OFF) + message(STATUS "CppUnit not found, disabling tests.") + set(BUILD_TESTS OFF) endif() diff --git a/config-taglib.h.cmake b/config-taglib.h.cmake index e57ff050..6d48cbf0 100644 --- a/config-taglib.h.cmake +++ b/config-taglib.h.cmake @@ -1,22 +1,25 @@ /* config-taglib.h. Generated by cmake from config-taglib.h.cmake */ -/* Define if you have libz */ -#cmakedefine HAVE_ZLIB 1 - /* Defined if your compiler supports the move semantics */ #cmakedefine SUPPORT_MOVE_SEMANTICS 1 -/* Defined if your compiler supports the template alias */ -#cmakedefine SUPPORT_TEMPLATE_ALIAS 1 - /* Defined if your compiler supports shared_ptr */ #cmakedefine HAVE_STD_SHARED_PTR 1 #cmakedefine HAVE_TR1_SHARED_PTR 1 #cmakedefine HAVE_BOOST_SHARED_PTR 1 +/* Defined if your compiler supports some atomic operations */ +#cmakedefine HAVE_GCC_ATOMIC 1 +#cmakedefine HAVE_MAC_ATOMIC 1 +#cmakedefine HAVE_WIN_ATOMIC 1 +#cmakedefine HAVE_IA64_ATOMIC 1 + /* Defined if your compiler has header */ #cmakedefine HAVE_CODECVT 1 +/* Define if you have libz */ +#cmakedefine HAVE_ZLIB 1 + #cmakedefine NO_ITUNES_HACKS 1 #cmakedefine WITH_ASF 1 #cmakedefine WITH_MP4 1 diff --git a/taglib/asf/asfattribute.h b/taglib/asf/asfattribute.h index 93e8c2e1..6bc9a847 100644 --- a/taglib/asf/asfattribute.h +++ b/taglib/asf/asfattribute.h @@ -205,7 +205,7 @@ namespace TagLib ByteVector render(const String &name, int kind = 0) const; class AttributePrivate; - RefCountPtr d; + TAGLIB_SHARED_PTR d; }; } diff --git a/taglib/asf/asfpicture.h b/taglib/asf/asfpicture.h index e2279462..e493428a 100644 --- a/taglib/asf/asfpicture.h +++ b/taglib/asf/asfpicture.h @@ -231,7 +231,7 @@ namespace TagLib #endif private: class PicturePrivate; - RefCountPtr d; + TAGLIB_SHARED_PTR d; }; } } diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index 78164e32..2d215bce 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -66,7 +66,7 @@ class FileRef::FileRefPrivate public: FileRefPrivate(File *f) : file(f) {} - RefCountPtr file; + TAGLIB_SHARED_PTR file; static List fileTypeResolvers; }; diff --git a/taglib/fileref.h b/taglib/fileref.h index c359ef22..01e2875c 100644 --- a/taglib/fileref.h +++ b/taglib/fileref.h @@ -278,7 +278,7 @@ namespace TagLib { private: class FileRefPrivate; - RefCountPtr d; + TAGLIB_SHARED_PTR d; }; } // namespace TagLib diff --git a/taglib/mp4/mp4coverart.h b/taglib/mp4/mp4coverart.h index a34e715e..3b78d747 100644 --- a/taglib/mp4/mp4coverart.h +++ b/taglib/mp4/mp4coverart.h @@ -66,7 +66,7 @@ namespace TagLib { private: class CoverArtPrivate; - RefCountPtr d; + TAGLIB_SHARED_PTR d; }; typedef List CoverArtList; diff --git a/taglib/mp4/mp4item.h b/taglib/mp4/mp4item.h index 44fae301..e78acee5 100644 --- a/taglib/mp4/mp4item.h +++ b/taglib/mp4/mp4item.h @@ -97,7 +97,7 @@ namespace TagLib { private: class ItemPrivate; - RefCountPtr d; + TAGLIB_SHARED_PTR d; }; } diff --git a/taglib/mpeg/mpegheader.h b/taglib/mpeg/mpegheader.h index cdc8669d..330746ce 100644 --- a/taglib/mpeg/mpegheader.h +++ b/taglib/mpeg/mpegheader.h @@ -181,7 +181,7 @@ namespace TagLib { void parse(const ByteVector &data); class HeaderPrivate; - RefCountPtr d; + TAGLIB_SHARED_PTR d; }; } } diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 38cb97ef..a94bc07c 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -272,7 +272,7 @@ public: { } - ByteVectorPrivate(RefCountPtr d, size_t o, size_t l) + ByteVectorPrivate(TAGLIB_SHARED_PTR d, size_t o, size_t l) : data(d->data) , offset(d->offset + o) , length(l) @@ -320,7 +320,7 @@ public: return *this; } - RefCountPtr data; + TAGLIB_SHARED_PTR data; size_t offset; size_t length; }; diff --git a/taglib/toolkit/tbytevector.h b/taglib/toolkit/tbytevector.h index ec8e7c3f..cea615d9 100644 --- a/taglib/toolkit/tbytevector.h +++ b/taglib/toolkit/tbytevector.h @@ -478,7 +478,7 @@ namespace TagLib { private: class ByteVectorPrivate; - RefCountPtr d; + TAGLIB_SHARED_PTR d; }; /*! diff --git a/taglib/toolkit/tlist.h b/taglib/toolkit/tlist.h index 0948ee70..48a1d356 100644 --- a/taglib/toolkit/tlist.h +++ b/taglib/toolkit/tlist.h @@ -305,7 +305,7 @@ namespace TagLib { private: #ifndef DO_NOT_DOCUMENT template class ListPrivate; - RefCountPtr > d; + TAGLIB_SHARED_PTR > d; #endif }; diff --git a/taglib/toolkit/tmap.h b/taglib/toolkit/tmap.h index 38a23896..b9d4a8fd 100644 --- a/taglib/toolkit/tmap.h +++ b/taglib/toolkit/tmap.h @@ -219,7 +219,7 @@ namespace TagLib { private: #ifndef DO_NOT_DOCUMENT template class MapPrivate; - RefCountPtr > d; + TAGLIB_SHARED_PTR > d; #endif }; diff --git a/taglib/toolkit/trefcountptr.h b/taglib/toolkit/trefcountptr.h index 535d0efd..54ecf307 100644 --- a/taglib/toolkit/trefcountptr.h +++ b/taglib/toolkit/trefcountptr.h @@ -37,19 +37,33 @@ #elif defined(HAVE_BOOST_SHARED_PTR) # include #else -# ifdef __APPLE__ -# include -# define TAGLIB_ATOMIC_MAC -# elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) -# define TAGLIB_ATOMIC_WIN -# elif defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 401) \ - && (defined(__i386__) || defined(__i486__) || defined(__i586__) || \ - defined(__i686__) || defined(__x86_64) || defined(__ia64)) \ - && !defined(__INTEL_COMPILER) -# define TAGLIB_ATOMIC_GCC -# elif defined(__ia64) && defined(__INTEL_COMPILER) -# include -# define TAGLIB_ATOMIC_GCC +# include +# if defined(HAVE_GCC_ATOMIC) +# define TAGLIB_ATOMIC_INT int +# define TAGLIB_ATOMIC_INC(x) __sync_add_and_fetch(&x, 1) +# define TAGLIB_ATOMIC_DEC(x) __sync_sub_and_fetch(&x, 1) +# elif defined(HAVE_WIN_ATOMIC) +# if !defined(NOMINMAX) +# define NOMINMAX +# endif +# include +# define TAGLIB_ATOMIC_INT long +# define TAGLIB_ATOMIC_INC(x) InterlockedIncrement(&x) +# define TAGLIB_ATOMIC_DEC(x) InterlockedDecrement(&x) +# elif defined(HAVE_MAC_ATOMIC) +# include +# define TAGLIB_ATOMIC_INT int32_t +# define TAGLIB_ATOMIC_INC(x) OSAtomicIncrement32Barrier(&x) +# define TAGLIB_ATOMIC_DEC(x) OSAtomicDecrement32Barrier(&x) +# elif defined(HAVE_IA64_ATOMIC) +# include +# define TAGLIB_ATOMIC_INT int +# define TAGLIB_ATOMIC_INC(x) __sync_add_and_fetch(&x, 1) +# define TAGLIB_ATOMIC_DEC(x) __sync_sub_and_fetch(&x, 1) +# else +# define TAGLIB_ATOMIC_INT int +# define TAGLIB_ATOMIC_INC(x) (++x) +# define TAGLIB_ATOMIC_DEC(x) (--x) # endif #endif @@ -63,55 +77,18 @@ namespace TagLib { -#if defined(HAVE_STD_SHARED_PTR) || defined(HAVE_TR1_SHARED_PTR) || defined(HAVE_BOOST_SHARED_PTR) +#if defined(HAVE_STD_SHARED_PTR) || defined(HAVE_TR1_SHARED_PTR) + +#define TAGLIB_SHARED_PTR std::tr1::shared_ptr -# if defined(SUPPORT_TEMPLATE_ALIAS) +#elif defined(HAVE_BOOST_SHARED_PTR) - // Defines RefCountPtr as an alias of shared_ptr - // if shared_ptr and the template alias are both available. - -# if defined(HAVE_STD_SHARED_PTR) || defined(HAVE_TR1_SHARED_PTR) - - template - using RefCountPtr = std::tr1::shared_ptr; - -# else - - template - using RefCountPtr = boost::shared_ptr; - -# endif - -# else - - // Defines RefCountPtr as a derived class of shared_ptr. - // if shared_ptr is available but the template alias is not. - -# if defined(HAVE_STD_SHARED_PTR) || defined(HAVE_TR1_SHARED_PTR) - - template - class RefCountPtr : public std::tr1::shared_ptr - { - public: - explicit RefCountPtr(T *p) : std::tr1::shared_ptr(p) {} - }; - -# else - - template - class RefCountPtr : public boost::shared_ptr - { - public: - explicit RefCountPtr(T *p) : boost::shared_ptr(p) {} - }; - -# endif - -# endif +#define TAGLIB_SHARED_PTR boost::shared_ptr #else // HAVE_*_SHARED_PTR - // Implements RefCountPtr if shared_ptr is not available. + // Self-implements RefCountPtr if shared_ptr is not available. + // I STRONGLY RECOMMEND using standard shared_ptr rather than this class. template class RefCountPtr @@ -134,12 +111,12 @@ namespace TagLib { void addref() { - increment(&count); + TAGLIB_ATOMIC_INC(count); } void release() { - if(decrement(&count) == 0) { + if(TAGLIB_ATOMIC_DEC(count) == 0) { dispose(); delete this; } @@ -153,33 +130,7 @@ namespace TagLib { virtual void dispose() = 0; private: -# if defined(TAGLIB_ATOMIC_MAC) - typedef volatile int32_t counter_t; - - inline static void increment(counter_t *c) { OSAtomicIncrement32Barrier(c); } - inline static counter_t decrement(counter_t *c) { return OSAtomicDecrement32Barrier(c); } - -# elif defined(TAGLIB_ATOMIC_WIN) - typedef volatile long counter_t; - - inline static void increment(counter_t *c) { InterlockedIncrement(c); } - inline static counter_t decrement(counter_t *c) { return InterlockedDecrement(c); } - -# elif defined(TAGLIB_ATOMIC_GCC) - typedef volatile int counter_t; - - inline static void increment(counter_t *c) { __sync_add_and_fetch(c, 1); } - inline static counter_t decrement(counter_t *c) { return __sync_sub_and_fetch(c, 1); } - -# else - typedef uint counter_t; - - inline static void increment(counter_t *c) { ++(*c) } - inline static counter_t decrement(counter_t *c) { return --(*c); } - -# endif - - counter_t count; + volatile TAGLIB_ATOMIC_INT count; }; // Counter impl class. Provides a dynamic deleter. @@ -208,31 +159,52 @@ namespace TagLib { }; public: + explicit RefCountPtr() + : counter(0) + { + } + template explicit RefCountPtr(U *p) : counter(new CounterImpl(p)) { } - RefCountPtr(const RefCountPtr &x) + RefCountPtr(const RefCountPtr &x) + : counter(x.counter) { - counter = x.counter; - counter->addref(); + if(counter) + counter->addref(); + } + + template + RefCountPtr(const RefCountPtr &x) + : counter(reinterpret_cast(x.counter)) + { + if(counter) + counter->addref(); } ~RefCountPtr() { - counter->release(); + if(counter) + counter->release(); } T *get() const { - return static_cast*>(counter)->get(); + if(counter) + return static_cast*>(counter)->get(); + else + return 0; } long use_count() const { - return counter->use_count(); + if(counter) + return counter->use_count(); + else + return 0; } bool unique() const @@ -244,20 +216,44 @@ namespace TagLib { void reset(U *p) { if(get() != p) - { - counter->release(); - counter = new CounterImpl(p); - } + RefCountPtr(p).swap(*this); + } + + void reset() + { + RefCountPtr().swap(*this); + } + + void swap(RefCountPtr &x) + { + std::swap(counter, x.counter); } RefCountPtr &operator=(const RefCountPtr &x) { - if(get() != x.get()) - { - counter->release(); + if(get() != x.get()) { + if(counter) + counter->release(); counter = x.counter; - counter->addref(); + + if(counter) + counter->addref(); + } + return *this; + } + + template + RefCountPtr &operator=(const RefCountPtr &x) + { + if(get() != x.get()) { + if(counter) + counter->release(); + + counter = reinterpret_cast(x.counter); + + if(counter) + counter->addref(); } return *this; } @@ -288,9 +284,19 @@ namespace TagLib { } private: - CounterBase *counter; + mutable CounterBase *counter; + + template friend class RefCountPtr; }; +# define TAGLIB_SHARED_PTR TagLib::RefCountPtr + + template + void swap(RefCountPtr &a, RefCountPtr &b) + { + a.swap(b); + } + #endif // HAVE_*_SHARED_PTR } #endif // DO_NOT_DOCUMENT diff --git a/taglib/toolkit/tstring.h b/taglib/toolkit/tstring.h index dfde414d..bfa35459 100644 --- a/taglib/toolkit/tstring.h +++ b/taglib/toolkit/tstring.h @@ -523,7 +523,7 @@ namespace TagLib { static const Type WCharByteOrder; class StringPrivate; - RefCountPtr d; + TAGLIB_SHARED_PTR d; }; /*! diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4e428af8..a369b5a1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,7 @@ SET(test_runner_SRCS main.cpp test_list.cpp test_map.cpp + test_smartptr.cpp test_mpeg.cpp test_synchdata.cpp test_trueaudio.cpp diff --git a/tests/test_smartptr.cpp b/tests/test_smartptr.cpp new file mode 100644 index 00000000..4fd9b3bf --- /dev/null +++ b/tests/test_smartptr.cpp @@ -0,0 +1,200 @@ +#include +#include +#include "utils.h" + +using namespace std; +using namespace TagLib; + +bool baseDestructorCalled; +bool derivedDestructorCalled; +bool incompleteDestructorCalled; + +class TestSmartptr : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestSmartptr); + CPPUNIT_TEST(testSharedptrBasic); + CPPUNIT_TEST(testDerivedClass); + CPPUNIT_TEST(testIncompleteClass); + CPPUNIT_TEST_SUITE_END(); + +private: + template + void ck( const T* v1, T v2 ) { CPPUNIT_ASSERT( *v1 == v2 ); } + +public: + void testSharedptrBasic() + { + int * ip = new int; + TAGLIB_SHARED_PTR cp ( ip ); + CPPUNIT_ASSERT( ip == cp.get() ); + CPPUNIT_ASSERT( cp.use_count() == 1 ); + + *cp = 54321; + CPPUNIT_ASSERT( *cp == 54321 ); + CPPUNIT_ASSERT( *ip == 54321 ); + ck( static_cast(cp.get()), 54321 ); + ck( static_cast(ip), *cp ); + + TAGLIB_SHARED_PTR cp2 ( cp ); + CPPUNIT_ASSERT( ip == cp2.get() ); + CPPUNIT_ASSERT( cp.use_count() == 2 ); + CPPUNIT_ASSERT( cp2.use_count() == 2 ); + + CPPUNIT_ASSERT( *cp == 54321 ); + CPPUNIT_ASSERT( *cp2 == 54321 ); + ck( static_cast(cp2.get()), 54321 ); + ck( static_cast(ip), *cp2 ); + + TAGLIB_SHARED_PTR cp3 ( cp ); + CPPUNIT_ASSERT( cp.use_count() == 3 ); + CPPUNIT_ASSERT( cp2.use_count() == 3 ); + CPPUNIT_ASSERT( cp3.use_count() == 3 ); + cp.reset(); + CPPUNIT_ASSERT( cp2.use_count() == 2 ); + CPPUNIT_ASSERT( cp3.use_count() == 2 ); + cp.reset( new int ); + *cp = 98765; + CPPUNIT_ASSERT( *cp == 98765 ); + *cp3 = 87654; + CPPUNIT_ASSERT( *cp3 == 87654 ); + CPPUNIT_ASSERT( *cp2 == 87654 ); + cp.swap( cp3 ); + CPPUNIT_ASSERT( *cp == 87654 ); + CPPUNIT_ASSERT( *cp2 == 87654 ); + CPPUNIT_ASSERT( *cp3 == 98765 ); + cp.swap( cp3 ); + CPPUNIT_ASSERT( *cp == 98765 ); + CPPUNIT_ASSERT( *cp2 == 87654 ); + CPPUNIT_ASSERT( *cp3 == 87654 ); + cp2 = cp2; + CPPUNIT_ASSERT( cp2.use_count() == 2 ); + CPPUNIT_ASSERT( *cp2 == 87654 ); + cp = cp2; + CPPUNIT_ASSERT( cp2.use_count() == 3 ); + CPPUNIT_ASSERT( *cp2 == 87654 ); + CPPUNIT_ASSERT( cp.use_count() == 3 ); + CPPUNIT_ASSERT( *cp == 87654 ); + + TAGLIB_SHARED_PTR cp4; + swap( cp2, cp4 ); + CPPUNIT_ASSERT( cp4.use_count() == 3 ); + CPPUNIT_ASSERT( *cp4 == 87654 ); + CPPUNIT_ASSERT( cp2.get() == 0 ); + + std::set< TAGLIB_SHARED_PTR > scp; + scp.insert(cp4); + CPPUNIT_ASSERT( scp.find(cp4) != scp.end() ); + CPPUNIT_ASSERT( scp.find(cp4) == scp.find( TAGLIB_SHARED_PTR(cp4) ) ); + } + +private: + class DummyBase + { + public: + DummyBase(int x) : value(x) + { + } + + virtual ~DummyBase() + { + baseDestructorCalled = true; + } + + int getValue() const + { + return value; + } + + private: + int value; + }; + + class DummyDerived : public DummyBase + { + public: + DummyDerived(int x) : DummyBase(x) + { + } + + virtual ~DummyDerived() + { + derivedDestructorCalled = true; + } + }; + +public: + void testDerivedClass() + { + baseDestructorCalled = false; + derivedDestructorCalled = false; + + { + TAGLIB_SHARED_PTR p1(new DummyDerived(100)); + CPPUNIT_ASSERT(p1->getValue() == 100); + } + + CPPUNIT_ASSERT(baseDestructorCalled); + CPPUNIT_ASSERT(derivedDestructorCalled); + + baseDestructorCalled = false; + derivedDestructorCalled = false; + + { + TAGLIB_SHARED_PTR p1(new DummyDerived(100)); + TAGLIB_SHARED_PTR p2 = p1; + + CPPUNIT_ASSERT(p1->getValue() == 100); + CPPUNIT_ASSERT(p2->getValue() == 100); + } + + CPPUNIT_ASSERT(baseDestructorCalled); + CPPUNIT_ASSERT(derivedDestructorCalled); + + baseDestructorCalled = false; + derivedDestructorCalled = false; + + { + TAGLIB_SHARED_PTR p1; + TAGLIB_SHARED_PTR p2; + + p1.reset(new DummyDerived(100)); + p2 = p1; + + CPPUNIT_ASSERT(p1->getValue() == 100); + CPPUNIT_ASSERT(p2->getValue() == 100); + } + + CPPUNIT_ASSERT(baseDestructorCalled); + CPPUNIT_ASSERT(derivedDestructorCalled); + } + +private: + class DummyIncomplete; + TAGLIB_SHARED_PTR pincomplete; + + class DummyIncomplete + { + public: + ~DummyIncomplete() + { + incompleteDestructorCalled = true; + } + + int getValue() const { return 100; } + }; + +public: + void testIncompleteClass() + { + incompleteDestructorCalled = false; + + pincomplete.reset(new DummyIncomplete()); + CPPUNIT_ASSERT(pincomplete->getValue() == 100); + + pincomplete.reset(); + CPPUNIT_ASSERT(incompleteDestructorCalled); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestSmartptr); From 93db8aa1350fa326356ca9652588d23ca20cc8ae Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 30 Apr 2013 16:40:41 +0900 Subject: [PATCH 6/7] Simplified copy-on-write implementation of ByteVector --- taglib/toolkit/tbytevector.cpp | 64 ++++++++++------------------------ 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index a94bc07c..a4168995 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -38,7 +38,7 @@ // // http://www.informit.com/isapi/product_id~{9C84DAB4-FE6E-49C5-BB0A-FB50331233EA}/content/index.asp -#define DATA(x) (&(x->data->data[0])) +#define DATA(x) (&(*(x->data))[0]) namespace TagLib { @@ -236,37 +236,11 @@ ByteVector fromNumber(T value, bool mostSignificantByteFirst) return ByteVector(reinterpret_cast(&value), size); } -class DataPrivate -{ -public: - DataPrivate() - { - } - - DataPrivate(const std::vector &v, size_t o, size_t l) - : data(v.begin() + o, v.begin() + o + l) - { - } - - // A char* can be an iterator. - DataPrivate(const char *begin, const char *end) - : data(begin, end) - { - } - - DataPrivate(size_t l, char c) - : data(l, c) - { - } - - std::vector data; -}; - class ByteVector::ByteVectorPrivate { public: ByteVectorPrivate() - : data(new DataPrivate()) + : data(new std::vector()) , offset(0) , length(0) { @@ -280,21 +254,21 @@ public: } ByteVectorPrivate(const std::vector &v, size_t o, size_t l) - : data(new DataPrivate(v, o, l)) + : data(new std::vector(v.begin() + o, v.begin() + o + l)) , offset(0) , length(l) { } ByteVectorPrivate(size_t l, char c) - : data(new DataPrivate(l, c)) + : data(new std::vector(l, c)) , offset(0) , length(l) { } ByteVectorPrivate(const char *s, size_t l) - : data(new DataPrivate(s, s + l)) + : data(new std::vector(s, s + l)) , offset(0) , length(l) { @@ -303,7 +277,7 @@ public: void detach() { if(!data.unique()) { - data.reset(new DataPrivate(data->data, offset, length)); + data.reset(new std::vector(data->begin() + offset, data->begin() + offset + length)); offset = 0; } } @@ -320,7 +294,7 @@ public: return *this; } - TAGLIB_SHARED_PTR data; + TAGLIB_SHARED_PTR > data; size_t offset; size_t length; }; @@ -609,7 +583,7 @@ size_t ByteVector::size() const ByteVector &ByteVector::resize(size_t size, char padding) { detach(); - d->data->data.resize(d->offset + size, padding); + d->data->resize(d->offset + size, padding); d->length = size; return *this; @@ -617,45 +591,45 @@ ByteVector &ByteVector::resize(size_t size, char padding) ByteVector::Iterator ByteVector::begin() { - return d->data->data.begin() + d->offset; + return d->data->begin() + d->offset; } ByteVector::ConstIterator ByteVector::begin() const { - return d->data->data.begin() + d->offset; + return d->data->begin() + d->offset; } ByteVector::Iterator ByteVector::end() { - return d->data->data.begin() + d->offset + d->length; + return d->data->begin() + d->offset + d->length; } ByteVector::ConstIterator ByteVector::end() const { - return d->data->data.begin() + d->offset + d->length; + return d->data->begin() + d->offset + d->length; } ByteVector::ReverseIterator ByteVector::rbegin() { - std::vector &v = d->data->data; + std::vector &v = *(d->data); return v.rbegin() + (v.size() - (d->offset + d->length)); } ByteVector::ConstReverseIterator ByteVector::rbegin() const { - std::vector &v = d->data->data; + std::vector &v = *(d->data); return v.rbegin() + (v.size() - (d->offset + d->length)); } ByteVector::ReverseIterator ByteVector::rend() { - std::vector &v = d->data->data; + std::vector &v = *(d->data); return v.rbegin() + (v.size() - d->offset); } ByteVector::ConstReverseIterator ByteVector::rend() const { - std::vector &v = d->data->data; + std::vector &v = *(d->data); return v.rbegin() + (v.size() - d->offset); } @@ -699,13 +673,13 @@ long long ByteVector::toInt64(bool mostSignificantByteFirst) const const char &ByteVector::operator[](size_t index) const { - return d->data->data[d->offset + index]; + return DATA(d)[d->offset + index]; } char &ByteVector::operator[](size_t index) { detach(); - return d->data->data[d->offset + index]; + return DATA(d)[d->offset + index]; } bool ByteVector::operator==(const ByteVector &v) const @@ -807,7 +781,7 @@ void ByteVector::detach() d->detach(); if(!d.unique()) - d.reset(new ByteVectorPrivate(d->data->data, d->offset, d->length)); + d.reset(new ByteVectorPrivate(*(d->data), d->offset, d->length)); } //////////////////////////////////////////////////////////////////////////////// From fbe329bb701e855131be9bc093a048d8f4dd4944 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Wed, 1 May 2013 14:54:17 +0900 Subject: [PATCH 7/7] Use snprintf instead of sprintf if possible. --- ConfigureChecks.cmake | 14 +++++++++- config-taglib.h.cmake | 4 +++ taglib/mp4/mp4item.cpp | 59 ++++++++++++++++++++++++++++-------------- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index c08f7377..42144de7 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -84,13 +84,25 @@ if(NOT HAVE_STD_SHARED_PTR AND NOT HAVE_TR1_SHARED_PTR AND NOT HAVE_BOOST_SHARED endif() endif() - # Determine whether your compiler supports codecvt header. check_cxx_source_compiles(" #include int main() { std::codecvt_utf8_utf16 x; return 0; } " HAVE_CODECVT) +# Determine whether your compiler supports some safer version of sprintf. +check_cxx_source_compiles(" + #include + int main() { char buf[20]; snprintf(buf, 20, \"%d\", 1); return 0; } +" HAVE_SNPRINTF) + +if(NOT HAVE_SNPRINTF) + check_cxx_source_compiles(" + #include + int main() { char buf[20]; sprintf_s(buf, \"%d\", 1); return 0; } + " HAVE_SPRINTF_S) +endif() + # check for libz using the cmake supplied FindZLIB.cmake find_package(ZLIB) if(ZLIB_FOUND) diff --git a/config-taglib.h.cmake b/config-taglib.h.cmake index 6d48cbf0..1e0adb37 100644 --- a/config-taglib.h.cmake +++ b/config-taglib.h.cmake @@ -14,6 +14,10 @@ #cmakedefine HAVE_WIN_ATOMIC 1 #cmakedefine HAVE_IA64_ATOMIC 1 +/* Defined if your compiler supports some safer version of sprintf */ +#cmakedefine HAVE_SNPRINTF 1 +#cmakedefine HAVE_SPRINTF_S 1 + /* Defined if your compiler has header */ #cmakedefine HAVE_CODECVT 1 diff --git a/taglib/mp4/mp4item.cpp b/taglib/mp4/mp4item.cpp index 6c1f3c40..65f45546 100644 --- a/taglib/mp4/mp4item.cpp +++ b/taglib/mp4/mp4item.cpp @@ -27,19 +27,44 @@ # include #endif -#include +#include +#include #include #include #include "mp4item.h" -#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later -# define SPRINTF sprintf_s +using namespace TagLib; + +namespace +{ + String format(const char *fmt, ...) + { + va_list args; + va_start(args,fmt); + + char buf[256]; + +#if defined(HAVE_SNPRINTF) + + vsnprintf(buf, sizeof(buf), fmt, args); + +#elif defined(HAVE_SPRINTF_S) + + vsprintf_s(buf, fmt, args); + #else -# define SPRINTF sprintf + + // Be careful. May cause a buffer overflow. + vsprintf(buf, fmt, args); + #endif -using namespace TagLib; + va_end(args); + + return String(buf); + } +} class MP4::Item::ItemPrivate { @@ -242,37 +267,31 @@ String MP4::Item::toString() const { StringList desc; - char tmp[256]; switch (d->type) { case TypeBool: return d->m_bool ? "true" : "false"; case TypeInt: - SPRINTF(tmp, "%d", d->m_int); - return tmp; + return format("%d", d->m_int); case TypeIntPair: - SPRINTF(tmp, "%d/%d", d->m_intPair.first, d->m_intPair.second); - return tmp; + return format("%d/%d", d->m_intPair.first, d->m_intPair.second); case TypeByte: - SPRINTF(tmp, "%d", d->m_byte); - return tmp; + return format("%d", d->m_byte); case TypeUInt: - SPRINTF(tmp, "%u", d->m_uint); - return tmp; + return format("%u", d->m_uint); case TypeLongLong: - SPRINTF(tmp, "%lld", d->m_longlong); - return tmp; + return format("%lld", d->m_longlong); case TypeStringList: return d->m_stringList.toString(" / "); case TypeByteVectorList: for(TagLib::uint i = 0; i < d->m_byteVectorList.size(); i++) { - SPRINTF(tmp, "[%d bytes of data]", static_cast(d->m_byteVectorList[i].size())); - desc.append(tmp); + desc.append(format( + "[%d bytes of data]", static_cast(d->m_byteVectorList[i].size()))); } return desc.toString(", "); case TypeCoverArtList: for(TagLib::uint i = 0; i < d->m_coverArtList.size(); i++) { - SPRINTF(tmp, "[%d bytes of data]", static_cast(d->m_coverArtList[i].data().size())); - desc.append(tmp); + desc.append(format( + "[%d bytes of data]", static_cast(d->m_coverArtList[i].data().size()))); } return desc.toString(", "); case TypeUndefined: