1 /***************************************************************************
2 copyright : (C) 2002 - 2008 by Scott Wheeler
3 email : wheeler@kde.org
4 ***************************************************************************/
6 /***************************************************************************
7 * This library is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU Lesser General Public License version *
9 * 2.1 as published by the Free Software Foundation. *
11 * This library is distributed in the hope that it will be useful, but *
12 * WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14 * Lesser General Public License for more details. *
16 * You should have received a copy of the GNU Lesser General Public *
17 * License along with this library; if not, write to the Free Software *
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
21 * Alternatively, this file is available under the Mozilla Public *
22 * License Version 1.1. You may obtain a copy of the License at *
23 * http://www.mozilla.org/MPL/ *
24 ***************************************************************************/
26 #include <tbytevectorlist.h>
33 #include "oggpageheader.h"
35 using namespace TagLib;
37 class Ogg::File::FilePrivate
41 streamSerialNumber(0),
47 pages.setAutoDelete(true);
52 delete firstPageHeader;
53 delete lastPageHeader;
56 uint streamSerialNumber;
58 PageHeader *firstPageHeader;
59 PageHeader *lastPageHeader;
60 std::vector< List<int> > packetToPageMap;
61 Map<int, ByteVector> dirtyPackets;
64 //! The current page for the reader -- used by nextPage()
66 //! The current page for the packet parser -- used by packet()
67 Page *currentPacketPage;
68 //! The packets for the currentPacketPage -- used by packet()
69 ByteVectorList currentPackets;
72 ////////////////////////////////////////////////////////////////////////////////
74 ////////////////////////////////////////////////////////////////////////////////
81 ByteVector Ogg::File::packet(uint i)
83 // Check to see if we're called setPacket() for this packet since the last
86 if(d->dirtyPackets.contains(i))
87 return d->dirtyPackets[i];
89 // If we haven't indexed the page where the packet we're interested in starts,
90 // begin reading pages until we have.
92 while(d->packetToPageMap.size() <= i) {
94 debug("Ogg::File::packet() -- Could not find the requested packet.");
95 return ByteVector::null;
99 // Start reading at the first page that contains part (or all) of this packet.
100 // If the last read stopped at the packet that we're interested in, don't
101 // reread its packet list. (This should make sequential packet reads fast.)
103 uint pageIndex = d->packetToPageMap[i].front();
104 if(d->currentPacketPage != d->pages[pageIndex]) {
105 d->currentPacketPage = d->pages[pageIndex];
106 d->currentPackets = d->currentPacketPage->packets();
109 // If the packet is completely contained in the first page that it's in, then
110 // just return it now.
112 if(d->currentPacketPage->containsPacket(i) & Page::CompletePacket)
113 return d->currentPackets[i - d->currentPacketPage->firstPacketIndex()];
115 // If the packet is *not* completely contained in the first page that it's a
116 // part of then that packet trails off the end of the page. Continue appending
117 // the pages' packet data until we hit a page that either does not end with the
118 // packet that we're fetching or where the last packet is complete.
120 ByteVector packet = d->currentPackets.back();
121 while(d->currentPacketPage->containsPacket(i) & Page::EndsWithPacket &&
122 !d->currentPacketPage->header()->lastPacketCompleted())
125 if(pageIndex == d->pages.size()) {
127 debug("Ogg::File::packet() -- Could not find the requested packet.");
128 return ByteVector::null;
131 d->currentPacketPage = d->pages[pageIndex];
132 d->currentPackets = d->currentPacketPage->packets();
133 packet.append(d->currentPackets.front());
139 void Ogg::File::setPacket(uint i, const ByteVector &p)
141 while(d->packetToPageMap.size() <= i) {
143 debug("Ogg::File::setPacket() -- Could not set the requested packet.");
148 List<int>::ConstIterator it = d->packetToPageMap[i].begin();
149 for(; it != d->packetToPageMap[i].end(); ++it)
150 d->dirtyPages.sortedInsert(*it, true);
152 d->dirtyPackets.insert(i, p);
155 const Ogg::PageHeader *Ogg::File::firstPageHeader()
157 if(d->firstPageHeader)
158 return d->firstPageHeader->isValid() ? d->firstPageHeader : 0;
160 long firstPageHeaderOffset = find("OggS");
162 if(firstPageHeaderOffset < 0)
165 d->firstPageHeader = new PageHeader(this, firstPageHeaderOffset);
166 return d->firstPageHeader->isValid() ? d->firstPageHeader : 0;
169 const Ogg::PageHeader *Ogg::File::lastPageHeader()
171 if(d->lastPageHeader)
172 return d->lastPageHeader->isValid() ? d->lastPageHeader : 0;
174 long lastPageHeaderOffset = rfind("OggS");
176 if(lastPageHeaderOffset < 0)
179 d->lastPageHeader = new PageHeader(this, lastPageHeaderOffset);
180 return d->lastPageHeader->isValid() ? d->lastPageHeader : 0;
183 bool Ogg::File::save()
186 debug("Ogg::File::save() - Cannot save to a read only file.");
192 for(List<int>::ConstIterator it = d->dirtyPages.begin(); it != d->dirtyPages.end(); ++it) {
193 if(!pageGroup.isEmpty() && pageGroup.back() + 1 != *it) {
194 writePageGroup(pageGroup);
198 pageGroup.append(*it);
200 writePageGroup(pageGroup);
201 d->dirtyPages.clear();
202 d->dirtyPackets.clear();
207 ////////////////////////////////////////////////////////////////////////////////
209 ////////////////////////////////////////////////////////////////////////////////
211 Ogg::File::File(FileName file) : TagLib::File(file)
216 ////////////////////////////////////////////////////////////////////////////////
218 ////////////////////////////////////////////////////////////////////////////////
220 bool Ogg::File::nextPage()
225 if(d->pages.isEmpty()) {
227 nextPageOffset = find("OggS");
228 if(nextPageOffset < 0)
232 if(d->currentPage->header()->lastPageOfStream())
235 if(d->currentPage->header()->lastPacketCompleted())
236 currentPacket = d->currentPage->firstPacketIndex() + d->currentPage->packetCount();
238 currentPacket = d->currentPage->firstPacketIndex() + d->currentPage->packetCount() - 1;
240 nextPageOffset = d->currentPage->fileOffset() + d->currentPage->size();
243 // Read the next page and add it to the page list.
245 d->currentPage = new Page(this, nextPageOffset);
247 if(!d->currentPage->header()->isValid()) {
248 delete d->currentPage;
253 d->currentPage->setFirstPacketIndex(currentPacket);
255 if(d->pages.isEmpty())
256 d->streamSerialNumber = d->currentPage->header()->streamSerialNumber();
258 d->pages.append(d->currentPage);
260 // Loop through the packets in the page that we just read appending the
261 // current page number to the packet to page map for each packet.
263 for(uint i = 0; i < d->currentPage->packetCount(); i++) {
264 uint currentPacket = d->currentPage->firstPacketIndex() + i;
265 if(d->packetToPageMap.size() <= currentPacket)
266 d->packetToPageMap.push_back(List<int>());
267 d->packetToPageMap[currentPacket].append(d->pages.size() - 1);
273 void Ogg::File::writePageGroup(const List<int> &thePageGroup)
275 if(thePageGroup.isEmpty())
279 // pages in the pageGroup and packets must be equivalent
280 // (originalSize and size of packets would not work together),
281 // therefore we sometimes have to add pages to the group
282 List<int> pageGroup(thePageGroup);
283 while (!d->pages[pageGroup.back()]->header()->lastPacketCompleted()) {
284 if (d->currentPage->header()->pageSequenceNumber() == pageGroup.back()) {
285 if (nextPage() == false) {
286 debug("broken ogg file");
289 pageGroup.append(d->currentPage->header()->pageSequenceNumber());
291 pageGroup.append(pageGroup.back() + 1);
295 ByteVectorList packets;
297 // If the first page of the group isn't dirty, append its partial content here.
299 if(!d->dirtyPages.contains(d->pages[pageGroup.front()]->firstPacketIndex()))
300 packets.append(d->pages[pageGroup.front()]->packets().front());
302 int previousPacket = -1;
303 int originalSize = 0;
305 for(List<int>::ConstIterator it = pageGroup.begin(); it != pageGroup.end(); ++it) {
306 uint firstPacket = d->pages[*it]->firstPacketIndex();
307 uint lastPacket = firstPacket + d->pages[*it]->packetCount() - 1;
309 List<int>::ConstIterator last = --pageGroup.end();
311 for(uint i = firstPacket; i <= lastPacket; i++) {
313 if(it == last && i == lastPacket && !d->dirtyPages.contains(i))
314 packets.append(d->pages[*it]->packets().back());
315 else if(int(i) != previousPacket) {
317 packets.append(packet(i));
320 originalSize += d->pages[*it]->size();
323 const bool continued = d->pages[pageGroup.front()]->header()->firstPacketContinued();
324 const bool completed = d->pages[pageGroup.back()]->header()->lastPacketCompleted();
326 // TODO: This pagination method isn't accurate for what's being done here.
327 // This should account for real possibilities like non-aligned packets and such.
329 List<Page *> pages = Page::paginate(packets, Page::SinglePagePerGroup,
330 d->streamSerialNumber, pageGroup.front(),
331 continued, completed);
333 List<Page *> renumberedPages;
335 // Correct the page numbering of following pages
337 if (pages.back()->header()->pageSequenceNumber() != pageGroup.back()) {
339 // TODO: change the internal data structure so that we don't need to hold the
340 // complete file in memory (is unavoidable at the moment)
342 // read the complete stream
343 while(!d->currentPage->header()->lastPageOfStream()) {
344 if(nextPage() == false) {
345 debug("broken ogg file");
350 // create a gap for the new pages
351 int numberOfNewPages = pages.back()->header()->pageSequenceNumber() - pageGroup.back();
352 List<Page *>::Iterator pageIter = d->pages.begin();
353 for(int i = 0; i < pageGroup.back(); i++) {
354 if(pageIter != d->pages.end()) {
358 debug("Ogg::File::writePageGroup() -- Page sequence is broken in original file.");
364 for(; pageIter != d->pages.end(); ++pageIter) {
366 (*pageIter)->getCopyWithNewPageSequenceNumber(
367 (*pageIter)->header()->pageSequenceNumber() + numberOfNewPages);
370 data.append(newPage->render());
371 insert(data, newPage->fileOffset(), data.size());
373 renumberedPages.append(newPage);
377 // insert the new data
380 for(List<Page *>::ConstIterator it = pages.begin(); it != pages.end(); ++it)
381 data.append((*it)->render());
383 // The insertion algorithms could also be improve to queue and prioritize data
384 // on the way out. Currently it requires rewriting the file for every page
385 // group rather than just once; however, for tagging applications there will
386 // generally only be one page group, so it's not worth the time for the
387 // optimization at the moment.
389 insert(data, d->pages[pageGroup.front()]->fileOffset(), originalSize);
391 // Update the page index to include the pages we just created and to delete the
394 // First step: Pages that contain the comment data
396 for(List<Page *>::ConstIterator it = pages.begin(); it != pages.end(); ++it) {
397 const unsigned int index = (*it)->header()->pageSequenceNumber();
398 if(index < d->pages.size()) {
399 delete d->pages[index];
400 d->pages[index] = *it;
402 else if(index == d->pages.size()) {
403 d->pages.append(*it);
406 // oops - there's a hole in the sequence
407 debug("Ogg::File::writePageGroup() -- Page sequence is broken.");
411 // Second step: the renumbered pages
413 for(List<Page *>::ConstIterator it = renumberedPages.begin(); it != renumberedPages.end(); ++it) {
414 const unsigned int index = (*it)->header()->pageSequenceNumber();
415 if(index < d->pages.size()) {
416 delete d->pages[index];
417 d->pages[index] = *it;
419 else if(index == d->pages.size()) {
420 d->pages.append(*it);
423 // oops - there's a hole in the sequence
424 debug("Ogg::File::writePageGroup() -- Page sequence is broken.");