1 /***************************************************************************
2 copyright : (C) 2002 - 2008 by Scott Wheeler
3 email : wheeler@kde.org
4 ***************************************************************************/
5
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. *
10 * *
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. *
15 * *
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., 51 Franklin Street, Fifth Floor, Boston, MA *
19 * 02110-1301 USA *
20 * *
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 ***************************************************************************/
25
26 #include <tbytevectorlist.h>
27 #include <tmap.h>
28 #include <tstring.h>
29 #include <tdebug.h>
30
31 #include "oggfile.h"
32 #include "oggpage.h"
33 #include "oggpageheader.h"
34
35 using namespace TagLib;
36
37 namespace
38 {
39 // Returns the first packet index of the right next page to the given one.
nextPacketIndex(const Ogg::Page * page)40 unsigned int nextPacketIndex(const Ogg::Page *page)
41 {
42 if(page->header()->lastPacketCompleted())
43 return page->firstPacketIndex() + page->packetCount();
44 else
45 return page->firstPacketIndex() + page->packetCount() - 1;
46 }
47 }
48
49 class Ogg::File::FilePrivate
50 {
51 public:
FilePrivate()52 FilePrivate() :
53 firstPageHeader(0),
54 lastPageHeader(0)
55 {
56 pages.setAutoDelete(true);
57 }
58
~FilePrivate()59 ~FilePrivate()
60 {
61 delete firstPageHeader;
62 delete lastPageHeader;
63 }
64
65 unsigned int streamSerialNumber;
66 List<Page *> pages;
67 PageHeader *firstPageHeader;
68 PageHeader *lastPageHeader;
69 Map<unsigned int, ByteVector> dirtyPackets;
70 };
71
72 ////////////////////////////////////////////////////////////////////////////////
73 // public members
74 ////////////////////////////////////////////////////////////////////////////////
75
~File()76 Ogg::File::~File()
77 {
78 delete d;
79 }
80
packet(unsigned int i)81 ByteVector Ogg::File::packet(unsigned int i)
82 {
83 // Check to see if we're called setPacket() for this packet since the last
84 // save:
85
86 if(d->dirtyPackets.contains(i))
87 return d->dirtyPackets[i];
88
89 // If we haven't indexed the page where the packet we're interested in starts,
90 // begin reading pages until we have.
91
92 if(!readPages(i)) {
93 debug("Ogg::File::packet() -- Could not find the requested packet.");
94 return ByteVector();
95 }
96
97 // Look for the first page in which the requested packet starts.
98
99 List<Page *>::ConstIterator it = d->pages.begin();
100 while((*it)->containsPacket(i) == Page::DoesNotContainPacket)
101 ++it;
102
103 // If the packet is completely contained in the first page that it's in.
104
105 // If the packet is *not* completely contained in the first page that it's a
106 // part of then that packet trails off the end of the page. Continue appending
107 // the pages' packet data until we hit a page that either does not end with the
108 // packet that we're fetching or where the last packet is complete.
109
110 ByteVector packet = (*it)->packets()[i - (*it)->firstPacketIndex()];
111
112 while(nextPacketIndex(*it) <= i) {
113 ++it;
114 packet.append((*it)->packets().front());
115 }
116
117 return packet;
118 }
119
setPacket(unsigned int i,const ByteVector & p)120 void Ogg::File::setPacket(unsigned int i, const ByteVector &p)
121 {
122 if(!readPages(i)) {
123 debug("Ogg::File::setPacket() -- Could not set the requested packet.");
124 return;
125 }
126
127 d->dirtyPackets[i] = p;
128 }
129
firstPageHeader()130 const Ogg::PageHeader *Ogg::File::firstPageHeader()
131 {
132 if(!d->firstPageHeader) {
133 const long firstPageHeaderOffset = find("OggS");
134 if(firstPageHeaderOffset < 0)
135 return 0;
136
137 d->firstPageHeader = new PageHeader(this, firstPageHeaderOffset);
138 }
139
140 return d->firstPageHeader->isValid() ? d->firstPageHeader : 0;
141 }
142
lastPageHeader()143 const Ogg::PageHeader *Ogg::File::lastPageHeader()
144 {
145 if(!d->lastPageHeader) {
146 const long lastPageHeaderOffset = rfind("OggS");
147 if(lastPageHeaderOffset < 0)
148 return 0;
149
150 d->lastPageHeader = new PageHeader(this, lastPageHeaderOffset);
151 }
152
153 return d->lastPageHeader->isValid() ? d->lastPageHeader : 0;
154 }
155
save()156 bool Ogg::File::save()
157 {
158 if(readOnly()) {
159 debug("Ogg::File::save() - Cannot save to a read only file.");
160 return false;
161 }
162
163 Map<unsigned int, ByteVector>::ConstIterator it;
164 for(it = d->dirtyPackets.begin(); it != d->dirtyPackets.end(); ++it)
165 writePacket(it->first, it->second);
166
167 d->dirtyPackets.clear();
168
169 return true;
170 }
171
172 ////////////////////////////////////////////////////////////////////////////////
173 // protected members
174 ////////////////////////////////////////////////////////////////////////////////
175
File(FileName file)176 Ogg::File::File(FileName file) :
177 TagLib::File(file),
178 d(new FilePrivate())
179 {
180 }
181
File(IOStream * stream)182 Ogg::File::File(IOStream *stream) :
183 TagLib::File(stream),
184 d(new FilePrivate())
185 {
186 }
187
188 ////////////////////////////////////////////////////////////////////////////////
189 // private members
190 ////////////////////////////////////////////////////////////////////////////////
191
readPages(unsigned int i)192 bool Ogg::File::readPages(unsigned int i)
193 {
194 while(true) {
195 unsigned int packetIndex;
196 long offset;
197
198 if(d->pages.isEmpty()) {
199 packetIndex = 0;
200 offset = find("OggS");
201 if(offset < 0)
202 return false;
203 }
204 else {
205 const Page *page = d->pages.back();
206 packetIndex = nextPacketIndex(page);
207 offset = page->fileOffset() + page->size();
208 }
209
210 // Enough pages have been fetched.
211
212 if(packetIndex > i)
213 return true;
214
215 // Read the next page and add it to the page list.
216
217 Page *nextPage = new Page(this, offset);
218 if(!nextPage->header()->isValid()) {
219 delete nextPage;
220 return false;
221 }
222
223 nextPage->setFirstPacketIndex(packetIndex);
224 d->pages.append(nextPage);
225
226 if(nextPage->header()->lastPageOfStream())
227 return false;
228 }
229 }
230
writePacket(unsigned int i,const ByteVector & packet)231 void Ogg::File::writePacket(unsigned int i, const ByteVector &packet)
232 {
233 if(!readPages(i)) {
234 debug("Ogg::File::writePacket() -- Could not find the requested packet.");
235 return;
236 }
237
238 // Look for the pages where the requested packet should belong to.
239
240 List<Page *>::ConstIterator it = d->pages.begin();
241 while((*it)->containsPacket(i) == Page::DoesNotContainPacket)
242 ++it;
243
244 const Page *firstPage = *it;
245
246 while(nextPacketIndex(*it) <= i)
247 ++it;
248
249 const Page *lastPage = *it;
250
251 // Replace the requested packet and create new pages to replace the located pages.
252
253 ByteVectorList packets = firstPage->packets();
254 packets[i - firstPage->firstPacketIndex()] = packet;
255
256 if(firstPage != lastPage && lastPage->packetCount() > 1) {
257 ByteVectorList lastPagePackets = lastPage->packets();
258 lastPagePackets.erase(lastPagePackets.begin());
259 packets.append(lastPagePackets);
260 }
261
262 // TODO: This pagination method isn't accurate for what's being done here.
263 // This should account for real possibilities like non-aligned packets and such.
264
265 List<Page *> pages = Page::paginate(packets,
266 Page::SinglePagePerGroup,
267 firstPage->header()->streamSerialNumber(),
268 firstPage->pageSequenceNumber(),
269 firstPage->header()->firstPacketContinued(),
270 lastPage->header()->lastPacketCompleted());
271 pages.setAutoDelete(true);
272
273 // Write the pages.
274
275 ByteVector data;
276 for(it = pages.begin(); it != pages.end(); ++it)
277 data.append((*it)->render());
278
279 const unsigned long originalOffset = firstPage->fileOffset();
280 const unsigned long originalLength = lastPage->fileOffset() + lastPage->size() - originalOffset;
281
282 insert(data, originalOffset, originalLength);
283
284 // Renumber the following pages if the pages have been split or merged.
285
286 const int numberOfNewPages
287 = pages.back()->pageSequenceNumber() - lastPage->pageSequenceNumber();
288
289 if(numberOfNewPages != 0) {
290 long pageOffset = originalOffset + data.size();
291
292 while(true) {
293 Page page(this, pageOffset);
294 if(!page.header()->isValid())
295 break;
296
297 page.setPageSequenceNumber(page.pageSequenceNumber() + numberOfNewPages);
298 const ByteVector data = page.render();
299
300 seek(pageOffset + 18);
301 writeBlock(data.mid(18, 8));
302
303 if(page.header()->lastPageOfStream())
304 break;
305
306 pageOffset += page.size();
307 }
308 }
309
310 // Discard all the pages to keep them up-to-date by fetching them again.
311
312 d->pages.clear();
313 }
314