You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
462 lines
12 KiB
462 lines
12 KiB
/*
|
|
*
|
|
* $Id: k3bcuefileparser.cpp 619556 2007-01-03 17:38:12Z trueg $
|
|
* Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org>
|
|
*
|
|
* This file is part of the K3b project.
|
|
* Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.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 of the License, or
|
|
* (at your option) any later version.
|
|
* See the file "COPYING" for the exact licensing terms.
|
|
*/
|
|
|
|
#include "k3bcuefileparser.h"
|
|
|
|
#include <k3bmsf.h>
|
|
#include <k3bglobals.h>
|
|
#include <k3btrack.h>
|
|
#include <k3bcdtext.h>
|
|
|
|
#include <tqfile.h>
|
|
#include <tqfileinfo.h>
|
|
#include <textstream.h>
|
|
#include <tqregexp.h>
|
|
#include <tqdir.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
// avoid usage of TQTextStream since K3b often
|
|
// tries to open big files (iso images) in a
|
|
// cue file parser to test it.
|
|
static TQString readLine( TQFile* f )
|
|
{
|
|
TQString s;
|
|
TQ_LONG r = f->readLine( s, 1024 );
|
|
if( r >= 0 ) {
|
|
// remove the trailing newline
|
|
return s.stripWhiteSpace();
|
|
}
|
|
else {
|
|
// end of file or error
|
|
return TQString();
|
|
}
|
|
}
|
|
|
|
|
|
// TODO: add method: usableByCdrecordDirectly()
|
|
// TODO: add Toc with sector sizes
|
|
|
|
class K3bCueFileParser::Private
|
|
{
|
|
public:
|
|
bool inFile;
|
|
bool inTrack;
|
|
int trackType;
|
|
int trackMode;
|
|
bool rawData;
|
|
bool haveIndex1;
|
|
K3b::Msf currentDataPos;
|
|
K3b::Msf index0;
|
|
|
|
K3bDevice::Toc toc;
|
|
int currentParsedTrack;
|
|
|
|
K3bDevice::CdText cdText;
|
|
};
|
|
|
|
|
|
|
|
K3bCueFileParser::K3bCueFileParser( const TQString& filename )
|
|
: K3bImageFileReader()
|
|
{
|
|
d = new Private;
|
|
openFile( filename );
|
|
}
|
|
|
|
|
|
K3bCueFileParser::~K3bCueFileParser()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
|
|
void K3bCueFileParser::readFile()
|
|
{
|
|
setValid(true);
|
|
|
|
d->inFile = d->inTrack = d->haveIndex1 = false;
|
|
d->trackMode = K3bDevice::Track::UNKNOWN;
|
|
d->toc.clear();
|
|
d->cdText.clear();
|
|
d->currentParsedTrack = 0;
|
|
|
|
TQFile f( filename() );
|
|
if( f.open( IO_ReadOnly ) ) {
|
|
TQString line = readLine( &f );
|
|
while( !line.isNull() ) {
|
|
|
|
if( !parseLine(line) ) {
|
|
setValid(false);
|
|
break;
|
|
}
|
|
|
|
line = readLine( &f );
|
|
}
|
|
|
|
if( isValid() ) {
|
|
// save last parsed track for which we do not have the proper length :(
|
|
if( d->currentParsedTrack > 0 ) {
|
|
d->toc.append( K3bDevice::Track( d->currentDataPos,
|
|
d->currentDataPos,
|
|
d->trackType,
|
|
d->trackMode ) );
|
|
}
|
|
|
|
// debug the toc
|
|
kdDebug() << "(K3bCueFileParser) successfully parsed cue file." << endl
|
|
<< "------------------------------------------------" << endl;
|
|
for( unsigned int i = 0; i < d->toc.count(); ++i ) {
|
|
K3bDevice::Track& track = d->toc[i];
|
|
kdDebug() << "Track " << (i+1)
|
|
<< " (" << ( track.type() == K3bDevice::Track::AUDIO ? "audio" : "data" ) << ") "
|
|
<< track.firstSector().toString() << " - " << track.lastSector().toString() << endl;
|
|
}
|
|
|
|
kdDebug() << "------------------------------------------------" << endl;
|
|
}
|
|
}
|
|
else {
|
|
kdDebug() << "(K3bCueFileParser) could not open file " << filename() << endl;
|
|
setValid(false);
|
|
}
|
|
}
|
|
|
|
|
|
bool K3bCueFileParser::parseLine( TQString& line )
|
|
{
|
|
// use cap(1) for the filename
|
|
static TQRegExp fileRx( "FILE\\s\"?([^\"]*)\"?\\s[^\"\\s]*" );
|
|
|
|
// use cap(1) for the flags
|
|
static TQRegExp flagsRx( "FLAGS(\\s(DCP|4CH|PRE|SCMS)){1,4}" );
|
|
|
|
// use cap(1) for the tracknumber and cap(2) for the datatype
|
|
static TQRegExp trackRx( "TRACK\\s(\\d{1,2})\\s(AUDIO|CDG|MODE1/2048|MODE1/2352|MODE2/2336|MODE2/2352|CDI/2336|CDI/2352)" );
|
|
|
|
// use cap(1) for the index number, cap(3) for the minutes, cap(4) for the seconds, cap(5) for the frames,
|
|
// and cap(2) for the MSF value string
|
|
static TQRegExp indexRx( "INDEX\\s(\\d{1,2})\\s((\\d+):([0-5]\\d):((?:[0-6]\\d)|(?:7[0-4])))" );
|
|
|
|
// use cap(1) for the MCN
|
|
static TQRegExp catalogRx( "CATALOG\\s(\\w{13,13})" );
|
|
|
|
// use cap(1) for the ISRC
|
|
static TQRegExp isrcRx( "ISRC\\s(\\w{5,5}\\d{7,7})" );
|
|
|
|
static TQString cdTextRxStr = "\"?([^\"]{0,80})\"?";
|
|
|
|
// use cap(1) for the string
|
|
static TQRegExp titleRx( "TITLE\\s" + cdTextRxStr );
|
|
static TQRegExp performerRx( "PERFORMER\\s" + cdTextRxStr );
|
|
static TQRegExp songwriterRx( "SONGWRITER\\s" + cdTextRxStr );
|
|
|
|
|
|
// simplify all white spaces except those in filenames and CD-TEXT
|
|
simplifyWhiteSpace( line );
|
|
|
|
// skip comments and empty lines
|
|
if( line.startsWith("REM") || line.startsWith("#") || line.isEmpty() )
|
|
return true;
|
|
|
|
|
|
//
|
|
// FILE
|
|
//
|
|
if( fileRx.exactMatch( line ) ) {
|
|
|
|
setValid( findImageFileName( fileRx.cap(1) ) );
|
|
|
|
if( d->inFile ) {
|
|
kdDebug() << "(K3bCueFileParser) only one FILE statement allowed." << endl;
|
|
return false;
|
|
}
|
|
d->inFile = true;
|
|
d->inTrack = false;
|
|
d->haveIndex1 = false;
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// TRACK
|
|
//
|
|
else if( trackRx.exactMatch( line ) ) {
|
|
if( !d->inFile ) {
|
|
kdDebug() << "(K3bCueFileParser) TRACK statement before FILE." << endl;
|
|
return false;
|
|
}
|
|
|
|
// check if we had index1 for the last track
|
|
if( d->inTrack && !d->haveIndex1 ) {
|
|
kdDebug() << "(K3bCueFileParser) TRACK without INDEX 1." << endl;
|
|
return false;
|
|
}
|
|
|
|
// save last track
|
|
// TODO: use d->rawData in some way
|
|
if( d->currentParsedTrack > 0 ) {
|
|
d->toc.append( K3bDevice::Track( d->currentDataPos,
|
|
d->currentDataPos,
|
|
d->trackType,
|
|
d->trackMode ) );
|
|
}
|
|
|
|
d->currentParsedTrack++;
|
|
|
|
d->cdText.resize( d->currentParsedTrack );
|
|
|
|
// parse the tracktype
|
|
if( trackRx.cap(2) == "AUDIO" ) {
|
|
d->trackType = K3bDevice::Track::AUDIO;
|
|
d->trackMode = K3bDevice::Track::UNKNOWN;
|
|
}
|
|
else {
|
|
d->trackType = K3bDevice::Track::DATA;
|
|
if( trackRx.cap(2).startsWith("MODE1") ) {
|
|
d->trackMode = K3bDevice::Track::MODE1;
|
|
d->rawData = (trackRx.cap(2) == "MODE1/2352");
|
|
}
|
|
else if( trackRx.cap(2).startsWith("MODE2") ) {
|
|
d->trackMode = K3bDevice::Track::MODE2;
|
|
d->rawData = (trackRx.cap(2) == "MODE2/2352");
|
|
}
|
|
else {
|
|
kdDebug() << "(K3bCueFileParser) unsupported track type: " << TQString(trackRx.cap(2)) << endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
d->haveIndex1 = false;
|
|
d->inTrack = true;
|
|
d->index0 = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// FLAGS
|
|
//
|
|
else if( flagsRx.exactMatch( line ) ) {
|
|
if( !d->inTrack ) {
|
|
kdDebug() << "(K3bCueFileParser) FLAGS statement without TRACK." << endl;
|
|
return false;
|
|
}
|
|
|
|
// TODO: save the flags
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// INDEX
|
|
//
|
|
else if( indexRx.exactMatch( line ) ) {
|
|
if( !d->inTrack ) {
|
|
kdDebug() << "(K3bCueFileParser) INDEX statement without TRACK." << endl;
|
|
return false;
|
|
}
|
|
|
|
unsigned int indexNumber = indexRx.cap(1).toInt();
|
|
|
|
K3b::Msf indexStart = K3b::Msf::fromString( indexRx.cap(2) );
|
|
|
|
if( indexNumber == 0 ) {
|
|
d->index0 = indexStart;
|
|
|
|
if( d->currentParsedTrack < 2 && indexStart > 0 ) {
|
|
kdDebug() << "(K3bCueFileParser) first track is not allowed to have a pregap > 0." << endl;
|
|
return false;
|
|
}
|
|
}
|
|
else if( indexNumber == 1 ) {
|
|
d->haveIndex1 = true;
|
|
d->currentDataPos = indexStart;
|
|
if( d->currentParsedTrack > 1 ) {
|
|
d->toc[d->currentParsedTrack-2].setLastSector( indexStart-1 );
|
|
if( d->index0 > 0 && d->index0 < indexStart ) {
|
|
d->toc[d->currentParsedTrack-2].setIndex0( d->index0 - d->toc[d->currentParsedTrack-2].firstSector() );
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// TODO: add index > 0
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// CATALOG
|
|
//
|
|
if( catalogRx.exactMatch( line ) ) {
|
|
// TODO: set the toc's mcn
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// ISRC
|
|
//
|
|
if( isrcRx.exactMatch( line ) ) {
|
|
if( d->inTrack ) {
|
|
// TODO: set the track's ISRC
|
|
return true;
|
|
}
|
|
else {
|
|
kdDebug() << "(K3bCueFileParser) ISRC without TRACK." << endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// CD-TEXT
|
|
// TODO: create K3bDevice::TrackCdText entries
|
|
//
|
|
else if( titleRx.exactMatch( line ) ) {
|
|
if( d->inTrack )
|
|
d->cdText[d->currentParsedTrack-1].setTitle( titleRx.cap(1) );
|
|
else
|
|
d->cdText.setTitle( titleRx.cap(1) );
|
|
return true;
|
|
}
|
|
|
|
else if( performerRx.exactMatch( line ) ) {
|
|
if( d->inTrack )
|
|
d->cdText[d->currentParsedTrack-1].setPerformer( performerRx.cap(1) );
|
|
else
|
|
d->cdText.setPerformer( performerRx.cap(1) );
|
|
return true;
|
|
}
|
|
|
|
else if( songwriterRx.exactMatch( line ) ) {
|
|
if( d->inTrack )
|
|
d->cdText[d->currentParsedTrack-1].setSongwriter( songwriterRx.cap(1) );
|
|
else
|
|
d->cdText.setSongwriter( songwriterRx.cap(1) );
|
|
return true;
|
|
}
|
|
|
|
else {
|
|
kdDebug() << "(K3bCueFileParser) unknown Cue line: '" << line << "'" << endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
void K3bCueFileParser::simplifyWhiteSpace( TQString& s )
|
|
{
|
|
s = s.stripWhiteSpace();
|
|
|
|
unsigned int i = 0;
|
|
bool insideQuote = false;
|
|
while( i < s.length() ) {
|
|
if( !insideQuote ) {
|
|
if( s[i].isSpace() && s[i+1].isSpace() )
|
|
s.remove( i, 1 );
|
|
}
|
|
|
|
if( s[i] == '"' )
|
|
insideQuote = !insideQuote;
|
|
|
|
++i;
|
|
}
|
|
}
|
|
|
|
|
|
const K3bDevice::Toc& K3bCueFileParser::toc() const
|
|
{
|
|
return d->toc;
|
|
}
|
|
|
|
|
|
const K3bDevice::CdText& K3bCueFileParser::cdText() const
|
|
{
|
|
return d->cdText;
|
|
}
|
|
|
|
|
|
bool K3bCueFileParser::findImageFileName( const TQString& dataFile )
|
|
{
|
|
//
|
|
// CDRDAO does not use this image filename but replaces the extension from the cue file
|
|
// with "bin" to get the image filename, we should take this into account
|
|
//
|
|
|
|
m_imageFilenameInCue = true;
|
|
|
|
// first try filename as a hole (absolut)
|
|
if( TQFile::exists( dataFile ) ) {
|
|
setImageFilename( TQFileInfo(dataFile).absFilePath() );
|
|
return true;
|
|
}
|
|
|
|
// try the filename in the cue's directory
|
|
if( TQFileInfo( K3b::parentDir(filename()) + dataFile.section( '/', -1 ) ).isFile() ) {
|
|
setImageFilename( K3b::parentDir(filename()) + dataFile.section( '/', -1 ) );
|
|
kdDebug() << "(K3bCueFileParser) found image file: " << imageFilename() << endl;
|
|
return true;
|
|
}
|
|
|
|
// try the filename ignoring case
|
|
if( TQFileInfo( K3b::parentDir(filename()) + TQString(dataFile.section( '/', -1 )).lower() ).isFile() ) {
|
|
setImageFilename( K3b::parentDir(filename()) + TQString(dataFile.section( '/', -1 )).lower() );
|
|
kdDebug() << "(K3bCueFileParser) found image file: " << imageFilename() << endl;
|
|
return true;
|
|
}
|
|
|
|
m_imageFilenameInCue = false;
|
|
|
|
// try removing the ending from the cue file (image.bin.cue and image.bin)
|
|
if( TQFileInfo( filename().left( filename().length()-4 ) ).isFile() ) {
|
|
setImageFilename( filename().left( filename().length()-4 ) );
|
|
kdDebug() << "(K3bCueFileParser) found image file: " << imageFilename() << endl;
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// we did not find the image specified in the cue.
|
|
// Search for another one having the same filename as the cue but a different extension
|
|
//
|
|
|
|
TQDir parentDir( K3b::parentDir(filename()) );
|
|
TQString filenamePrefix = filename().section( '/', -1 );
|
|
filenamePrefix.truncate( filenamePrefix.length() - 3 ); // remove cue extension
|
|
kdDebug() << "(K3bCueFileParser) checking folder " << parentDir.path() << " for files: " << filenamePrefix << "*" << endl;
|
|
|
|
//
|
|
// we cannot use the nameFilter in TQDir because of the spaces that may occur in filenames
|
|
//
|
|
TQStringList possibleImageFiles = parentDir.entryList( TQDir::Files );
|
|
int cnt = 0;
|
|
for( TQStringList::const_iterator it = possibleImageFiles.constBegin(); it != possibleImageFiles.constEnd(); ++it ) {
|
|
if( (*it).lower() == TQString(dataFile.section( '/', -1 )).lower() ||
|
|
(*it).startsWith( filenamePrefix ) && !(*it).endsWith( "cue" ) ) {
|
|
++cnt;
|
|
setImageFilename( K3b::parentDir(filename()) + *it );
|
|
}
|
|
}
|
|
|
|
//
|
|
// we only do this if there is one unique file which fits the requirements.
|
|
// Otherwise we cannot be certain to have the right file.
|
|
//
|
|
return ( cnt == 1 && TQFileInfo( imageFilename() ).isFile() );
|
|
}
|