1 /***************************************************************************
2 copyright : (C) 2004-2005 by Allan Sandfeld Jensen
3 email : kde@carewolf.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 <tbytevector.h>
27 #include <tstring.h>
28 #include <tdebug.h>
29 #include <tpropertymap.h>
30 #include <tagutils.h>
31
32 #include <xiphcomment.h>
33 #include "oggflacfile.h"
34
35 using namespace TagLib;
36 using TagLib::FLAC::Properties;
37
38 class Ogg::FLAC::File::FilePrivate
39 {
40 public:
FilePrivate()41 FilePrivate() :
42 comment(0),
43 properties(0),
44 streamStart(0),
45 streamLength(0),
46 scanned(false),
47 hasXiphComment(false),
48 commentPacket(0) {}
49
~FilePrivate()50 ~FilePrivate()
51 {
52 delete comment;
53 delete properties;
54 }
55
56 Ogg::XiphComment *comment;
57
58 Properties *properties;
59 ByteVector streamInfoData;
60 ByteVector xiphCommentData;
61 long streamStart;
62 long streamLength;
63 bool scanned;
64
65 bool hasXiphComment;
66 int commentPacket;
67 };
68
69 ////////////////////////////////////////////////////////////////////////////////
70 // static members
71 ////////////////////////////////////////////////////////////////////////////////
72
isSupported(IOStream * stream)73 bool Ogg::FLAC::File::isSupported(IOStream *stream)
74 {
75 // An Ogg FLAC file has IDs "OggS" and "fLaC" somewhere.
76
77 const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false);
78 return (buffer.find("OggS") >= 0 && buffer.find("fLaC") >= 0);
79 }
80
81 ////////////////////////////////////////////////////////////////////////////////
82 // public members
83 ////////////////////////////////////////////////////////////////////////////////
84
File(FileName file,bool readProperties,Properties::ReadStyle propertiesStyle)85 Ogg::FLAC::File::File(FileName file, bool readProperties,
86 Properties::ReadStyle propertiesStyle) :
87 Ogg::File(file),
88 d(new FilePrivate())
89 {
90 if(isOpen())
91 read(readProperties, propertiesStyle);
92 }
93
File(IOStream * stream,bool readProperties,Properties::ReadStyle propertiesStyle)94 Ogg::FLAC::File::File(IOStream *stream, bool readProperties,
95 Properties::ReadStyle propertiesStyle) :
96 Ogg::File(stream),
97 d(new FilePrivate())
98 {
99 if(isOpen())
100 read(readProperties, propertiesStyle);
101 }
102
~File()103 Ogg::FLAC::File::~File()
104 {
105 delete d;
106 }
107
tag() const108 Ogg::XiphComment *Ogg::FLAC::File::tag() const
109 {
110 return d->comment;
111 }
112
properties() const113 PropertyMap Ogg::FLAC::File::properties() const
114 {
115 return d->comment->properties();
116 }
117
setProperties(const PropertyMap & properties)118 PropertyMap Ogg::FLAC::File::setProperties(const PropertyMap &properties)
119 {
120 return d->comment->setProperties(properties);
121 }
122
audioProperties() const123 Properties *Ogg::FLAC::File::audioProperties() const
124 {
125 return d->properties;
126 }
127
128
save()129 bool Ogg::FLAC::File::save()
130 {
131 d->xiphCommentData = d->comment->render(false);
132
133 // Create FLAC metadata-block:
134
135 // Put the size in the first 32 bit (I assume no more than 24 bit are used)
136
137 ByteVector v = ByteVector::fromUInt(d->xiphCommentData.size());
138
139 // Set the type of the metadata-block to be a Xiph / Vorbis comment
140
141 v[0] = 4;
142
143 // Append the comment-data after the 32 bit header
144
145 v.append(d->xiphCommentData);
146
147 // Save the packet at the old spot
148 // FIXME: Use padding if size is increasing
149
150 setPacket(d->commentPacket, v);
151
152 return Ogg::File::save();
153 }
154
hasXiphComment() const155 bool Ogg::FLAC::File::hasXiphComment() const
156 {
157 return d->hasXiphComment;
158 }
159
160 ////////////////////////////////////////////////////////////////////////////////
161 // private members
162 ////////////////////////////////////////////////////////////////////////////////
163
read(bool readProperties,Properties::ReadStyle propertiesStyle)164 void Ogg::FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
165 {
166 // Sanity: Check if we really have an Ogg/FLAC file
167
168 /*
169 ByteVector oggHeader = packet(0);
170
171 if (oggHeader.mid(28,4) != "fLaC") {
172 debug("Ogg::FLAC::File::read() -- Not an Ogg/FLAC file");
173 setValid(false);
174 return;
175 }*/
176
177 // Look for FLAC metadata, including vorbis comments
178
179 scan();
180
181 if (!d->scanned) {
182 setValid(false);
183 return;
184 }
185
186
187 if(d->hasXiphComment)
188 d->comment = new Ogg::XiphComment(xiphCommentData());
189 else
190 d->comment = new Ogg::XiphComment();
191
192
193 if(readProperties)
194 d->properties = new Properties(streamInfoData(), streamLength(), propertiesStyle);
195 }
196
streamInfoData()197 ByteVector Ogg::FLAC::File::streamInfoData()
198 {
199 scan();
200 return d->streamInfoData;
201 }
202
xiphCommentData()203 ByteVector Ogg::FLAC::File::xiphCommentData()
204 {
205 scan();
206 return d->xiphCommentData;
207 }
208
streamLength()209 long Ogg::FLAC::File::streamLength()
210 {
211 scan();
212 return d->streamLength;
213 }
214
scan()215 void Ogg::FLAC::File::scan()
216 {
217 // Scan the metadata pages
218
219 if(d->scanned)
220 return;
221
222 if(!isValid())
223 return;
224
225 int ipacket = 0;
226 long overhead = 0;
227
228 ByteVector metadataHeader = packet(ipacket);
229 if(metadataHeader.isEmpty())
230 return;
231
232 if(!metadataHeader.startsWith("fLaC")) {
233 // FLAC 1.1.2+
234 // See https://xiph.org/flac/ogg_mapping.html for the header specification.
235 if(metadataHeader.size() < 13)
236 return;
237
238 if(metadataHeader[0] != 0x7f)
239 return;
240
241 if(metadataHeader.mid(1, 4) != "FLAC")
242 return;
243
244 if(metadataHeader[5] != 1 && metadataHeader[6] != 0)
245 return; // not version 1.0
246
247 if(metadataHeader.mid(9, 4) != "fLaC")
248 return;
249
250 metadataHeader = metadataHeader.mid(13);
251 }
252 else {
253 // FLAC 1.1.0 & 1.1.1
254 metadataHeader = packet(++ipacket);
255 }
256
257 ByteVector header = metadataHeader.mid(0, 4);
258 if(header.size() != 4) {
259 debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header");
260 return;
261 }
262
263 // Header format (from spec):
264 // <1> Last-metadata-block flag
265 // <7> BLOCK_TYPE
266 // 0 : STREAMINFO
267 // 1 : PADDING
268 // ..
269 // 4 : VORBIS_COMMENT
270 // ..
271 // <24> Length of metadata to follow
272
273 char blockType = header[0] & 0x7f;
274 bool lastBlock = (header[0] & 0x80) != 0;
275 unsigned int length = header.toUInt(1, 3, true);
276 overhead += length;
277
278 // Sanity: First block should be the stream_info metadata
279
280 if(blockType != 0) {
281 debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC stream");
282 return;
283 }
284
285 d->streamInfoData = metadataHeader.mid(4, length);
286
287 // Search through the remaining metadata
288
289 while(!lastBlock) {
290 metadataHeader = packet(++ipacket);
291 header = metadataHeader.mid(0, 4);
292 if(header.size() != 4) {
293 debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header");
294 return;
295 }
296
297 blockType = header[0] & 0x7f;
298 lastBlock = (header[0] & 0x80) != 0;
299 length = header.toUInt(1, 3, true);
300 overhead += length;
301
302 if(blockType == 1) {
303 // debug("Ogg::FLAC::File::scan() -- Padding found");
304 }
305 else if(blockType == 4) {
306 // debug("Ogg::FLAC::File::scan() -- Vorbis-comments found");
307 d->xiphCommentData = metadataHeader.mid(4, length);
308 d->hasXiphComment = true;
309 d->commentPacket = ipacket;
310 }
311 else if(blockType > 5) {
312 debug("Ogg::FLAC::File::scan() -- Unknown metadata block");
313 }
314 }
315
316 // End of metadata, now comes the datastream
317 d->streamStart = overhead;
318 d->streamLength = File::length() - d->streamStart;
319
320 d->scanned = true;
321 }
322