This commit was manufactured by cvs2svn to accommodate

a server-side copy/move.


git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/kdesupport/taglib@288617 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
This commit is contained in:
Scott Wheeler
2004-02-17 02:11:05 +00:00
commit 7fe6647435
97 changed files with 17567 additions and 0 deletions

23
ogg/Makefile.am Normal file
View File

@ -0,0 +1,23 @@
SUBDIRS = vorbis
INCLUDES = -I$(top_srcdir)/taglib -I$(top_srcdir)/taglib/toolkit $(all_includes)
noinst_LTLIBRARIES = libogg.la
libogg_la_SOURCES = \
oggfile.cpp \
oggpage.cpp \
oggpageheader.cpp \
xiphcomment.cpp
taglib_include_HEADERS = \
oggfile.h \
oggpage.h \
oggpageheader.h \
xiphcomment.h
taglib_includedir = $(includedir)/taglib
libogg_la_LIBADD = ./vorbis/libvorbis.la
EXTRA_DIST = $(libogg_la_SOURCES) $(taglib_include_HEADERS)

325
ogg/oggfile.cpp Normal file
View File

@ -0,0 +1,325 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#include <tbytevectorlist.h>
#include <tmap.h>
#include <tstring.h>
#include <tdebug.h>
#include "oggfile.h"
#include "oggpage.h"
#include "oggpageheader.h"
using namespace TagLib;
class Ogg::File::FilePrivate
{
public:
FilePrivate() :
streamSerialNumber(0),
firstPageHeader(0),
lastPageHeader(0),
currentPage(0),
currentPacketPage(0)
{
pages.setAutoDelete(true);
}
~FilePrivate()
{
delete firstPageHeader;
delete lastPageHeader;
}
uint streamSerialNumber;
List<Page *> pages;
PageHeader *firstPageHeader;
PageHeader *lastPageHeader;
std::vector< List<int> > packetToPageMap;
Map<int, ByteVector> dirtyPackets;
List<int> dirtyPages;
//! The current page for the reader -- used by nextPage()
Page *currentPage;
//! The current page for the packet parser -- used by packet()
Page *currentPacketPage;
//! The packets for the currentPacketPage -- used by packet()
ByteVectorList currentPackets;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Ogg::File::~File()
{
delete d;
}
ByteVector Ogg::File::packet(uint i)
{
// Check to see if we're called setPacket() for this packet since the last
// save:
if(d->dirtyPackets.contains(i))
return d->dirtyPackets[i];
// If we haven't indexed the page where the packet we're interested in starts,
// begin reading pages until we have.
while(d->packetToPageMap.size() <= i) {
if(!nextPage()) {
debug("Ogg::File::packet() -- Could not find the requested packet.");
return ByteVector::null;
}
}
// Start reading at the first page that contains part (or all) of this packet.
// If the last read stopped at the packet that we're interested in, don't
// reread its packet list. (This should make sequential packet reads fast.)
uint pageIndex = d->packetToPageMap[i].front();
if(d->currentPacketPage != d->pages[pageIndex]) {
d->currentPacketPage = d->pages[pageIndex];
d->currentPackets = d->currentPacketPage->packets();
}
// If the packet is completely contained in the first page that it's in, then
// just return it now.
if(d->currentPacketPage->containsPacket(i) & Page::CompletePacket)
return d->currentPackets[i - d->currentPacketPage->firstPacketIndex()];
// If the packet is *not* completely contained in the first page that it's a
// part of then that packet trails off the end of the page. Continue appending
// the pages' packet data until we hit a page that either does not end with the
// packet that we're fetching or where the last packet is complete.
ByteVector packet = d->currentPackets.back();
while(d->currentPacketPage->containsPacket(i) & Page::EndsWithPacket &&
!d->currentPacketPage->header()->lastPacketCompleted())
{
pageIndex++;
if(pageIndex == d->pages.size()) {
if(!nextPage()) {
debug("Ogg::File::packet() -- Could not find the requested packet.");
return ByteVector::null;
}
}
d->currentPacketPage = d->pages[pageIndex];
d->currentPackets = d->currentPacketPage->packets();
packet.append(d->currentPackets.front());
}
return packet;
}
void Ogg::File::setPacket(uint i, const ByteVector &p)
{
while(d->packetToPageMap.size() <= i) {
if(!nextPage()) {
debug("Ogg::File::setPacket() -- Could not set the requested packet.");
return;
}
}
List<int>::ConstIterator it = d->packetToPageMap[i].begin();
for(; it != d->packetToPageMap[i].end(); ++it)
d->dirtyPages.sortedInsert(*it, true);
d->dirtyPackets.insert(i, p);
}
const Ogg::PageHeader *Ogg::File::firstPageHeader()
{
if(d->firstPageHeader)
return d->firstPageHeader->isValid() ? d->firstPageHeader : 0;
long firstPageHeaderOffset = find("OggS");
if(firstPageHeaderOffset < 0)
return 0;
d->firstPageHeader = new PageHeader(this, firstPageHeaderOffset);
return d->firstPageHeader->isValid() ? d->firstPageHeader : 0;
}
const Ogg::PageHeader *Ogg::File::lastPageHeader()
{
if(d->lastPageHeader)
return d->lastPageHeader->isValid() ? d->lastPageHeader : 0;
long lastPageHeaderOffset = rfind("OggS");
if(lastPageHeaderOffset < 0)
return 0;
d->lastPageHeader = new PageHeader(this, lastPageHeaderOffset);
return d->lastPageHeader->isValid() ? d->lastPageHeader : 0;
}
void Ogg::File::save()
{
List<int> pageGroup;
for(List<int>::ConstIterator it = d->dirtyPages.begin(); it != d->dirtyPages.end(); ++it) {
if(!pageGroup.isEmpty() && pageGroup.back() + 1 != *it) {
writePageGroup(pageGroup);
pageGroup.clear();
}
else
pageGroup.append(*it);
}
writePageGroup(pageGroup);
d->dirtyPages.clear();
d->dirtyPackets.clear();
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
Ogg::File::File(const char *file) : TagLib::File(file)
{
d = new FilePrivate;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
bool Ogg::File::nextPage()
{
long nextPageOffset;
int currentPacket;
if(d->pages.isEmpty()) {
currentPacket = 0;
nextPageOffset = find("OggS");
if(nextPageOffset < 0)
return false;
}
else {
if(d->currentPage->header()->lastPageOfStream())
return false;
if(d->currentPage->header()->lastPacketCompleted())
currentPacket = d->currentPage->firstPacketIndex() + d->currentPage->packetCount();
else
currentPacket = d->currentPage->firstPacketIndex() + d->currentPage->packetCount() - 1;
nextPageOffset = d->currentPage->fileOffset() + d->currentPage->size();
}
// Read the next page and add it to the page list.
d->currentPage = new Page(this, nextPageOffset);
if(!d->currentPage->header()->isValid()) {
delete d->currentPage;
d->currentPage = 0;
return false;
}
d->currentPage->setFirstPacketIndex(currentPacket);
if(d->pages.isEmpty())
d->streamSerialNumber = d->currentPage->header()->streamSerialNumber();
d->pages.append(d->currentPage);
// Loop through the packets in the page that we just read appending the
// current page number to the packet to page map for each packet.
for(uint i = 0; i < d->currentPage->packetCount(); i++) {
uint currentPacket = d->currentPage->firstPacketIndex() + i;
if(d->packetToPageMap.size() <= currentPacket)
d->packetToPageMap.push_back(List<int>());
d->packetToPageMap[currentPacket].append(d->pages.size() - 1);
}
return true;
}
void Ogg::File::writePageGroup(const List<int> &pageGroup)
{
if(pageGroup.isEmpty())
return;
ByteVectorList packets;
// If the first page of the group isn't dirty, append its partial content here.
if(!d->dirtyPages.contains(d->pages[pageGroup.front()]->firstPacketIndex()))
packets.append(d->pages[pageGroup.front()]->packets().front());
int previousPacket = -1;
int originalSize = 0;
for(List<int>::ConstIterator it = pageGroup.begin(); it != pageGroup.end(); ++it) {
uint firstPacket = d->pages[*it]->firstPacketIndex();
uint lastPacket = firstPacket + d->pages[*it]->packetCount() - 1;
List<int>::ConstIterator last = --pageGroup.end();
for(uint i = firstPacket; i <= lastPacket; i++) {
if(it == last && i == lastPacket && !d->dirtyPages.contains(i))
packets.append(d->pages[*it]->packets().back());
else if(int(i) != previousPacket) {
previousPacket = i;
packets.append(packet(i));
}
}
originalSize += d->pages[*it]->size();
}
const bool continued = d->pages[pageGroup.front()]->header()->firstPacketContinued();
const bool completed = d->pages[pageGroup.back()]->header()->lastPacketCompleted();
// TODO: This pagination method isn't accurate for what's being done here.
// This should account for real possibilities like non-aligned packets and such.
List<Page *> pages = Page::paginate(packets, Page::SinglePagePerGroup,
d->streamSerialNumber, pageGroup.front(),
continued, completed);
ByteVector data;
for(List<Page *>::ConstIterator it = pages.begin(); it != pages.end(); ++it)
data.append((*it)->render());
// The insertion algorithms could also be improve to queue and prioritize data
// on the way out. Currently it requires rewriting the file for every page
// group rather than just once; however, for tagging applications there will
// generally only be one page group, so it's not worth the time for the
// optimization at the moment.
insert(data, d->pages[pageGroup.front()]->fileOffset(), originalSize);
// Update the page index to include the pages we just created and to delete the
// old pages.
for(List<Page *>::ConstIterator it = pages.begin(); it != pages.end(); ++it) {
const int index = (*it)->header()->pageSequenceNumber();
delete d->pages[index];
d->pages[index] = *it;
}
}

107
ogg/oggfile.h Normal file
View File

@ -0,0 +1,107 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#include <tfile.h>
#include <tbytevectorlist.h>
#ifndef TAGLIB_OGGFILE_H
#define TAGLIB_OGGFILE_H
namespace TagLib {
//! A namespace for the classes used by Ogg-based metadata files
namespace Ogg {
class PageHeader;
//! An implementation of TagLib::File with some helpers for Ogg based formats
/*!
* This is an implementation of Ogg file page and packet rendering and is of
* use to Ogg based formats. While the API is small this handles the
* non-trivial details of breaking up an Ogg stream into packets and makes
* these available (via subclassing) to the codec meta data implementations.
*/
class File : public TagLib::File
{
public:
virtual ~File();
/*!
* Returns the packet contents for the i-th packet (starting from zero)
* in the Ogg bitstream.
*
* \warning The requires reading at least the packet header for every page
* up to the requested page.
*/
ByteVector packet(uint i);
/*!
* Sets the packet with index \a i to the value \a p.
*/
void setPacket(uint i, const ByteVector &p);
/*!
* Returns a pointer to the PageHeader for the first page in the stream or
* null if the page could not be found.
*/
const PageHeader *firstPageHeader();
/*!
* Returns a pointer to the PageHeader for the last page in the stream or
* null if the page could not be found.
*/
const PageHeader *lastPageHeader();
virtual void save();
protected:
/*!
* Contructs an Ogg file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*
* \note This constructor is protected since Ogg::File shouldn't be
* instantiated directly but rather should be used through the codec
* specific subclasses.
*/
File(const char *file);
private:
File(const File &);
File &operator=(const File &);
/*!
* Reads the next page and updates the internal "current page" pointer.
*/
bool nextPage();
void writePageGroup(const List<int> &group);
class FilePrivate;
FilePrivate *d;
};
}
}
#endif

251
ogg/oggpage.cpp Normal file
View File

@ -0,0 +1,251 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#include <tstring.h>
#include "oggpage.h"
#include "oggpageheader.h"
#include "oggfile.h"
using namespace TagLib;
class Ogg::Page::PagePrivate
{
public:
PagePrivate(File *f = 0, long pageOffset = -1) :
file(f),
fileOffset(pageOffset),
packetOffset(0),
header(f, pageOffset),
firstPacketIndex(-1)
{
if(file) {
packetOffset = fileOffset + header.size();
packetSizes = header.packetSizes();
dataSize = header.dataSize();
}
}
File *file;
long fileOffset;
long packetOffset;
int dataSize;
List<int> packetSizes;
PageHeader header;
int firstPacketIndex;
ByteVectorList packets;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Ogg::Page::Page(Ogg::File *file, long pageOffset)
{
d = new PagePrivate(file, pageOffset);
}
Ogg::Page::~Page()
{
delete d;
}
long Ogg::Page::fileOffset() const
{
return d->fileOffset;
}
const Ogg::PageHeader *Ogg::Page::header() const
{
return &d->header;
}
int Ogg::Page::firstPacketIndex() const
{
return d->firstPacketIndex;
}
void Ogg::Page::setFirstPacketIndex(int index)
{
d->firstPacketIndex = index;
}
Ogg::Page::ContainsPacketFlags Ogg::Page::containsPacket(int index) const
{
int lastPacketIndex = d->firstPacketIndex + packetCount() - 1;
if(index < d->firstPacketIndex || index > lastPacketIndex)
return DoesNotContainPacket;
ContainsPacketFlags flags = DoesNotContainPacket;
if(index == d->firstPacketIndex)
flags = ContainsPacketFlags(flags | BeginsWithPacket);
if(index == lastPacketIndex)
flags = ContainsPacketFlags(flags | EndsWithPacket);
// If there's only one page and it's complete:
if(packetCount() == 1 &&
!d->header.firstPacketContinued() &&
d->header.lastPacketCompleted())
{
flags = ContainsPacketFlags(flags | CompletePacket);
}
// Or if the page is (a) the first page and it's complete or (b) the last page
// and it's complete or (c) a page in the middle.
else if((flags & BeginsWithPacket && !d->header.firstPacketContinued()) ||
(flags & EndsWithPacket && d->header.lastPacketCompleted()) ||
(!flags & BeginsWithPacket && !flags & EndsWithPacket))
{
flags = ContainsPacketFlags(flags | CompletePacket);
}
return flags;
}
TagLib::uint Ogg::Page::packetCount() const
{
return d->header.packetSizes().size();
}
ByteVectorList Ogg::Page::packets() const
{
if(!d->packets.isEmpty())
return d->packets;
ByteVectorList l;
if(d->file && d->header.isValid()) {
d->file->seek(d->packetOffset);
List<int> packetSizes = d->header.packetSizes();
List<int>::ConstIterator it = packetSizes.begin();
for(; it != packetSizes.end(); ++it)
l.append(d->file->readBlock(*it));
}
else
debug("Ogg::Page::packets() -- attempting to read packets from an invalid page.");
return l;
}
int Ogg::Page::size() const
{
return d->header.size() + d->header.dataSize();
}
ByteVector Ogg::Page::render() const
{
ByteVector data;
data.append(d->header.render());
if(d->packets.isEmpty()) {
if(d->file) {
d->file->seek(d->packetOffset);
data.append(d->file->readBlock(d->dataSize));
}
else
debug("Ogg::Page::render() -- this page is empty!");
}
else {
ByteVectorList::ConstIterator it = d->packets.begin();
for(; it != d->packets.end(); ++it)
data.append(*it);
}
// Compute and set the checksum for the Ogg page. The checksum is taken over
// the entire page with the 4 bytes reserved for the checksum zeroed and then
// inserted in bytes 22-25 of the page header.
ByteVector checksum = ByteVector::fromUInt(data.checksum(), false);
for(int i = 0; i < 4; i++)
data[i + 22] = checksum[i];
return data;
}
List<Ogg::Page *> Ogg::Page::paginate(const ByteVectorList &packets,
PaginationStrategy strategy,
uint streamSerialNumber,
int firstPage,
bool firstPacketContinued,
bool lastPacketCompleted,
bool containsLastPacket)
{
List<Page *> l;
int totalSize = 0;
for(ByteVectorList::ConstIterator it = packets.begin(); it != packets.end(); ++it)
totalSize += (*it).size();
if(strategy == Repaginate || totalSize + packets.size() > 255 * 256) {
debug("Ogg::Page::paginate() -- Sorry! Repagination is not yet implemented.");
return l;
}
// TODO: Handle creation of multiple pages here with appropriate pagination.
Page *p = new Page(packets, streamSerialNumber, firstPage, firstPacketContinued,
lastPacketCompleted, containsLastPacket);
l.append(p);
return l;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
Ogg::Page::Page(const ByteVectorList &packets,
uint streamSerialNumber,
int pageNumber,
bool firstPacketContinued,
bool lastPacketCompleted,
bool containsLastPacket)
{
d = new PagePrivate;
ByteVector data;
List<int> packetSizes;
d->header.setFirstPageOfStream(pageNumber == 0 && !firstPacketContinued);
d->header.setLastPageOfStream(containsLastPacket);
d->header.setFirstPacketContinued(firstPacketContinued);
d->header.setLastPacketCompleted(lastPacketCompleted);
d->header.setStreamSerialNumber(streamSerialNumber);
d->header.setPageSequenceNumber(pageNumber);
// Build a page from the list of packets.
for(ByteVectorList::ConstIterator it = packets.begin(); it != packets.end(); ++it) {
packetSizes.append((*it).size());
data.append(*it);
}
d->packets = packets;
d->header.setPacketSizes(packetSizes);
}

198
ogg/oggpage.h Normal file
View File

@ -0,0 +1,198 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#ifndef TAGLIB_OGGPAGE_H
#define TAGLIB_OGGPAGE_H
#include <tbytevectorlist.h>
namespace TagLib {
namespace Ogg {
class File;
class PageHeader;
//! An implementation of Ogg pages
/*!
* This is an implementation of the pages that make up an Ogg stream.
* This handles parsing pages and breaking them down into packets and handles
* the details of packets spanning multiple pages and pages that contiain
* multiple packets.
*
* In most Xiph.org formats the comments are found in the first few packets,
* this however is a reasonably complete implementation of Ogg pages that
* could potentially be useful for non-meta data purposes.
*/
class Page
{
public:
/*!
* Read an Ogg page from the \a file at the position \a pageOffset.
*/
Page(File *file, long pageOffset);
virtual ~Page();
/*!
* Returns the page's position within the file (in bytes).
*/
long fileOffset() const;
/*!
* Returns a pointer to the header for this page. This pointer will become
* invalid when the page is deleted.
*/
const PageHeader *header() const;
/*!
* Returns the index of the first packet wholly or partially contained in
* this page.
*
* \see setFirstPacketIndex()
*/
int firstPacketIndex() const;
/*!
* Sets the index of the first packet in the page.
*
* \see firstPacketIndex()
*/
void setFirstPacketIndex(int index);
/*!
* When checking to see if a page contains a given packet this set of flags
* represents the possible values for that packets status in the page.
*
* \see containsPacket()
*/
enum ContainsPacketFlags {
//! No part of the packet is contained in the page
DoesNotContainPacket = 0x0000,
//! The packet is wholly contained in the page
CompletePacket = 0x0001,
//! The page starts with the given packet
BeginsWithPacket = 0x0002,
//! The page ends with the given packet
EndsWithPacket = 0x0004
};
/*!
* Checks to see if the specified \a packet is contained in the current
* page.
*
* \see ContainsPacketFlags
*/
ContainsPacketFlags containsPacket(int index) const;
/*!
* Returns the number of packets (whole or partial) in this page.
*/
uint packetCount() const;
/*!
* Returns a list of the packets in this page.
*
* \note Either or both the first and last packets may be only partial.
* \see PageHeader::firstPacketContinued()
*/
ByteVectorList packets() const;
/*!
* Returns the size of the page in bytes.
*/
int size() const;
ByteVector render() const;
/*!
* Defines a strategy for pagination, or grouping pages into Ogg packets,
* for use with pagination methods.
*
* \note Yes, I'm aware that this is not a canonical "Strategy Pattern",
* the term was simply convenient.
*/
enum PaginationStrategy {
/*!
* Attempt to put the specified set of packets into a single Ogg packet.
* If the sum of the packet data is greater than will fit into a single
* Ogg page -- 65280 bytes -- this will fall back to repagination using
* the recommended page sizes.
*/
SinglePagePerGroup,
/*!
* Split the packet or group of packets into pages that conform to the
* sizes recommended in the Ogg standard.
*/
Repaginate
};
/*!
* Pack \a packets into Ogg pages using the \a strategy for pagination.
* The page number indicater inside of the rendered packets will start
* with \a firstPage and be incremented for each page rendered.
* \a containsLastPacket should be set to true if \a packets contains the
* last page in the stream and will set the appropriate flag in the last
* rendered Ogg page's header. \a streamSerialNumber should be set to
* the serial number for this stream.
*
* \note The "absolute granule position" is currently always zeroed using
* this method as this suffices for the comment headers.
*
* \warning The pages returned by this method must be deleted by the user.
* You can use List<T>::setAutoDelete(true) to set these pages to be
* automatically deleted when this list passes out of scope.
*
* \see PaginationStrategy
* \see List::setAutoDelete()
*/
static List<Page *> paginate(const ByteVectorList &packets,
PaginationStrategy strategy,
uint streamSerialNumber,
int firstPage,
bool firstPacketContinued = false,
bool lastPacketCompleted = true,
bool containsLastPacket = false);
protected:
/*!
* Creates an Ogg packet based on the data in \a packets. The page number
* for each page will be set to \a pageNumber.
*/
Page(const ByteVectorList &packets,
uint streamSerialNumber,
int pageNumber,
bool firstPacketContinued = false,
bool lastPacketCompleted = true,
bool containsLastPacket = false);
private:
Page(const Page &);
Page &operator=(const Page &);
class PagePrivate;
PagePrivate *d;
};
}
}
#endif

317
ogg/oggpageheader.cpp Normal file
View File

@ -0,0 +1,317 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#include <bitset>
#include <tstring.h>
#include <tdebug.h>
#include <taglib.h>
#include "oggpageheader.h"
#include "oggfile.h"
using namespace TagLib;
class Ogg::PageHeader::PageHeaderPrivate
{
public:
PageHeaderPrivate(File *f, long pageOffset) :
file(f),
fileOffset(pageOffset),
isValid(false),
firstPacketContinued(false),
lastPacketCompleted(false),
firstPageOfStream(false),
lastPageOfStream(false),
absoluteGranularPosition(0),
streamSerialNumber(0),
pageSequenceNumber(-1),
size(0),
dataSize(0)
{}
File *file;
long fileOffset;
bool isValid;
List<int> packetSizes;
bool firstPacketContinued;
bool lastPacketCompleted;
bool firstPageOfStream;
bool lastPageOfStream;
long long absoluteGranularPosition;
uint streamSerialNumber;
int pageSequenceNumber;
int size;
int dataSize;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Ogg::PageHeader::PageHeader(Ogg::File *file, long pageOffset)
{
d = new PageHeaderPrivate(file, pageOffset);
if(file && pageOffset >= 0)
read();
}
Ogg::PageHeader::~PageHeader()
{
delete d;
}
bool Ogg::PageHeader::isValid() const
{
return d->isValid;
}
List<int> Ogg::PageHeader::packetSizes() const
{
return d->packetSizes;
}
void Ogg::PageHeader::setPacketSizes(const List<int> &sizes)
{
d->packetSizes = sizes;
}
bool Ogg::PageHeader::firstPacketContinued() const
{
return d->firstPacketContinued;
}
void Ogg::PageHeader::setFirstPacketContinued(bool continued)
{
d->firstPacketContinued = continued;
}
bool Ogg::PageHeader::lastPacketCompleted() const
{
return d->lastPacketCompleted;
}
void Ogg::PageHeader::setLastPacketCompleted(bool completed)
{
d->lastPacketCompleted = completed;
}
bool Ogg::PageHeader::firstPageOfStream() const
{
return d->firstPageOfStream;
}
void Ogg::PageHeader::setFirstPageOfStream(bool first)
{
d->firstPageOfStream = first;
}
bool Ogg::PageHeader::lastPageOfStream() const
{
return d->lastPageOfStream;
}
void Ogg::PageHeader::setLastPageOfStream(bool last)
{
d->lastPageOfStream = last;
}
long long Ogg::PageHeader::absoluteGranularPosition() const
{
return d->absoluteGranularPosition;
}
void Ogg::PageHeader::setAbsoluteGranularPosition(long long agp)
{
d->absoluteGranularPosition = agp;
}
int Ogg::PageHeader::pageSequenceNumber() const
{
return d->pageSequenceNumber;
}
void Ogg::PageHeader::setPageSequenceNumber(int sequenceNumber)
{
d->pageSequenceNumber = sequenceNumber;
}
TagLib::uint Ogg::PageHeader::streamSerialNumber() const
{
return d->streamSerialNumber;
}
void Ogg::PageHeader::setStreamSerialNumber(uint n)
{
d->streamSerialNumber = n;
}
int Ogg::PageHeader::size() const
{
return d->size;
}
int Ogg::PageHeader::dataSize() const
{
return d->dataSize;
}
ByteVector Ogg::PageHeader::render() const
{
ByteVector data;
// capture patern
data.append("OggS");
// stream structure version
data.append(char(0));
// header type flag
std::bitset<8> flags;
flags[0] = d->firstPacketContinued;
flags[1] = d->pageSequenceNumber == 0;
flags[2] = d->lastPageOfStream;
data.append(char(flags.to_ulong()));
// absolute granular position
data.append(ByteVector::fromLongLong(d->absoluteGranularPosition, false));
// stream serial number
data.append(ByteVector::fromUInt(d->streamSerialNumber, false));
// page sequence number
data.append(ByteVector::fromUInt(d->pageSequenceNumber, false));
// checksum -- this is left empty and should be filled in by the Ogg::Page
// class
data.append(ByteVector(4, 0));
// page segment count and page segment table
ByteVector pageSegments = lacingValues();
data.append(char(uchar(pageSegments.size())));
data.append(pageSegments);
return data;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void Ogg::PageHeader::read()
{
d->file->seek(d->fileOffset);
// An Ogg page header is at least 27 bytes, so we'll go ahead and read that
// much and then get the rest when we're ready for it.
ByteVector data = d->file->readBlock(27);
// Sanity check -- make sure that we were in fact able to read as much data as
// we asked for and that the page begins with "OggS".
if(data.size() != 27 || data.mid(0, 4) != "OggS") {
debug("Ogg::PageHeader::read() -- error reading page header");
return;
}
std::bitset<8> flags(data[5]);
d->firstPacketContinued = flags.test(0);
d->firstPageOfStream = flags.test(1);
d->lastPageOfStream = flags.test(2);
d->absoluteGranularPosition = data.mid(6, 8).toLongLong(false);
d->streamSerialNumber = data.mid(14, 4).toUInt(false);
d->pageSequenceNumber = data.mid(18, 4).toUInt(false);
// Byte number 27 is the number of page segments, which is the only variable
// length portion of the page header. After reading the number of page
// segments we'll then read in the coresponding data for this count.
int pageSegmentCount = uchar(data[26]);
ByteVector pageSegments = d->file->readBlock(pageSegmentCount);
// Another sanity check.
if(pageSegmentCount < 1 || int(pageSegments.size()) != pageSegmentCount)
return;
// The base size of an Ogg page 27 bytes plus the number of lacing values.
d->size = 27 + pageSegmentCount;
int packetSize = 0;
for(int i = 0; i < pageSegmentCount; i++) {
d->dataSize += uchar(pageSegments[i]);
packetSize += uchar(pageSegments[i]);
if(uchar(pageSegments[i]) < 255) {
d->packetSizes.append(packetSize);
packetSize = 0;
}
}
if(packetSize > 0) {
d->packetSizes.append(packetSize);
d->lastPacketCompleted = false;
}
else
d->lastPacketCompleted = true;
d->isValid = true;
}
ByteVector Ogg::PageHeader::lacingValues() const
{
ByteVector data;
List<int> sizes = d->packetSizes;
for(List<int>::ConstIterator it = sizes.begin(); it != sizes.end(); ++it) {
// The size of a packet in an Ogg page is indicated by a series of "lacing
// values" where the sum of the values is the packet size in bytes. Each of
// these values is a byte. A value of less than 255 (0xff) indicates the end
// of the packet.
div_t n = div(*it, 255);
for(int i = 0; i < n.quot; i++)
data.append(char(uchar(255)));
if(it != --sizes.end() || d->lastPacketCompleted)
data.append(char(uchar(n.rem)));
}
return data;
}

227
ogg/oggpageheader.h Normal file
View File

@ -0,0 +1,227 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#ifndef TAGLIB_OGGPAGEHEADER_H
#define TAGLIB_OGGPAGEHEADER_H
#include <tlist.h>
#include <tbytevector.h>
namespace TagLib {
namespace Ogg {
class File;
//! An implementation of the page headers associated with each Ogg::Page
/*!
* This class implements Ogg page headers which contain the information
* about Ogg pages needed to break them into packets which can be passed on
* to the codecs.
*/
class PageHeader
{
public:
/*!
* Reads a PageHeader from \a file starting at \a pageOffset. The defaults
* create a page with no (and as such, invalid) data that must be set
* later.
*/
PageHeader(File *file = 0, long pageOffset = -1);
/*!
* Deletes this instance of the PageHeader.
*/
virtual ~PageHeader();
/*!
* Returns true if the header parsed properly and is valid.
*/
bool isValid() const;
/*!
* Ogg pages contain a list of packets (which are used by the contained
* codecs). The sizes of these pages is encoded in the page header. This
* returns a list of the packet sizes in bytes.
*
* \see setPacketSizes()
*/
List<int> packetSizes() const;
/*!
* Sets the sizes of the packets in this page to \a sizes. Internally this
* updates the lacing values in the header.
*
* \see packetSizes()
*/
void setPacketSizes(const List<int> &sizes);
/*!
* Some packets can be <i>continued</i> across multiple pages. If the
* first packet in the current page is a continuation this will return
* true. If this is page starts with a new packet this will return false.
*
* \see lastPacketCompleted()
* \see setFirstPacketContinued()
*/
bool firstPacketContinued() const;
/*!
* Sets the internal flag indicating if the first packet in this page is
* continued to \a continued.
*
* \see firstPacketContinued()
*/
void setFirstPacketContinued(bool continued);
/*!
* Returns true if the last packet of this page is completely contained in
* this page.
*
* \see firstPacketContinued()
* \see setLastPacketCompleted()
*/
bool lastPacketCompleted() const;
/*!
* Sets the internal flag indicating if the last packet in this page is
* complete to \a completed.
*
* \see lastPacketCompleted()
*/
void setLastPacketCompleted(bool completed);
/*!
* This returns true if this is the first page of the Ogg (logical) stream.
*
* \see setFirstPageOfStream()
*/
bool firstPageOfStream() const;
/*!
* Marks this page as the first page of the Ogg stream.
*
* \see firstPageOfStream()
*/
void setFirstPageOfStream(bool first);
/*!
* This returns true if this is the last page of the Ogg (logical) stream.
*
* \see setLastPageOfStream()
*/
bool lastPageOfStream() const;
/*!
* Marks this page as the last page of the Ogg stream.
*
* \see lastPageOfStream()
*/
void setLastPageOfStream(bool last);
/*!
* A special value of containing the position of the packet to be
* interpreted by the codec. In the case of Vorbis this contains the PCM
* value and is used to calculate the length of the stream.
*
* \see setAbsoluteGranularPosition()
*/
long long absoluteGranularPosition() const;
/*!
* A special value of containing the position of the packet to be
* interpreted by the codec. It is only supported here so that it may be
* coppied from one page to another.
*
* \see absoluteGranularPosition()
*/
void setAbsoluteGranularPosition(long long agp);
/*!
* Every Ogg logical stream is given a random serial number which is common
* to every page in that logical stream. This returns the serial number of
* the stream associated with this packet.
*
* \see setStreamSerialNumber()
*/
uint streamSerialNumber() const;
/*!
* Every Ogg logical stream is given a random serial number which is common
* to every page in that logical stream. This sets this pages serial
* number. This method should be used when adding new pages to a logical
* stream.
*
* \see streamSerialNumber()
*/
void setStreamSerialNumber(uint n);
/*!
* Returns the index of the page within the Ogg stream. This helps make it
* possible to determine if pages have been lost.
*
* \see setPageSequenceNumber()
*/
int pageSequenceNumber() const;
/*!
* Sets the page's position in the stream to \a sequenceNumber.
*
* \see pageSequenceNumber()
*/
void setPageSequenceNumber(int sequenceNumber);
/*!
* Returns the complete header size.
*/
int size() const;
/*!
* Returns the size of the data portion of the page -- i.e. the size of the
* page less the header size.
*/
int dataSize() const;
/*!
* Render the page header to binary data.
*
* \note The checksum -- bytes 22 - 25 -- will be left empty and must be
* filled in when rendering the entire page.
*/
ByteVector render() const;
private:
PageHeader(const PageHeader &);
PageHeader &operator=(const PageHeader &);
void read();
ByteVector lacingValues() const;
class PageHeaderPrivate;
PageHeaderPrivate *d;
};
}
}
#endif

14
ogg/vorbis/Makefile.am Normal file
View File

@ -0,0 +1,14 @@
INCLUDES = \
-I$(top_srcdir)/taglib \
-I$(top_srcdir)/taglib/toolkit \
-I$(top_srcdir)/taglib/ogg \
$(all_includes)
noinst_LTLIBRARIES = libvorbis.la
libvorbis_la_SOURCES = vorbisfile.cpp vorbisproperties.cpp
taglib_include_HEADERS = vorbisfile.h vorbisproperties.h
taglib_includedir = $(includedir)/taglib
EXTRA_DIST = $(libvorbis_la_SOURCES) $(taglib_include_HEADERS)

113
ogg/vorbis/vorbisfile.cpp Normal file
View File

@ -0,0 +1,113 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#include <bitset>
#include <tstring.h>
#include <tdebug.h>
#include "vorbisfile.h"
using namespace TagLib;
class Vorbis::File::FilePrivate
{
public:
FilePrivate() :
comment(0),
properties(0) {}
~FilePrivate()
{
delete comment;
delete properties;
}
Ogg::XiphComment *comment;
Properties *properties;
};
namespace TagLib {
/*!
* Vorbis headers can be found with one type ID byte and the string "vorbis" in
* an Ogg stream. 0x03 indicates the comment header.
*/
static const char vorbisCommentHeaderID[] = { 0x03, 'v', 'o', 'r', 'b', 'i', 's', 0 };
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Vorbis::File::File(const char *file, bool readProperties,
Properties::ReadStyle propertiesStyle) : Ogg::File(file)
{
d = new FilePrivate;
read(readProperties, propertiesStyle);
}
Vorbis::File::~File()
{
delete d;
}
Ogg::XiphComment *Vorbis::File::tag() const
{
return d->comment;
}
Vorbis::Properties *Vorbis::File::audioProperties() const
{
return d->properties;
}
void Vorbis::File::save()
{
ByteVector v(vorbisCommentHeaderID);
if(!d->comment)
d->comment = new Ogg::XiphComment;
v.append(d->comment->render());
setPacket(1, v);
Ogg::File::save();
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void Vorbis::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
{
ByteVector commentHeaderData = packet(1);
if(commentHeaderData.mid(0, 7) != vorbisCommentHeaderID) {
debug("Vorbis::File::read() - Could not find the Vorbis comment header.");
setValid(false);
return;
}
d->comment = new Ogg::XiphComment(commentHeaderData.mid(7));
if(readProperties)
d->properties = new Properties(this, propertiesStyle);
}

89
ogg/vorbis/vorbisfile.h Normal file
View File

@ -0,0 +1,89 @@
/***************************************************************************
copyright : (C) 2002 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#ifndef TAGLIB_VORBISFILE_H
#define TAGLIB_VORBISFILE_H
#include <oggfile.h>
#include <xiphcomment.h>
#include "vorbisproperties.h"
namespace TagLib {
//! A namespace containing classes for Vorbis metadata
namespace Vorbis {
//! An implementation of Ogg::File with Vorbis specific methods
/*!
* This is the central class in the Ogg Vorbis metadata processing collection
* of classes. It's built upon Ogg::File which handles processing of the Ogg
* logical bitstream and breaking it down into pages which are handled by
* the codec implementations, in this case Vorbis specifically.
*/
class File : public Ogg::File
{
public:
/*!
* Contructs a Vorbis file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*/
File(const char *file, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);
/*!
* Destroys this instance of the File.
*/
virtual ~File();
/*!
* Returns the XiphComment for this file. XiphComment implements the tag
* interface, so this serves as the reimplementation of
* TagLib::File::tag().
*/
virtual Ogg::XiphComment *tag() const;
/*!
* Returns the Vorbis::Properties for this file. If no audio properties
* were read then this will return a null pointer.
*/
virtual Properties *audioProperties() const;
virtual void save();
private:
File(const File &);
File &operator=(const File &);
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
class FilePrivate;
FilePrivate *d;
};
}
}
#endif

View File

@ -0,0 +1,179 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#include <tstring.h>
#include <tdebug.h>
#include <oggpageheader.h>
#include "vorbisproperties.h"
#include "vorbisfile.h"
using namespace TagLib;
class Vorbis::Properties::PropertiesPrivate
{
public:
PropertiesPrivate(File *f, ReadStyle s) :
file(f),
style(s),
length(0),
bitrate(0),
sampleRate(0),
channels(0),
vorbisVersion(0),
bitrateMaximum(0),
bitrateNominal(0),
bitrateMinimum(0) {}
File *file;
ReadStyle style;
int length;
int bitrate;
int sampleRate;
int channels;
int vorbisVersion;
int bitrateMaximum;
int bitrateNominal;
int bitrateMinimum;
};
namespace TagLib {
/*!
* Vorbis headers can be found with one type ID byte and the string "vorbis" in
* an Ogg stream. 0x01 indicates the setup header.
*/
static const char vorbisSetupHeaderID[] = { 0x01, 'v', 'o', 'r', 'b', 'i', 's', 0 };
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Vorbis::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style)
{
d = new PropertiesPrivate(file, style);
read();
}
Vorbis::Properties::~Properties()
{
delete d;
}
int Vorbis::Properties::length() const
{
return d->length;
}
int Vorbis::Properties::bitrate() const
{
return int(float(d->bitrate) / float(1000) + 0.5);
}
int Vorbis::Properties::sampleRate() const
{
return d->sampleRate;
}
int Vorbis::Properties::channels() const
{
return d->channels;
}
int Vorbis::Properties::vorbisVersion() const
{
return d->vorbisVersion;
}
int Vorbis::Properties::bitrateMaximum() const
{
return d->bitrateMaximum;
}
int Vorbis::Properties::bitrateNominal() const
{
return d->bitrateNominal;
}
int Vorbis::Properties::bitrateMinimum() const
{
return d->bitrateMinimum;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void Vorbis::Properties::read()
{
// Get the identification header from the Ogg implementation.
ByteVector data = d->file->packet(0);
int pos = 0;
if(data.mid(pos, 7) != vorbisSetupHeaderID) {
debug("Vorbis::Properties::read() -- invalid Vorbis identification header");
return;
}
pos += 7;
d->vorbisVersion = data.mid(pos, 4).toUInt(false);
pos += 4;
d->channels = uchar(data[pos]);
pos += 1;
d->sampleRate = data.mid(pos, 4).toUInt(false);
pos += 4;
d->bitrateMaximum = data.mid(pos, 4).toUInt(false);
pos += 4;
d->bitrateNominal = data.mid(pos, 4).toUInt(false);
pos += 4;
d->bitrateMinimum = data.mid(pos, 4).toUInt(false);
// TODO: Later this should be only the "fast" mode.
d->bitrate = d->bitrateNominal;
// Find the length of the file. See http://wiki.xiph.org/VorbisStreamLength/
// for my notes on the topic.
const Ogg::PageHeader *first = d->file->firstPageHeader();
const Ogg::PageHeader *last = d->file->lastPageHeader();
if(first && last) {
long long start = first->absoluteGranularPosition();
long long end = last->absoluteGranularPosition();
if(start >= 0 && end >= 0 && d->sampleRate > 0)
d->length = (end - start) / (long long) d->sampleRate;
else
debug("Vorbis::Properties::read() -- Either the PCM values for the start or "
"end of this file was incorrect or the sample rate is zero.");
}
else
debug("Vorbis::Properties::read() -- Could not find valid first and last Ogg pages.");
}

View File

@ -0,0 +1,96 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#ifndef TAGLIB_VORBISPROPERTIES_H
#define TAGLIB_VORBISPROPERTIES_H
#include <audioproperties.h>
namespace TagLib {
namespace Vorbis {
class File;
//! An implementation of audio property reading for Ogg Vorbis
/*!
* This reads the data from an Ogg Vorbis stream found in the AudioProperties
* API.
*/
class Properties : public AudioProperties
{
public:
/*!
* Create an instance of Vorbis::Properties with the data read from the
* Vorbis::File \a file.
*/
Properties(File *file, ReadStyle style = Average);
/*!
* Destroys this VorbisProperties instance.
*/
virtual ~Properties();
// Reimplementations.
virtual int length() const;
virtual int bitrate() const;
virtual int sampleRate() const;
virtual int channels() const;
/*!
* Returns the Vorbis version, currently "0" (as specified by the spec).
*/
int vorbisVersion() const;
/*!
* Returns the maximum bitrate as read from the Vorbis identification
* header.
*/
int bitrateMaximum() const;
/*!
* Returns the nominal bitrate as read from the Vorbis identification
* header.
*/
int bitrateNominal() const;
/*!
* Returns the minimum bitrate as read from the Vorbis identification
* header.
*/
int bitrateMinimum() const;
private:
Properties(const Properties &);
Properties &operator=(const Properties &);
void read();
class PropertiesPrivate;
PropertiesPrivate *d;
};
}
}
#endif

287
ogg/xiphcomment.cpp Normal file
View File

@ -0,0 +1,287 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#include <tbytevector.h>
#include <tdebug.h>
#include <xiphcomment.h>
using namespace TagLib;
class Ogg::XiphComment::XiphCommentPrivate
{
public:
FieldListMap fieldListMap;
String vendorID;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Ogg::XiphComment::XiphComment() : TagLib::Tag()
{
d = new XiphCommentPrivate;
}
Ogg::XiphComment::XiphComment(const ByteVector &data) : TagLib::Tag()
{
d = new XiphCommentPrivate;
parse(data);
}
Ogg::XiphComment::~XiphComment()
{
delete d;
}
String Ogg::XiphComment::title() const
{
if(d->fieldListMap["TITLE"].isEmpty())
return String::null;
return d->fieldListMap["TITLE"].front();
}
String Ogg::XiphComment::artist() const
{
if(d->fieldListMap["ARTIST"].isEmpty())
return String::null;
return d->fieldListMap["ARTIST"].front();
}
String Ogg::XiphComment::album() const
{
if(d->fieldListMap["ALBUM"].isEmpty())
return String::null;
return d->fieldListMap["ALBUM"].front();
}
String Ogg::XiphComment::comment() const
{
if(d->fieldListMap["DESCRIPTION"].isEmpty())
return String::null;
return d->fieldListMap["DESCRIPTION"].front();
}
String Ogg::XiphComment::genre() const
{
if(d->fieldListMap["GENRE"].isEmpty())
return String::null;
return d->fieldListMap["GENRE"].front();
}
TagLib::uint Ogg::XiphComment::year() const
{
if(d->fieldListMap["DATE"].isEmpty())
return 0;
return d->fieldListMap["DATE"].front().toInt();
}
TagLib::uint Ogg::XiphComment::track() const
{
if(d->fieldListMap["TRACKNUMBER"].isEmpty())
return 0;
return d->fieldListMap["TRACKNUMBER"].front().toInt();
}
void Ogg::XiphComment::setTitle(const String &s)
{
addField("TITLE", s);
}
void Ogg::XiphComment::setArtist(const String &s)
{
addField("ARTIST", s);
}
void Ogg::XiphComment::setAlbum(const String &s)
{
addField("ALBUM", s);
}
void Ogg::XiphComment::setComment(const String &s)
{
addField("DESCRIPTION", s);
}
void Ogg::XiphComment::setGenre(const String &s)
{
addField("GENRE", s);
}
void Ogg::XiphComment::setYear(uint i)
{
if(i == 0)
removeField("DATE");
else
addField("DATE", String::number(i));
}
void Ogg::XiphComment::setTrack(uint i)
{
if(i == 0)
removeField("TRACKNUMBER");
else
addField("TRACKNUMBER", String::number(i));
}
bool Ogg::XiphComment::isEmpty() const
{
FieldListMap::ConstIterator it = d->fieldListMap.begin();
for(; it != d->fieldListMap.end(); ++it)
if(!(*it).second.isEmpty())
return false;
return true;
}
TagLib::uint Ogg::XiphComment::fieldCount() const
{
uint count = 0;
FieldListMap::ConstIterator it = d->fieldListMap.begin();
for(; it != d->fieldListMap.end(); ++it)
count += (*it).second.size();
return count;
}
const Ogg::FieldListMap &Ogg::XiphComment::fieldListMap() const
{
return d->fieldListMap;
}
String Ogg::XiphComment::vendorID() const
{
return d->vendorID;
}
void Ogg::XiphComment::addField(const String &key, const String &value, bool replace)
{
if(replace)
removeField(key.upper());
if(!key.isEmpty())
d->fieldListMap[key.upper()].append(value);
}
void Ogg::XiphComment::removeField(const String &key, const String &value)
{
if(!value.isNull()) {
StringList::Iterator it = d->fieldListMap[key].begin();
for(; it != d->fieldListMap[key].end(); ++it) {
if(value == *it)
d->fieldListMap[key].erase(it);
}
}
else
d->fieldListMap[key].clear();
}
ByteVector Ogg::XiphComment::render() const
{
ByteVector data;
// Add the vendor ID length and the vendor ID. It's important to use the
// lenght of the data(String::UTF8) rather than the lenght of the the string
// since this is UTF8 text and there may be more characters in the data than
// in the UTF16 string.
ByteVector vendorData = d->vendorID.data(String::UTF8);
data.append(ByteVector::fromUInt(vendorData.size(), false));
data.append(vendorData);
// Add the number of fields.
data.append(ByteVector::fromUInt(fieldCount(), false));
// Iterate over the the field lists. Our iterator returns a
// std::pair<String, StringList> where the first String is the field name and
// the StringList is the values associated with that field.
FieldListMap::ConstIterator it = d->fieldListMap.begin();
for(; it != d->fieldListMap.end(); ++it) {
// And now iterate over the values of the current list.
String fieldName = (*it).first;
StringList values = (*it).second;
StringList::ConstIterator valuesIt = values.begin();
for(; valuesIt != values.end(); ++valuesIt) {
ByteVector fieldData = fieldName.data(String::UTF8);
fieldData.append('=');
fieldData.append((*valuesIt).data(String::UTF8));
data.append(ByteVector::fromUInt(fieldData.size(), false));
data.append(fieldData);
}
}
// Append the "framing bit".
data.append(char(1));
return data;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
void Ogg::XiphComment::parse(const ByteVector &data)
{
// The first thing in the comment data is the vendor ID length, followed by a
// UTF8 string with the vendor ID.
int pos = 0;
int vendorLength = data.mid(0, 4).toUInt(false);
pos += 4;
d->vendorID = String(data.mid(pos, vendorLength), String::UTF8);
pos += vendorLength;
// Next the number of fields in the comment vector.
int commentFields = data.mid(pos, 4).toUInt(false);
pos += 4;
for(int i = 0; i < commentFields; i++) {
// Each comment field is in the format "KEY=value" in a UTF8 string and has
// 4 bytes before the text starts that gives the length.
int commentLength = data.mid(pos, 4).toUInt(false);
pos += 4;
String comment = String(data.mid(pos, commentLength), String::UTF8);
pos += commentLength;
int commentSeparatorPosition = comment.find("=");
String key = comment.substr(0, commentSeparatorPosition);
String value = comment.substr(commentSeparatorPosition + 1);
addField(key, value, false);
}
}

181
ogg/xiphcomment.h Normal file
View File

@ -0,0 +1,181 @@
/***************************************************************************
copyright : (C) 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#ifndef TAGLIB_VORBISCOMMENT_H
#define TAGLIB_VORBISCOMMENT_H
#include <tag.h>
#include <tlist.h>
#include <tmap.h>
#include <tstring.h>
#include <tstringlist.h>
#include <tbytevector.h>
namespace TagLib {
namespace Ogg {
/*!
* A mapping between a list of field names, or keys, and a list of values
* associated with that field.
*
* \see XiphComment::fieldListMap()
*/
typedef Map<String, StringList> FieldListMap;
//! Ogg Vorbis comment implementation
/*!
* This class is an implementation of the Ogg Vorbis comment specification,
* to be found in section 5 of the Ogg Vorbis specification. Because this
* format is also used in other (currently unsupported) Xiph.org formats, it
* has been made part of a generic implementation rather than being limited
* to strictly Vorbis.
*
* Vorbis comments are a simple vector of keys and values, called fields.
* Multiple values for a given key are supported.
*
* \see fieldListMap()
*/
class XiphComment : public TagLib::Tag
{
public:
/*!
* Constructs an empty Vorbis comment.
*/
XiphComment();
/*!
* Constructs a Vorbis comment from \a data.
*/
XiphComment(const ByteVector &data);
/*!
* Destroys this instance of the XiphComment.
*/
virtual ~XiphComment();
virtual String title() const;
virtual String artist() const;
virtual String album() const;
virtual String comment() const;
virtual String genre() const;
virtual uint year() const;
virtual uint track() const;
virtual void setTitle(const String &s);
virtual void setArtist(const String &s);
virtual void setAlbum(const String &s);
virtual void setComment(const String &s);
virtual void setGenre(const String &s);
virtual void setYear(uint i);
virtual void setTrack(uint i);
virtual bool isEmpty() const;
/*!
* Returns the number of fields present in the comment.
*/
uint fieldCount() const;
/*!
* Returns a reference to the map of field lists. Because Xiph comments
* support multiple fields with the same key, a pure Map would not work.
* As such this is a Map of string lists, keyed on the comment field name.
*
* The standard set of Xiph/Vorbis fields (which may or may not be
* contained in any specific comment) is:
*
* <ul>
* <li>TITLE</li>
* <li>VERSION</li>
* <li>ALBUM</li>
* <li>ARTIST</li>
* <li>PERFORMER</li>
* <li>COPYRIGHT</li>
* <li>ORGANIZATION</li>
* <li>DESCRIPTION</li>
* <li>GENRE</li>
* <li>DATE</li>
* <li>LOCATION</li>
* <li>CONTACT</li>
* <li>ISRC</li>
* </ul>
*
* For a more detailed description of these fields, please see the Ogg
* Vorbis specification, section 5.2.2.1.
*
* \note The Ogg Vorbis comment specification does allow these key values
* to be either upper or lower case. However, it is conventional for them
* to be upper case. As such, TagLib, when parsing a Xiph/Vorbis comment,
* converts all fields to uppercase. When you are using this data
* structure, you will need to specify the field name in upper case.
*
* \warning You should not modify this data structure directly, instead
* use addField() and removeField().
*/
const FieldListMap &fieldListMap() const;
/*!
* Returns the vendor ID of the Ogg Vorbis encoder. libvorbis 1.0 as the
* most common case always returns "Xiph.Org libVorbis I 20020717".
*/
String vendorID() const;
/*!
* Add the field specified by \a key with the data \a value. If \a replace
* is true, then all of the other fields with the same key will be removed
* first.
*
* If the field value is empty, the field will be removed.
*/
void addField(const String &key, const String &value, bool replace = true);
/*!
* Remove the field specified by \a key with the data \a value. If
* \a value is null, all of the fields with the given key will be removed.
*/
void removeField(const String &key, const String &value = String::null);
/*!
* Renders the comment to a ByteVector suitable for inserting into a file.
*/
ByteVector render() const;
protected:
/*!
* Reads the tag from the file specified in the constructor and fills the
* FieldListMap.
*/
void parse(const ByteVector &data);
private:
XiphComment(const XiphComment &);
XiphComment &operator=(const XiphComment &);
class XiphCommentPrivate;
XiphCommentPrivate *d;
};
}
}
#endif