Clementine-audio-player-Mac.../3rdparty/taglib/ogg/oggfile.cpp

314 lines
9.0 KiB
C++

/***************************************************************************
copyright : (C) 2002 - 2008 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., 51 Franklin Street, Fifth Floor, Boston, MA *
* 02110-1301 USA *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include <tbytevectorlist.h>
#include <tmap.h>
#include <tstring.h>
#include <tdebug.h>
#include "oggfile.h"
#include "oggpage.h"
#include "oggpageheader.h"
using namespace TagLib;
namespace
{
// Returns the first packet index of the right next page to the given one.
unsigned int nextPacketIndex(const Ogg::Page *page)
{
if(page->header()->lastPacketCompleted())
return page->firstPacketIndex() + page->packetCount();
else
return page->firstPacketIndex() + page->packetCount() - 1;
}
}
class Ogg::File::FilePrivate
{
public:
FilePrivate() :
firstPageHeader(0),
lastPageHeader(0)
{
pages.setAutoDelete(true);
}
~FilePrivate()
{
delete firstPageHeader;
delete lastPageHeader;
}
unsigned int streamSerialNumber;
List<Page *> pages;
PageHeader *firstPageHeader;
PageHeader *lastPageHeader;
Map<unsigned int, ByteVector> dirtyPackets;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Ogg::File::~File()
{
delete d;
}
ByteVector Ogg::File::packet(unsigned int 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.
if(!readPages(i)) {
debug("Ogg::File::packet() -- Could not find the requested packet.");
return ByteVector();
}
// Look for the first page in which the requested packet starts.
List<Page *>::ConstIterator it = d->pages.begin();
while((*it)->containsPacket(i) == Page::DoesNotContainPacket)
++it;
// If the packet is completely contained in the first page that it's in.
// 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 = (*it)->packets()[i - (*it)->firstPacketIndex()];
while(nextPacketIndex(*it) <= i) {
++it;
packet.append((*it)->packets().front());
}
return packet;
}
void Ogg::File::setPacket(unsigned int i, const ByteVector &p)
{
if(!readPages(i)) {
debug("Ogg::File::setPacket() -- Could not set the requested packet.");
return;
}
d->dirtyPackets[i] = p;
}
const Ogg::PageHeader *Ogg::File::firstPageHeader()
{
if(!d->firstPageHeader) {
const 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) {
const long lastPageHeaderOffset = rfind("OggS");
if(lastPageHeaderOffset < 0)
return 0;
d->lastPageHeader = new PageHeader(this, lastPageHeaderOffset);
}
return d->lastPageHeader->isValid() ? d->lastPageHeader : 0;
}
bool Ogg::File::save()
{
if(readOnly()) {
debug("Ogg::File::save() - Cannot save to a read only file.");
return false;
}
Map<unsigned int, ByteVector>::ConstIterator it;
for(it = d->dirtyPackets.begin(); it != d->dirtyPackets.end(); ++it)
writePacket(it->first, it->second);
d->dirtyPackets.clear();
return true;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
Ogg::File::File(FileName file) :
TagLib::File(file),
d(new FilePrivate())
{
}
Ogg::File::File(IOStream *stream) :
TagLib::File(stream),
d(new FilePrivate())
{
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
bool Ogg::File::readPages(unsigned int i)
{
while(true) {
unsigned int packetIndex;
long offset;
if(d->pages.isEmpty()) {
packetIndex = 0;
offset = find("OggS");
if(offset < 0)
return false;
}
else {
const Page *page = d->pages.back();
packetIndex = nextPacketIndex(page);
offset = page->fileOffset() + page->size();
}
// Enough pages have been fetched.
if(packetIndex > i)
return true;
// Read the next page and add it to the page list.
Page *nextPage = new Page(this, offset);
if(!nextPage->header()->isValid()) {
delete nextPage;
return false;
}
nextPage->setFirstPacketIndex(packetIndex);
d->pages.append(nextPage);
if(nextPage->header()->lastPageOfStream())
return false;
}
}
void Ogg::File::writePacket(unsigned int i, const ByteVector &packet)
{
if(!readPages(i)) {
debug("Ogg::File::writePacket() -- Could not find the requested packet.");
return;
}
// Look for the pages where the requested packet should belong to.
List<Page *>::ConstIterator it = d->pages.begin();
while((*it)->containsPacket(i) == Page::DoesNotContainPacket)
++it;
const Page *firstPage = *it;
while(nextPacketIndex(*it) <= i)
++it;
const Page *lastPage = *it;
// Replace the requested packet and create new pages to replace the located pages.
ByteVectorList packets = firstPage->packets();
packets[i - firstPage->firstPacketIndex()] = packet;
if(firstPage != lastPage && lastPage->packetCount() > 1) {
ByteVectorList lastPagePackets = lastPage->packets();
lastPagePackets.erase(lastPagePackets.begin());
packets.append(lastPagePackets);
}
// 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,
firstPage->header()->streamSerialNumber(),
firstPage->pageSequenceNumber(),
firstPage->header()->firstPacketContinued(),
lastPage->header()->lastPacketCompleted());
pages.setAutoDelete(true);
// Write the pages.
ByteVector data;
for(it = pages.begin(); it != pages.end(); ++it)
data.append((*it)->render());
const unsigned long originalOffset = firstPage->fileOffset();
const unsigned long originalLength = lastPage->fileOffset() + lastPage->size() - originalOffset;
insert(data, originalOffset, originalLength);
// Renumber the following pages if the pages have been split or merged.
const int numberOfNewPages
= pages.back()->pageSequenceNumber() - lastPage->pageSequenceNumber();
if(numberOfNewPages != 0) {
long pageOffset = originalOffset + data.size();
while(true) {
Page page(this, pageOffset);
if(!page.header()->isValid())
break;
page.setPageSequenceNumber(page.pageSequenceNumber() + numberOfNewPages);
const ByteVector data = page.render();
seek(pageOffset + 18);
writeBlock(data.mid(18, 8));
if(page.header()->lastPageOfStream())
break;
pageOffset += page.size();
}
}
// Discard all the pages to keep them up-to-date by fetching them again.
d->pages.clear();
}