2 * Copyright (C) 2005-2013 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
;
83 CCueDocument::~CCueDocument(void)
86 ////////////////////////////////////////////////////////////////////////////////////
88 // Opens the .cue file for reading, and constructs the track database information
89 ////////////////////////////////////////////////////////////////////////////////////
90 bool CCueDocument::Parse(const CStdString
&strFile
)
92 if (!m_file
.Open(strFile
))
97 CStdString strCurrentFile
= "";
98 bool bCurrentFileChanged
= false;
101 // Run through the .CUE file and extract the tracks...
104 if (!ReadNextLine(strLine
))
106 if (StringUtils::StartsWithNoCase(strLine
,"INDEX 01"))
108 if (bCurrentFileChanged
)
110 OutputDebugString("Track split over multiple files, unsupported ('" + strFile
+ "')\n");
114 // find the end of the number section
115 time
= ExtractTimeFromIndex(strLine
);
118 OutputDebugString("Mangled Time in INDEX 0x tag in CUE file!\n");
121 if (m_iTotalTracks
> 0) // Set the end time of the last track
122 m_Track
[m_iTotalTracks
- 1].iEndTime
= time
;
124 if (m_iTotalTracks
>= 0)
125 m_Track
[m_iTotalTracks
].iStartTime
= time
; // start time of the next track
127 else if (StringUtils::StartsWithNoCase(strLine
,"TITLE"))
129 if (m_iTotalTracks
== -1) // No tracks yet
130 m_strAlbum
= ExtractInfo(strLine
.substr(5));
132 m_Track
[m_iTotalTracks
].strTitle
= ExtractInfo(strLine
.substr(5));
134 else if (StringUtils::StartsWithNoCase(strLine
,"PERFORMER"))
136 if (m_iTotalTracks
== -1) // No tracks yet
137 m_strArtist
= ExtractInfo(strLine
.substr(9));
138 else // New Artist for this track
139 m_Track
[m_iTotalTracks
].strArtist
= ExtractInfo(strLine
.substr(9));
141 else if (StringUtils::StartsWithNoCase(strLine
,"TRACK"))
143 int iTrackNumber
= ExtractNumericInfo(strLine
.substr(5));
148 m_Track
.push_back(track
);
149 m_Track
[m_iTotalTracks
].strFile
= strCurrentFile
;
151 if (iTrackNumber
> 0)
152 m_Track
[m_iTotalTracks
].iTrackNumber
= iTrackNumber
;
154 m_Track
[m_iTotalTracks
].iTrackNumber
= m_iTotalTracks
+ 1;
156 bCurrentFileChanged
= false;
158 else if (StringUtils::StartsWithNoCase(strLine
,"REM DISCNUMBER"))
160 int iDiscNumber
= ExtractNumericInfo(strLine
.substr(14));
162 m_iDiscNumber
= iDiscNumber
;
164 else if (StringUtils::StartsWithNoCase(strLine
,"FILE"))
166 // already a file name? then the time computation will be changed
167 if(strCurrentFile
.size() > 0)
168 bCurrentFileChanged
= true;
170 strCurrentFile
= ExtractInfo(strLine
.substr(4));
172 // Resolve absolute paths (if needed).
173 if (strCurrentFile
.length() > 0)
174 ResolvePath(strCurrentFile
, strFile
);
176 else if (StringUtils::StartsWithNoCase(strLine
,"REM DATE"))
178 int iYear
= ExtractNumericInfo(strLine
.substr(8));
182 else if (StringUtils::StartsWithNoCase(strLine
,"REM GENRE"))
184 m_strGenre
= ExtractInfo(strLine
.substr(9));
186 else if (StringUtils::StartsWithNoCase(strLine
,"REM REPLAYGAIN_ALBUM_GAIN"))
187 m_replayGainAlbumGain
= (float)atof(strLine
.substr(26).c_str());
188 else if (StringUtils::StartsWithNoCase(strLine
,"REM REPLAYGAIN_ALBUM_PEAK"))
189 m_replayGainAlbumPeak
= (float)atof(strLine
.substr(26).c_str());
190 else if (StringUtils::StartsWithNoCase(strLine
,"REM REPLAYGAIN_TRACK_GAIN") && m_iTotalTracks
>= 0)
191 m_Track
[m_iTotalTracks
].replayGainTrackGain
= (float)atof(strLine
.substr(26).c_str());
192 else if (StringUtils::StartsWithNoCase(strLine
,"REM REPLAYGAIN_TRACK_PEAK") && m_iTotalTracks
>= 0)
193 m_Track
[m_iTotalTracks
].replayGainTrackPeak
= (float)atof(strLine
.substr(26).c_str());
196 // reset track counter to 0, and fill in the last tracks end time
198 if (m_iTotalTracks
>= 0)
199 m_Track
[m_iTotalTracks
].iEndTime
= 0;
201 OutputDebugString("No INDEX 01 tags in CUE file!\n");
203 if (m_iTotalTracks
>= 0)
207 return (m_iTotalTracks
> 0);
210 //////////////////////////////////////////////////////////////////////////////////
211 // Function:GetNextItem()
212 // Returns the track information from the next item in the cuelist
213 //////////////////////////////////////////////////////////////////////////////////
214 void CCueDocument::GetSongs(VECSONGS
&songs
)
216 for (int i
= 0; i
< m_iTotalTracks
; i
++)
219 if ((m_Track
[i
].strArtist
.length() == 0) && (m_strArtist
.length() > 0))
220 song
.artist
= StringUtils::Split(m_strArtist
, g_advancedSettings
.m_musicItemSeparator
);
222 song
.artist
= StringUtils::Split(m_Track
[i
].strArtist
, g_advancedSettings
.m_musicItemSeparator
);
223 song
.albumArtist
= StringUtils::Split(m_strArtist
, g_advancedSettings
.m_musicItemSeparator
);
224 song
.strAlbum
= m_strAlbum
;
225 song
.genre
= StringUtils::Split(m_strGenre
, g_advancedSettings
.m_musicItemSeparator
);
226 song
.iYear
= m_iYear
;
227 song
.iTrack
= m_Track
[i
].iTrackNumber
;
228 if ( m_iDiscNumber
> 0 )
229 song
.iTrack
|= (m_iDiscNumber
<< 16); // see CMusicInfoTag::GetDiscNumber()
230 if (m_Track
[i
].strTitle
.length() == 0) // No track information for this track!
231 song
.strTitle
= StringUtils::Format("Track %2d", i
+ 1);
233 song
.strTitle
= m_Track
[i
].strTitle
;
234 song
.strFileName
= m_Track
[i
].strFile
;
235 song
.iStartOffset
= m_Track
[i
].iStartTime
;
236 song
.iEndOffset
= m_Track
[i
].iEndTime
;
238 song
.iDuration
= (song
.iEndOffset
- song
.iStartOffset
+ 37) / 75;
241 // TODO: replayGain goes here
242 songs
.push_back(song
);
246 void CCueDocument::GetMediaFiles(vector
<CStdString
>& mediaFiles
)
248 set
<CStdString
> uniqueFiles
;
249 for (int i
= 0; i
< m_iTotalTracks
; i
++)
250 uniqueFiles
.insert(m_Track
[i
].strFile
);
252 for (set
<CStdString
>::iterator it
= uniqueFiles
.begin(); it
!= uniqueFiles
.end(); it
++)
253 mediaFiles
.push_back(*it
);
256 CStdString
CCueDocument::GetMediaTitle()
261 // Private Functions start here
263 ////////////////////////////////////////////////////////////////////////////////////
264 // Function: ReadNextLine()
265 // Returns the next non-blank line of the textfile, stripping any whitespace from
267 ////////////////////////////////////////////////////////////////////////////////////
268 bool CCueDocument::ReadNextLine(CStdString
&szLine
)
270 // Read the next line.
271 while (m_file
.ReadString(m_szBuffer
, 1023)) // Bigger than MAX_PATH_SIZE, for usage with relax!
273 // Remove the white space at the beginning and end of the line.
275 StringUtils::Trim(szLine
);
278 // If we are here, we have an empty line so try the next line
283 ////////////////////////////////////////////////////////////////////////////////////
284 // Function: ExtractInfo()
285 // Extracts the information in quotes from the string line, returning it in quote
286 ////////////////////////////////////////////////////////////////////////////////////
287 CStdString
CCueDocument::ExtractInfo(const CStdString
&line
)
289 size_t left
= line
.find('\"');
290 if (left
!= std::string::npos
)
292 size_t right
= line
.find('\"', left
+ 1);
293 if (right
!= std::string::npos
)
295 CStdString text
= line
.substr(left
+ 1, right
- left
- 1);
296 g_charsetConverter
.unknownToUTF8(text
);
300 CStdString text
= line
;
301 StringUtils::Trim(text
);
302 g_charsetConverter
.unknownToUTF8(text
);
306 ////////////////////////////////////////////////////////////////////////////////////
307 // Function: ExtractTimeFromIndex()
308 // Extracts the time information from the index string index, returning it as a value in
310 // Assumed format is:
311 // MM:SS:FF where MM is minutes, SS seconds, and FF frames (75 frames in a second)
312 ////////////////////////////////////////////////////////////////////////////////////
313 int CCueDocument::ExtractTimeFromIndex(const CStdString
&index
)
315 // Get rid of the index number and any whitespace
316 CStdString numberTime
= index
.substr(5);
317 StringUtils::TrimLeft(numberTime
);
318 while (!numberTime
.empty())
320 if (!isdigit(numberTime
[0]))
322 numberTime
.erase(0, 1);
324 StringUtils::TrimLeft(numberTime
);
325 // split the resulting string
326 CStdStringArray time
;
327 StringUtils::SplitString(numberTime
, ":", time
);
328 if (time
.size() != 3)
331 int mins
= atoi(time
[0].c_str());
332 int secs
= atoi(time
[1].c_str());
333 int frames
= atoi(time
[2].c_str());
335 return (mins
*60 + secs
)*75 + frames
;
338 ////////////////////////////////////////////////////////////////////////////////////
339 // Function: ExtractNumericInfo()
340 // Extracts the numeric info from the string info, returning it as an integer value
341 ////////////////////////////////////////////////////////////////////////////////////
342 int CCueDocument::ExtractNumericInfo(const CStdString
&info
)
344 CStdString
number(info
);
345 StringUtils::TrimLeft(number
);
346 if (number
.empty() || !isdigit(number
[0]))
348 return atoi(number
.c_str());
351 ////////////////////////////////////////////////////////////////////////////////////
352 // Function: ResolvePath()
353 // Determines whether strPath is a relative path or not, and if so, converts it to an
354 // absolute path using the path information in strBase
355 ////////////////////////////////////////////////////////////////////////////////////
356 bool CCueDocument::ResolvePath(CStdString
&strPath
, const CStdString
&strBase
)
358 CStdString strDirectory
= URIUtils::GetDirectory(strBase
);
359 CStdString strFilename
= URIUtils::GetFileName(strPath
);
361 strPath
= URIUtils::AddFileToFolder(strDirectory
, strFilename
);
364 if (!CFile::Exists(strPath
))
367 CDirectory::GetDirectory(strDirectory
,items
);
368 for (int i
=0;i
<items
.Size();++i
)
370 if (items
[i
]->GetPath().Equals(strPath
))
372 strPath
= items
[i
]->GetPath();
376 CLog::Log(LOGERROR
,"Could not find '%s' referenced in cue, case sensitivity issue?", strPath
.c_str());