mirror of
https://github.com/taglib/taglib.git
synced 2025-07-18 21:14:23 -04:00
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:
23
ogg/Makefile.am
Normal file
23
ogg/Makefile.am
Normal 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
325
ogg/oggfile.cpp
Normal 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
107
ogg/oggfile.h
Normal 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
251
ogg/oggpage.cpp
Normal 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
198
ogg/oggpage.h
Normal 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
317
ogg/oggpageheader.cpp
Normal 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
227
ogg/oggpageheader.h
Normal 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
14
ogg/vorbis/Makefile.am
Normal 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
113
ogg/vorbis/vorbisfile.cpp
Normal 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
89
ogg/vorbis/vorbisfile.h
Normal 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
|
179
ogg/vorbis/vorbisproperties.cpp
Normal file
179
ogg/vorbis/vorbisproperties.cpp
Normal 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.");
|
||||
}
|
96
ogg/vorbis/vorbisproperties.h
Normal file
96
ogg/vorbis/vorbisproperties.h
Normal 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
287
ogg/xiphcomment.cpp
Normal 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
181
ogg/xiphcomment.h
Normal 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
|
Reference in New Issue
Block a user