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.
k3b/libk3b/projects/k3bcuefileparser.cpp

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 <tqtextstream.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() );
}