1 /***************************************************************************
2     copyright            : (C) 2010 by Alex Novichkov
3     email                : novichko@atnet.ru
4 
5     copyright            : (C) 2006 by Lukáš Lalinský
6     email                : lalinsky@gmail.com
7                            (original WavPack implementation)
8  ***************************************************************************/
9 
10 /***************************************************************************
11  *   This library is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU Lesser General Public License version   *
13  *   2.1 as published by the Free Software Foundation.                     *
14  *                                                                         *
15  *   This library is distributed in the hope that it will be useful, but   *
16  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
17  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
18  *   Lesser General Public License for more details.                       *
19  *                                                                         *
20  *   You should have received a copy of the GNU Lesser General Public      *
21  *   License along with this library; if not, write to the Free Software   *
22  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA         *
23  *   02110-1301  USA                                                       *
24  *                                                                         *
25  *   Alternatively, this file is available under the Mozilla Public        *
26  *   License Version 1.1.  You may obtain a copy of the License at         *
27  *   http://www.mozilla.org/MPL/                                           *
28  ***************************************************************************/
29 
30 #include <tstring.h>
31 #include <tdebug.h>
32 #include <bitset>
33 #include "id3v2tag.h"
34 #include "apeproperties.h"
35 #include "apefile.h"
36 #include "apetag.h"
37 #include "apefooter.h"
38 
39 using namespace TagLib;
40 
41 class APE::Properties::PropertiesPrivate
42 {
43 public:
PropertiesPrivate()44   PropertiesPrivate() :
45     length(0),
46     bitrate(0),
47     sampleRate(0),
48     channels(0),
49     version(0),
50     bitsPerSample(0),
51     sampleFrames(0) {}
52 
53   int length;
54   int bitrate;
55   int sampleRate;
56   int channels;
57   int version;
58   int bitsPerSample;
59   unsigned int sampleFrames;
60 };
61 
62 ////////////////////////////////////////////////////////////////////////////////
63 // public members
64 ////////////////////////////////////////////////////////////////////////////////
65 
Properties(File *,ReadStyle style)66 APE::Properties::Properties(File *, ReadStyle style) :
67   AudioProperties(style),
68   d(new PropertiesPrivate())
69 {
70   debug("APE::Properties::Properties() -- This constructor is no longer used.");
71 }
72 
Properties(File * file,long streamLength,ReadStyle style)73 APE::Properties::Properties(File *file, long streamLength, ReadStyle style) :
74   AudioProperties(style),
75   d(new PropertiesPrivate())
76 {
77   read(file, streamLength);
78 }
79 
~Properties()80 APE::Properties::~Properties()
81 {
82   delete d;
83 }
84 
length() const85 int APE::Properties::length() const
86 {
87   return lengthInSeconds();
88 }
89 
lengthInSeconds() const90 int APE::Properties::lengthInSeconds() const
91 {
92   return d->length / 1000;
93 }
94 
lengthInMilliseconds() const95 int APE::Properties::lengthInMilliseconds() const
96 {
97   return d->length;
98 }
99 
bitrate() const100 int APE::Properties::bitrate() const
101 {
102   return d->bitrate;
103 }
104 
sampleRate() const105 int APE::Properties::sampleRate() const
106 {
107   return d->sampleRate;
108 }
109 
channels() const110 int APE::Properties::channels() const
111 {
112   return d->channels;
113 }
114 
version() const115 int APE::Properties::version() const
116 {
117   return d->version;
118 }
119 
bitsPerSample() const120 int APE::Properties::bitsPerSample() const
121 {
122   return d->bitsPerSample;
123 }
124 
sampleFrames() const125 unsigned int APE::Properties::sampleFrames() const
126 {
127   return d->sampleFrames;
128 }
129 
130 ////////////////////////////////////////////////////////////////////////////////
131 // private members
132 ////////////////////////////////////////////////////////////////////////////////
133 
134 namespace
135 {
headerVersion(const ByteVector & header)136   int headerVersion(const ByteVector &header)
137   {
138     if(header.size() < 6 || !header.startsWith("MAC "))
139       return -1;
140 
141     return header.toUShort(4, false);
142   }
143 }
144 
read(File * file,long streamLength)145 void APE::Properties::read(File *file, long streamLength)
146 {
147   // First, we assume that the file pointer is set at the first descriptor.
148   long offset = file->tell();
149   int version = headerVersion(file->readBlock(6));
150 
151   // Next, we look for the descriptor.
152   if(version < 0) {
153     offset = file->find("MAC ", offset);
154     file->seek(offset);
155     version = headerVersion(file->readBlock(6));
156   }
157 
158   if(version < 0) {
159     debug("APE::Properties::read() -- APE descriptor not found");
160     return;
161   }
162 
163   d->version = version;
164 
165   if(d->version >= 3980)
166     analyzeCurrent(file);
167   else
168     analyzeOld(file);
169 
170   if(d->sampleFrames > 0 && d->sampleRate > 0) {
171     const double length = d->sampleFrames * 1000.0 / d->sampleRate;
172     d->length  = static_cast<int>(length + 0.5);
173     d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
174   }
175 }
176 
analyzeCurrent(File * file)177 void APE::Properties::analyzeCurrent(File *file)
178 {
179   // Read the descriptor
180   file->seek(2, File::Current);
181   const ByteVector descriptor = file->readBlock(44);
182   if(descriptor.size() < 44) {
183     debug("APE::Properties::analyzeCurrent() -- descriptor is too short.");
184     return;
185   }
186 
187   const unsigned int descriptorBytes = descriptor.toUInt(0, false);
188 
189   if((descriptorBytes - 52) > 0)
190     file->seek(descriptorBytes - 52, File::Current);
191 
192   // Read the header
193   const ByteVector header = file->readBlock(24);
194   if(header.size() < 24) {
195     debug("APE::Properties::analyzeCurrent() -- MAC header is too short.");
196     return;
197   }
198 
199   // Get the APE info
200   d->channels      = header.toShort(18, false);
201   d->sampleRate    = header.toUInt(20, false);
202   d->bitsPerSample = header.toShort(16, false);
203 
204   const unsigned int totalFrames = header.toUInt(12, false);
205   if(totalFrames == 0)
206     return;
207 
208   const unsigned int blocksPerFrame   = header.toUInt(4, false);
209   const unsigned int finalFrameBlocks = header.toUInt(8, false);
210   d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
211 }
212 
analyzeOld(File * file)213 void APE::Properties::analyzeOld(File *file)
214 {
215   const ByteVector header = file->readBlock(26);
216   if(header.size() < 26) {
217     debug("APE::Properties::analyzeOld() -- MAC header is too short.");
218     return;
219   }
220 
221   const unsigned int totalFrames = header.toUInt(18, false);
222 
223   // Fail on 0 length APE files (catches non-finalized APE files)
224   if(totalFrames == 0)
225     return;
226 
227   const short compressionLevel = header.toShort(0, false);
228   unsigned int blocksPerFrame;
229   if(d->version >= 3950)
230     blocksPerFrame = 73728 * 4;
231   else if(d->version >= 3900 || (d->version >= 3800 && compressionLevel == 4000))
232     blocksPerFrame = 73728;
233   else
234     blocksPerFrame = 9216;
235 
236   // Get the APE info
237   d->channels   = header.toShort(4, false);
238   d->sampleRate = header.toUInt(6, false);
239 
240   const unsigned int finalFrameBlocks = header.toUInt(22, false);
241   d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks;
242 
243   // Get the bit depth from the RIFF-fmt chunk.
244   file->seek(16, File::Current);
245   const ByteVector fmt = file->readBlock(28);
246   if(fmt.size() < 28 || !fmt.startsWith("WAVEfmt ")) {
247     debug("APE::Properties::analyzeOld() -- fmt header is too short.");
248     return;
249   }
250 
251   d->bitsPerSample = fmt.toShort(26, false);
252 }
253