mirror of
https://github.com/taglib/taglib.git
synced 2025-07-18 04:54:19 -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:
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user