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