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 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..8c08ac71 --- /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.toInt64()); + 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::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 + // 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) + == static_cast(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 (static_cast(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().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().toInt64()); +} + +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::fromUInt64(number)); +} + +EBML::Element *EBML::Element::addElement(EBML::ulli id, EBML::ulli number) +{ + return addElement(id, ByteVector::fromUInt64(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::fromUInt64(number)); +} + +void EBML::Element::setAsUnsigned(EBML::ulli number) +{ + setAsBinary(ByteVector::fromUInt64(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..7a330f82 --- /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.toInt64()) != 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..3ae9734d --- /dev/null +++ b/taglib/ebml/matroska/ebmlmatroskaaudio.cpp @@ -0,0 +1,120 @@ +/*************************************************************************** + 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() * (1 / 1000000000)); + } + } + + 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