/* * * $Id: k3baudiojob.cpp 690212 2007-07-20 11:02:13Z trueg $ * Copyright (C) 2003 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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 "k3baudiojob.h" #include "k3baudioimager.h" #include #include "k3baudiotrack.h" #include "k3baudiodatasource.h" #include "k3baudionormalizejob.h" #include "k3baudiojobtempdata.h" #include "k3baudiomaxspeedjob.h" #include "k3baudiocdtracksource.h" #include "k3baudiofile.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static TQString createNonExistingFilesString( const TQValueList& items, unsigned int max ) { TQString s; unsigned int cnt = 0; for( TQValueList::const_iterator it = items.begin(); it != items.end(); ++it ) { s += KStringHandler::csqueeze( (*it)->filename(), 60 ); ++cnt; if( cnt >= max || it == items.end() ) break; s += "
"; } if( items.count() > max ) s += "..."; return s; } class K3bAudioJob::Private { public: Private() : copies(1), copiesDone(0) { } int copies; int copiesDone; int usedSpeed; bool useCdText; bool maxSpeed; bool zeroPregap; bool less4Sec; }; K3bAudioJob::K3bAudioJob( K3bAudioDoc* doc, K3bJobHandler* hdl, TQObject* parent ) : K3bBurnJob( hdl, parent ), m_doc( doc ), m_normalizeJob(0), m_maxSpeedJob(0) { d = new Private; m_audioImager = new K3bAudioImager( m_doc, this, this ); connect( m_audioImager, TQT_SIGNAL(infoMessage(const TQString&, int)), this, TQT_SIGNAL(infoMessage(const TQString&, int)) ); connect( m_audioImager, TQT_SIGNAL(percent(int)), this, TQT_SLOT(slotAudioDecoderPercent(int)) ); connect( m_audioImager, TQT_SIGNAL(subPercent(int)), this, TQT_SLOT(slotAudioDecoderSubPercent(int)) ); connect( m_audioImager, TQT_SIGNAL(finished(bool)), this, TQT_SLOT(slotAudioDecoderFinished(bool)) ); connect( m_audioImager, TQT_SIGNAL(nextTrack(int, int)), this, TQT_SLOT(slotAudioDecoderNextTrack(int, int)) ); m_writer = 0; m_tempData = new K3bAudioJobTempData( m_doc, this ); } K3bAudioJob::~K3bAudioJob() { delete d; } K3bDevice::Device* K3bAudioJob::writer() const { if( m_doc->onlyCreateImages() ) return 0; // no writer needed -> no blocking on K3bBurnJob else return m_doc->burner(); } K3bDoc* K3bAudioJob::doc() const { return m_doc; } void K3bAudioJob::start() { jobStarted(); m_written = true; m_canceled = false; m_errorOccuredAndAlreadyReported = false; d->copies = m_doc->copies(); d->copiesDone = 0; d->useCdText = m_doc->cdText(); d->usedSpeed = m_doc->speed(); d->maxSpeed = false; if( m_doc->dummy() ) d->copies = 1; emit newTask( i18n("Preparing data") ); // // Check if all files exist // TQValueList nonExistingFiles; K3bAudioTrack* track = m_doc->firstTrack(); while( track ) { K3bAudioDataSource* source = track->firstSource(); while( source ) { if( K3bAudioFile* file = dynamic_cast( source ) ) { if( !TQFile::exists( file->filename() ) ) nonExistingFiles.append( file ); } source = source->next(); } track = track->next(); } if( !nonExistingFiles.isEmpty() ) { if( questionYesNo( "

" + i18n("The following files could not be found. Do you want to remove them from the " "project and continue without adding them to the image?") + "

" + createNonExistingFilesString( nonExistingFiles, 10 ), i18n("Warning"), i18n("Remove missing files and continue"), i18n("Cancel and go back") ) ) { for( TQValueList::const_iterator it = nonExistingFiles.begin(); it != nonExistingFiles.end(); ++it ) { delete *it; } } else { m_canceled = true; emit canceled(); jobFinished(false); return; } } // // Make sure the project is not empty // if( m_doc->numOfTracks() == 0 ) { emit infoMessage( i18n("Please add files to your project first."), ERROR ); jobFinished(false); return; } if( m_doc->onTheFly() && !checkAudioSources() ) { emit infoMessage( i18n("Unable to write on-the-fly with these audio sources."), WARNING ); m_doc->setOnTheFly(false); } // we don't need this when only creating image and it is possible // that the burn device is null if( !m_doc->onlyCreateImages() ) { // // there are a lot of writers out there which produce coasters // in dao mode if the CD contains pregaps of length 0 (or maybe already != 2 secs?) // // Also most writers do not accept cuesheets with tracks smaller than 4 seconds (a violation // of the red book standard) in DAO mode. // d->zeroPregap = false; d->less4Sec = false; track = m_doc->firstTrack(); while( track ) { if( track->postGap() == 0 && track->next() != 0 ) // the last track's postgap is always 0 d->zeroPregap = true; if( track->length() < K3b::Msf( 0, 4, 0 ) ) d->less4Sec = true; track = track->next(); } // determine writing mode if( m_doc->writingMode() == K3b::WRITING_MODE_AUTO ) { // // DAO is always the first choice // RAW second and TAO last // there are none-DAO writers that are supported by cdrdao // // older cdrecord versions do not support the -shorttrack option in RAW writing mode // if( !writer()->dao() && writingApp() == K3b::CDRECORD ) { if(!writer()->supportsRawWriting() && ( !d->less4Sec || k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "short-track-raw" ) ) ) m_usedWritingMode = K3b::RAW; else m_usedWritingMode = K3b::TAO; } else { if( (d->zeroPregap||d->less4Sec) && writer()->supportsRawWriting() ) { m_usedWritingMode = K3b::RAW; if( d->less4Sec ) emit infoMessage( i18n("Tracklengths below 4 seconds violate the Red Book standard."), WARNING ); } else m_usedWritingMode = K3b::DAO; } } else m_usedWritingMode = m_doc->writingMode(); bool cdrecordOnTheFly = false; bool cdrecordCdText = false; if( k3bcore->externalBinManager()->binObject("cdrecord") ) { cdrecordOnTheFly = k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "audio-stdin" ); cdrecordCdText = k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "cdtext" ); } // determine writing app if( writingApp() == K3b::DEFAULT ) { if( m_usedWritingMode == K3b::DAO ) { // there are none-DAO writers that are supported by cdrdao if( !writer()->dao() || ( !cdrecordOnTheFly && m_doc->onTheFly() ) || ( d->useCdText && !cdrecordCdText ) || m_doc->hideFirstTrack() ) m_usedWritingApp = K3b::CDRDAO; else m_usedWritingApp = K3b::CDRECORD; } else m_usedWritingApp = K3b::CDRECORD; } else m_usedWritingApp = writingApp(); // on-the-fly writing with cdrecord >= 2.01a13 if( m_usedWritingApp == K3b::CDRECORD && m_doc->onTheFly() && !cdrecordOnTheFly ) { emit infoMessage( i18n("On-the-fly writing with cdrecord < 2.01a13 not supported."), ERROR ); m_doc->setOnTheFly(false); } if( m_usedWritingApp == K3b::CDRECORD && d->useCdText ) { if( !cdrecordCdText ) { emit infoMessage( i18n("Cdrecord %1 does not support CD-Text writing.") .arg(k3bcore->externalBinManager()->binObject("cdrecord")->version), ERROR ); d->useCdText = false; } else if( m_usedWritingMode == K3b::TAO ) { emit infoMessage( i18n("It is not possible to write CD-Text in TAO mode."), WARNING ); d->useCdText = false; } } } if( !m_doc->onlyCreateImages() && m_doc->onTheFly() ) { if( m_doc->speed() == 0 ) { // try to determine the max possible speed emit newSubTask( i18n("Determining maximum writing speed") ); if( !m_maxSpeedJob ) { m_maxSpeedJob = new K3bAudioMaxSpeedJob( m_doc, this, this ); connect( m_maxSpeedJob, TQT_SIGNAL(percent(int)), this, TQT_SIGNAL(subPercent(int)) ); connect( m_maxSpeedJob, TQT_SIGNAL(finished(bool)), this, TQT_SLOT(slotMaxSpeedJobFinished(bool)) ); } m_maxSpeedJob->start(); return; } else { if( !prepareWriter() ) { cleanupAfterError(); jobFinished(false); return; } if( startWriting() ) { // now the writer is running and we can get it's stdin // we only use this method when writing on-the-fly since // we cannot easily change the audioDecode fd while it's working // which we would need to do since we write into several // image files. m_audioImager->writeToFd( m_writer->fd() ); } else { // startWriting() already did the cleanup return; } } } else { emit burning(false); emit infoMessage( i18n("Creating image files in %1").arg(m_doc->tempDir()), INFO ); emit newTask( i18n("Creating image files") ); m_tempData->prepareTempFileNames( doc()->tempDir() ); TQStringList filenames; for( int i = 1; i <= m_doc->numOfTracks(); ++i ) filenames += m_tempData->bufferFileName( i ); m_audioImager->setImageFilenames( filenames ); } m_audioImager->start(); } void K3bAudioJob::slotMaxSpeedJobFinished( bool success ) { d->maxSpeed = success; if( !success ) emit infoMessage( i18n("Unable to determine maximum speed for some reason. Ignoring."), WARNING ); // now start the writing // same code as above. See the commecnts there if( !prepareWriter() ) { cleanupAfterError(); jobFinished(false); return; } if( startWriting() ) m_audioImager->writeToFd( m_writer->fd() ); m_audioImager->start(); } void K3bAudioJob::cancel() { m_canceled = true; if( m_maxSpeedJob ) m_maxSpeedJob->cancel(); if( m_writer ) m_writer->cancel(); m_audioImager->cancel(); emit infoMessage( i18n("Writing canceled."), K3bJob::ERROR ); removeBufferFiles(); emit canceled(); jobFinished(false); } void K3bAudioJob::slotWriterFinished( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; if( !success ) { cleanupAfterError(); jobFinished(false); return; } else { d->copiesDone++; if( d->copiesDone == d->copies ) { if( m_doc->onTheFly() || m_doc->removeImages() ) removeBufferFiles(); jobFinished(true); } else { K3bDevice::eject( m_doc->burner() ); if( startWriting() ) { if( m_doc->onTheFly() ) { // now the writer is running and we can get it's stdin // we only use this method when writing on-the-fly since // we cannot easily change the audioDecode fd while it's working // which we would need to do since we write into several // image files. m_audioImager->writeToFd( m_writer->fd() ); m_audioImager->start(); } } } } } void K3bAudioJob::slotAudioDecoderFinished( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; if( !success ) { if( m_audioImager->lastErrorType() == K3bAudioImager::ERROR_FD_WRITE ) { // this means that the writer job failed so let's use the error handling there. return; } emit infoMessage( i18n("Error while decoding audio tracks."), ERROR ); cleanupAfterError(); jobFinished(false); return; } if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) { emit infoMessage( i18n("Successfully decoded all tracks."), SUCCESS ); if( m_doc->normalize() ) { normalizeFiles(); } else if( !m_doc->onlyCreateImages() ) { if( !prepareWriter() ) { cleanupAfterError(); jobFinished(false); } else startWriting(); } else { jobFinished(true); } } } void K3bAudioJob::slotAudioDecoderNextTrack( int t, int tt ) { if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) { K3bAudioTrack* track = m_doc->getTrack(t); emit newSubTask( i18n("Decoding audio track %1 of %2%3") .arg(t) .arg(tt) .arg( track->title().isEmpty() || track->artist().isEmpty() ? TQString() : " (" + track->artist() + " - " + track->title() + ")" ) ); } } bool K3bAudioJob::prepareWriter() { delete m_writer; if( m_usedWritingApp == K3b::CDRECORD ) { if( !writeInfFiles() ) { kdDebug() << "(K3bAudioJob) could not write inf-files." << endl; emit infoMessage( i18n("IO Error. Most likely no space left on harddisk."), ERROR ); return false; } K3bCdrecordWriter* writer = new K3bCdrecordWriter( m_doc->burner(), this, this ); writer->setWritingMode( m_usedWritingMode ); writer->setSimulate( m_doc->dummy() ); writer->setBurnSpeed( d->usedSpeed ); writer->addArgument( "-useinfo" ); if( d->useCdText ) { writer->setRawCdText( m_doc->cdTextData().rawPackData() ); } // add all the audio tracks writer->addArgument( "-audio" ); // we only need to pad in one case. cdrecord < 2.01.01a03 cannot handle shorttrack + raw if( d->less4Sec ) { if( m_usedWritingMode == K3b::RAW && !k3bcore->externalBinManager()->binObject( "cdrecord" )->hasFeature( "short-track-raw" ) ) { writer->addArgument( "-pad" ); } else { // Allow tracks shorter than 4 seconds writer->addArgument( "-shorttrack" ); } } K3bAudioTrack* track = m_doc->firstTrack(); while( track ) { if( m_doc->onTheFly() ) { // this is only supported by cdrecord versions >= 2.01a13 writer->addArgument( TQFile::encodeName( m_tempData->infFileName( track ) ) ); } else { writer->addArgument( TQFile::encodeName( m_tempData->bufferFileName( track ) ) ); } track = track->next(); } m_writer = writer; } else { if( !writeTocFile() ) { kdDebug() << "(K3bDataJob) could not write tocfile." << endl; emit infoMessage( i18n("IO Error"), ERROR ); return false; } // create the writer // create cdrdao job K3bCdrdaoWriter* writer = new K3bCdrdaoWriter( m_doc->burner(), this, this ); writer->setCommand( K3bCdrdaoWriter::WRITE ); writer->setSimulate( m_doc->dummy() ); writer->setBurnSpeed( d->usedSpeed ); writer->setTocFile( m_tempData->tocFileName() ); m_writer = writer; } connect( m_writer, TQT_SIGNAL(infoMessage(const TQString&, int)), this, TQT_SIGNAL(infoMessage(const TQString&, int)) ); connect( m_writer, TQT_SIGNAL(percent(int)), this, TQT_SLOT(slotWriterJobPercent(int)) ); connect( m_writer, TQT_SIGNAL(processedSize(int, int)), this, TQT_SIGNAL(processedSize(int, int)) ); connect( m_writer, TQT_SIGNAL(subPercent(int)), this, TQT_SIGNAL(subPercent(int)) ); connect( m_writer, TQT_SIGNAL(processedSubSize(int, int)), this, TQT_SIGNAL(processedSubSize(int, int)) ); connect( m_writer, TQT_SIGNAL(nextTrack(int, int)), this, TQT_SLOT(slotWriterNextTrack(int, int)) ); connect( m_writer, TQT_SIGNAL(buffer(int)), this, TQT_SIGNAL(bufferStatus(int)) ); connect( m_writer, TQT_SIGNAL(deviceBuffer(int)), this, TQT_SIGNAL(deviceBuffer(int)) ); connect( m_writer, TQT_SIGNAL(writeSpeed(int, int)), this, TQT_SIGNAL(writeSpeed(int, int)) ); connect( m_writer, TQT_SIGNAL(finished(bool)), this, TQT_SLOT(slotWriterFinished(bool)) ); // connect( m_writer, TQT_SIGNAL(newTask(const TQString&)), this, TQT_SIGNAL(newTask(const TQString&)) ); connect( m_writer, TQT_SIGNAL(newSubTask(const TQString&)), this, TQT_SIGNAL(newSubTask(const TQString&)) ); connect( m_writer, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)), this, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)) ); return true; } void K3bAudioJob::slotWriterNextTrack( int t, int tt ) { K3bAudioTrack* track = m_doc->getTrack(t); // t is in range 1..tt if( m_doc->hideFirstTrack() ) track = m_doc->getTrack(t+1); emit newSubTask( i18n("Writing track %1 of %2%3") .arg(t) .arg(tt) .arg( track->title().isEmpty() || track->artist().isEmpty() ? TQString() : " (" + track->artist() + " - " + track->title() + ")" ) ); } void K3bAudioJob::slotWriterJobPercent( int p ) { double totalTasks = d->copies; double tasksDone = d->copiesDone; if( m_doc->normalize() ) { totalTasks+=1.0; tasksDone+=1.0; } if( !m_doc->onTheFly() ) { totalTasks+=1.0; tasksDone+=1.0; } emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } void K3bAudioJob::slotAudioDecoderPercent( int p ) { if( m_doc->onlyCreateImages() ) { if( m_doc->normalize() ) emit percent( p/2 ); else emit percent( p ); } else if( !m_doc->onTheFly() ) { double totalTasks = d->copies; double tasksDone = d->copiesDone; // =0 when creating an image if( m_doc->normalize() ) { totalTasks+=1.0; } if( !m_doc->onTheFly() ) { totalTasks+=1.0; } emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } } void K3bAudioJob::slotAudioDecoderSubPercent( int p ) { // when writing on the fly the writer produces the subPercent if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) { emit subPercent( p ); } } bool K3bAudioJob::startWriting() { if( m_doc->dummy() ) emit newTask( i18n("Simulating") ); else if( d->copies > 1 ) emit newTask( i18n("Writing Copy %1").arg(d->copiesDone+1) ); else emit newTask( i18n("Writing") ); emit newSubTask( i18n("Waiting for media") ); if( waitForMedia( m_doc->burner() ) < 0 ) { cancel(); return false; } // just to be sure we did not get canceled during the async discWaiting if( m_canceled ) return false; // in case we determined the max possible writing speed we have to reset the speed on the writer job // here since an inserted media is necessary // the Max speed job will compare the max speed value with the supported values of the writer if( d->maxSpeed ) m_writer->setBurnSpeed( m_maxSpeedJob->maxSpeed() ); emit burning(true); m_writer->start(); return true; } void K3bAudioJob::cleanupAfterError() { m_errorOccuredAndAlreadyReported = true; m_audioImager->cancel(); if( m_writer ) m_writer->cancel(); // remove the temp files removeBufferFiles(); } void K3bAudioJob::removeBufferFiles() { if ( !m_doc->onTheFly() ) { emit infoMessage( i18n("Removing temporary files."), INFO ); } // removes buffer images and temp toc or inf files m_tempData->cleanup(); } void K3bAudioJob::normalizeFiles() { if( !m_normalizeJob ) { m_normalizeJob = new K3bAudioNormalizeJob( this, this ); connect( m_normalizeJob, TQT_SIGNAL(infoMessage(const TQString&, int)), this, TQT_SIGNAL(infoMessage(const TQString&, int)) ); connect( m_normalizeJob, TQT_SIGNAL(percent(int)), this, TQT_SLOT(slotNormalizeProgress(int)) ); connect( m_normalizeJob, TQT_SIGNAL(subPercent(int)), this, TQT_SLOT(slotNormalizeSubProgress(int)) ); connect( m_normalizeJob, TQT_SIGNAL(finished(bool)), this, TQT_SLOT(slotNormalizeJobFinished(bool)) ); connect( m_normalizeJob, TQT_SIGNAL(newTask(const TQString&)), this, TQT_SIGNAL(newSubTask(const TQString&)) ); connect( m_normalizeJob, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)), this, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)) ); } // add all the files // TODO: we may need to split the wave files and put them back together! TQValueVector files; K3bAudioTrack* track = m_doc->firstTrack(); while( track ) { files.append( m_tempData->bufferFileName(track) ); track = track->next(); } m_normalizeJob->setFilesToNormalize( files ); emit newTask( i18n("Normalizing volume levels") ); m_normalizeJob->start(); } void K3bAudioJob::slotNormalizeJobFinished( bool success ) { if( m_canceled || m_errorOccuredAndAlreadyReported ) return; if( success ) { if( m_doc->onlyCreateImages() ) { jobFinished(true); } else { // start the writing if( !prepareWriter() ) { cleanupAfterError(); jobFinished(false); } else startWriting(); } } else { cleanupAfterError(); jobFinished(false); } } void K3bAudioJob::slotNormalizeProgress( int p ) { double totalTasks = d->copies+2.0; double tasksDone = 1; // the decoding has been finished emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); } void K3bAudioJob::slotNormalizeSubProgress( int p ) { emit subPercent( p ); } bool K3bAudioJob::writeTocFile() { K3bTocFileWriter tocWriter; tocWriter.setData( m_doc->toToc() ); tocWriter.setHideFirstTrack( m_doc->hideFirstTrack() ); if( d->useCdText ) tocWriter.setCdText( m_doc->cdTextData() ); if( !m_doc->onTheFly() ) { TQStringList filenames; for( int i = 1; i <= m_doc->numOfTracks(); ++i ) filenames += m_tempData->bufferFileName( i ); tocWriter.setFilenames( filenames ); } return tocWriter.save( m_tempData->tocFileName() ); } bool K3bAudioJob::writeInfFiles() { K3bInfFileWriter infFileWriter; K3bAudioTrack* track = m_doc->firstTrack(); while( track ) { infFileWriter.setTrack( track->toCdTrack() ); infFileWriter.setTrackNumber( track->trackNumber() ); if( !m_doc->onTheFly() ) infFileWriter.setBigEndian( false ); if( !infFileWriter.save( m_tempData->infFileName(track) ) ) return false; track = track->next(); } return true; } // checks if the doc contains sources from an audio cd which cannot be read on-the-fly bool K3bAudioJob::checkAudioSources() { K3bAudioTrack* track = m_doc->firstTrack(); K3bAudioDataSource* source = track->firstSource(); while( source ) { if( K3bAudioCdTrackSource* cdSource = dynamic_cast(source) ) { // // If which cases we cannot wite on-the-fly: // 1. the writing device contains one of the audio cds // 2. Well, one of the cds is missing // K3bDevice::Device* dev = cdSource->searchForAudioCD(); if( !dev || dev == writer() ) return false; else cdSource->setDevice( dev ); } // next source source = source->next(); if( !source ) { track = track->next(); if( track ) source = track->firstSource(); } } return true; } TQString K3bAudioJob::jobDescription() const { return i18n("Writing Audio CD") + ( m_doc->title().isEmpty() ? TQString() : TQString( " (%1)" ).arg(m_doc->title()) ); } TQString K3bAudioJob::jobDetails() const { return ( i18n( "1 track (%1 minutes)", "%n tracks (%1 minutes)", m_doc->numOfTracks() ).arg(m_doc->length().toString()) + ( m_doc->copies() > 1 && !m_doc->dummy() ? i18n(" - %n copy", " - %n copies", m_doc->copies()) : TQString() ) ); } #include "k3baudiojob.moc"