2 * Copyright (C) 2005-2012 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 ////////////////////////////////////////////////////////////////////////////////////
23 // This class handles the .cue file format. This is produced by programs such as
24 // EAC and CDRwin when one extracts audio data from a CD as a continuous .WAV
25 // containing all the audio tracks in one big file. The .cue file contains all the
26 // track and timing information. An example file is:
28 // PERFORMER "Pink Floyd"
29 // TITLE "The Dark Side Of The Moon"
30 // FILE "The Dark Side Of The Moon.mp3" WAVE
32 // TITLE "Speak To Me / Breathe"
33 // PERFORMER "Pink Floyd"
38 // PERFORMER "Pink Floyd"
43 // PERFORMER "Pink Floyd"
49 // The CCueDocument class member functions extract this information, and construct
50 // the playlist items needed to seek to a track directly. This works best on CBR
51 // compressed files - VBR files do not seek accurately enough for it to work well.
53 ////////////////////////////////////////////////////////////////////////////////////
55 #include "CueDocument.h"
56 #include "utils/log.h"
57 #include "utils/URIUtils.h"
58 #include "utils/StringUtils.h"
59 #include "utils/CharsetConverter.h"
60 #include "filesystem/File.h"
61 #include "filesystem/Directory.h"
63 #include "settings/AdvancedSettings.h"
68 using namespace XFILE;
70 CCueDocument::CCueDocument(void)
76 m_replayGainAlbumPeak = 0.0f;
77 m_replayGainAlbumGain = 0.0f;
82 CCueDocument::~CCueDocument(void)
85 ////////////////////////////////////////////////////////////////////////////////////
87 // Opens the .cue file for reading, and constructs the track database information
88 ////////////////////////////////////////////////////////////////////////////////////
89 bool CCueDocument::Parse(const CStdString &strFile)
91 if (!m_file.Open(strFile))
96 CStdString strCurrentFile = "";
97 bool bCurrentFileChanged = false;
100 // Run through the .CUE file and extract the tracks...
103 if (!ReadNextLine(strLine))
105 if (strLine.Left(8) == "INDEX 01")
107 if (bCurrentFileChanged)
109 OutputDebugString("Track split over multiple files, unsupported");
113 // find the end of the number section
114 time = ExtractTimeFromIndex(strLine);
117 OutputDebugString("Mangled Time in INDEX 0x tag in CUE file!\n");
120 if (m_iTotalTracks > 0) // Set the end time of the last track
121 m_Track[m_iTotalTracks - 1].iEndTime = time;
123 if (m_iTotalTracks >= 0)
124 m_Track[m_iTotalTracks].iStartTime = time; // start time of the next track
126 else if (strLine.Left(5) == "TITLE")
128 if (m_iTotalTracks == -1) // No tracks yet
129 ExtractQuoteInfo(strLine, m_strAlbum);
130 else if (!ExtractQuoteInfo(strLine, m_Track[m_iTotalTracks].strTitle))
132 // lets manage tracks titles without quotes
133 CStdString titleNoQuote = strLine.Mid(5);
134 titleNoQuote.TrimLeft();
135 if (!titleNoQuote.IsEmpty())
137 g_charsetConverter.unknownToUTF8(titleNoQuote);
138 m_Track[m_iTotalTracks].strTitle = titleNoQuote;
142 else if (strLine.Left(9) == "PERFORMER")
144 if (m_iTotalTracks == -1) // No tracks yet
145 ExtractQuoteInfo(strLine, m_strArtist);
146 else // New Artist for this track
147 ExtractQuoteInfo(strLine, m_Track[m_iTotalTracks].strArtist);
149 else if (strLine.Left(5) == "TRACK")
151 int iTrackNumber = ExtractNumericInfo(strLine.c_str() + 5);
156 m_Track.push_back(track);
157 m_Track[m_iTotalTracks].strFile = strCurrentFile;
159 if (iTrackNumber > 0)
160 m_Track[m_iTotalTracks].iTrackNumber = iTrackNumber;
162 m_Track[m_iTotalTracks].iTrackNumber = m_iTotalTracks + 1;
164 bCurrentFileChanged = false;
166 else if (strLine.Left(4) == "FILE")
168 // already a file name? then the time computation will be changed
169 if(strCurrentFile.size() > 0)
170 bCurrentFileChanged = true;
172 ExtractQuoteInfo(strLine, strCurrentFile);
174 // Resolve absolute paths (if needed).
175 if (strCurrentFile.length() > 0)
176 ResolvePath(strCurrentFile, strFile);
178 else if (strLine.Left(8) == "REM DATE")
180 int iYear = ExtractNumericInfo(strLine.c_str() + 8);
184 else if (strLine.Left(9) == "REM GENRE")
186 if (!ExtractQuoteInfo(strLine, m_strGenre))
188 CStdString genreNoQuote = strLine.Mid(9);
189 genreNoQuote.TrimLeft();
190 if (!genreNoQuote.IsEmpty())
192 g_charsetConverter.unknownToUTF8(genreNoQuote);
193 m_strGenre = genreNoQuote;
197 else if (strLine.Left(25) == "REM REPLAYGAIN_ALBUM_GAIN")
198 m_replayGainAlbumGain = (float)atof(strLine.Mid(26));
199 else if (strLine.Left(25) == "REM REPLAYGAIN_ALBUM_PEAK")
200 m_replayGainAlbumPeak = (float)atof(strLine.Mid(26));
201 else if (strLine.Left(25) == "REM REPLAYGAIN_TRACK_GAIN" && m_iTotalTracks >= 0)
202 m_Track[m_iTotalTracks].replayGainTrackGain = (float)atof(strLine.Mid(26));
203 else if (strLine.Left(25) == "REM REPLAYGAIN_TRACK_PEAK" && m_iTotalTracks >= 0)
204 m_Track[m_iTotalTracks].replayGainTrackPeak = (float)atof(strLine.Mid(26));
207 // reset track counter to 0, and fill in the last tracks end time
209 if (m_iTotalTracks >= 0)
210 m_Track[m_iTotalTracks].iEndTime = 0;
212 OutputDebugString("No INDEX 01 tags in CUE file!\n");
214 if (m_iTotalTracks >= 0)
218 return (m_iTotalTracks > 0);
221 //////////////////////////////////////////////////////////////////////////////////
222 // Function:GetNextItem()
223 // Returns the track information from the next item in the cuelist
224 //////////////////////////////////////////////////////////////////////////////////
225 void CCueDocument::GetSongs(VECSONGS &songs)
227 for (int i = 0; i < m_iTotalTracks; i++)
230 if ((m_Track[i].strArtist.length() == 0) && (m_strArtist.length() > 0))
231 song.artist = StringUtils::Split(m_strArtist, g_advancedSettings.m_musicItemSeparator);
233 song.artist = StringUtils::Split(m_Track[i].strArtist, g_advancedSettings.m_musicItemSeparator);
234 song.albumArtist = StringUtils::Split(m_strArtist, g_advancedSettings.m_musicItemSeparator);
235 song.strAlbum = m_strAlbum;
236 song.genre = StringUtils::Split(m_strGenre, g_advancedSettings.m_musicItemSeparator);
237 song.iYear = m_iYear;
238 song.iTrack = m_Track[i].iTrackNumber;
239 if (m_Track[i].strTitle.length() == 0) // No track information for this track!
240 song.strTitle.Format("Track %2d", i + 1);
242 song.strTitle = m_Track[i].strTitle;
243 song.strFileName = m_Track[i].strFile;
244 song.iStartOffset = m_Track[i].iStartTime;
245 song.iEndOffset = m_Track[i].iEndTime;
247 song.iDuration = (song.iEndOffset - song.iStartOffset + 37) / 75;
250 // TODO: replayGain goes here
251 songs.push_back(song);
255 void CCueDocument::GetMediaFiles(vector<CStdString>& mediaFiles)
257 set<CStdString> uniqueFiles;
258 for (int i = 0; i < m_iTotalTracks; i++)
259 uniqueFiles.insert(m_Track[i].strFile);
261 for (set<CStdString>::iterator it = uniqueFiles.begin(); it != uniqueFiles.end(); it++)
262 mediaFiles.push_back(*it);
265 CStdString CCueDocument::GetMediaTitle()
270 // Private Functions start here
272 ////////////////////////////////////////////////////////////////////////////////////
273 // Function: ReadNextLine()
274 // Returns the next non-blank line of the textfile, stripping any whitespace from
276 ////////////////////////////////////////////////////////////////////////////////////
277 bool CCueDocument::ReadNextLine(CStdString &szLine)
279 // Read the next line.
280 while (m_file.ReadString(m_szBuffer, 1023)) // Bigger than MAX_PATH_SIZE, for usage with relax!
282 // Remove the white space at the beginning and end of the line.
287 // If we are here, we have an empty line so try the next line
292 ////////////////////////////////////////////////////////////////////////////////////
293 // Function: ExtractQuoteInfo()
294 // Extracts the information in quotes from the string line, returning it in quote
295 ////////////////////////////////////////////////////////////////////////////////////
296 bool CCueDocument::ExtractQuoteInfo(const CStdString &line, CStdString "e)
299 int left = line.Find('\"');
300 if (left < 0) return false;
301 int right = line.Find('\"', left + 1);
302 if (right < 0) return false;
303 quote = line.Mid(left + 1, right - left - 1);
304 g_charsetConverter.unknownToUTF8(quote);
308 ////////////////////////////////////////////////////////////////////////////////////
309 // Function: ExtractTimeFromIndex()
310 // Extracts the time information from the index string index, returning it as a value in
312 // Assumed format is:
313 // MM:SS:FF where MM is minutes, SS seconds, and FF frames (75 frames in a second)
314 ////////////////////////////////////////////////////////////////////////////////////
315 int CCueDocument::ExtractTimeFromIndex(const CStdString &index)
317 // Get rid of the index number and any whitespace
318 CStdString numberTime = index.Mid(5);
319 numberTime.TrimLeft();
320 while (!numberTime.IsEmpty())
322 if (!isdigit(numberTime[0]))
324 numberTime.erase(0, 1);
326 numberTime.TrimLeft();
327 // split the resulting string
328 CStdStringArray time;
329 StringUtils::SplitString(numberTime, ":", time);
330 if (time.size() != 3)
333 int mins = atoi(time[0].c_str());
334 int secs = atoi(time[1].c_str());
335 int frames = atoi(time[2].c_str());
337 return (mins*60 + secs)*75 + frames;
340 ////////////////////////////////////////////////////////////////////////////////////
341 // Function: ExtractNumericInfo()
342 // Extracts the numeric info from the string info, returning it as an integer value
343 ////////////////////////////////////////////////////////////////////////////////////
344 int CCueDocument::ExtractNumericInfo(const CStdString &info)
346 CStdString number(info);
348 if (number.IsEmpty() || !isdigit(number[0]))
350 return atoi(number.c_str());
353 ////////////////////////////////////////////////////////////////////////////////////
354 // Function: ResolvePath()
355 // Determines whether strPath is a relative path or not, and if so, converts it to an
356 // absolute path using the path information in strBase
357 ////////////////////////////////////////////////////////////////////////////////////
358 bool CCueDocument::ResolvePath(CStdString &strPath, const CStdString &strBase)
360 CStdString strDirectory;
361 URIUtils::GetDirectory(strBase, strDirectory);
363 CStdString strFilename = strPath;
364 URIUtils::GetFileName(strFilename);
366 URIUtils::AddFileToFolder(strDirectory, strFilename, strPath);
369 if (!CFile::Exists(strPath))
372 CDirectory::GetDirectory(strDirectory,items);
373 for (int i=0;i<items.Size();++i)
375 if (items[i]->GetPath().Equals(strPath))
377 strPath = items[i]->GetPath();
381 CLog::Log(LOGERROR,"Could not find FILE referenced in cue, case sensitivity issue?");