Merge pull request #156 from Rachus/taglib2

A Matroska implementation
This commit is contained in:
Tsuda Kageyu 2013-05-01 01:17:23 -07:00
commit 6b56510f99
11 changed files with 2140 additions and 0 deletions

View File

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

View File

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

496
taglib/ebml/ebmlelement.cpp Normal file
View File

@ -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<Element *> 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<Element *>::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<ulli>(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<signed long long>(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<Element *>::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<offset_t>(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<offset_t>(content.size())) {
// Write to file
document->writeBlock(content);
// Update parent
for(List<Element *>::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<offset_t>(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<Element *>::Iterator i = d->children.begin(); i != d->children.end();
++i) {
if ((*i)->d->id == id)
return *i;
}
return 0;
}
List<EBML::Element *> EBML::Element::getChildren(EBML::ulli id)
{
populate();
List<Element *> result;
for(List<Element *>::Iterator i = d->children.begin(); i != d->children.end();
++i) {
if ((*i)->d->id == id)
result.append(*i);
}
return result;
}
List<EBML::Element *> 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<ulli>(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<float *>(&sum);
return result;
}
else {
double result = *reinterpret_cast<double *>(&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<signed long long>(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<Element *>::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<Element *>::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<Element *>::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<signed long long>(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)
{}

276
taglib/ebml/ebmlelement.h Normal file
View File

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

102
taglib/ebml/ebmlfile.cpp Normal file
View File

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

86
taglib/ebml/ebmlfile.h Normal file
View File

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

View File

@ -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<int>(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<int>(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;
}

View File

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

View File

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

View File

@ -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<Element *> entries = elem->getChildren(Constants::Tag);
for(List<Element *>::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<Element *> simpleTags = (*i)->getChildren(Constants::SimpleTag);
for(List<Element *>::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<PropertyMap, std::pair<Element *, ulli> >(tagEntries, std::pair<Element *, ulli>(*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<std::pair<PropertyMap, std::pair<Element *, ulli> > > 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<std::pair<PropertyMap, std::pair<Element *, ulli> > >::Iterator best = d->tags.end();
for(List<std::pair<PropertyMap, std::pair<Element *, ulli> > >::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<std::pair<PropertyMap, std::pair<Element *, ulli> > >::Iterator best = d->tags.end();
for(List<std::pair<PropertyMap, std::pair<Element *, ulli> > >::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<PropertyMap, std::pair<Element *, ulli> > 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<std::pair<PropertyMap, std::pair<Element *, ulli> > >::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<Element *> simpleTags = i->second.first->getChildren(Constants::SimpleTag);
StringList::Iterator str = j->second.begin();
List<Element *>::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<Element *> simpleTags = i->second.first->getChildren(Constants::SimpleTag);
for(List<Element *>::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<std::pair<PropertyMap, std::pair<Element *, ulli> > >::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<std::pair<PropertyMap, std::pair<Element *, ulli> > >::Iterator
find(ulli ttv)
{
for(List<std::pair<PropertyMap, std::pair<Element *, ulli> > >::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<std::pair<PropertyMap, std::pair<Element *, ulli> > >::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<std::pair<PropertyMap, std::pair<Element *, ulli> > >::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<PropertyMap, std::pair<Element *, ulli> >(pm,
std::pair<Element *, ulli>(0, ttv)
)
);
}
// The PropertyMap from the Matroska::File
File *document;
// Iterators to the tags.
List<std::pair<PropertyMap, std::pair<Element *, ulli> > >::Iterator title;
List<std::pair<PropertyMap, std::pair<Element *, ulli> > >::Iterator artist;
List<std::pair<PropertyMap, std::pair<Element *, ulli> > >::Iterator album;
List<std::pair<PropertyMap, std::pair<Element *, ulli> > >::Iterator comment;
List<std::pair<PropertyMap, std::pair<Element *, ulli> > >::Iterator genre;
List<std::pair<PropertyMap, std::pair<Element *, ulli> > >::Iterator year;
List<std::pair<PropertyMap, std::pair<Element *, ulli> > >::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);
}

View File

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