[droid] Do not show any ui if pre-checks are OK
[vuplus_xbmc] / xbmc / CueDocument.cpp
1 /*
2  *      Copyright (C) 2005-2012 Team XBMC
3  *      http://www.xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 ////////////////////////////////////////////////////////////////////////////////////
22 // Class: CueDocument
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:
27 //
28 // PERFORMER "Pink Floyd"
29 // TITLE "The Dark Side Of The Moon"
30 // FILE "The Dark Side Of The Moon.mp3" WAVE
31 //   TRACK 01 AUDIO
32 //     TITLE "Speak To Me / Breathe"
33 //     PERFORMER "Pink Floyd"
34 //     INDEX 00 00:00:00
35 //     INDEX 01 00:00:32
36 //   TRACK 02 AUDIO
37 //     TITLE "On The Run"
38 //     PERFORMER "Pink Floyd"
39 //     INDEX 00 03:58:72
40 //     INDEX 01 04:00:72
41 //   TRACK 03 AUDIO
42 //     TITLE "Time"
43 //     PERFORMER "Pink Floyd"
44 //     INDEX 00 07:31:70
45 //     INDEX 01 07:33:70
46 //
47 // etc.
48 //
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.
52 //
53 ////////////////////////////////////////////////////////////////////////////////////
54
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"
62 #include "FileItem.h"
63 #include "settings/AdvancedSettings.h"
64
65 #include <set>
66
67 using namespace std;
68 using namespace XFILE;
69
70 CCueDocument::CCueDocument(void)
71 {
72   m_strArtist = "";
73   m_strAlbum = "";
74   m_strGenre = "";
75   m_iYear = 0;
76   m_replayGainAlbumPeak = 0.0f;
77   m_replayGainAlbumGain = 0.0f;
78   m_iTotalTracks = 0;
79   m_iTrack = 0;
80 }
81
82 CCueDocument::~CCueDocument(void)
83 {}
84
85 ////////////////////////////////////////////////////////////////////////////////////
86 // Function: Parse()
87 // Opens the .cue file for reading, and constructs the track database information
88 ////////////////////////////////////////////////////////////////////////////////////
89 bool CCueDocument::Parse(const CStdString &strFile)
90 {
91   if (!m_file.Open(strFile))
92     return false;
93
94   CStdString strLine;
95   m_iTotalTracks = -1;
96   CStdString strCurrentFile = "";
97   bool bCurrentFileChanged = false;
98   int time;
99
100   // Run through the .CUE file and extract the tracks...
101   while (true)
102   {
103     if (!ReadNextLine(strLine))
104       break;
105     if (strLine.Left(8) == "INDEX 01")
106     {
107       if (bCurrentFileChanged)
108       {
109         OutputDebugString("Track split over multiple files, unsupported");
110         return false;
111       }
112
113       // find the end of the number section
114       time = ExtractTimeFromIndex(strLine);
115       if (time == -1)
116       { // Error!
117         OutputDebugString("Mangled Time in INDEX 0x tag in CUE file!\n");
118         return false;
119       }
120       if (m_iTotalTracks > 0)  // Set the end time of the last track
121         m_Track[m_iTotalTracks - 1].iEndTime = time;
122
123       if (m_iTotalTracks >= 0)
124         m_Track[m_iTotalTracks].iStartTime = time; // start time of the next track
125     }
126     else if (strLine.Left(5) == "TITLE")
127     {
128       if (m_iTotalTracks == -1) // No tracks yet
129         ExtractQuoteInfo(strLine, m_strAlbum);
130       else if (!ExtractQuoteInfo(strLine, m_Track[m_iTotalTracks].strTitle))
131       {
132         // lets manage tracks titles without quotes
133         CStdString titleNoQuote = strLine.Mid(5);
134         titleNoQuote.TrimLeft();
135         if (!titleNoQuote.IsEmpty())
136         {
137           g_charsetConverter.unknownToUTF8(titleNoQuote);
138           m_Track[m_iTotalTracks].strTitle = titleNoQuote;
139         }
140       }
141     }
142     else if (strLine.Left(9) == "PERFORMER")
143     {
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);
148     }
149     else if (strLine.Left(5) == "TRACK")
150     {
151       int iTrackNumber = ExtractNumericInfo(strLine.c_str() + 5);
152
153       m_iTotalTracks++;
154
155       CCueTrack track;
156       m_Track.push_back(track);
157       m_Track[m_iTotalTracks].strFile = strCurrentFile;
158
159       if (iTrackNumber > 0)
160         m_Track[m_iTotalTracks].iTrackNumber = iTrackNumber;
161       else
162         m_Track[m_iTotalTracks].iTrackNumber = m_iTotalTracks + 1;
163
164       bCurrentFileChanged = false;
165     }
166     else if (strLine.Left(4) == "FILE")
167     {
168       // already a file name? then the time computation will be changed
169       if(strCurrentFile.size() > 0)
170         bCurrentFileChanged = true;
171
172       ExtractQuoteInfo(strLine, strCurrentFile);
173
174       // Resolve absolute paths (if needed).
175       if (strCurrentFile.length() > 0)
176         ResolvePath(strCurrentFile, strFile);
177     }
178     else if (strLine.Left(8) == "REM DATE")
179     {
180       int iYear = ExtractNumericInfo(strLine.c_str() + 8);
181       if (iYear > 0)
182         m_iYear = iYear;
183     }
184     else if (strLine.Left(9) == "REM GENRE")
185     {
186       if (!ExtractQuoteInfo(strLine, m_strGenre))
187       {
188         CStdString genreNoQuote = strLine.Mid(9);
189         genreNoQuote.TrimLeft();
190         if (!genreNoQuote.IsEmpty())
191         {
192           g_charsetConverter.unknownToUTF8(genreNoQuote);
193           m_strGenre = genreNoQuote;
194         }
195       }
196     }
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));
205   }
206
207   // reset track counter to 0, and fill in the last tracks end time
208   m_iTrack = 0;
209   if (m_iTotalTracks >= 0)
210     m_Track[m_iTotalTracks].iEndTime = 0;
211   else
212     OutputDebugString("No INDEX 01 tags in CUE file!\n");
213   m_file.Close();
214   if (m_iTotalTracks >= 0)
215   {
216     m_iTotalTracks++;
217   }
218   return (m_iTotalTracks > 0);
219 }
220
221 //////////////////////////////////////////////////////////////////////////////////
222 // Function:GetNextItem()
223 // Returns the track information from the next item in the cuelist
224 //////////////////////////////////////////////////////////////////////////////////
225 void CCueDocument::GetSongs(VECSONGS &songs)
226 {
227   for (int i = 0; i < m_iTotalTracks; i++)
228   {
229     CSong song;
230     if ((m_Track[i].strArtist.length() == 0) && (m_strArtist.length() > 0))
231       song.artist = StringUtils::Split(m_strArtist, g_advancedSettings.m_musicItemSeparator);
232     else
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);
241     else
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;
246     if (song.iEndOffset)
247       song.iDuration = (song.iEndOffset - song.iStartOffset + 37) / 75;
248     else
249       song.iDuration = 0;
250     // TODO: replayGain goes here
251     songs.push_back(song);
252   }
253 }
254
255 void CCueDocument::GetMediaFiles(vector<CStdString>& mediaFiles)
256 {
257   set<CStdString> uniqueFiles;
258   for (int i = 0; i < m_iTotalTracks; i++)
259     uniqueFiles.insert(m_Track[i].strFile);
260
261   for (set<CStdString>::iterator it = uniqueFiles.begin(); it != uniqueFiles.end(); it++)
262     mediaFiles.push_back(*it);
263 }
264
265 CStdString CCueDocument::GetMediaTitle()
266 {
267   return m_strAlbum;
268 }
269
270 // Private Functions start here
271
272 ////////////////////////////////////////////////////////////////////////////////////
273 // Function: ReadNextLine()
274 // Returns the next non-blank line of the textfile, stripping any whitespace from
275 // the left.
276 ////////////////////////////////////////////////////////////////////////////////////
277 bool CCueDocument::ReadNextLine(CStdString &szLine)
278 {
279   // Read the next line.
280   while (m_file.ReadString(m_szBuffer, 1023)) // Bigger than MAX_PATH_SIZE, for usage with relax!
281   {
282     // Remove the white space at the beginning and end of the line.
283     szLine = m_szBuffer;
284     szLine.Trim();
285     if (!szLine.empty())
286       return true;
287     // If we are here, we have an empty line so try the next line
288   }
289   return false;
290 }
291
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 &quote)
297 {
298   quote.Empty();
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);
305   return true;
306 }
307
308 ////////////////////////////////////////////////////////////////////////////////////
309 // Function: ExtractTimeFromIndex()
310 // Extracts the time information from the index string index, returning it as a value in
311 // milliseconds.
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)
316 {
317   // Get rid of the index number and any whitespace
318   CStdString numberTime = index.Mid(5);
319   numberTime.TrimLeft();
320   while (!numberTime.IsEmpty())
321   {
322     if (!isdigit(numberTime[0]))
323       break;
324     numberTime.erase(0, 1);
325   }
326   numberTime.TrimLeft();
327   // split the resulting string
328   CStdStringArray time;
329   StringUtils::SplitString(numberTime, ":", time);
330   if (time.size() != 3)
331     return -1;
332
333   int mins = atoi(time[0].c_str());
334   int secs = atoi(time[1].c_str());
335   int frames = atoi(time[2].c_str());
336
337   return (mins*60 + secs)*75 + frames;
338 }
339
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)
345 {
346   CStdString number(info);
347   number.TrimLeft();
348   if (number.IsEmpty() || !isdigit(number[0]))
349     return -1;
350   return atoi(number.c_str());
351 }
352
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)
359 {
360   CStdString strDirectory;
361   URIUtils::GetDirectory(strBase, strDirectory);
362
363   CStdString strFilename = strPath;
364   URIUtils::GetFileName(strFilename);
365
366   URIUtils::AddFileToFolder(strDirectory, strFilename, strPath);
367
368   // i *hate* windows
369   if (!CFile::Exists(strPath))
370   {
371     CFileItemList items;
372     CDirectory::GetDirectory(strDirectory,items);
373     for (int i=0;i<items.Size();++i)
374     {
375       if (items[i]->GetPath().Equals(strPath))
376       {
377         strPath = items[i]->GetPath();
378         return true;
379       }
380     }
381     CLog::Log(LOGERROR,"Could not find FILE referenced in cue, case sensitivity issue?");
382     return false;
383   }
384
385   return true;
386 }