-
Notifications
You must be signed in to change notification settings - Fork 0
/
CueDocument.cpp
390 lines (356 loc) · 12.4 KB
/
CueDocument.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
/*
* Copyright (C) 2005-2008 Team XBMC
* http://www.xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
* http://www.gnu.org/copyleft/gpl.html
*
*/
////////////////////////////////////////////////////////////////////////////////////
// Class: CueDocument
// This class handles the .cue file format. This is produced by programs such as
// EAC and CDRwin when one extracts audio data from a CD as a continuous .WAV
// containing all the audio tracks in one big file. The .cue file contains all the
// track and timing information. An example file is:
//
// PERFORMER "Pink Floyd"
// TITLE "The Dark Side Of The Moon"
// FILE "The Dark Side Of The Moon.mp3" WAVE
// TRACK 01 AUDIO
// TITLE "Speak To Me / Breathe"
// PERFORMER "Pink Floyd"
// INDEX 00 00:00:00
// INDEX 01 00:00:32
// TRACK 02 AUDIO
// TITLE "On The Run"
// PERFORMER "Pink Floyd"
// INDEX 00 03:58:72
// INDEX 01 04:00:72
// TRACK 03 AUDIO
// TITLE "Time"
// PERFORMER "Pink Floyd"
// INDEX 00 07:31:70
// INDEX 01 07:33:70
//
// etc.
//
// The CCueDocument class member functions extract this information, and construct
// the playlist items needed to seek to a track directly. This works best on CBR
// compressed files - VBR files do not seek accurately enough for it to work well.
//
////////////////////////////////////////////////////////////////////////////////////
#include "CueDocument.h"
#include "utils/log.h"
#include "utils/URIUtils.h"
#include "utils/StringUtils.h"
#include "utils/CharsetConverter.h"
#include "filesystem/File.h"
#include "filesystem/Directory.h"
#include "FileItem.h"
#include <set>
using namespace std;
using namespace XFILE;
CCueDocument::CCueDocument(void)
{
m_strArtist = "";
m_strAlbum = "";
m_strGenre = "";
m_iYear = 0;
m_replayGainAlbumPeak = 0.0f;
m_replayGainAlbumGain = 0.0f;
m_iTotalTracks = 0;
m_iTrack = 0;
}
CCueDocument::~CCueDocument(void)
{}
////////////////////////////////////////////////////////////////////////////////////
// Function: Parse()
// Opens the .cue file for reading, and constructs the track database information
////////////////////////////////////////////////////////////////////////////////////
bool CCueDocument::Parse(const CStdString &strFile)
{
if (!m_file.Open(strFile))
return false;
CStdString strLine;
m_iTotalTracks = -1;
CStdString strCurrentFile = "";
bool bCurrentFileChanged = false;
int time;
// Run through the .CUE file and extract the tracks...
while (true)
{
if (!ReadNextLine(strLine))
break;
if (strLine.Left(7) == "INDEX 0")
{
if (bCurrentFileChanged)
{
OutputDebugString("Track split over multiple files, unsupported");
return false;
}
// find the end of the number section
time = ExtractTimeFromIndex(strLine);
if (time == -1)
{ // Error!
OutputDebugString("Mangled Time in INDEX 0x tag in CUE file!\n");
return false;
}
if (m_iTotalTracks > 0) // Set the end time of the last track
m_Track[m_iTotalTracks - 1].iEndTime = time;
if (m_iTotalTracks >= 0)
m_Track[m_iTotalTracks].iStartTime = time; // start time of the next track
}
else if (strLine.Left(5) == "TITLE")
{
if (m_iTotalTracks == -1) // No tracks yet
ExtractQuoteInfo(strLine, m_strAlbum);
else if (!ExtractQuoteInfo(strLine, m_Track[m_iTotalTracks].strTitle))
{
// lets manage tracks titles without quotes
CStdString titleNoQuote = strLine.Mid(5);
titleNoQuote.TrimLeft();
if (!titleNoQuote.IsEmpty())
{
g_charsetConverter.unknownToUTF8(titleNoQuote);
m_Track[m_iTotalTracks].strTitle = titleNoQuote;
}
}
}
else if (strLine.Left(9) == "PERFORMER")
{
if (m_iTotalTracks == -1) // No tracks yet
ExtractQuoteInfo(strLine, m_strArtist);
else // New Artist for this track
ExtractQuoteInfo(strLine, m_Track[m_iTotalTracks].strArtist);
}
else if (strLine.Left(5) == "TRACK")
{
int iTrackNumber = ExtractNumericInfo(strLine.c_str() + 5);
m_iTotalTracks++;
CCueTrack track;
m_Track.push_back(track);
m_Track[m_iTotalTracks].strFile = strCurrentFile;
if (iTrackNumber > 0)
m_Track[m_iTotalTracks].iTrackNumber = iTrackNumber;
else
m_Track[m_iTotalTracks].iTrackNumber = m_iTotalTracks + 1;
bCurrentFileChanged = false;
}
else if (strLine.Left(4) == "FILE")
{
// already a file name? then the time computation will be changed
if(strCurrentFile.size() > 0)
bCurrentFileChanged = true;
ExtractQuoteInfo(strLine, strCurrentFile);
// Resolve absolute paths (if needed).
if (strCurrentFile.length() > 0)
ResolvePath(strCurrentFile, strFile);
}
else if (strLine.Left(8) == "REM DATE")
{
int iYear = ExtractNumericInfo(strLine.c_str() + 8);
if (iYear > 0)
m_iYear = iYear;
}
else if (strLine.Left(9) == "REM GENRE")
{
if (!ExtractQuoteInfo(strLine, m_strGenre))
{
CStdString genreNoQuote = strLine.Mid(9);
genreNoQuote.TrimLeft();
if (!genreNoQuote.IsEmpty())
{
g_charsetConverter.unknownToUTF8(genreNoQuote);
m_strGenre = genreNoQuote;
}
}
}
else if (strLine.Left(25) == "REM REPLAYGAIN_ALBUM_GAIN")
m_replayGainAlbumGain = (float)atof(strLine.Mid(26));
else if (strLine.Left(25) == "REM REPLAYGAIN_ALBUM_PEAK")
m_replayGainAlbumPeak = (float)atof(strLine.Mid(26));
else if (strLine.Left(25) == "REM REPLAYGAIN_TRACK_GAIN" && m_iTotalTracks >= 0)
m_Track[m_iTotalTracks].replayGainTrackGain = (float)atof(strLine.Mid(26));
else if (strLine.Left(25) == "REM REPLAYGAIN_TRACK_PEAK" && m_iTotalTracks >= 0)
m_Track[m_iTotalTracks].replayGainTrackPeak = (float)atof(strLine.Mid(26));
}
// reset track counter to 0, and fill in the last tracks end time
m_iTrack = 0;
if (m_iTotalTracks >= 0)
m_Track[m_iTotalTracks].iEndTime = 0;
else
OutputDebugString("No INDEX 01 tags in CUE file!\n");
m_file.Close();
if (m_iTotalTracks >= 0)
{
m_iTotalTracks++;
}
return (m_iTotalTracks > 0);
}
//////////////////////////////////////////////////////////////////////////////////
// Function:GetNextItem()
// Returns the track information from the next item in the cuelist
//////////////////////////////////////////////////////////////////////////////////
void CCueDocument::GetSongs(VECSONGS &songs)
{
for (int i = 0; i < m_iTotalTracks; i++)
{
CSong song;
if ((m_Track[i].strArtist.length() == 0) && (m_strArtist.length() > 0))
song.strArtist = m_strArtist;
else
song.strArtist = m_Track[i].strArtist;
song.strAlbumArtist = m_strArtist;
song.strAlbum = m_strAlbum;
song.strGenre = m_strGenre;
song.iYear = m_iYear;
song.iTrack = m_Track[i].iTrackNumber;
if (m_Track[i].strTitle.length() == 0) // No track information for this track!
song.strTitle.Format("Track %2d", i + 1);
else
song.strTitle = m_Track[i].strTitle;
song.strFileName = m_Track[i].strFile;
song.iStartOffset = m_Track[i].iStartTime;
song.iEndOffset = m_Track[i].iEndTime;
if (song.iEndOffset)
song.iDuration = (song.iEndOffset - song.iStartOffset + 37) / 75;
else
song.iDuration = 0;
// TODO: replayGain goes here
songs.push_back(song);
}
}
void CCueDocument::GetMediaFiles(vector<CStdString>& mediaFiles)
{
set<CStdString> uniqueFiles;
for (int i = 0; i < m_iTotalTracks; i++)
uniqueFiles.insert(m_Track[i].strFile);
for (set<CStdString>::iterator it = uniqueFiles.begin(); it != uniqueFiles.end(); it++)
mediaFiles.push_back(*it);
}
CStdString CCueDocument::GetMediaTitle()
{
return m_strAlbum;
}
// Private Functions start here
////////////////////////////////////////////////////////////////////////////////////
// Function: ReadNextLine()
// Returns the next non-blank line of the textfile, stripping any whitespace from
// the left.
////////////////////////////////////////////////////////////////////////////////////
bool CCueDocument::ReadNextLine(CStdString &szLine)
{
char *pos;
// Read the next line.
while (m_file.ReadString(m_szBuffer, 1023)) // Bigger than MAX_PATH_SIZE, for usage with relax!
{
// Remove the white space at the beginning of the line.
pos = m_szBuffer;
while (pos && (*pos == ' ' || *pos == '\t' || *pos == '\r' || *pos == '\n')) pos++;
if (pos)
{
szLine = pos;
return true;
}
// If we are here, we have an empty line so try the next line
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////
// Function: ExtractQuoteInfo()
// Extracts the information in quotes from the string line, returning it in quote
////////////////////////////////////////////////////////////////////////////////////
bool CCueDocument::ExtractQuoteInfo(const CStdString &line, CStdString "e)
{
quote.Empty();
int left = line.Find('\"');
if (left < 0) return false;
int right = line.Find('\"', left + 1);
if (right < 0) return false;
quote = line.Mid(left + 1, right - left - 1);
g_charsetConverter.unknownToUTF8(quote);
return true;
}
////////////////////////////////////////////////////////////////////////////////////
// Function: ExtractTimeFromIndex()
// Extracts the time information from the index string index, returning it as a value in
// milliseconds.
// Assumed format is:
// MM:SS:FF where MM is minutes, SS seconds, and FF frames (75 frames in a second)
////////////////////////////////////////////////////////////////////////////////////
int CCueDocument::ExtractTimeFromIndex(const CStdString &index)
{
// Get rid of the index number and any whitespace
CStdString numberTime = index.Mid(5);
numberTime.TrimLeft();
while (!numberTime.IsEmpty())
{
if (!isdigit(numberTime[0]))
break;
numberTime.erase(0, 1);
}
numberTime.TrimLeft();
// split the resulting string
CStdStringArray time;
StringUtils::SplitString(numberTime, ":", time);
if (time.size() != 3)
return -1;
int mins = atoi(time[0].c_str());
int secs = atoi(time[1].c_str());
int frames = atoi(time[2].c_str());
return (mins*60 + secs)*75 + frames;
}
////////////////////////////////////////////////////////////////////////////////////
// Function: ExtractNumericInfo()
// Extracts the numeric info from the string info, returning it as an integer value
////////////////////////////////////////////////////////////////////////////////////
int CCueDocument::ExtractNumericInfo(const CStdString &info)
{
CStdString number(info);
number.TrimLeft();
if (number.IsEmpty() || !isdigit(number[0]))
return -1;
return atoi(number.c_str());
}
////////////////////////////////////////////////////////////////////////////////////
// Function: ResolvePath()
// Determines whether strPath is a relative path or not, and if so, converts it to an
// absolute path using the path information in strBase
////////////////////////////////////////////////////////////////////////////////////
bool CCueDocument::ResolvePath(CStdString &strPath, const CStdString &strBase)
{
CStdString strDirectory;
URIUtils::GetDirectory(strBase, strDirectory);
CStdString strFilename = strPath;
URIUtils::GetFileName(strFilename);
URIUtils::AddFileToFolder(strDirectory, strFilename, strPath);
// i *hate* windows
if (!CFile::Exists(strPath))
{
CFileItemList items;
CDirectory::GetDirectory(strDirectory,items);
for (int i=0;i<items.Size();++i)
{
if (items[i]->m_strPath.Equals(strPath))
{
strPath = items[i]->m_strPath;
return true;
}
}
CLog::Log(LOGERROR,"Could not find FILE referenced in cue, case sensitivity issue?");
return false;
}
return true;
}