File indexing completed on 2024-05-05 04:51:13
0001 /* 0002 SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "k3bcuefileparser.h" 0008 0009 #include "k3bmsf.h" 0010 #include "k3bglobals.h" 0011 #include "k3btrack.h" 0012 #include "k3bcdtext.h" 0013 0014 #include <QDebug> 0015 #include <QDir> 0016 #include <QFile> 0017 #include <QFileInfo> 0018 #include <QRegExp> 0019 0020 0021 // TODO: add method: usableByCdrecordDirectly() 0022 // TODO: add Toc with sector sizes 0023 0024 class K3b::CueFileParser::Private 0025 { 0026 public: 0027 bool inFile; 0028 bool inTrack; 0029 Device::Track::TrackType trackType; 0030 Device::Track::DataMode trackMode; 0031 bool rawData; 0032 bool haveIndex1; 0033 K3b::Msf currentDataPos; 0034 K3b::Msf index0; 0035 0036 K3b::Device::Toc toc; 0037 int currentParsedTrack; 0038 0039 K3b::Device::CdText cdText; 0040 0041 QString imageFileType; 0042 }; 0043 0044 0045 0046 K3b::CueFileParser::CueFileParser( const QString& filename ) 0047 : K3b::ImageFileReader() 0048 { 0049 d = new Private; 0050 openFile( filename ); 0051 } 0052 0053 0054 K3b::CueFileParser::~CueFileParser() 0055 { 0056 delete d; 0057 } 0058 0059 0060 void K3b::CueFileParser::readFile() 0061 { 0062 setValid(true); 0063 0064 d->inFile = d->inTrack = d->haveIndex1 = false; 0065 d->trackMode = K3b::Device::Track::UNKNOWN; 0066 d->toc.clear(); 0067 d->cdText.clear(); 0068 d->currentParsedTrack = 0; 0069 0070 QFile f( filename() ); 0071 if( f.open( QIODevice::ReadOnly ) ) { 0072 while( !f.atEnd() ) { 0073 if( !parseLine( QString::fromUtf8(f.readLine() ) ) ) { 0074 setValid(false); 0075 break; 0076 } 0077 } 0078 0079 if( isValid() ) { 0080 // save last parsed track for which we do not have the proper length :( 0081 if( d->currentParsedTrack > 0 ) { 0082 d->toc.append( K3b::Device::Track( d->currentDataPos, 0083 d->currentDataPos, 0084 d->trackType, 0085 d->trackMode ) ); 0086 } 0087 0088 // debug the toc 0089 qDebug() << "(K3b::CueFileParser) successfully parsed cue file." << Qt::endl 0090 << "------------------------------------------------" << Qt::endl; 0091 for( int i = 0; i < d->toc.count(); ++i ) { 0092 K3b::Device::Track& track = d->toc[i]; 0093 qDebug() << "Track " << (i+1) 0094 << " (" << ( track.type() == K3b::Device::Track::TYPE_AUDIO ? "audio" : "data" ) << ") " 0095 << track.firstSector().toString() << " - " << track.lastSector().toString() << Qt::endl; 0096 } 0097 0098 qDebug() << "------------------------------------------------"; 0099 } 0100 } 0101 else { 0102 qDebug() << "(K3b::CueFileParser) could not open file " << filename(); 0103 setValid(false); 0104 } 0105 } 0106 0107 0108 bool K3b::CueFileParser::parseLine( QString line ) 0109 { 0110 // use cap(1) for the filename 0111 static QRegExp fileRx( "FILE\\s\"?([^\"].*[^\"\\s])\"?\\s\"?(.*)\"?" ); 0112 0113 // use cap(1) for the flags 0114 static QRegExp flagsRx( "FLAGS(\\s(DCP|4CH|PRE|SCMS)){1,4}" ); 0115 0116 // use cap(1) for the tracknumber and cap(2) for the datatype 0117 static QRegExp trackRx( "TRACK\\s(\\d{1,2})\\s(AUDIO|CDG|MODE1/2048|MODE1/2352|MODE2/2336|MODE2/2352|CDI/2336|CDI/2352)" ); 0118 0119 // use cap(1) for the index number, cap(3) for the minutes, cap(4) for the seconds, cap(5) for the frames, 0120 // and cap(2) for the MSF value string 0121 static QRegExp indexRx( "INDEX\\s(\\d{1,2})\\s((\\d+):([0-5]\\d):((?:[0-6]\\d)|(?:7[0-4])))" ); 0122 0123 // use cap(1) for the MCN 0124 static QRegExp catalogRx( "CATALOG\\s(\\w{13,13})" ); 0125 0126 // use cap(1) for the ISRC 0127 static QRegExp isrcRx( "ISRC\\s(\\w{5,5}\\d{7,7})" ); 0128 0129 static QString cdTextRxStr = "\"?([^\"]{0,80})\"?"; 0130 0131 // use cap(1) for the string 0132 static QRegExp titleRx( "TITLE\\s" + cdTextRxStr ); 0133 static QRegExp performerRx( "PERFORMER\\s" + cdTextRxStr ); 0134 static QRegExp songwriterRx( "SONGWRITER\\s" + cdTextRxStr ); 0135 0136 0137 // simplify all white spaces except those in filenames and CD-TEXT 0138 simplified( line ); 0139 0140 // skip comments and empty lines 0141 if( line.startsWith("REM") || line.startsWith('#') || line.isEmpty() ) 0142 return true; 0143 0144 0145 // 0146 // FILE 0147 // 0148 if( fileRx.exactMatch( line ) ) { 0149 0150 setValid( findImageFileName( fileRx.cap(1) ) ); 0151 0152 if( d->inFile ) { 0153 qDebug() << "(K3b::CueFileParser) only one FILE statement allowed."; 0154 return false; 0155 } 0156 d->inFile = true; 0157 d->inTrack = false; 0158 d->haveIndex1 = false; 0159 d->imageFileType = fileRx.cap( 2 ).toLower(); 0160 return true; 0161 } 0162 0163 0164 // 0165 // TRACK 0166 // 0167 else if( trackRx.exactMatch( line ) ) { 0168 if( !d->inFile ) { 0169 qDebug() << "(K3b::CueFileParser) TRACK statement before FILE."; 0170 return false; 0171 } 0172 0173 // check if we had index1 for the last track 0174 if( d->inTrack && !d->haveIndex1 ) { 0175 qDebug() << "(K3b::CueFileParser) TRACK without INDEX 1."; 0176 return false; 0177 } 0178 0179 // save last track 0180 // TODO: use d->rawData in some way 0181 if( d->currentParsedTrack > 0 ) { 0182 d->toc.append( K3b::Device::Track( d->currentDataPos, 0183 d->currentDataPos, 0184 d->trackType, 0185 d->trackMode ) ); 0186 } 0187 0188 d->currentParsedTrack++; 0189 0190 // parse the tracktype 0191 if( trackRx.cap(2) == "AUDIO" ) { 0192 d->trackType = K3b::Device::Track::TYPE_AUDIO; 0193 d->trackMode = K3b::Device::Track::UNKNOWN; 0194 } 0195 else { 0196 d->trackType = K3b::Device::Track::TYPE_DATA; 0197 if( trackRx.cap(2).startsWith("MODE1") ) { 0198 d->trackMode = K3b::Device::Track::MODE1; 0199 d->rawData = (trackRx.cap(2) == "MODE1/2352"); 0200 } 0201 else if( trackRx.cap(2).startsWith("MODE2") ) { 0202 d->trackMode = K3b::Device::Track::MODE2; 0203 d->rawData = (trackRx.cap(2) == "MODE2/2352"); 0204 } 0205 else { 0206 qDebug() << "(K3b::CueFileParser) unsupported track type: " << trackRx.cap(2); 0207 return false; 0208 } 0209 } 0210 0211 d->haveIndex1 = false; 0212 d->inTrack = true; 0213 d->index0 = 0; 0214 0215 return true; 0216 } 0217 0218 0219 // 0220 // FLAGS 0221 // 0222 else if( flagsRx.exactMatch( line ) ) { 0223 if( !d->inTrack ) { 0224 qDebug() << "(K3b::CueFileParser) FLAGS statement without TRACK."; 0225 return false; 0226 } 0227 0228 // TODO: save the flags 0229 return true; 0230 } 0231 0232 0233 // 0234 // INDEX 0235 // 0236 else if( indexRx.exactMatch( line ) ) { 0237 if( !d->inTrack ) { 0238 qDebug() << "(K3b::CueFileParser) INDEX statement without TRACK."; 0239 return false; 0240 } 0241 0242 unsigned int indexNumber = indexRx.cap(1).toInt(); 0243 0244 K3b::Msf indexStart = K3b::Msf::fromString( indexRx.cap(2) ); 0245 0246 if( indexNumber == 0 ) { 0247 d->index0 = indexStart; 0248 0249 if( d->currentParsedTrack < 2 && indexStart > 0 ) { 0250 qDebug() << "(K3b::CueFileParser) first track is not allowed to have a pregap > 0."; 0251 return false; 0252 } 0253 } 0254 else if( indexNumber == 1 ) { 0255 d->haveIndex1 = true; 0256 d->currentDataPos = indexStart; 0257 if( d->currentParsedTrack > 1 ) { 0258 d->toc[d->currentParsedTrack-2].setLastSector( indexStart-1 ); 0259 if( d->index0 > 0 && d->index0 < indexStart ) { 0260 d->toc[d->currentParsedTrack-2].setIndex0( d->index0 - d->toc[d->currentParsedTrack-2].firstSector() ); 0261 } 0262 } 0263 } 0264 else { 0265 // TODO: add index > 0 0266 } 0267 0268 return true; 0269 } 0270 0271 0272 // 0273 // CATALOG 0274 // 0275 if( catalogRx.exactMatch( line ) ) { 0276 // TODO: set the toc's mcn 0277 return true; 0278 } 0279 0280 0281 // 0282 // ISRC 0283 // 0284 if( isrcRx.exactMatch( line ) ) { 0285 if( d->inTrack ) { 0286 // TODO: set the track's ISRC 0287 return true; 0288 } 0289 else { 0290 qDebug() << "(K3b::CueFileParser) ISRC without TRACK."; 0291 return false; 0292 } 0293 } 0294 0295 0296 // 0297 // CD-TEXT 0298 // TODO: create K3b::Device::TrackCdText entries 0299 // 0300 else if( titleRx.exactMatch( line ) ) { 0301 if( d->inTrack ) 0302 d->cdText[d->currentParsedTrack-1].setTitle( titleRx.cap(1) ); 0303 else 0304 d->cdText.setTitle( titleRx.cap(1) ); 0305 return true; 0306 } 0307 0308 else if( performerRx.exactMatch( line ) ) { 0309 if( d->inTrack ) 0310 d->cdText[d->currentParsedTrack-1].setPerformer( performerRx.cap(1) ); 0311 else 0312 d->cdText.setPerformer( performerRx.cap(1) ); 0313 return true; 0314 } 0315 0316 else if( songwriterRx.exactMatch( line ) ) { 0317 if( d->inTrack ) 0318 d->cdText[d->currentParsedTrack-1].setSongwriter( songwriterRx.cap(1) ); 0319 else 0320 d->cdText.setSongwriter( songwriterRx.cap(1) ); 0321 return true; 0322 } 0323 0324 else { 0325 qDebug() << "(K3b::CueFileParser) unknown Cue line: '" << line << "'"; 0326 return false; 0327 } 0328 } 0329 0330 0331 void K3b::CueFileParser::simplified( QString& s ) 0332 { 0333 s = s.trimmed(); 0334 0335 int i = 0; 0336 bool insideQuote = false; 0337 while( i < s.length() ) { 0338 if( !insideQuote ) { 0339 if( s[i].isSpace() && s[i+1].isSpace() ) 0340 s.remove( i, 1 ); 0341 } 0342 0343 if( s[i] == '"' ) 0344 insideQuote = !insideQuote; 0345 0346 ++i; 0347 } 0348 } 0349 0350 0351 K3b::Device::Toc K3b::CueFileParser::toc() const 0352 { 0353 return d->toc; 0354 } 0355 0356 0357 K3b::Device::CdText K3b::CueFileParser::cdText() const 0358 { 0359 return d->cdText; 0360 } 0361 0362 0363 QString K3b::CueFileParser::imageFileType() const 0364 { 0365 return d->imageFileType; 0366 } 0367 0368 0369 bool K3b::CueFileParser::findImageFileName( const QString& dataFile ) 0370 { 0371 // 0372 // CDRDAO does not use this image filename but replaces the extension from the cue file 0373 // with "bin" to get the image filename, we should take this into account 0374 // 0375 0376 qDebug() << "(K3b::CueFileParser) trying to find file: " << dataFile; 0377 0378 m_imageFilenameInCue = true; 0379 0380 // first try filename as a whole (absolute) 0381 if( QFile::exists( dataFile ) ) { 0382 setImageFilename( QFileInfo(dataFile).absoluteFilePath() ); 0383 return true; 0384 } 0385 0386 // try the filename in the cue's directory 0387 if( QFileInfo( K3b::parentDir(filename()) + dataFile.section( '/', -1 ) ).isFile() ) { 0388 setImageFilename( K3b::parentDir(filename()) + dataFile.section( '/', -1 ) ); 0389 qDebug() << "(K3b::CueFileParser) found image file: " << imageFilename(); 0390 return true; 0391 } 0392 0393 // try the filename ignoring case 0394 if( QFileInfo( K3b::parentDir(filename()) + dataFile.section( '/', -1 ).toLower() ).isFile() ) { 0395 setImageFilename( K3b::parentDir(filename()) + dataFile.section( '/', -1 ).toLower() ); 0396 qDebug() << "(K3b::CueFileParser) found image file: " << imageFilename(); 0397 return true; 0398 } 0399 0400 m_imageFilenameInCue = false; 0401 0402 // try removing the ending from the cue file (image.bin.cue and image.bin) 0403 if( QFileInfo( filename().left( filename().length()-4 ) ).isFile() ) { 0404 setImageFilename( filename().left( filename().length()-4 ) ); 0405 qDebug() << "(K3b::CueFileParser) found image file: " << imageFilename(); 0406 return true; 0407 } 0408 0409 // 0410 // we did not find the image specified in the cue. 0411 // Search for another one having the same filename as the cue but a different extension 0412 // 0413 0414 QDir parentDir( K3b::parentDir(filename()) ); 0415 QString filenamePrefix = filename().section( '/', -1 ); 0416 filenamePrefix.truncate( filenamePrefix.length() - 3 ); // remove cue extension 0417 qDebug() << "(K3b::CueFileParser) checking folder " << parentDir.path() << " for files: " << filenamePrefix << "*"; 0418 0419 // 0420 // we cannot use the nameFilter in QDir because of the spaces that may occur in filenames 0421 // 0422 QStringList possibleImageFiles = parentDir.entryList( QDir::Files ); 0423 int cnt = 0; 0424 for( QStringList::const_iterator it = possibleImageFiles.constBegin(); it != possibleImageFiles.constEnd(); ++it ) { 0425 if( (*it).toLower() == dataFile.section( '/', -1 ).toLower() || 0426 ((*it).startsWith( filenamePrefix ) && !(*it).endsWith( "cue" )) ) { 0427 ++cnt; 0428 setImageFilename( K3b::parentDir(filename()) + *it ); 0429 } 0430 } 0431 0432 // 0433 // we only do this if there is one unique file which fits the requirements. 0434 // Otherwise we cannot be certain to have the right file. 0435 // 0436 return ( cnt == 1 && QFileInfo( imageFilename() ).isFile() ); 0437 }