[Windows] Fix driver version detection of AMD RDNA+ GPU on Windows 10
[xbmc.git] / xbmc / CueDocument.cpp
blobabc5185e63927fe304ff6c2579c5ad2f8b46f634
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 ////////////////////////////////////////////////////////////////////////////////////
10 // Class: CueDocument
11 // This class handles the .cue file format. This is produced by programs such as
12 // EAC and CDRwin when one extracts audio data from a CD as a continuous .WAV
13 // containing all the audio tracks in one big file. The .cue file contains all the
14 // track and timing information. An example file is:
16 // PERFORMER "Pink Floyd"
17 // TITLE "The Dark Side Of The Moon"
18 // FILE "The Dark Side Of The Moon.mp3" WAVE
19 // TRACK 01 AUDIO
20 // TITLE "Speak To Me / Breathe"
21 // PERFORMER "Pink Floyd"
22 // INDEX 00 00:00:00
23 // INDEX 01 00:00:32
24 // TRACK 02 AUDIO
25 // TITLE "On The Run"
26 // PERFORMER "Pink Floyd"
27 // INDEX 00 03:58:72
28 // INDEX 01 04:00:72
29 // TRACK 03 AUDIO
30 // TITLE "Time"
31 // PERFORMER "Pink Floyd"
32 // INDEX 00 07:31:70
33 // INDEX 01 07:33:70
35 // etc.
37 // The CCueDocument class member functions extract this information, and construct
38 // the playlist items needed to seek to a track directly. This works best on CBR
39 // compressed files - VBR files do not seek accurately enough for it to work well.
41 ////////////////////////////////////////////////////////////////////////////////////
43 #include "CueDocument.h"
45 #include "FileItem.h"
46 #include "FileItemList.h"
47 #include "ServiceBroker.h"
48 #include "Util.h"
49 #include "filesystem/Directory.h"
50 #include "filesystem/File.h"
51 #include "settings/AdvancedSettings.h"
52 #include "settings/SettingsComponent.h"
53 #include "utils/CharsetConverter.h"
54 #include "utils/StringUtils.h"
55 #include "utils/URIUtils.h"
56 #include "utils/log.h"
58 #include <cstdlib>
59 #include <set>
61 using namespace XFILE;
63 // Stuff for read CUE data from different sources.
64 class CueReader
66 public:
67 virtual bool ready() const = 0;
68 virtual bool ReadLine(std::string &line) = 0;
69 virtual ~CueReader() = default;
70 private:
71 std::string m_sourcePath;
74 class FileReader
75 : public CueReader
77 public:
78 explicit FileReader(const std::string &strFile) : m_szBuffer{}
80 m_opened = m_file.Open(strFile);
82 bool ReadLine(std::string &line) override
84 // Read the next line.
85 while (m_file.ReadString(m_szBuffer, 1023)) // Bigger than MAX_PATH_SIZE, for usage with relax!
87 // Remove the white space at the beginning and end of the line.
88 line = m_szBuffer;
89 StringUtils::Trim(line);
90 if (!line.empty())
91 return true;
92 // If we are here, we have an empty line so try the next line
94 return false;
96 bool ready() const override
98 return m_opened;
100 ~FileReader() override
102 if (m_opened)
103 m_file.Close();
106 private:
107 CFile m_file;
108 bool m_opened;
109 char m_szBuffer[1024];
112 class BufferReader
113 : public CueReader
115 public:
116 explicit BufferReader(const std::string& strContent) : m_data(strContent) {}
117 bool ReadLine(std::string &line) override
119 // Read the next line.
120 line.clear();
121 while (m_pos < m_data.size())
123 // Remove the white space at the beginning of the line.
124 char ch = m_data.at(m_pos++);
125 if (ch == '\r' || ch == '\n') {
126 StringUtils::Trim(line);
127 if (!line.empty())
128 return true;
130 else
132 line.push_back(ch);
136 StringUtils::Trim(line);
137 return !line.empty();
139 bool ready() const override
141 return m_data.size() > 0;
143 private:
144 std::string m_data;
145 size_t m_pos = 0;
148 CCueDocument::~CCueDocument() = default;
150 ////////////////////////////////////////////////////////////////////////////////////
151 // Function: ParseFile()
152 // Opens the CUE file for reading, and constructs the track database information
153 ////////////////////////////////////////////////////////////////////////////////////
154 bool CCueDocument::ParseFile(const std::string &strFilePath)
156 FileReader reader(strFilePath);
157 return Parse(reader, strFilePath);
160 ////////////////////////////////////////////////////////////////////////////////////
161 // Function: ParseTag()
162 // Reads CUE data from string buffer, and constructs the track database information
163 ////////////////////////////////////////////////////////////////////////////////////
164 bool CCueDocument::ParseTag(const std::string &strContent)
166 BufferReader reader(strContent);
167 return Parse(reader);
170 //////////////////////////////////////////////////////////////////////////////////
171 // Function:GetSongs()
172 // Store track information into songs list.
173 //////////////////////////////////////////////////////////////////////////////////
174 void CCueDocument::GetSongs(VECSONGS &songs)
176 const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
178 for (const auto& track : m_tracks)
180 CSong aSong;
181 //Pass artist to MusicInfoTag object by setting artist description string only.
182 //Artist credits not used during loading from cue sheet.
183 if (track.strArtist.empty() && !m_strArtist.empty())
184 aSong.strArtistDesc = m_strArtist;
185 else
186 aSong.strArtistDesc = track.strArtist;
187 //Pass album artist to MusicInfoTag object by setting album artist vector.
188 aSong.SetAlbumArtist(StringUtils::Split(m_strArtist, advancedSettings->m_musicItemSeparator));
189 aSong.strAlbum = m_strAlbum;
190 aSong.genre = StringUtils::Split(m_strGenre, advancedSettings->m_musicItemSeparator);
191 aSong.strReleaseDate = StringUtils::Format("{:04}", m_iYear);
192 aSong.iTrack = track.iTrackNumber;
193 if (m_iDiscNumber > 0)
194 aSong.iTrack |= (m_iDiscNumber << 16); // see CMusicInfoTag::GetDiscNumber()
195 if (track.strTitle.length() == 0) // No track information for this track!
196 aSong.strTitle = StringUtils::Format("Track {:2d}", track.iTrackNumber);
197 else
198 aSong.strTitle = track.strTitle;
199 aSong.strFileName = track.strFile;
200 aSong.iStartOffset = track.iStartTime;
201 aSong.iEndOffset = track.iEndTime;
202 if (aSong.iEndOffset)
203 // Convert offset in frames (75 per second) to duration in whole seconds with rounding
204 aSong.iDuration = CUtil::ConvertMilliSecsToSecsIntRounded(aSong.iEndOffset - aSong.iStartOffset);
205 else
206 aSong.iDuration = 0;
208 if (m_albumReplayGain.Valid())
209 aSong.replayGain.Set(ReplayGain::ALBUM, m_albumReplayGain);
211 if (track.replayGain.Valid())
212 aSong.replayGain.Set(ReplayGain::TRACK, track.replayGain);
214 songs.push_back(aSong);
218 void CCueDocument::UpdateMediaFile(const std::string& oldMediaFile, const std::string& mediaFile)
220 for (Tracks::iterator it = m_tracks.begin(); it != m_tracks.end(); ++it)
222 if (it->strFile == oldMediaFile)
223 it->strFile = mediaFile;
227 void CCueDocument::GetMediaFiles(std::vector<std::string>& mediaFiles)
229 typedef std::set<std::string> TSet;
230 TSet uniqueFiles;
231 for (Tracks::const_iterator it = m_tracks.begin(); it != m_tracks.end(); ++it)
232 uniqueFiles.insert(it->strFile);
234 for (TSet::const_iterator it = uniqueFiles.begin(); it != uniqueFiles.end(); ++it)
235 mediaFiles.push_back(*it);
238 std::string CCueDocument::GetMediaTitle()
240 return m_strAlbum;
243 bool CCueDocument::IsLoaded() const
245 return !m_tracks.empty();
248 bool CCueDocument::IsOneFilePerTrack() const
250 return m_bOneFilePerTrack;
253 // Private Functions start here
255 void CCueDocument::Clear()
257 m_strArtist.clear();
258 m_strAlbum.clear();
259 m_strGenre.clear();
260 m_iYear = 0;
261 m_iTrack = 0;
262 m_iDiscNumber = 0;
263 m_albumReplayGain = ReplayGain::Info();
264 m_tracks.clear();
266 ////////////////////////////////////////////////////////////////////////////////////
267 // Function: Parse()
268 // Constructs the track database information from CUE source
269 ////////////////////////////////////////////////////////////////////////////////////
270 bool CCueDocument::Parse(CueReader& reader, const std::string& strFile)
272 Clear();
273 if (!reader.ready())
274 return false;
276 std::string strLine;
277 std::string strCurrentFile = "";
278 bool bCurrentFileChanged = false;
279 int time;
280 int totalTracks = -1;
281 int numberFiles = -1;
283 // Run through the .CUE file and extract the tracks...
284 while (reader.ReadLine(strLine))
286 if (StringUtils::StartsWithNoCase(strLine, "INDEX 01"))
288 if (bCurrentFileChanged)
290 CLog::Log(LOGERROR, "Track split over multiple files, unsupported.");
291 return false;
294 // find the end of the number section
295 time = ExtractTimeFromIndex(strLine);
296 if (time == -1)
297 { // Error!
298 CLog::Log(LOGERROR, "Mangled Time in INDEX 0x tag in CUE file!");
299 return false;
301 if (totalTracks > 0 && m_tracks[totalTracks - 1].strFile == strCurrentFile) // Set the end time of the last track
302 m_tracks[totalTracks - 1].iEndTime = time;
304 if (totalTracks >= 0) // start time of the next track
305 m_tracks[totalTracks].iStartTime = time;
307 else if (StringUtils::StartsWithNoCase(strLine, "TITLE"))
309 if (totalTracks == -1) // No tracks yet
310 m_strAlbum = ExtractInfo(strLine.substr(5));
311 else
312 m_tracks[totalTracks].strTitle = ExtractInfo(strLine.substr(5));
314 else if (StringUtils::StartsWithNoCase(strLine, "PERFORMER"))
316 if (totalTracks == -1) // No tracks yet
317 m_strArtist = ExtractInfo(strLine.substr(9));
318 else // New Artist for this track
319 m_tracks[totalTracks].strArtist = ExtractInfo(strLine.substr(9));
321 else if (StringUtils::StartsWithNoCase(strLine, "TRACK"))
323 int iTrackNumber = ExtractNumericInfo(strLine.substr(5));
325 totalTracks++;
327 CCueTrack track;
328 m_tracks.push_back(track);
329 m_tracks[totalTracks].strFile = strCurrentFile;
330 if (iTrackNumber > 0)
331 m_tracks[totalTracks].iTrackNumber = iTrackNumber;
332 else
333 m_tracks[totalTracks].iTrackNumber = totalTracks + 1;
335 bCurrentFileChanged = false;
337 else if (StringUtils::StartsWithNoCase(strLine, "REM DISCNUMBER"))
339 int iDiscNumber = ExtractNumericInfo(strLine.substr(14));
340 if (iDiscNumber > 0)
341 m_iDiscNumber = iDiscNumber;
343 else if (StringUtils::StartsWithNoCase(strLine, "FILE"))
345 numberFiles++;
346 // already a file name? then the time computation will be changed
347 if (!strCurrentFile.empty())
348 bCurrentFileChanged = true;
350 strCurrentFile = ExtractInfo(strLine.substr(4));
352 // Resolve absolute paths (if needed).
353 if (!strFile.empty() && !strCurrentFile.empty())
354 ResolvePath(strCurrentFile, strFile);
356 else if (StringUtils::StartsWithNoCase(strLine, "REM DATE"))
358 int iYear = ExtractNumericInfo(strLine.substr(8));
359 if (iYear > 0)
360 m_iYear = iYear;
362 else if (StringUtils::StartsWithNoCase(strLine, "REM GENRE"))
364 m_strGenre = ExtractInfo(strLine.substr(9));
366 else if (StringUtils::StartsWithNoCase(strLine, "REM REPLAYGAIN_ALBUM_GAIN"))
367 m_albumReplayGain.SetGain(strLine.substr(26));
368 else if (StringUtils::StartsWithNoCase(strLine, "REM REPLAYGAIN_ALBUM_PEAK"))
369 m_albumReplayGain.SetPeak(strLine.substr(26));
370 else if (StringUtils::StartsWithNoCase(strLine, "REM REPLAYGAIN_TRACK_GAIN") && totalTracks >= 0)
371 m_tracks[totalTracks].replayGain.SetGain(strLine.substr(26));
372 else if (StringUtils::StartsWithNoCase(strLine, "REM REPLAYGAIN_TRACK_PEAK") && totalTracks >= 0)
373 m_tracks[totalTracks].replayGain.SetPeak(strLine.substr(26));
376 // reset track counter to 0, and fill in the last tracks end time
377 m_iTrack = 0;
378 if (totalTracks >= 0)
379 m_tracks[totalTracks].iEndTime = 0;
380 else
381 CLog::Log(LOGERROR, "No INDEX 01 tags in CUE file!");
383 if ( totalTracks == numberFiles )
384 m_bOneFilePerTrack = true;
386 return (totalTracks >= 0);
389 ////////////////////////////////////////////////////////////////////////////////////
390 // Function: ExtractInfo()
391 // Extracts the information in quotes from the string line, returning it in quote
392 ////////////////////////////////////////////////////////////////////////////////////
393 std::string CCueDocument::ExtractInfo(const std::string &line)
395 size_t left = line.find('\"');
396 if (left != std::string::npos)
398 size_t right = line.find('\"', left + 1);
399 if (right != std::string::npos)
401 std::string text = line.substr(left + 1, right - left - 1);
402 g_charsetConverter.unknownToUTF8(text);
403 return text;
406 std::string text = line;
407 StringUtils::Trim(text);
408 g_charsetConverter.unknownToUTF8(text);
409 return text;
412 ////////////////////////////////////////////////////////////////////////////////////
413 // Function: ExtractTimeFromIndex()
414 // Extracts the time information from the index string index, returning it as a value in
415 // milliseconds.
416 // Assumed format is:
417 // MM:SS:FF where MM is minutes, SS seconds, and FF frames (75 frames in a second)
418 ////////////////////////////////////////////////////////////////////////////////////
419 int CCueDocument::ExtractTimeFromIndex(const std::string &index)
421 // Get rid of the index number and any whitespace
422 std::string numberTime = index.substr(5);
423 StringUtils::TrimLeft(numberTime);
424 while (!numberTime.empty())
426 if (!StringUtils::isasciidigit(numberTime[0]))
427 break;
428 numberTime.erase(0, 1);
430 StringUtils::TrimLeft(numberTime);
431 // split the resulting string
432 std::vector<std::string> time = StringUtils::Split(numberTime, ":");
433 if (time.size() != 3)
434 return -1;
436 int mins = atoi(time[0].c_str());
437 int secs = atoi(time[1].c_str());
438 int frames = atoi(time[2].c_str());
440 return CUtil::ConvertSecsToMilliSecs(mins*60 + secs) + frames * 1000 / 75;
443 ////////////////////////////////////////////////////////////////////////////////////
444 // Function: ExtractNumericInfo()
445 // Extracts the numeric info from the string info, returning it as an integer value
446 ////////////////////////////////////////////////////////////////////////////////////
447 int CCueDocument::ExtractNumericInfo(const std::string &info)
449 std::string number(info);
450 StringUtils::TrimLeft(number);
451 if (number.empty() || !StringUtils::isasciidigit(number[0]))
452 return -1;
453 return atoi(number.c_str());
456 ////////////////////////////////////////////////////////////////////////////////////
457 // Function: ResolvePath()
458 // Determines whether strPath is a relative path or not, and if so, converts it to an
459 // absolute path using the path information in strBase
460 ////////////////////////////////////////////////////////////////////////////////////
461 bool CCueDocument::ResolvePath(std::string &strPath, const std::string &strBase)
463 std::string strDirectory = URIUtils::GetDirectory(strBase);
464 std::string strFilename = URIUtils::GetFileName(strPath);
466 strPath = URIUtils::AddFileToFolder(strDirectory, strFilename);
468 // i *hate* windows
469 if (!CFile::Exists(strPath))
471 CFileItemList items;
472 CDirectory::GetDirectory(strDirectory, items, "", DIR_FLAG_DEFAULTS);
473 for (int i=0;i<items.Size();++i)
475 if (items[i]->IsPath(strPath))
477 strPath = items[i]->GetPath();
478 return true;
481 CLog::Log(LOGERROR, "Could not find '{}' referenced in cue, case sensitivity issue?", strPath);
482 return false;
485 return true;