Files
XBMC4Xbox/xbmc/CueDocument.cpp
Rocky5 921f9fe9f8 Clean Copy
If ever adding the XBMC4Xbox source to GitHub use "git add -f -A" or it wont add all the files.
2024-01-08 19:01:29 +00:00

369 lines
12 KiB
C++

/*
* Copyright (C) 2005-2013 Team XBMC
* http://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, see
* <http://www.gnu.org/licenses/>.
*
*/
////////////////////////////////////////////////////////////////////////////////////
// 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 "Util.h"
#include "utils/URIUtils.h"
#include "utils/StringUtils.h"
#include "utils/CharsetConverter.h"
#include <set>
using namespace std;
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 &quote)
{
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);
return true;
}