diff options
Diffstat (limited to 'libk3b/jobs')
33 files changed, 8650 insertions, 0 deletions
diff --git a/libk3b/jobs/Makefile.am b/libk3b/jobs/Makefile.am new file mode 100644 index 0000000..72a9eac --- /dev/null +++ b/libk3b/jobs/Makefile.am @@ -0,0 +1,43 @@ +AM_CPPFLAGS = -I$(srcdir)/../core \ + -I$(srcdir)/../../libk3bdevice \ + -I$(srcdir)/../../src \ + -I$(srcdir)/../tools \ + -I$(srcdir)/../cddb \ + -I$(srcdir)/../plugin \ + -I$(srcdir)/../projects \ + -I$(srcdir)/../videodvd \ + -I$(srcdir)/../projects/audiocd \ + $(all_includes) + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libjobs.la + +if include_videodvdrip +libjobs_la_SOURCES = k3bdatatrackreader.cpp k3breadcdreader.cpp \ + k3bcdcopyjob.cpp k3bclonejob.cpp k3baudiosessionreadingjob.cpp \ + k3bdvdcopyjob.cpp k3bvideodvdtitletranscodingjob.cpp k3bvideodvdtitledetectclippingjob.cpp \ + k3baudiocuefilewritingjob.cpp k3bbinimagewritingjob.cpp \ + k3biso9660imagewritingjob.cpp \ + k3bdvdformattingjob.cpp k3bblankingjob.cpp k3bclonetocreader.cpp \ + k3bverificationjob.cpp + +include_HEADERS = k3bcdcopyjob.h k3bdvdcopyjob.h k3bclonejob.h \ + k3baudiocuefilewritingjob.h k3bbinimagewritingjob.h \ + k3biso9660imagewritingjob.h k3bdvdformattingjob.h \ + k3bblankingjob.h k3bvideodvdtitletranscodingjob.h k3bvideodvdtitledetectclippingjob.h \ + k3bverificationjob.h +else +libjobs_la_SOURCES = k3bdatatrackreader.cpp k3breadcdreader.cpp \ + k3bcdcopyjob.cpp k3bclonejob.cpp k3baudiosessionreadingjob.cpp \ + k3bdvdcopyjob.cpp \ + k3baudiocuefilewritingjob.cpp k3bbinimagewritingjob.cpp \ + k3biso9660imagewritingjob.cpp \ + k3bdvdformattingjob.cpp k3bblankingjob.cpp k3bclonetocreader.cpp \ + k3bverificationjob.cpp + +include_HEADERS = k3bcdcopyjob.h k3bdvdcopyjob.h k3bclonejob.h \ + k3baudiocuefilewritingjob.h k3bbinimagewritingjob.h \ + k3biso9660imagewritingjob.h k3bdvdformattingjob.h \ + k3bblankingjob.h k3bverificationjob.h +endif diff --git a/libk3b/jobs/k3baudiocuefilewritingjob.cpp b/libk3b/jobs/k3baudiocuefilewritingjob.cpp new file mode 100644 index 0000000..0c5cd9a --- /dev/null +++ b/libk3b/jobs/k3baudiocuefilewritingjob.cpp @@ -0,0 +1,272 @@ +/* + * + * $Id: k3baudiocuefilewritingjob.cpp 619556 2007-01-03 17:38:12Z trueg $ + * Copyright (C) 2005 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 "k3baudiocuefilewritingjob.h" + +#include <k3baudiodoc.h> +#include <k3baudiojob.h> +#include <k3bdevice.h> +#include <k3baudiodecoder.h> +#include <k3baudiotrack.h> +#include <k3baudiofile.h> +#include <k3bcuefileparser.h> +#include <k3bthread.h> +#include <k3bthreadjob.h> + +#include <kdebug.h> +#include <klocale.h> + + +class K3bAudioCueFileWritingJob::AnalyserThread : public K3bThread +{ +public: + AnalyserThread() + : K3bThread() { + } + + void setDecoder( K3bAudioDecoder* dec ) { m_decoder = dec; } + +protected: + void run() { + emitStarted(); + m_decoder->analyseFile(); + emitFinished(true); + } + +private: + K3bAudioDecoder* m_decoder; +}; + + +K3bAudioCueFileWritingJob::K3bAudioCueFileWritingJob( K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bBurnJob( jh, parent, name ), + m_decoder(0) +{ + m_analyserThread = new AnalyserThread(); + m_analyserJob = new K3bThreadJob( m_analyserThread, this, this ); + connect( m_analyserJob, SIGNAL(finished(bool)), this, SLOT(slotAnalyserThreadFinished(bool)) ); + + m_audioDoc = new K3bAudioDoc( this ); + m_audioDoc->newDocument(); + m_audioJob = new K3bAudioJob( m_audioDoc, this, this ); + + // just loop all through + connect( m_audioJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); + connect( m_audioJob, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( m_audioJob, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + connect( m_audioJob, SIGNAL(infoMessage(const QString&, int)), + this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_audioJob, SIGNAL(finished(bool)), this, SIGNAL(finished(bool)) ); + connect( m_audioJob, SIGNAL(canceled()), this, SIGNAL(canceled()) ); + connect( m_audioJob, SIGNAL(percent(int)), this, SIGNAL(percent(int)) ); + connect( m_audioJob, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) ); + connect( m_audioJob, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); + connect( m_audioJob, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); + connect( m_audioJob, SIGNAL(burning(bool)), this, SIGNAL(burning(bool)) ); + connect( m_audioJob, SIGNAL(bufferStatus(int)), this, SIGNAL(bufferStatus(int)) ); + connect( m_audioJob, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); + connect( m_audioJob, SIGNAL(writeSpeed(int, int)), this, SIGNAL(writeSpeed(int, int)) ); + + m_canceled = false; + m_audioJobRunning = false; +} + + +K3bAudioCueFileWritingJob::~K3bAudioCueFileWritingJob() +{ + // the threadjob does not delete the thread + delete m_analyserThread; +} + + +K3bDevice::Device* K3bAudioCueFileWritingJob::writer() const +{ + return m_audioDoc->burner(); +} + + +QString K3bAudioCueFileWritingJob::jobDescription() const +{ + return i18n("Writing Audio Cue File"); +} + + +QString K3bAudioCueFileWritingJob::jobDetails() const +{ + return m_cueFile.section( '/', -1 ); +} + + +void K3bAudioCueFileWritingJob::start() +{ + // FIXME: here we trust that a job won't be started twice :( + jobStarted(); + m_canceled = false; + m_audioJobRunning = false; + importCueInProject(); +} + + +void K3bAudioCueFileWritingJob::cancel() +{ + m_canceled = true; + + // the AudioJob cancel method is very stupid. It emits the canceled signal even if it was never running :( + if( m_audioJobRunning ) + m_audioJob->cancel(); + m_analyserJob->cancel(); +} + + +void K3bAudioCueFileWritingJob::setCueFile( const QString& s ) +{ + m_cueFile = s; +} + + +void K3bAudioCueFileWritingJob::setOnTheFly( bool b ) +{ + m_audioDoc->setOnTheFly( b ); +} + + +void K3bAudioCueFileWritingJob::setSpeed( int s ) +{ + m_audioDoc->setSpeed( s ); +} + + +void K3bAudioCueFileWritingJob::setBurnDevice( K3bDevice::Device* dev ) +{ + m_audioDoc->setBurner( dev ); +} + + +void K3bAudioCueFileWritingJob::setWritingMode( int mode ) +{ + m_audioDoc->setWritingMode( mode ); +} + + +void K3bAudioCueFileWritingJob::setSimulate( bool b ) +{ + m_audioDoc->setDummy( b ); +} + + +void K3bAudioCueFileWritingJob::setCopies( int c ) +{ + m_audioDoc->setCopies( c ); +} + + +void K3bAudioCueFileWritingJob::setTempDir( const QString& s ) +{ + m_audioDoc->setTempDir( s ); +} + + +void K3bAudioCueFileWritingJob::slotAnalyserThreadFinished( bool ) +{ + if( !m_canceled ) { + if( m_audioDoc->lastTrack()->length() == 0 ) { + emit infoMessage( i18n("Analysing the audio file failed. Corrupt file?"), ERROR ); + jobFinished(false); + } + else { + // FIXME: m_audioJobRunning is never reset + m_audioJobRunning = true; + m_audioJob->start(); // from here on the audio job takes over completely + } + } + else { + emit canceled(); + jobFinished(false); + } +} + + +void K3bAudioCueFileWritingJob::importCueInProject() +{ + // cleanup the project (this wil also delete the decoder) + // we do not use newDocument as that would overwrite the settings already made + while( m_audioDoc->firstTrack() ) + delete m_audioDoc->firstTrack()->take(); + + m_decoder = 0; + + K3bCueFileParser parser( m_cueFile ); + if( parser.isValid() && parser.toc().contentType() == K3bDevice::AUDIO ) { + + kdDebug() << "(K3bAudioCueFileWritingJob::importCueFile) parsed with image: " << parser.imageFilename() << endl; + + // global cd-text + m_audioDoc->setTitle( parser.cdText().title() ); + m_audioDoc->setPerformer( parser.cdText().performer() ); + m_audioDoc->writeCdText( !parser.cdText().title().isEmpty() ); + + m_decoder = K3bAudioDecoderFactory::createDecoder( parser.imageFilename() ); + if( m_decoder ) { + m_decoder->setFilename( parser.imageFilename() ); + + K3bAudioTrack* after = 0; + K3bAudioFile* newFile = 0; + unsigned int i = 0; + for( K3bDevice::Toc::const_iterator it = parser.toc().begin(); + it != parser.toc().end(); ++it ) { + const K3bDevice::Track& track = *it; + + newFile = new K3bAudioFile( m_decoder, m_audioDoc ); + newFile->setStartOffset( track.firstSector() ); + newFile->setEndOffset( track.lastSector()+1 ); + + K3bAudioTrack* newTrack = new K3bAudioTrack( m_audioDoc ); + newTrack->addSource( newFile ); + newTrack->moveAfter( after ); + + // cd-text + newTrack->setTitle( parser.cdText()[i].title() ); + newTrack->setPerformer( parser.cdText()[i].performer() ); + + // add the next track after this one + after = newTrack; + ++i; + } + + // let the last source use the data up to the end of the file + if( newFile ) + newFile->setEndOffset(0); + + // now analyze the source + emit newTask( i18n("Analysing the audio file") ); + emit newSubTask( i18n("Analysing %1").arg( parser.imageFilename() ) ); + + // start the analyser thread + m_analyserThread->setDecoder( m_decoder ); + m_analyserJob->start(); + } + else { + emit infoMessage( i18n("Unable to handle '%1' due to an unsupported format.").arg( m_cueFile ), ERROR ); + jobFinished(false); + } + } + else { + emit infoMessage( i18n("No valid audio cue file: '%1'").arg( m_cueFile ), ERROR ); + jobFinished(false); + } +} + +#include "k3baudiocuefilewritingjob.moc" diff --git a/libk3b/jobs/k3baudiocuefilewritingjob.h b/libk3b/jobs/k3baudiocuefilewritingjob.h new file mode 100644 index 0000000..6e0a3c2 --- /dev/null +++ b/libk3b/jobs/k3baudiocuefilewritingjob.h @@ -0,0 +1,79 @@ +/* + * + * $Id: k3baudiocuefilewritingjob.h 619556 2007-01-03 17:38:12Z trueg $ + * Copyright (C) 2005 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. + */ + +#ifndef _K3B_AUDIO_CUE_FILEWRITING_JOB_H_ +#define _K3B_AUDIO_CUE_FILEWRITING_JOB_H_ + +#include <k3bjob.h> +#include "k3b_export.h" +class K3bAudioDoc; +class K3bAudioJob; +class K3bAudioDecoder; +class K3bThreadJob; +namespace K3bDevice { + class Device; +} + + +class LIBK3B_EXPORT K3bAudioCueFileWritingJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bAudioCueFileWritingJob( K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bAudioCueFileWritingJob(); + + K3bDevice::Device* writer() const; + + QString jobDescription() const; + QString jobDetails() const; + + const QString& cueFile() const { return m_cueFile; } + + public slots: + void start(); + void cancel(); + + void setCueFile( const QString& ); + void setSpeed( int s ); + void setBurnDevice( K3bDevice::Device* dev ); + void setWritingMode( int mode ); + void setSimulate( bool b ); + void setCopies( int c ); + void setOnTheFly( bool b ); + void setTempDir( const QString& ); + + private slots: + void slotAnalyserThreadFinished(bool); + + private: + void importCueInProject(); + + K3bDevice::Device* m_device; + + QString m_cueFile; + K3bAudioDoc* m_audioDoc; + K3bAudioJob* m_audioJob; + K3bAudioDecoder* m_decoder; + + bool m_canceled; + bool m_audioJobRunning; + + class AnalyserThread; + AnalyserThread* m_analyserThread; + K3bThreadJob* m_analyserJob; +}; + +#endif diff --git a/libk3b/jobs/k3baudiosessionreadingjob.cpp b/libk3b/jobs/k3baudiosessionreadingjob.cpp new file mode 100644 index 0000000..f4ac550 --- /dev/null +++ b/libk3b/jobs/k3baudiosessionreadingjob.cpp @@ -0,0 +1,278 @@ +/* + * + * $Id: k3baudiosessionreadingjob.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 "k3baudiosessionreadingjob.h" + +#include <k3bthread.h> +#include <k3btoc.h> +#include <k3bcdparanoialib.h> +#include <k3bwavefilewriter.h> +#include <k3bglobals.h> +#include <k3bdevice.h> +#include <k3bcore.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <unistd.h> + + +class K3bAudioSessionReadingJob::WorkThread : public K3bThread +{ +public: + WorkThread(); + ~WorkThread(); + + void init(); + void run(); + void cancel(); + + bool canceled; + + int fd; + K3bCdparanoiaLib* paranoia; + K3bDevice::Device* device; + K3bDevice::Toc toc; + K3bWaveFileWriter* waveFileWriter; + QStringList filenames; + int paranoiaMode; + int retries; + bool neverSkip; +}; + + +K3bAudioSessionReadingJob::WorkThread::WorkThread() + : K3bThread(), + fd(-1), + paranoia(0), + waveFileWriter(0), + paranoiaMode(0), + retries(50), + neverSkip(false) +{ +} + + +K3bAudioSessionReadingJob::WorkThread::~WorkThread() +{ + delete waveFileWriter; + delete paranoia; +} + + +void K3bAudioSessionReadingJob::WorkThread::init() +{ + canceled = false; +} + + +void K3bAudioSessionReadingJob::WorkThread::run() +{ + if( !paranoia ) + paranoia = K3bCdparanoiaLib::create(); + + if( !paranoia ) { + emitInfoMessage( i18n("Could not load libcdparanoia."), K3bJob::ERROR ); + emitFinished(false); + return; + } + + if( toc.isEmpty() ) + toc = device->readToc(); + + if( !paranoia->initParanoia( device, toc ) ) { + emitInfoMessage( i18n("Could not open device %1").arg(device->blockDeviceName()), + K3bJob::ERROR ); + emitFinished(false); + return; + } + + if( !paranoia->initReading() ) { + emitInfoMessage( i18n("Error while initializing audio ripping."), K3bJob::ERROR ); + emitFinished(false); + return; + } + + device->block( true ); + + // init settings + paranoia->setMaxRetries( retries ); + paranoia->setParanoiaMode( paranoiaMode ); + paranoia->setNeverSkip( neverSkip ); + + bool writeError = false; + unsigned int trackNum = 1; + unsigned int currentTrack = 0; + unsigned long trackRead = 0; + unsigned long totalRead = 0; + unsigned int lastTrackPercent = 0; + unsigned int lastTotalPercent = 0; + bool newTrack = true; + int status = 0; + char* buffer = 0; + while( !canceled && (buffer = paranoia->read( &status, &trackNum, fd == -1 /*when writing to a wav be want little endian */ )) ) { + + if( currentTrack != trackNum ) { + emitNextTrack( trackNum, paranoia->toc().count() ); + trackRead = 0; + lastTrackPercent = 0; + + currentTrack = trackNum; + newTrack = true; + } + + if( fd > 0 ) { + if( ::write( fd, buffer, CD_FRAMESIZE_RAW ) != CD_FRAMESIZE_RAW ) { + kdDebug() << "(K3bAudioSessionCopyJob::WorkThread) error while writing to fd " << fd << endl; + writeError = true; + break; + } + } + else { + if( newTrack ) { + newTrack = false; + + if( !waveFileWriter ) + waveFileWriter = new K3bWaveFileWriter(); + + if( filenames.count() < currentTrack ) { + kdDebug() << "(K3bAudioSessionCopyJob) not enough image filenames given: " << currentTrack << endl; + writeError = true; + break; + } + + if( !waveFileWriter->open( filenames[currentTrack-1] ) ) { + emitInfoMessage( i18n("Unable to open '%1' for writing.").arg(filenames[currentTrack-1]), K3bJob::ERROR ); + writeError = true; + break; + } + } + + waveFileWriter->write( buffer, + CD_FRAMESIZE_RAW, + K3bWaveFileWriter::LittleEndian ); + } + + trackRead++; + totalRead++; + + unsigned int trackPercent = 100 * trackRead / toc[currentTrack-1].length().lba(); + if( trackPercent > lastTrackPercent ) { + lastTrackPercent = trackPercent; + emitSubPercent( lastTrackPercent ); + } + unsigned int totalPercent = 100 * totalRead / paranoia->rippedDataLength(); + if( totalPercent > lastTotalPercent ) { + lastTotalPercent = totalPercent; + emitPercent( lastTotalPercent ); + } + } + + if( waveFileWriter ) + waveFileWriter->close(); + + paranoia->close(); + + device->block( false ); + + if( status != K3bCdparanoiaLib::S_OK ) { + emitInfoMessage( i18n("Unrecoverable error while ripping track %1.").arg(trackNum), K3bJob::ERROR ); + emitFinished(false); + return; + } + + emitFinished( !writeError & !canceled ); +} + + +void K3bAudioSessionReadingJob::WorkThread::cancel() +{ + canceled = true; + // FIXME: add backup killing like in the audio ripping and make sure to close paranoia +} + + + + +K3bAudioSessionReadingJob::K3bAudioSessionReadingJob( K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bThreadJob( jh, parent, name ) +{ + m_thread = new WorkThread(); + setThread( m_thread ); +} + + +K3bAudioSessionReadingJob::~K3bAudioSessionReadingJob() +{ + delete m_thread; +} + + +void K3bAudioSessionReadingJob::setDevice( K3bDevice::Device* dev ) +{ + m_thread->device = dev; + m_thread->toc = K3bDevice::Toc(); +} + + +void K3bAudioSessionReadingJob::setToc( const K3bDevice::Toc& toc ) +{ + m_thread->toc = toc; +} + + +void K3bAudioSessionReadingJob::writeToFd( int fd ) +{ + m_thread->fd = fd; +} + +void K3bAudioSessionReadingJob::setImageNames( const QStringList& l ) +{ + m_thread->filenames = l; + m_thread->fd = -1; +} + + +void K3bAudioSessionReadingJob::setParanoiaMode( int m ) +{ + m_thread->paranoiaMode = m; +} + + +void K3bAudioSessionReadingJob::setReadRetries( int r ) +{ + m_thread->retries = r; +} + +void K3bAudioSessionReadingJob::setNeverSkip( bool b ) +{ + m_thread->neverSkip = b; +} + + +void K3bAudioSessionReadingJob::start() +{ + k3bcore->blockDevice( m_thread->device ); + K3bThreadJob::start(); +} + + +void K3bAudioSessionReadingJob::cleanupJob( bool success ) +{ + Q_UNUSED( success ); + k3bcore->unblockDevice( m_thread->device ); +} + +#include "k3baudiosessionreadingjob.moc" diff --git a/libk3b/jobs/k3baudiosessionreadingjob.h b/libk3b/jobs/k3baudiosessionreadingjob.h new file mode 100644 index 0000000..21f3d50 --- /dev/null +++ b/libk3b/jobs/k3baudiosessionreadingjob.h @@ -0,0 +1,75 @@ +/* + * + * $Id: k3baudiosessionreadingjob.h 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. + */ + +#ifndef _K3B_AUDIOSESSION_READING_JOB_H_ +#define _K3B_AUDIOSESSION_READING_JOB_H_ + +#include <k3bthreadjob.h> + +#include <qstringlist.h> + + +namespace K3bDevice { + class Device; + class Toc; +} + + +class K3bAudioSessionReadingJob : public K3bThreadJob +{ + Q_OBJECT + + public: + K3bAudioSessionReadingJob( K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bAudioSessionReadingJob(); + + /** + * For now this simply reads all the audio tracks at the beginning + * since we only support CD-Extra mixed mode cds. + */ + void setDevice( K3bDevice::Device* ); + + /** + * Use for faster initialization + */ + void setToc( const K3bDevice::Toc& toc ); + + /** + * the data gets written directly into fd instead of imagefiles. + * To disable just set fd to -1 (the default) + */ + void writeToFd( int fd ); + + /** + * Used if fd == -1 + */ + void setImageNames( const QStringList& l ); + + void setParanoiaMode( int m ); + void setReadRetries( int ); + void setNeverSkip( bool b ); + + public slots: + void start(); + + protected: + void cleanupJob( bool success ); + + private: + class WorkThread; + WorkThread* m_thread; +}; + +#endif diff --git a/libk3b/jobs/k3bbinimagewritingjob.cpp b/libk3b/jobs/k3bbinimagewritingjob.cpp new file mode 100644 index 0000000..de76e3f --- /dev/null +++ b/libk3b/jobs/k3bbinimagewritingjob.cpp @@ -0,0 +1,234 @@ +/* + * + * $Id: k3bbinimagewritingjob.cpp 619556 2007-01-03 17:38:12Z trueg $ + * Copyright (C) 2003 Klaus-Dieter Krannich <kd@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 "k3bbinimagewritingjob.h" +#include <k3bcdrecordwriter.h> +#include <k3bcdrdaowriter.h> +#include <k3bcore.h> +#include <k3bdevice.h> +#include <k3bglobals.h> +#include <k3bexternalbinmanager.h> + +#include <klocale.h> +#include <kdebug.h> + +#include <qfile.h> +#include <qtextstream.h> + + + +K3bBinImageWritingJob::K3bBinImageWritingJob( K3bJobHandler* hdl, QObject* parent ) + : K3bBurnJob( hdl, parent ), + m_device(0), + m_simulate(false), + m_force(false), + m_noFix(false), + m_tocFile(0), + m_speed(2), + m_copies(1), + m_writer(0) +{ +} + +K3bBinImageWritingJob::~K3bBinImageWritingJob() +{ +} + +void K3bBinImageWritingJob::start() +{ + m_canceled = false; + + if( m_copies < 1 ) + m_copies = 1; + m_finishedCopies = 0; + + jobStarted(); + emit newTask( i18n("Write Binary Image") ); + + if( prepareWriter() ) + writerStart(); + else + cancel(); + +} + +void K3bBinImageWritingJob::cancel() +{ + m_canceled = true; + m_writer->cancel(); + emit canceled(); + jobFinished( false ); +} + +bool K3bBinImageWritingJob::prepareWriter() +{ + if( m_writer ) + delete m_writer; + + int usedWritingApp = writingApp(); + const K3bExternalBin* cdrecordBin = k3bcore->externalBinManager()->binObject("cdrecord"); + if( usedWritingApp == K3b::CDRECORD || + ( usedWritingApp == K3b::DEFAULT && cdrecordBin && cdrecordBin->hasFeature("cuefile") && m_device->dao() ) ) { + usedWritingApp = K3b::CDRECORD; + + // IMPROVEME: check if it's a cdrdao toc-file + if( m_tocFile.right(4) == ".toc" ) { + kdDebug() << "(K3bBinImageWritingJob) imagefile has ending toc." << endl; + usedWritingApp = K3b::CDRDAO; + } + else { + // TODO: put this into K3bCueFileParser + // TODO: check K3bCueFileParser::imageFilenameInCue() + // let's see if cdrecord can handle the cue file + QFile f( m_tocFile ); + if( f.open( IO_ReadOnly ) ) { + QTextStream fStr( &f ); + if( fStr.read().contains( "MODE1/2352" ) ) { + kdDebug() << "(K3bBinImageWritingJob) cuefile contains MODE1/2352 track. using cdrdao." << endl; + usedWritingApp = K3b::CDRDAO; + } + f.close(); + } + else + kdDebug() << "(K3bBinImageWritingJob) could not open file " << m_tocFile << endl; + } + } + else + usedWritingApp = K3b::CDRDAO; + + if( usedWritingApp == K3b::CDRECORD ) { + // create cdrecord job + K3bCdrecordWriter* writer = new K3bCdrecordWriter( m_device, this ); + + writer->setDao( true ); + writer->setSimulate( m_simulate ); + writer->setBurnSpeed( m_speed ); + writer->setCueFile ( m_tocFile ); + + if( m_noFix ) { + writer->addArgument("-multi"); + } + + if( m_force ) { + writer->addArgument("-force"); + } + + m_writer = writer; + } + else { + // create cdrdao job + K3bCdrdaoWriter* writer = new K3bCdrdaoWriter( m_device, this ); + writer->setCommand( K3bCdrdaoWriter::WRITE ); + writer->setSimulate( m_simulate ); + writer->setBurnSpeed( m_speed ); + writer->setForce( m_force ); + + // multisession + writer->setMulti( m_noFix ); + + writer->setTocFile( m_tocFile ); + + m_writer = writer; + } + + connect( m_writer, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_writer, SIGNAL(percent(int)), this, SLOT(copyPercent(int)) ); + connect( m_writer, SIGNAL(subPercent(int)), this, SLOT(copySubPercent(int)) ); + connect( m_writer, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); + connect( m_writer, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); + connect( m_writer, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); + connect( m_writer, SIGNAL(writeSpeed(int, int)), this, SIGNAL(writeSpeed(int, int)) ); + connect( m_writer, SIGNAL(finished(bool)), this, SLOT(writerFinished(bool)) ); + connect( m_writer, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); + connect( m_writer, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( m_writer, SIGNAL(nextTrack(int, int)), this, SLOT(slotNextTrack(int, int)) ); + connect( m_writer, SIGNAL(debuggingOutput(const QString&, const QString&)), this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + + return true; +} + + +void K3bBinImageWritingJob::writerStart() +{ + + if( waitForMedia( m_device ) < 0 ) { + cancel(); + } + // just to be sure we did not get canceled during the async discWaiting + else if( !m_canceled ) { + emit burning(true); + m_writer->start(); + } +} + +void K3bBinImageWritingJob::copyPercent(int p) +{ + emit percent( (100*m_finishedCopies + p)/m_copies ); +} + +void K3bBinImageWritingJob::copySubPercent(int p) +{ + emit subPercent(p); +} + +void K3bBinImageWritingJob::writerFinished(bool ok) +{ + if( m_canceled ) + return; + + if (ok) { + m_finishedCopies++; + if ( m_finishedCopies == m_copies ) { + emit infoMessage( i18n("%n copy successfully created", "%n copies successfully created", m_copies),K3bJob::INFO ); + jobFinished( true ); + } + else { + writerStart(); + } + } + else { + jobFinished(false); + } +} + + +void K3bBinImageWritingJob::slotNextTrack( int t, int tt ) +{ + emit newSubTask( i18n("Writing track %1 of %2").arg(t).arg(tt) ); +} + + +QString K3bBinImageWritingJob::jobDescription() const +{ + return ( i18n("Writing cue/bin Image") + + ( m_copies > 1 + ? i18n(" - %n Copy", " - %n Copies", m_copies) + : QString::null ) ); +} + + +QString K3bBinImageWritingJob::jobDetails() const +{ + return m_tocFile.section("/", -1); +} + + +void K3bBinImageWritingJob::setTocFile(const QString& s) +{ + m_tocFile = s; +} + +#include "k3bbinimagewritingjob.moc" diff --git a/libk3b/jobs/k3bbinimagewritingjob.h b/libk3b/jobs/k3bbinimagewritingjob.h new file mode 100644 index 0000000..3666793 --- /dev/null +++ b/libk3b/jobs/k3bbinimagewritingjob.h @@ -0,0 +1,79 @@ +/* + * + * $Id$ + * Copyright (C) 2003 Klaus-Dieter Krannich <kd@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. + */ + + +#ifndef K3BBINIMAGEWRITINGJOB_H +#define K3BBINIMAGEWRITINGJOB_H + +#include <k3bjob.h> +#include "k3b_export.h" +class K3bAbstractWriter; +namespace K3bDevice { + class Device; +} + +/** + *@author Klaus-Dieter Krannich + */ +class LIBK3B_EXPORT K3bBinImageWritingJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bBinImageWritingJob( K3bJobHandler*, QObject* parent = 0 ); + ~K3bBinImageWritingJob(); + + K3bDevice::Device* writer() const { return m_device; }; + + QString jobDescription() const; + QString jobDetails() const; + + public slots: + void start(); + void cancel(); + + void setWriter( K3bDevice::Device* dev ) { m_device = dev; } + void setSimulate( bool b ) { m_simulate = b; } + void setForce(bool b) { m_force = b; } + void setMulti( bool b ) { m_noFix = b; } + void setTocFile( const QString& s); + void setCopies(int c) { m_copies = c; } + void setSpeed( int s ) { m_speed = s; } + + private slots: + void writerFinished(bool); + void copyPercent(int p); + void copySubPercent(int p); + void slotNextTrack( int, int ); + + private: + void writerStart(); + bool prepareWriter(); + + K3bDevice::Device* m_device; + bool m_simulate; + bool m_force; + bool m_noFix; + QString m_tocFile; + int m_speed; + int m_copies; + int m_finishedCopies; + + bool m_canceled; + + K3bAbstractWriter* m_writer; +}; + +#endif diff --git a/libk3b/jobs/k3bblankingjob.cpp b/libk3b/jobs/k3bblankingjob.cpp new file mode 100644 index 0000000..c11f4b4 --- /dev/null +++ b/libk3b/jobs/k3bblankingjob.cpp @@ -0,0 +1,176 @@ +/* + * + * $Id: k3bblankingjob.cpp 630823 2007-02-06 14:07:10Z 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 "k3bblankingjob.h" +#include "k3bcdrecordwriter.h" +#include "k3bcdrdaowriter.h" + +#include <k3bglobals.h> +#include <k3bdevice.h> +#include <k3bdevicehandler.h> + +#include <kconfig.h> +#include <klocale.h> +#include <kio/global.h> +#include <kio/job.h> +#include <kdebug.h> + +#include <qstring.h> + + + +K3bBlankingJob::K3bBlankingJob( K3bJobHandler* hdl, QObject* parent ) + : K3bBurnJob( hdl, parent ), + m_writerJob(0), + m_force(true), + m_device(0), + m_speed(0), + m_mode(Fast), + m_writingApp(K3b::DEFAULT), + m_canceled(false), + m_forceNoEject(false) +{ +} + + +K3bBlankingJob::~K3bBlankingJob() +{ + delete m_writerJob; +} + + +K3bDevice::Device* K3bBlankingJob::writer() const +{ + return m_device; +} + + +void K3bBlankingJob::setDevice( K3bDevice::Device* dev ) +{ + m_device = dev; +} + + +void K3bBlankingJob::start() +{ + if( m_device == 0 ) + return; + + jobStarted(); + + slotStartErasing(); +} + +void K3bBlankingJob::slotStartErasing() +{ + m_canceled = false; + + if( m_writerJob ) + delete m_writerJob; + + if( m_writingApp == K3b::CDRDAO ) { + K3bCdrdaoWriter* writer = new K3bCdrdaoWriter( m_device, this ); + m_writerJob = writer; + + writer->setCommand(K3bCdrdaoWriter::BLANK); + writer->setBlankMode( m_mode == Fast ? K3bCdrdaoWriter::MINIMAL : K3bCdrdaoWriter::FULL ); + writer->setForce(m_force); + writer->setBurnSpeed(m_speed); + writer->setForceNoEject( m_forceNoEject ); + } + else { + K3bCdrecordWriter* writer = new K3bCdrecordWriter( m_device, this ); + m_writerJob = writer; + + QString mode; + switch( m_mode ) { + case Fast: + mode = "fast"; + break; + case Complete: + mode = "all"; + break; + case Track: + mode = "track"; + break; + case Unclose: + mode = "unclose"; + break; + case Session: + mode = "session"; + break; + } + + writer->addArgument("blank="+ mode); + + if (m_force) + writer->addArgument("-force"); + writer->setBurnSpeed(m_speed); + writer->setForceNoEject( m_forceNoEject ); + } + + connect(m_writerJob, SIGNAL(finished(bool)), this, SLOT(slotFinished(bool))); + connect(m_writerJob, SIGNAL(infoMessage( const QString&, int)), + this,SIGNAL(infoMessage( const QString&, int))); + connect( m_writerJob, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + + if( waitForMedia( m_device, + K3bDevice::STATE_COMPLETE|K3bDevice::STATE_INCOMPLETE, + K3bDevice::MEDIA_CD_RW, + i18n("Please insert a rewritable CD medium into drive<p><b>%1 %2 (%3)</b>.") + .arg(m_device->vendor()) + .arg(m_device->description()) + .arg(m_device->devicename()) ) < 0 ) { + emit canceled(); + jobFinished(false); + return; + } + + m_writerJob->start(); +} + + +void K3bBlankingJob::cancel() +{ + m_canceled = true; + + if( m_writerJob ) + m_writerJob->cancel(); +} + + +void K3bBlankingJob::slotFinished(bool success) +{ + if( success ) { + emit infoMessage( i18n("Process completed successfully"), K3bJob::SUCCESS ); + jobFinished( true ); + } + else { + if( m_canceled ) { + emit infoMessage( i18n("Canceled."), ERROR ); + emit canceled(); + } + else { + emit infoMessage( i18n("Blanking error "), K3bJob::ERROR ); + emit infoMessage( i18n("Sorry, no error handling yet."), K3bJob::ERROR ); + } + jobFinished( false ); + } +} + + + +#include "k3bblankingjob.moc" diff --git a/libk3b/jobs/k3bblankingjob.h b/libk3b/jobs/k3bblankingjob.h new file mode 100644 index 0000000..8cfe0a1 --- /dev/null +++ b/libk3b/jobs/k3bblankingjob.h @@ -0,0 +1,71 @@ +/* + * + * $Id: k3bblankingjob.h 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. + */ + +#ifndef K3B_BLANKING_JOB_H +#define K3B_BLANKING_JOB_H + +#include <k3bjob.h> +#include "k3b_export.h" +class KProcess; +class QString; +class K3bDevice::Device; +class K3bAbstractWriter; + + +class LIBK3B_EXPORT K3bBlankingJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bBlankingJob( K3bJobHandler*, QObject* parent = 0 ); + ~K3bBlankingJob(); + + K3bDevice::Device* writer() const; + + bool hasBeenCanceled() const { return m_canceled; } + + enum blank_mode { Fast, Complete, Track, Unclose, Session }; + + public slots: + void start(); + void cancel(); + void setForce( bool f ) { m_force = f; } + void setDevice( K3bDevice::Device* d ); + void setSpeed( int s ) { m_speed = s; } + void setMode( int m ) { m_mode = m; } + void setWritingApp (int app) { m_writingApp = app; } + + /** + * If set true the job ignores the global K3b setting + * and does not eject the CD-RW after finishing + */ + void setForceNoEject( bool b ) { m_forceNoEject = b; } + + private slots: + void slotFinished(bool); + void slotStartErasing(); + + private: + K3bAbstractWriter* m_writerJob; + bool m_force; + K3bDevice::Device* m_device; + int m_speed; + int m_mode; + int m_writingApp; + bool m_canceled; + bool m_forceNoEject; +}; + +#endif diff --git a/libk3b/jobs/k3bcdcopyjob.cpp b/libk3b/jobs/k3bcdcopyjob.cpp new file mode 100644 index 0000000..ff8f35d --- /dev/null +++ b/libk3b/jobs/k3bcdcopyjob.cpp @@ -0,0 +1,1213 @@ +/* + * + * $Id.cpp,v 1.82 2005/02/04 09:27:19 trueg Exp $ + * Copyright (C) 2003-2007 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 "k3bcdcopyjob.h" +#include "k3baudiosessionreadingjob.h" + +#include <k3bexternalbinmanager.h> +#include <k3bdevice.h> +#include <k3bdiskinfo.h> +#include <k3btoc.h> +#include <k3bglobals.h> +#include <k3bdevicehandler.h> +#include <k3breadcdreader.h> +#include <k3bdatatrackreader.h> +#include <k3bcdrecordwriter.h> +#include <k3bcdtext.h> +#include <k3bcddb.h> +#include <k3bcddbresult.h> +#include <k3bcddbquery.h> +#include <k3bcore.h> +#include <k3binffilewriter.h> + +#include <kconfig.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <kdebug.h> +#include <ktempfile.h> +#include <kio/netaccess.h> +#include <kio/job.h> +#include <kio/global.h> + +#include <qtimer.h> +#include <qstringlist.h> +#include <qfile.h> +#include <qregexp.h> +#include <qtextstream.h> +#include <qcstring.h> +#include <qfileinfo.h> +#include <qdir.h> +#include <qapplication.h> + + +class K3bCdCopyJob::Private +{ +public: + Private() + : canceled(false), + running(false), + readcdReader(0), + dataTrackReader(0), + audioSessionReader(0), + cdrecordWriter(0), + infFileWriter(0), + cddb(0) { + } + + bool canceled; + bool error; + bool readingSuccessful; + bool running; + + unsigned int numSessions; + bool doNotCloseLastSession; + + unsigned int doneCopies; + unsigned int currentReadSession; + unsigned int currentWrittenSession; + + K3bDevice::Toc toc; + QByteArray cdTextRaw; + + K3bReadcdReader* readcdReader; + K3bDataTrackReader* dataTrackReader; + K3bAudioSessionReadingJob* audioSessionReader; + K3bCdrecordWriter* cdrecordWriter; + K3bInfFileWriter* infFileWriter; + + bool audioReaderRunning; + bool dataReaderRunning; + bool writerRunning; + + // image filenames, one for every track + QStringList imageNames; + + // inf-filenames for writing audio tracks + QStringList infNames; + + // indicates if we created a dir or not + bool deleteTempDir; + + K3bCddb* cddb; + K3bCddbResultEntry cddbInfo; + + bool haveCddb; + bool haveCdText; + + QValueVector<bool> dataSessionProbablyTAORecorded; + + // used to determine progress + QValueVector<long> sessionSizes; + long overallSize; +}; + + +K3bCdCopyJob::K3bCdCopyJob( K3bJobHandler* hdl, QObject* parent ) + : K3bBurnJob( hdl, parent ), + m_simulate(false), + m_copies(1), + m_onlyCreateImages(false), + m_onTheFly(true), + m_ignoreDataReadErrors(false), + m_ignoreAudioReadErrors(true), + m_noCorrection(false), + m_dataReadRetries(128), + m_audioReadRetries(5), + m_preferCdText(false), + m_copyCdText(true), + m_writingMode( K3b::WRITING_MODE_AUTO ) +{ + d = new Private(); +} + + +K3bCdCopyJob::~K3bCdCopyJob() +{ + delete d->infFileWriter; + delete d; +} + + +void K3bCdCopyJob::start() +{ + d->running = true; + d->canceled = false; + d->error = false; + d->readingSuccessful = false; + d->audioReaderRunning = d->dataReaderRunning = d->writerRunning = false; + d->sessionSizes.clear(); + d->dataSessionProbablyTAORecorded.clear(); + d->deleteTempDir = false; + d->haveCdText = false; + d->haveCddb = false; + + jobStarted(); + + emit newTask( i18n("Checking Source Medium") ); + + emit burning(false); + emit newSubTask( i18n("Waiting for source medium") ); + + // wait for a source disk + if( waitForMedia( m_readerDevice, + K3bDevice::STATE_COMPLETE|K3bDevice::STATE_INCOMPLETE, + K3bDevice::MEDIA_WRITABLE_CD|K3bDevice::MEDIA_CD_ROM ) < 0 ) { + finishJob( true, false ); + return; + } + + emit newSubTask( i18n("Checking source medium") ); + + // FIXME: read ISRCs and MCN + + connect( K3bDevice::diskInfo( m_readerDevice ), SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, SLOT(slotDiskInfoReady(K3bDevice::DeviceHandler*)) ); +} + + +void K3bCdCopyJob::slotDiskInfoReady( K3bDevice::DeviceHandler* dh ) +{ + if( dh->success() ) { + d->toc = dh->toc(); + + // + // for now we copy audio, pure data (aka 1 data track), cd-extra (2 session, audio and data), + // and data multisession which one track per session. + // Everything else will be rejected + // + bool canCopy = true; + bool audio = false; + d->numSessions = dh->diskInfo().numSessions(); + d->doNotCloseLastSession = (dh->diskInfo().diskState() == K3bDevice::STATE_INCOMPLETE); + switch( dh->toc().contentType() ) { + case K3bDevice::DATA: + // check if every track is in it's own session + // only then we copy the cd + if( (int)dh->toc().count() != dh->diskInfo().numSessions() ) { + emit infoMessage( i18n("K3b does not copy CDs containing multiple data tracks."), ERROR ); + canCopy = false; + } + else if( dh->diskInfo().numSessions() > 1 ) + emit infoMessage( i18n("Copying Multisession Data CD."), INFO ); + else + emit infoMessage( i18n("Copying Data CD."), INFO ); + break; + + case K3bDevice::MIXED: + audio = true; + if( dh->diskInfo().numSessions() != 2 || d->toc[0].type() != K3bDevice::Track::AUDIO ) { + emit infoMessage( i18n("K3b can only copy CD-Extra mixed mode CDs."), ERROR ); + canCopy = false; + } + else + emit infoMessage( i18n("Copying Enhanced Audio CD (CD-Extra)."), INFO ); + break; + + case K3bDevice::AUDIO: + audio = true; + emit infoMessage( i18n("Copying Audio CD."), INFO ); + break; + + case K3bDevice::NONE: + default: + emit infoMessage( i18n("The source disk is empty."), ERROR ); + canCopy = false; + break; + } + + // + // A data track recorded in TAO mode has two run-out blocks which cannot be read and contain + // zero data anyway. The problem is that I do not know of a valid method to determine if a track + // was written in TAO (the control nibble does definitely not work, I never saw one which did not + // equal 4). + // So the solution for now is to simply try to read the last sector of a data track. If this is not + // possible we assume it was written in TAO mode and reduce the length by 2 sectors + // + unsigned char buffer[2048]; + int i = 1; + for( K3bDevice::Toc::iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { + if( (*it).type() == K3bDevice::Track::DATA ) { + // we try twice just to be sure + if( m_readerDevice->read10( buffer, 2048, (*it).lastSector().lba(), 1 ) || + m_readerDevice->read10( buffer, 2048, (*it).lastSector().lba(), 1 ) ) { + d->dataSessionProbablyTAORecorded.append(false); + kdDebug() << "(K3bCdCopyJob) track " << i << " probably DAO recorded." << endl; + } + else { + d->dataSessionProbablyTAORecorded.append(true); + kdDebug() << "(K3bCdCopyJob) track " << i << " probably TAO recorded." << endl; + } + } + + ++i; + } + + + // + // To copy mode2 data tracks we need cdrecord >= 2.01a12 which introduced the -xa1 and -xamix options + // + if( k3bcore->externalBinManager()->binObject("cdrecord") && + !k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "xamix" ) ) { + for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { + if( (*it).type() == K3bDevice::Track::DATA && + ( (*it).mode() == K3bDevice::Track::XA_FORM1 || + (*it).mode() == K3bDevice::Track::XA_FORM2 ) ) { + emit infoMessage( i18n("K3b needs cdrecord 2.01a12 or newer to copy Mode2 data tracks."), ERROR ); + finishJob( true, false ); + return; + } + } + } + + + // + // It is not possible to create multisession cds in raw writing mode + // + if( d->numSessions > 1 && m_writingMode == K3b::RAW ) { + if( !questionYesNo( i18n("You will only be able to copy the first session in raw writing mode. " + "Continue anyway?"), + i18n("Multisession CD") ) ) { + finishJob( true, false ); + return; + } + else { + emit infoMessage( i18n("Only copying first session."), WARNING ); + // TODO: remove the second session from the progress stuff + } + } + + + // + // We already create the temp filenames here since we need them to check the free space + // + if( !m_onTheFly || m_onlyCreateImages ) { + if( !prepareImageFiles() ) { + finishJob( false, true ); + return; + } + + // + // check free temp space + // + KIO::filesize_t imageSpaceNeeded = 0; + for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { + if( (*it).type() == K3bDevice::Track::AUDIO ) + imageSpaceNeeded += (*it).length().audioBytes() + 44; + else + imageSpaceNeeded += (*it).length().mode1Bytes(); + } + + unsigned long avail, size; + QString pathToTest = m_tempPath.left( m_tempPath.findRev( '/' ) ); + if( !K3b::kbFreeOnFs( pathToTest, size, avail ) ) { + emit infoMessage( i18n("Unable to determine free space in temporary directory '%1'.").arg(pathToTest), ERROR ); + d->error = true; + canCopy = false; + } + else { + if( avail < imageSpaceNeeded/1024 ) { + emit infoMessage( i18n("Not enough space left in temporary directory."), ERROR ); + d->error = true; + canCopy = false; + } + } + } + + if( canCopy ) { + if( K3b::isMounted( m_readerDevice ) ) { + emit infoMessage( i18n("Unmounting source medium"), INFO ); + K3b::unmount( m_readerDevice ); + } + + d->overallSize = 0; + + // now create some progress helper values + for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { + d->overallSize += (*it).length().lba(); + if( d->sessionSizes.isEmpty() || (*it).type() == K3bDevice::Track::DATA ) + d->sessionSizes.append( (*it).length().lba() ); + else + d->sessionSizes[0] += (*it).length().lba(); + } + + if( audio && !m_onlyCreateImages ) { + if( m_copyCdText ) + searchCdText(); + else + queryCddb(); + } + else + startCopy(); + } + else { + finishJob( false, true ); + } + } + else { + emit infoMessage( i18n("Unable to read TOC"), ERROR ); + finishJob( false, true ); + } +} + + +void K3bCdCopyJob::searchCdText() +{ + emit newSubTask( i18n("Searching CD-TEXT") ); + + connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::CD_TEXT_RAW, m_readerDevice ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotCdTextReady(K3bDevice::DeviceHandler*)) ); +} + + +void K3bCdCopyJob::slotCdTextReady( K3bDevice::DeviceHandler* dh ) +{ + if( dh->success() ) { + if( K3bDevice::CdText::checkCrc( dh->cdTextRaw() ) ) { + K3bDevice::CdText cdt( dh->cdTextRaw() ); + emit infoMessage( i18n("Found CD-TEXT (%1 - %2).").arg(cdt.performer()).arg(cdt.title()), SUCCESS ); + d->haveCdText = true; + d->cdTextRaw = dh->cdTextRaw(); + } + else { + emit infoMessage( i18n("Found corrupted CD-TEXT. Ignoring it."), WARNING ); + d->haveCdText = false; + } + + if( d->haveCdText && m_preferCdText ) + startCopy(); + else + queryCddb(); + } + else { + emit infoMessage( i18n("No CD-TEXT found."), INFO ); + + d->haveCdText = false; + + queryCddb(); + } +} + + +void K3bCdCopyJob::queryCddb() +{ + emit newSubTask( i18n("Querying Cddb") ); + + d->haveCddb = false; + + if( !d->cddb ) { + d->cddb = new K3bCddb( this ); + connect( d->cddb, SIGNAL(queryFinished(int)), + this, SLOT(slotCddbQueryFinished(int)) ); + } + + KConfig* c = k3bcore->config(); + c->setGroup("Cddb"); + + d->cddb->readConfig( c ); + d->cddb->query( d->toc ); +} + + +void K3bCdCopyJob::slotCddbQueryFinished( int error ) +{ + if( error == K3bCddbQuery::SUCCESS ) { + d->cddbInfo = d->cddb->result(); + d->haveCddb = true; + + emit infoMessage( i18n("Found Cddb entry (%1 - %2).").arg(d->cddbInfo.cdArtist).arg(d->cddbInfo.cdTitle), SUCCESS ); + + // save the entry locally + KConfig* c = k3bcore->config(); + c->setGroup( "Cddb" ); + if( c->readBoolEntry( "save cddb entries locally", true ) ) + d->cddb->saveEntry( d->cddbInfo ); + } + else if( error == K3bCddbQuery::NO_ENTRY_FOUND ) { + emit infoMessage( i18n("No Cddb entry found."), WARNING ); + } + else { + emit infoMessage( i18n("Cddb error (%1).").arg(d->cddb->errorString()), ERROR ); + } + + startCopy(); +} + + +void K3bCdCopyJob::startCopy() +{ + d->currentWrittenSession = d->currentReadSession = 1; + d->doneCopies = 0; + + if( m_onTheFly ) { + emit newSubTask( i18n("Preparing write process...") ); + + if( writeNextSession() ) + readNextSession(); + else { + finishJob( d->canceled, d->error ); + } + } + else + readNextSession(); +} + + +void K3bCdCopyJob::cancel() +{ + d->canceled = true; + + if( d->writerRunning ) { + // + // we will handle cleanup in slotWriterFinished() + // if we are writing onthefly the reader won't be able to write + // anymore and will finish unsuccessfully, too + // + d->cdrecordWriter->cancel(); + } + else if( d->audioReaderRunning ) + d->audioSessionReader->cancel(); + else if( d->dataReaderRunning ) + // d->readcdReader->cancel(); + d->dataTrackReader->cancel(); +} + + +bool K3bCdCopyJob::prepareImageFiles() +{ + kdDebug() << "(K3bCdCopyJob) prepareImageFiles()" << endl; + + d->imageNames.clear(); + d->infNames.clear(); + d->deleteTempDir = false; + + QFileInfo fi( m_tempPath ); + + if( d->toc.count() > 1 || d->toc.contentType() == K3bDevice::AUDIO ) { + // create a directory which contains all the images and inf and stuff + // and save it in some cool structure + + bool tempDirReady = false; + if( !fi.isDir() ) { + if( QFileInfo( m_tempPath.section( '/', 0, -2 ) ).isDir() ) { + if( !QFile::exists( m_tempPath ) ) { + QDir dir( m_tempPath.section( '/', 0, -2 ) ); + dir.mkdir( m_tempPath.section( '/', -1 ) ); + tempDirReady = true; + } + else + m_tempPath = m_tempPath.section( '/', 0, -2 ); + } + else { + emit infoMessage( i18n("Specified an unusable temporary path. Using default."), WARNING ); + m_tempPath = K3b::defaultTempPath(); + } + } + + // create temp dir + if( !tempDirReady ) { + QDir dir( m_tempPath ); + m_tempPath = K3b::findUniqueFilePrefix( "k3bCdCopy", m_tempPath ); + kdDebug() << "(K3bCdCopyJob) creating temp dir: " << m_tempPath << endl; + if( !dir.mkdir( m_tempPath, true ) ) { + emit infoMessage( i18n("Unable to create temporary directory '%1'.").arg(m_tempPath), ERROR ); + return false; + } + d->deleteTempDir = true; + } + + m_tempPath = K3b::prepareDir( m_tempPath ); + emit infoMessage( i18n("Using temporary directory %1.").arg(m_tempPath), INFO ); + + // create temp filenames + int i = 1; + for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { + if( (*it).type() == K3bDevice::Track::AUDIO ) { + d->imageNames.append( m_tempPath + QString("Track%1.wav").arg(QString::number(i).rightJustify(2, '0')) ); + d->infNames.append( m_tempPath + QString("Track%1.inf").arg(QString::number(i).rightJustify(2, '0')) ); + } + else + d->imageNames.append( m_tempPath + QString("Track%1.iso").arg(QString::number(i).rightJustify(2, '0')) ); + ++i; + } + + kdDebug() << "(K3bCdCopyJob) created image filenames:" << endl; + for( unsigned int i = 0; i < d->imageNames.count(); ++i ) + kdDebug() << "(K3bCdCopyJob) " << d->imageNames[i] << endl; + + return true; + } + else { + // we only need a single image file + if( !fi.isFile() || + questionYesNo( i18n("Do you want to overwrite %1?").arg(m_tempPath), + i18n("File Exists") ) ) { + if( fi.isDir() ) + m_tempPath = K3b::findTempFile( "iso", m_tempPath ); + else if( !QFileInfo( m_tempPath.section( '/', 0, -2 ) ).isDir() ) { + emit infoMessage( i18n("Specified an unusable temporary path. Using default."), WARNING ); + m_tempPath = K3b::findTempFile( "iso" ); + } + // else the user specified a file in an existing dir + + emit infoMessage( i18n("Writing image file to %1.").arg(m_tempPath), INFO ); + } + else + return false; + + d->imageNames.append( m_tempPath ); + + return true; + } +} + + +void K3bCdCopyJob::readNextSession() +{ + if( !m_onTheFly || m_onlyCreateImages ) { + if( d->numSessions > 1 ) + emit newTask( i18n("Reading Session %1").arg(d->currentReadSession) ); + else + emit newTask( i18n("Reading Source Medium") ); + + if( d->currentReadSession == 1 ) + emit newSubTask( i18n("Reading track %1 of %2").arg(1).arg(d->toc.count()) ); + } + + // there is only one situation where we need the audiosessionreader: + // if the first session is an audio session. That means the first track + // is an audio track + if( d->currentReadSession == 1 && d->toc[0].type() == K3bDevice::Track::AUDIO ) { + if( !d->audioSessionReader ) { + d->audioSessionReader = new K3bAudioSessionReadingJob( this, this ); + connect( d->audioSessionReader, SIGNAL(nextTrack(int, int)), + this, SLOT(slotReadingNextTrack(int, int)) ); + connectSubJob( d->audioSessionReader, + SLOT(slotSessionReaderFinished(bool)), + true, + SLOT(slotReaderProgress(int)), + SLOT(slotReaderSubProgress(int)) ); + } + + d->audioSessionReader->setDevice( m_readerDevice ); + d->audioSessionReader->setToc( d->toc ); + d->audioSessionReader->setParanoiaMode( m_paranoiaMode ); + d->audioSessionReader->setReadRetries( m_audioReadRetries ); + d->audioSessionReader->setNeverSkip( !m_ignoreAudioReadErrors ); + if( m_onTheFly ) + d->audioSessionReader->writeToFd( d->cdrecordWriter->fd() ); + else + d->audioSessionReader->setImageNames( d->imageNames ); // the audio tracks are always the first tracks + + d->audioReaderRunning = true; + d->audioSessionReader->start(); + } + else { + if( !d->dataTrackReader ) { + d->dataTrackReader = new K3bDataTrackReader( this, this ); + connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) ); + connect( d->dataTrackReader, SIGNAL(processedSize(int, int)), this, SLOT(slotReaderProcessedSize(int, int)) ); + connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotSessionReaderFinished(bool)) ); + connect( d->dataTrackReader, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( d->dataTrackReader, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + } + + d->dataTrackReader->setDevice( m_readerDevice ); + d->dataTrackReader->setIgnoreErrors( m_ignoreDataReadErrors ); + d->dataTrackReader->setNoCorrection( m_noCorrection ); + d->dataTrackReader->setRetries( m_dataReadRetries ); + if( m_onlyCreateImages ) + d->dataTrackReader->setSectorSize( K3bDataTrackReader::MODE1 ); + else + d->dataTrackReader->setSectorSize( K3bDataTrackReader::AUTO ); + + K3bTrack* track = 0; + unsigned int dataTrackIndex = 0; + if( d->toc.contentType() == K3bDevice::MIXED ) { + track = &d->toc[d->toc.count()-1]; + dataTrackIndex = 0; + } + else { + track = &d->toc[d->currentReadSession-1]; // only one track per session + dataTrackIndex = d->currentReadSession-1; + } + + // HACK: if the track is TAO recorded cut the two run-out sectors + if( d->dataSessionProbablyTAORecorded.count() > dataTrackIndex && + d->dataSessionProbablyTAORecorded[dataTrackIndex] ) + d->dataTrackReader->setSectorRange( track->firstSector(), track->lastSector() - 2 ); + else + d->dataTrackReader->setSectorRange( track->firstSector(), track->lastSector() ); + + int trackNum = d->currentReadSession; + if( d->toc.contentType() == K3bDevice::MIXED ) + trackNum = d->toc.count(); + + if( m_onTheFly ) + d->dataTrackReader->writeToFd( d->cdrecordWriter->fd() ); + else + d->dataTrackReader->setImagePath( d->imageNames[trackNum-1] ); + + d->dataReaderRunning = true; + if( !m_onTheFly || m_onlyCreateImages ) + slotReadingNextTrack( 1, 1 ); + + d->dataTrackReader->start(); + } +} + + +bool K3bCdCopyJob::writeNextSession() +{ + // we emit our own task since the cdrecord task is way too simple + if( d->numSessions > 1 ) { + if( m_simulate ) + emit newTask( i18n("Simulating Session %1").arg(d->currentWrittenSession) ); + else if( m_copies > 1 ) + emit newTask( i18n("Writing Copy %1 (Session %2)").arg(d->doneCopies+1).arg(d->currentWrittenSession) ); + else + emit newTask( i18n("Writing Copy (Session %2)").arg(d->currentWrittenSession) ); + } + else { + if( m_simulate ) + emit newTask( i18n("Simulating") ); + else if( m_copies > 1 ) + emit newTask( i18n("Writing Copy %1").arg(d->doneCopies+1) ); + else + emit newTask( i18n("Writing Copy") ); + } + + emit newSubTask( i18n("Waiting for media") ); + + // if session > 1 we wait for an appendable CD + if( waitForMedia( m_writerDevice, + d->currentWrittenSession > 1 && !m_simulate + ? K3bDevice::STATE_INCOMPLETE + : K3bDevice::STATE_EMPTY, + K3bDevice::MEDIA_WRITABLE_CD ) < 0 ) { + + finishJob( true, false ); + return false; + } + + if( !d->cdrecordWriter ) { + d->cdrecordWriter = new K3bCdrecordWriter( m_writerDevice, this, this ); + connect( d->cdrecordWriter, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( d->cdrecordWriter, SIGNAL(percent(int)), this, SLOT(slotWriterProgress(int)) ); + connect( d->cdrecordWriter, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); + connect( d->cdrecordWriter, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) ); + connect( d->cdrecordWriter, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); + connect( d->cdrecordWriter, SIGNAL(nextTrack(int, int)), this, SLOT(slotWritingNextTrack(int, int)) ); + connect( d->cdrecordWriter, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); + connect( d->cdrecordWriter, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); + connect( d->cdrecordWriter, SIGNAL(writeSpeed(int, int)), this, SIGNAL(writeSpeed(int, int)) ); + connect( d->cdrecordWriter, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) ); + // connect( d->cdrecordWriter, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); + connect( d->cdrecordWriter, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( d->cdrecordWriter, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + } + + d->cdrecordWriter->setBurnDevice( m_writerDevice ); + d->cdrecordWriter->clearArguments(); + d->cdrecordWriter->setSimulate( m_simulate ); + d->cdrecordWriter->setBurnSpeed( m_speed ); + + + // create the cdrecord arguments + if( d->currentWrittenSession == 1 && d->toc[0].type() == K3bDevice::Track::AUDIO ) { + // + // Audio session + // + + + if( !d->infFileWriter ) + d->infFileWriter = new K3bInfFileWriter(); + + // + // create the inf files if not already done + // + if( d->infNames.isEmpty() || !QFile::exists( d->infNames[0] ) ) { + + unsigned int trackNumber = 1; + + for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { + const K3bDevice::Track& track = *it; + + if( track.type() == K3bDevice::Track::DATA ) + break; + + d->infFileWriter->setTrack( track ); + d->infFileWriter->setTrackNumber( trackNumber ); + + if( d->haveCddb ) { + d->infFileWriter->setTrackTitle( d->cddbInfo.titles[trackNumber-1] ); + d->infFileWriter->setTrackPerformer( d->cddbInfo.artists[trackNumber-1] ); + d->infFileWriter->setTrackMessage( d->cddbInfo.extInfos[trackNumber-1] ); + + d->infFileWriter->setAlbumTitle( d->cddbInfo.cdTitle ); + d->infFileWriter->setAlbumPerformer( d->cddbInfo.cdArtist ); + } + + if( m_onTheFly ) { + + d->infFileWriter->setBigEndian( true ); + + // we let KTempFile choose a temp file but delete it on our own + // the same way we delete them when writing with images + // It is important that the files have the ending inf because + // cdrecord only checks this + + KTempFile tmp( QString::null, ".inf" ); + d->infNames.append( tmp.name() ); + bool success = d->infFileWriter->save( *tmp.textStream() ); + tmp.close(); + if( !success ) + return false; + } + else { + d->infFileWriter->setBigEndian( false ); + + if( !d->infFileWriter->save( d->infNames[trackNumber-1] ) ) + return false; + } + + ++trackNumber; + } + } + + // + // the inf files are ready and named correctly when writing with images + // + int usedWritingMode = m_writingMode; + if( usedWritingMode == K3b::WRITING_MODE_AUTO ) { + // + // 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?) + // + bool zeroPregap = false; + if( d->numSessions == 1 ) { + for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { + const K3bDevice::Track& track = *it; + if( track.index0() == 0 ) { + ++it; + if( it != d->toc.end() ) + zeroPregap = true; + --it; + } + } + } + + if( zeroPregap && m_writerDevice->supportsRawWriting() ) { + if( d->numSessions == 1 ) + usedWritingMode = K3b::RAW; + else + usedWritingMode = K3b::TAO; + } + else if( m_writerDevice->dao() ) + usedWritingMode = K3b::DAO; + else if( m_writerDevice->supportsRawWriting() ) + usedWritingMode = K3b::RAW; + else + usedWritingMode = K3b::TAO; + } + d->cdrecordWriter->setWritingMode( usedWritingMode ); + + if( d->numSessions > 1 ) + d->cdrecordWriter->addArgument( "-multi" ); + + if( d->haveCddb || d->haveCdText ) { + if( usedWritingMode == K3b::TAO ) { + emit infoMessage( i18n("It is not possible to write CD-Text in TAO mode."), WARNING ); + } + else if( d->haveCdText && ( !d->haveCddb || m_preferCdText ) ) { + // use the raw CDTEXT data + d->cdrecordWriter->setRawCdText( d->cdTextRaw ); + } + else { + // make sure the writer job does not create raw cdtext + d->cdrecordWriter->setRawCdText( QByteArray() ); + // cdrecord will use the cdtext data in the inf files + d->cdrecordWriter->addArgument( "-text" ); + } + } + + d->cdrecordWriter->addArgument( "-useinfo" ); + + // + // add all the audio tracks + // + d->cdrecordWriter->addArgument( "-audio" )->addArgument( "-shorttrack" ); + + for( unsigned int i = 0; i < d->infNames.count(); ++i ) { + if( m_onTheFly ) + d->cdrecordWriter->addArgument( d->infNames[i] ); + else + d->cdrecordWriter->addArgument( d->imageNames[i] ); + } + } + else { + // + // Data Session + // + K3bTrack* track = 0; + unsigned int dataTrackIndex = 0; + if( d->toc.contentType() == K3bDevice::MIXED ) { + track = &d->toc[d->toc.count()-1]; + dataTrackIndex = 0; + } + else { + track = &d->toc[d->currentWrittenSession-1]; + dataTrackIndex = d->currentWrittenSession-1; + } + + bool multi = d->doNotCloseLastSession || (d->numSessions > 1 && d->currentWrittenSession < d->toc.count()); + int usedWritingMode = m_writingMode; + if( usedWritingMode == K3b::WRITING_MODE_AUTO ) { + // at least the NEC3540a does write 2056 byte sectors only in tao mode. Same for LG4040b + // since writing data tracks in TAO mode is no loss let's default to TAO in the case of 2056 byte + // sectors (which is when writing xa form1 sectors here) + if( m_writerDevice->dao() && + d->toc.count() == 1 && + !multi && + track->mode() == K3bDevice::Track::MODE1 ) + usedWritingMode = K3b::DAO; + else + usedWritingMode = K3b::TAO; + } + d->cdrecordWriter->setWritingMode( usedWritingMode ); + + // + // all but the last session of a multisession disk are written in multi mode + // and every data track has it's own session which we forced above + // + if( multi ) + d->cdrecordWriter->addArgument( "-multi" ); + + // just to let the reader init + if( m_onTheFly ) + d->cdrecordWriter->addArgument( "-waiti" ); + + if( track->mode() == K3bDevice::Track::MODE1 ) + d->cdrecordWriter->addArgument( "-data" ); + else if( track->mode() == K3bDevice::Track::XA_FORM1 ) + d->cdrecordWriter->addArgument( "-xa1" ); + else + d->cdrecordWriter->addArgument( "-xamix" ); + + if( m_onTheFly ) { + // HACK: if the track is TAO recorded cut the two run-out sectors + unsigned long trackLen = track->length().lba(); + if( d->dataSessionProbablyTAORecorded.count() > dataTrackIndex && + d->dataSessionProbablyTAORecorded[dataTrackIndex] ) + trackLen -= 2; + + if( track->mode() == K3bDevice::Track::MODE1 ) + trackLen = trackLen * 2048; + else if( track->mode() == K3bDevice::Track::XA_FORM1 ) + trackLen = trackLen * 2056; // see k3bdatatrackreader.h + else + trackLen = trackLen * 2332; // see k3bdatatrackreader.h + d->cdrecordWriter->addArgument( QString("-tsize=%1").arg(trackLen) )->addArgument("-"); + } + else if( d->toc.contentType() == K3bDevice::MIXED ) + d->cdrecordWriter->addArgument( d->imageNames[d->toc.count()-1] ); + else + d->cdrecordWriter->addArgument( d->imageNames[d->currentWrittenSession-1] ); + + // clear cd text from previous sessions + d->cdrecordWriter->setRawCdText( QByteArray() ); + } + + + // + // Finally start the writer + // + emit burning(true); + d->writerRunning = true; + d->cdrecordWriter->start(); + + return true; +} + + +// both the readcdreader and the audiosessionreader are connected to this slot +void K3bCdCopyJob::slotSessionReaderFinished( bool success ) +{ + d->audioReaderRunning = d->dataReaderRunning = false; + + if( success ) { + if( d->numSessions > 1 ) + emit infoMessage( i18n("Successfully read session %1.").arg(d->currentReadSession), SUCCESS ); + else + emit infoMessage( i18n("Successfully read source disk."), SUCCESS ); + + if( !m_onTheFly ) { + if( d->numSessions > d->currentReadSession ) { + d->currentReadSession++; + readNextSession(); + } + else { + d->readingSuccessful = true; + if( !m_onlyCreateImages ) { + if( m_readerDevice == m_writerDevice ) { + // eject the media (we do this blocking to know if it worked + // becasue if it did not it might happen that k3b overwrites a CD-RW + // source) + if( !m_readerDevice->eject() ) { + blockingInformation( i18n("K3b was unable to eject the source disk. Please do so manually.") ); + } + } + + if( !writeNextSession() ) { + // nothing is running here... + finishJob( d->canceled, d->error ); + } + } + else { + finishJob( false, false ); + } + } + } + } + else { + if( !d->canceled ) { + emit infoMessage( i18n("Error while reading session %1.").arg(d->currentReadSession), ERROR ); + if( m_onTheFly ) + d->cdrecordWriter->setSourceUnreadable(true); + } + + finishJob( d->canceled, !d->canceled ); + } +} + + +void K3bCdCopyJob::slotWriterFinished( bool success ) +{ + emit burning(false); + + d->writerRunning = false; + + if( success ) { + // + // if this was the last written session we need to reset d->currentWrittenSession + // and start a new writing if more copies are wanted + // + + if( d->currentWrittenSession < d->numSessions ) { + d->currentWrittenSession++; + d->currentReadSession++; + + // reload the media + emit newSubTask( i18n("Reloading the medium") ); + connect( K3bDevice::reload( m_writerDevice ), SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, SLOT(slotMediaReloadedForNextSession(K3bDevice::DeviceHandler*)) ); + } + else { + d->doneCopies++; + + if( !m_simulate && d->doneCopies < m_copies ) { + // start next copy + K3bDevice::eject( m_writerDevice ); + + d->currentWrittenSession = 1; + d->currentReadSession = 1; + if( writeNextSession() ) { + if( m_onTheFly ) + readNextSession(); + } + else { + // nothing running here... + finishJob( d->canceled, d->error ); + } + } + else { + finishJob( false, false ); + } + } + } + else { + // + // If we are writing on the fly the reader will also stop when it is not able to write anymore + // The error handling will be done only here in that case + // + + // the K3bCdrecordWriter emitted an error message + + finishJob( d->canceled, !d->canceled ); + } +} + + +void K3bCdCopyJob::slotMediaReloadedForNextSession( K3bDevice::DeviceHandler* dh ) +{ + if( !dh->success() ) + blockingInformation( i18n("Please reload the medium and press 'ok'"), + i18n("Unable to close the tray") ); + + if( !writeNextSession() ) { + // nothing is running here... + finishJob( d->canceled, d->error ); + } + else if( m_onTheFly ) + readNextSession(); +} + + +void K3bCdCopyJob::cleanup() +{ + if( m_onTheFly || !m_keepImage || ((d->canceled || d->error) && !d->readingSuccessful) ) { + emit infoMessage( i18n("Removing temporary files."), INFO ); + for( QStringList::iterator it = d->infNames.begin(); it != d->infNames.end(); ++it ) + QFile::remove( *it ); + } + + if( !m_onTheFly && (!m_keepImage || ((d->canceled || d->error) && !d->readingSuccessful)) ) { + emit infoMessage( i18n("Removing image files."), INFO ); + for( QStringList::iterator it = d->imageNames.begin(); it != d->imageNames.end(); ++it ) + QFile::remove( *it ); + + // remove the tempdir created in prepareImageFiles() + if( d->deleteTempDir ) { + KIO::NetAccess::del( KURL::fromPathOrURL(m_tempPath), 0 ); + d->deleteTempDir = false; + } + } +} + + +void K3bCdCopyJob::slotReaderProgress( int p ) +{ + if( !m_onTheFly || m_onlyCreateImages ) { + int bigParts = ( m_onlyCreateImages ? 1 : (m_simulate ? 2 : m_copies + 1 ) ); + double done = (double)p * (double)d->sessionSizes[d->currentReadSession-1] / 100.0; + for( unsigned int i = 0; i < d->currentReadSession-1; ++i ) + done += (double)d->sessionSizes[i]; + emit percent( (int)(100.0*done/(double)d->overallSize/(double)bigParts) ); + + if( d->dataReaderRunning ) + emit subPercent(p); + } +} + + +void K3bCdCopyJob::slotReaderSubProgress( int p ) +{ + // only if reading an audiosession + if( !m_onTheFly || m_onlyCreateImages ) { + emit subPercent( p ); + } +} + + +void K3bCdCopyJob::slotReaderProcessedSize( int p, int pp ) +{ + if( !m_onTheFly ) + emit processedSubSize( p, pp ); +} + + +void K3bCdCopyJob::slotWriterProgress( int p ) +{ + int bigParts = ( m_simulate ? 1 : m_copies ) + ( m_onTheFly ? 0 : 1 ); + long done = ( m_onTheFly ? d->doneCopies : d->doneCopies+1 ) * d->overallSize + + (p * d->sessionSizes[d->currentWrittenSession-1] / 100); + for( unsigned int i = 0; i < d->currentWrittenSession-1; ++i ) + done += d->sessionSizes[i]; + emit percent( 100*done/d->overallSize/bigParts ); +} + + +void K3bCdCopyJob::slotWritingNextTrack( int t, int tt ) +{ + if( d->toc.contentType() == K3bDevice::MIXED ) { + if( d->currentWrittenSession == 1 ) + emit newSubTask( i18n("Writing track %1 of %2").arg(t).arg(d->toc.count()) ); + else + emit newSubTask( i18n("Writing track %1 of %2").arg(d->toc.count()).arg(d->toc.count()) ); + } + else if( d->numSessions > 1 ) + emit newSubTask( i18n("Writing track %1 of %2").arg(d->currentWrittenSession).arg(d->toc.count()) ); + else + emit newSubTask( i18n("Writing track %1 of %2").arg(t).arg(tt) ); +} + + +void K3bCdCopyJob::slotReadingNextTrack( int t, int ) +{ + if( !m_onTheFly || m_onlyCreateImages ) { + int track = t; + if( d->audioReaderRunning ) + track = t; + else if( d->toc.contentType() == K3bDevice::MIXED ) + track = d->toc.count(); + else + track = d->currentReadSession; + + emit newSubTask( i18n("Reading track %1 of %2").arg(track).arg(d->toc.count()) ); + } +} + + +QString K3bCdCopyJob::jobDescription() const +{ + if( m_onlyCreateImages ) { + return i18n("Creating CD Image"); + } + else if( m_simulate ) { + if( m_onTheFly ) + return i18n("Simulating CD Copy On-The-Fly"); + else + return i18n("Simulating CD Copy"); + } + else { + if( m_onTheFly ) + return i18n("Copying CD On-The-Fly"); + else + return i18n("Copying CD"); + } +} + + +QString K3bCdCopyJob::jobDetails() const +{ + return i18n("Creating 1 copy", + "Creating %n copies", + (m_simulate||m_onlyCreateImages) ? 1 : m_copies ); +} + + +void K3bCdCopyJob::finishJob( bool c, bool e ) +{ + if( d->running ) { + if( c ) { + d->canceled = true; + emit canceled(); + } + if( e ) + d->error = true; + + cleanup(); + + d->running = false; + + jobFinished( !(c||e) ); + } +} + +#include "k3bcdcopyjob.moc" diff --git a/libk3b/jobs/k3bcdcopyjob.h b/libk3b/jobs/k3bcdcopyjob.h new file mode 100644 index 0000000..3ab77e8 --- /dev/null +++ b/libk3b/jobs/k3bcdcopyjob.h @@ -0,0 +1,117 @@ +/* + * + * $Id: k3bcdcopyjob.h 690187 2007-07-20 09:18:03Z 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. + */ + + +#ifndef _K3BCDCOPYJOB_H_ +#define _K3BCDCOPYJOB_H_ + +#include <k3bjob.h> +#include "k3b_export.h" + +namespace K3bDevice { + class Device; + class DeviceHandler; +} + + +/** + *@author Sebastian Trueg + */ +class LIBK3B_EXPORT K3bCdCopyJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bCdCopyJob( K3bJobHandler* hdl, QObject* parent = 0 ); + ~K3bCdCopyJob(); + + K3bDevice::Device* writer() const { return m_onlyCreateImages ? 0 : m_writerDevice; } + K3bDevice::Device* reader() const { return m_readerDevice; } + + QString jobDescription() const; + QString jobDetails() const; + + public slots: + void start(); + void cancel(); + + public: + void setWriterDevice( K3bDevice::Device* dev ) { m_writerDevice = dev; } + void setReaderDevice( K3bDevice::Device* dev ) { m_readerDevice = dev; } + void setWritingMode( int m ) { m_writingMode = m; } + void setSpeed( int s ) { m_speed = s; } + void setOnTheFly( bool b ) { m_onTheFly = b; } + void setKeepImage( bool b ) { m_keepImage = b; } + void setOnlyCreateImage( bool b ) { m_onlyCreateImages = b; } + void setSimulate( bool b ) { m_simulate = b; } + void setTempPath( const QString& path ) { m_tempPath= path; } + void setCopies( unsigned int c ) { m_copies = c; } + void setParanoiaMode( int i ) { m_paranoiaMode = i; } + void setIgnoreDataReadErrors( bool b ) { m_ignoreDataReadErrors = b; } + void setDataReadRetries( int i ) { m_dataReadRetries = i; } + void setIgnoreAudioReadErrors( bool b ) { m_ignoreAudioReadErrors = b; } + void setAudioReadRetries( int i ) { m_audioReadRetries = i; } + void setPreferCdText( bool b ) { m_preferCdText = b; } + void setCopyCdText( bool b ) { m_copyCdText = b; } + void setNoCorrection( bool b ) { m_noCorrection = b; } + + private slots: + void slotDiskInfoReady( K3bDevice::DeviceHandler* ); + void slotCdTextReady( K3bDevice::DeviceHandler* ); + void slotMediaReloadedForNextSession( K3bDevice::DeviceHandler* dh ); + void slotCddbQueryFinished(int); + void slotWritingNextTrack( int t, int tt ); + void slotReadingNextTrack( int t, int tt ); + void slotSessionReaderFinished( bool success ); + void slotWriterFinished( bool success ); + void slotReaderProgress( int p ); + void slotReaderSubProgress( int p ); + void slotWriterProgress( int p ); + void slotReaderProcessedSize( int p, int pp ); + + private: + void startCopy(); + void searchCdText(); + void queryCddb(); + bool writeNextSession(); + void readNextSession(); + bool prepareImageFiles(); + void cleanup(); + void finishJob( bool canceled, bool error ); + + K3bDevice::Device* m_writerDevice; + K3bDevice::Device* m_readerDevice; + bool m_simulate; + int m_speed; + int m_paranoiaMode; + unsigned int m_copies; + bool m_keepImage; + bool m_onlyCreateImages; + bool m_onTheFly; + bool m_ignoreDataReadErrors; + bool m_ignoreAudioReadErrors; + bool m_noCorrection; + int m_dataReadRetries; + int m_audioReadRetries; + bool m_preferCdText; + bool m_copyCdText; + QString m_tempPath; + int m_writingMode; + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/jobs/k3bcdda2wavreader.cpp b/libk3b/jobs/k3bcdda2wavreader.cpp new file mode 100644 index 0000000..3df87d3 --- /dev/null +++ b/libk3b/jobs/k3bcdda2wavreader.cpp @@ -0,0 +1,254 @@ +/* + * + * $Id: k3bcdda2wavreader.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 "k3bcdda2wavreader.h" + +#include <k3bexternalbinmanager.h> +#include <k3bdevice.h> +#include <k3bdevicemanager.h> +#include <k3bcore.h> +#include <k3bprocess.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <qvaluevector.h> +#include <qregexp.h> + + +class K3bCdda2wavReader::Private +{ +public: + Private() + : cdda2wavBin(0), + process(0), + cancaled(false), + running(false), + fdToWriteTo(-1) { + } + + const K3bExternalBin* cdda2wavBin; + K3bProcess* process; + + bool cancaled; + bool running; + + int fdToWriteTo; + + int currentTrack; + QValueVector<int> trackOffsets; +}; + + +K3bCdda2wavReader::K3bCdda2wavReader( QObject* parent, const char* name ) + : K3bJob( parent, name ) +{ + d = new Private(); +} + + +K3bCdda2wavReader::~K3bCdda2wavReader() +{ + delete d->process; + delete d; +} + + +bool K3bCdda2wavReader::active() const +{ + return d->running; +} + + +void K3bCdda2wavReader::writeToFd( int fd ) +{ + d->fdToWriteTo = fd; +} + + +void K3bCdda2wavReader::start() +{ + start( false ); +} + + +void K3bCdda2wavReader::start( bool onlyInfo ) +{ + d->running = true; + d->cancaled = false; + d->currentTrack = 1; + d->trackOffsets.clear(); + + jobStarted(); + + d->cdda2wavBin = k3bcore->externalBinManager()->binObject( "cdda2wav" ); + if( !d->cdda2wavBin ) { + emit infoMessage( i18n("Could not find %1 executable.").arg("cdda2wav"), ERROR ); + jobFinished(false); + d->running = false; + return; + } + + // prepare the process + delete d->process; + d->process = new K3bProcess(); + d->process->setSplitStdout(true); + d->process->setSuppressEmptyLines(true); + d->process->setWorkingDirectory( m_imagePath ); + connect( d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotProcessLine(const QString&)) ); + connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotProcessLine(const QString&)) ); + connect( d->process, SIGNAL(processExited(KProcess*)), this, SLOT(slotProcessExited(KProcess*)) ); + + // create the command line + *d->process << d->cdda2wavBin->path; + *d->process << "-vall" << ( d->cdda2wavBin->hasFeature( "gui" ) ? "-gui" : "-g" ); + if( d->cdda2wavBin->hasFeature( "dev" ) ) + *d->process << QString("dev=%1").arg(K3bDevice::externalBinDeviceParameter(m_device, d->cdda2wavBin)); + else + *d->process << "-D" << K3bDevice::externalBinDeviceParameter(m_device, d->cdda2wavBin); + *d->process << ( d->cdda2wavBin->hasFeature( "bulk" ) ? "-bulk" : "-B" ); + if( onlyInfo ) + *d->process << ( d->cdda2wavBin->hasFeature( "info-only" ) ? "-info-only" : "-J" ); + else if( d->fdToWriteTo != -1 ) + *d->process << ( d->cdda2wavBin->hasFeature( "no-infofile" ) ? "-no-infofile" : "-H" ); + + // additional user parameters from config + const QStringList& params = d->cdda2wavBin->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *d->process << *it; + + // start the thing + if( !d->process->start( KProcess::NotifyOnExit, KProcess::All ) ) { + // something went wrong when starting the program + // it "should" be the executable + kdDebug() << "(K3bCdda2wavReader) could not start cdda2wav" << endl; + emit infoMessage( i18n("Could not start %1.").arg("cdda2wav"), K3bJob::ERROR ); + d->running = false; + jobFinished(false); + } +} + + +void K3bCdda2wavReader::cancel() +{ + if( d->running ) { + d->cancaled = true; + if( d->process ) + if( d->process->isRunning() ) + d->process->kill(); + } +} + + +void K3bCdda2wavReader::slotProcessLine( const QString& line ) +{ + // Tracks:11 44:37.30 + // CDINDEX discid: ZvzBXv614ACgzn1bWWy107cs0nA- + // CDDB discid: 0x8a0a730b + // CD-Text: not detected + // CD-Extra: not detected + // Album title: '' from '' + // T01: 0 3:39.70 audio linear copydenied stereo title '' from '' + // T02: 16495 3:10.47 audio linear copydenied stereo title '' from '' + // T03: 30792 3:30.00 audio linear copydenied stereo title '' from '' + // T04: 46542 4:05.05 audio linear copydenied stereo title '' from '' + // T05: 64922 3:44.35 audio linear copydenied stereo title '' from '' + // T06: 81757 4:36.45 audio linear copydenied stereo title '' from '' + // T07: 102502 3:59.30 audio linear copydenied stereo title '' from '' + // T08: 120457 5:24.30 audio linear copydenied stereo title '' from '' + // T09: 144787 3:26.28 audio linear copydenied stereo title '' from '' + // T10: 160265 4:07.20 audio linear copydenied stereo title '' from '' + // T11: 178810 4:51.20 audio linear copydenied stereo title '' from '' + + // percent_done: + // 100% track 1 successfully recorded + // 100% track 2 successfully recorded + // 100% track 3 successfully recorded + + + + static QRegExp rx( "T\\d\\d:" ); + if( rx.exactMatch( line.left(4) ) || line.startsWith( "Leadout" ) ) { + int pos = line.find( " " ); + int endpos = line.find( QRegExp( "\\d" ), pos ); + endpos = line.find( " ", endpos ); + bool ok; + int offset = line.mid( pos, endpos-pos ).toInt(&ok); + if( ok ) + d->trackOffsets.append( offset ); + else + kdDebug() << "(K3bCdda2wavReader) track offset parsing error: '" << line.mid( pos, endpos-pos ) << "'" << endl; + } + + else if( line.startsWith( "percent_done" ) ) { + // the reading starts + d->currentTrack = 1; + emit nextTrack( d->currentTrack, d->trackOffsets.count() ); + } + + else if( line.contains("successfully recorded") ) { + d->currentTrack++; + emit nextTrack( d->currentTrack, d->trackOffsets.count() ); + } + + else if( line.contains("%") ) { + // parse progress + bool ok; + int p = line.left(3).toInt(&ok); + if( ok ) { + emit subPercent( p ); + + int overall = d->trackOffsets[d->currentTrack-1]; + int tSize = d->trackOffsets[d->currentTrack] - d->trackOffsets[d->currentTrack-1]; + overall += (tSize*p/100); + + emit percent( overall*100/d->trackOffsets[d->trackOffsets.count()-1] ); + } + else + kdDebug() << "(K3bCdda2wavReader) track progress parsing error: '" << line.left(3) << "'" << endl; + } +} + + +void K3bCdda2wavReader::slotProcessExited( KProcess* p ) +{ + d->running = false; + + if( d->cancaled ) { + emit canceled(); + jobFinished(false); + return; + } + + if( p->normalExit() ) { + // TODO: improve this + + if( p->exitStatus() == 0 ) { + jobFinished( true ); + } + else { + emit infoMessage( i18n("%1 returned an unknown error (code %2).") + .arg("Cdda2wav").arg(p->exitStatus()), ERROR ); + jobFinished( false ); + } + } + else { + emit infoMessage( i18n("%1 did not exit cleanly.").arg("Cdda2wav"), + ERROR ); + jobFinished( false ); + } +} + +#include "k3bcdda2wavreader.moc" diff --git a/libk3b/jobs/k3bcdda2wavreader.h b/libk3b/jobs/k3bcdda2wavreader.h new file mode 100644 index 0000000..edde65c --- /dev/null +++ b/libk3b/jobs/k3bcdda2wavreader.h @@ -0,0 +1,70 @@ +/* + * + * $Id: k3bcdda2wavreader.h 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. + */ + +#ifndef _K3B_CDDA2WAV_READER_H_ +#define _K3B_CDDA2WAV_READER_H_ + +#include <k3bjob.h> + +class KProcess; +namespace K3bDevice { + class Device; +}; + + +/** + * An Audio CD reader completely based on cdda2wav. + * It does not use K3bDevice::Device but parses the track offsets + * from the cdda2wav output. + */ +class K3bCdda2wavReader : public K3bJob +{ + Q_OBJECT + + public: + K3bCdda2wavReader( QObject* parent = 0, const char* name = 0 ); + ~K3bCdda2wavReader(); + + bool active() const; + + public slots: + void start(); + void start( bool onlyReadInfo ); + void cancel(); + + void setReadDevice( K3bDevice::Device* dev ) { m_device = dev; } + void setImagePath( const QString& p ) { m_imagePath = p; } + + /** + * the data gets written directly into fd instead of the imagefile. + * Be aware that this only makes sense before starting the job. + * To disable just set fd to -1 + */ + void writeToFd( int fd ); + + private slots: + void slotProcessLine( const QString& ); + void slotProcessExited( KProcess* ); + + private: + K3bDevice::Device* m_device; + + QString m_imagePath; + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/jobs/k3bclonejob.cpp b/libk3b/jobs/k3bclonejob.cpp new file mode 100644 index 0000000..9fb61ab --- /dev/null +++ b/libk3b/jobs/k3bclonejob.cpp @@ -0,0 +1,375 @@ +/* + * + * $Id: k3bclonejob.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 "k3bclonejob.h" + +#include <k3breadcdreader.h> +#include <k3bcdrecordwriter.h> +#include <k3bexternalbinmanager.h> +#include <k3bdevice.h> +#include <k3bdevicehandler.h> +#include <k3bglobals.h> +#include <k3bcore.h> +#include <k3bclonetocreader.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <qfile.h> +#include <qfileinfo.h> + + + +class K3bCloneJob::Private +{ +public: + Private() + : doneCopies(0) { + } + + int doneCopies; +}; + + +K3bCloneJob::K3bCloneJob( K3bJobHandler* hdl, QObject* parent, const char* name ) + : K3bBurnJob( hdl, parent, name ), + m_writerDevice(0), + m_readerDevice(0), + m_writerJob(0), + m_readcdReader(0), + m_removeImageFiles(false), + m_canceled(false), + m_running(false), + m_simulate(false), + m_speed(1), + m_copies(1), + m_onlyCreateImage(false), + m_onlyBurnExistingImage(false), + m_readRetries(128) +{ + d = new Private; +} + + +K3bCloneJob::~K3bCloneJob() +{ + delete d; +} + + +void K3bCloneJob::start() +{ + jobStarted(); + + m_canceled = false; + m_running = true; + + + // TODO: check the cd size and warn the user if not enough space + + // + // We first check if cdrecord has clone support + // The readcdReader will check the same for readcd + // + const K3bExternalBin* cdrecordBin = k3bcore->externalBinManager()->binObject( "cdrecord" ); + if( !cdrecordBin ) { + emit infoMessage( i18n("Could not find %1 executable.").arg("cdrecord"), ERROR ); + jobFinished(false); + m_running = false; + return; + } + else if( !cdrecordBin->hasFeature( "clone" ) ) { + emit infoMessage( i18n("Cdrecord version %1 does not have cloning support.").arg(cdrecordBin->version), ERROR ); + jobFinished(false); + m_running = false; + return; + } + + if( (!m_onlyCreateImage && !writer()) || + (!m_onlyBurnExistingImage && !readingDevice()) ) { + emit infoMessage( i18n("No device set."), ERROR ); + jobFinished(false); + m_running = false; + return; + } + + if( !m_onlyCreateImage ) { + if( !writer()->supportsWritingMode( K3bDevice::RAW_R96R ) && + !writer()->supportsWritingMode( K3bDevice::RAW_R16 ) ) { + emit infoMessage( i18n("CD writer %1 does not support cloning.") + .arg(writer()->vendor()) + .arg(writer()->description()), ERROR ); + m_running = false; + jobFinished(false); + return; + } + } + + if( m_imagePath.isEmpty() ) { + m_imagePath = K3b::findTempFile( "img" ); + } + else if( QFileInfo(m_imagePath).isDir() ) { + m_imagePath = K3b::findTempFile( "img", m_imagePath ); + } + + if( m_onlyBurnExistingImage ) { + startWriting(); + } + else { + emit burning( false ); + + prepareReader(); + + if( waitForMedia( readingDevice(), + K3bDevice::STATE_COMPLETE, + K3bDevice::MEDIA_WRITABLE_CD|K3bDevice::MEDIA_CD_ROM ) < 0 ) { + m_running = false; + emit canceled(); + jobFinished(false); + return; + } + + emit newTask( i18n("Reading clone image") ); + + m_readcdReader->start(); + } +} + + +void K3bCloneJob::prepareReader() +{ + if( !m_readcdReader ) { + m_readcdReader = new K3bReadcdReader( this, this ); + connect( m_readcdReader, SIGNAL(percent(int)), this, SLOT(slotReadingPercent(int)) ); + connect( m_readcdReader, SIGNAL(percent(int)), this, SIGNAL(subPercent(int)) ); + connect( m_readcdReader, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); + connect( m_readcdReader, SIGNAL(finished(bool)), this, SLOT(slotReadingFinished(bool)) ); + connect( m_readcdReader, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_readcdReader, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( m_readcdReader, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + } + + m_readcdReader->setReadDevice( readingDevice() ); + m_readcdReader->setReadSpeed( 0 ); // MAX + m_readcdReader->setDisableCorrection( m_noCorrection ); + m_readcdReader->setImagePath( m_imagePath ); + m_readcdReader->setClone( true ); + m_readcdReader->setRetries( m_readRetries ); +} + + +void K3bCloneJob::prepareWriter() +{ + if( !m_writerJob ) { + m_writerJob = new K3bCdrecordWriter( writer(), this, this ); + connect( m_writerJob, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_writerJob, SIGNAL(percent(int)), this, SLOT(slotWriterPercent(int)) ); + connect( m_writerJob, SIGNAL(percent(int)), this, SIGNAL(subPercent(int)) ); + connect( m_writerJob, SIGNAL(nextTrack(int, int)), this, SLOT(slotWriterNextTrack(int, int)) ); + connect( m_writerJob, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); + connect( m_writerJob, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); + connect( m_writerJob, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); + connect( m_writerJob, SIGNAL(writeSpeed(int, int)), this, SIGNAL(writeSpeed(int, int)) ); + connect( m_writerJob, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) ); + // connect( m_writerJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); + connect( m_writerJob, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( m_writerJob, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + } + + m_writerJob->clearArguments(); + m_writerJob->setWritingMode( K3b::RAW ); + m_writerJob->setClone( true ); + m_writerJob->setSimulate( m_simulate ); + m_writerJob->setBurnSpeed( m_speed ); + m_writerJob->addArgument( m_imagePath ); +} + + +void K3bCloneJob::cancel() +{ + if( m_running ) { + m_canceled = true; + if( m_readcdReader ) + m_readcdReader->cancel(); + if( m_writerJob ) + m_writerJob->cancel(); + } +} + + +void K3bCloneJob::slotWriterPercent( int p ) +{ + if( m_onlyBurnExistingImage ) + emit percent( (int)((double)(d->doneCopies)*100.0/(double)(m_copies) + (double)p/(double)(m_copies)) ); + else + emit percent( (int)((double)(1+d->doneCopies)*100.0/(double)(1+m_copies) + (double)p/(double)(1+m_copies)) ); +} + + +void K3bCloneJob::slotWriterNextTrack( int t, int tt ) +{ + emit newSubTask( i18n("Writing Track %1 of %2").arg(t).arg(tt) ); +} + + +void K3bCloneJob::slotWriterFinished( bool success ) +{ + if( m_canceled ) { + removeImageFiles(); + m_running = false; + emit canceled(); + jobFinished(false); + return; + } + + if( success ) { + d->doneCopies++; + + emit infoMessage( i18n("Successfully written clone copy %1.").arg(d->doneCopies), INFO ); + + if( d->doneCopies < m_copies ) { + K3bDevice::eject( writer() ); + startWriting(); + } + else { + if( m_removeImageFiles ) + removeImageFiles(); + m_running = false; + jobFinished(true); + } + } + else { + removeImageFiles(); + m_running = false; + jobFinished(false); + } +} + + +void K3bCloneJob::slotReadingPercent( int p ) +{ + emit percent( m_onlyCreateImage ? p : (int)((double)p/(double)(1+m_copies)) ); +} + + +void K3bCloneJob::slotReadingFinished( bool success ) +{ + if( m_canceled ) { + removeImageFiles(); + m_running = false; + emit canceled(); + jobFinished(false); + return; + } + + if( success ) { + // + // Make a quick test if the image is really valid. + // Readcd does not seem to have proper exit codes + // + K3bCloneTocReader ctr( m_imagePath ); + if( ctr.isValid() ) { + emit infoMessage( i18n("Successfully read disk."), INFO ); + if( m_onlyCreateImage ) { + m_running = false; + jobFinished(true); + } + else { + if( writer() == readingDevice() ) + K3bDevice::eject( writer() ); + startWriting(); + } + } + else { + emit infoMessage( i18n("Failed to read disk completely in clone mode."), ERROR ); + removeImageFiles(); + m_running = false; + jobFinished(false); + } + } + else { + emit infoMessage( i18n("Error while reading disk."), ERROR ); + removeImageFiles(); + m_running = false; + jobFinished(false); + } +} + + +void K3bCloneJob::startWriting() +{ + emit burning( true ); + + // start writing + prepareWriter(); + + if( waitForMedia( writer(), + K3bDevice::STATE_EMPTY, + K3bDevice::MEDIA_WRITABLE_CD ) < 0 ) { + removeImageFiles(); + m_running = false; + emit canceled(); + jobFinished(false); + return; + } + + if( m_simulate ) + emit newTask( i18n("Simulating clone copy") ); + else + emit newTask( i18n("Writing clone copy %1").arg(d->doneCopies+1) ); + + m_writerJob->start(); +} + + +void K3bCloneJob::removeImageFiles() +{ + if( !m_onlyBurnExistingImage ) { + emit infoMessage( i18n("Removing image files."), INFO ); + if( QFile::exists( m_imagePath ) ) + QFile::remove( m_imagePath ); + if( QFile::exists( m_imagePath + ".toc" ) ) + QFile::remove( m_imagePath + ".toc" ); + } +} + + +QString K3bCloneJob::jobDescription() const +{ + if( m_onlyCreateImage ) + return i18n("Creating Clone Image"); + else if( m_onlyBurnExistingImage ) { + if( m_simulate ) + return i18n("Simulating Clone Image"); + else + return i18n("Burning Clone Image"); + } + else if( m_simulate ) + return i18n("Simulating CD Cloning"); + else + return i18n("Cloning CD"); +} + + +QString K3bCloneJob::jobDetails() const +{ + return i18n("Creating 1 clone copy", + "Creating %n clone copies", + (m_simulate||m_onlyCreateImage) ? 1 : m_copies ); +} + +#include "k3bclonejob.moc" diff --git a/libk3b/jobs/k3bclonejob.h b/libk3b/jobs/k3bclonejob.h new file mode 100644 index 0000000..80c8ea9 --- /dev/null +++ b/libk3b/jobs/k3bclonejob.h @@ -0,0 +1,99 @@ +/* + * + * $Id: k3bclonejob.h 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. + */ + +#ifndef _K3B_CLONE_JOB_H_ +#define _K3B_CLONE_JOB_H_ + +#include <k3bjob.h> +#include "k3b_export.h" +#include <qstring.h> + + +namespace K3bDevice { + class Device; +} +class K3bCdrecordWriter; +class K3bReadcdReader; + + +class LIBK3B_EXPORT K3bCloneJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bCloneJob( K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bCloneJob(); + + K3bDevice::Device* writer() const { return m_writerDevice; } + K3bDevice::Device* readingDevice() const { return m_readerDevice; } + + QString jobDescription() const; + QString jobDetails() const; + + public slots: + void start(); + void cancel(); + + void setWriterDevice( K3bDevice::Device* w ) { m_writerDevice = w; } + void setReaderDevice( K3bDevice::Device* w ) { m_readerDevice = w; } + void setImagePath( const QString& p ) { m_imagePath = p; } + void setNoCorrection( bool b ) { m_noCorrection = b; } + void setRemoveImageFiles( bool b ) { m_removeImageFiles = b; } + void setOnlyCreateImage( bool b ) { m_onlyCreateImage = b; } + void setOnlyBurnExistingImage( bool b ) { m_onlyBurnExistingImage = b; } + void setSimulate( bool b ) { m_simulate = b; } + void setWriteSpeed( int s ) { m_speed = s; } + void setCopies( int c ) { m_copies = c; } + void setReadRetries( int i ) { m_readRetries = i; } + + private slots: + void slotWriterPercent( int ); + void slotWriterFinished( bool ); + void slotWriterNextTrack( int, int ); + void slotReadingPercent( int ); + void slotReadingFinished( bool ); + + private: + void removeImageFiles(); + void prepareReader(); + void prepareWriter(); + void startWriting(); + + K3bDevice::Device* m_writerDevice; + K3bDevice::Device* m_readerDevice; + QString m_imagePath; + + K3bCdrecordWriter* m_writerJob; + K3bReadcdReader* m_readcdReader; + + bool m_noCorrection; + bool m_removeImageFiles; + + bool m_canceled; + bool m_running; + + bool m_simulate; + int m_speed; + int m_copies; + bool m_onlyCreateImage; + bool m_onlyBurnExistingImage; + int m_readRetries; + + class Private; + Private* d; +}; + + +#endif diff --git a/libk3b/jobs/k3bclonetocreader.cpp b/libk3b/jobs/k3bclonetocreader.cpp new file mode 100644 index 0000000..5dd8b8b --- /dev/null +++ b/libk3b/jobs/k3bclonetocreader.cpp @@ -0,0 +1,235 @@ +/* + * + * $Id: k3bclonetocreader.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 <config.h> + + +#include "k3bclonetocreader.h" + +#include <k3bdeviceglobals.h> +#include <k3bglobals.h> + +#include <qfile.h> +#include <qtextstream.h> + +#include <kdebug.h> + + +class K3bCloneTocReader::Private +{ +public: + Private() + : size(0) { + } + + K3b::Msf size; + QString tocFile; +}; + + + +K3bCloneTocReader::K3bCloneTocReader( const QString& filename ) + : K3bImageFileReader() +{ + d = new Private; + openFile( filename ); +} + + +K3bCloneTocReader::~K3bCloneTocReader() +{ + delete d; +} + + +const K3b::Msf& K3bCloneTocReader::imageSize() const +{ + return d->size; +} + + +void K3bCloneTocReader::readFile() +{ + // first of all we check if we find the image file which contains the data for this toc + // cdrecord always uses this strange file naming: + // somedata + // somedata.toc + + // filename should always be the toc file + if( filename().right( 4 ) == ".toc" ) + d->tocFile = filename(); + else + d->tocFile = filename() + ".toc"; + + // now get rid of the ".toc" extension + QString imageFileName = d->tocFile.left( d->tocFile.length()-4 ); + if( !QFile::exists( imageFileName ) ) { + kdDebug() << "(K3bCloneTocReader) could not find image file " << imageFileName << endl; + return; + } + + setImageFilename( imageFileName ); + + d->size = 0; + + QFile f( d->tocFile ); + if( f.open( IO_ReadOnly ) ) { + // + // Inspired by clone.c from the cdrecord sources + // + char buffer[2048]; + int read = f.readBlock( buffer, 2048 ); + f.close(); + + if( read == 2048 ) { + kdDebug() << "(K3bCloneTocReader) TOC too large." << endl; + return; + } + + // the toc starts with a tocheader + struct tocheader { + unsigned char len[2]; + unsigned char first; // first session + unsigned char last; // last session + }; + + struct tocheader* th = (struct tocheader*)buffer; + int dataLen = K3bDevice::from2Byte( th->len ) + 2; // the len field does not include it's own length + + if( th->first != 1 ) { + kdDebug() << "(K3bCloneTocReader) first session != 1" << endl; + return; + } + + // the following bytes are multiple instances of + struct ftrackdesc { + unsigned char sess_number; +#ifdef WORDS_BIGENDIAN // __BYTE_ORDER == __BIG_ENDIAN + unsigned char adr : 4; + unsigned char control : 4; +#else + unsigned char control : 4; + unsigned char adr : 4; +#endif + unsigned char track; + unsigned char point; + unsigned char amin; + unsigned char asec; + unsigned char aframe; + unsigned char res7; + unsigned char pmin; + unsigned char psec; + unsigned char pframe; + }; + + for( int i = 4; i < dataLen; i += 11) { + struct ftrackdesc* ft = (struct ftrackdesc*)&buffer[i]; + + if( ft->sess_number != 1 ) { + kdDebug() << "(K3bCloneTocReader} session number != 1" << endl; + return; + } + + // now we check some of the values + if( ft->point >= 0x1 && ft->point <= 0x63 ) { + if( ft->adr == 1 ) { + // check track starttime + if( ft->psec > 60 || ft->pframe > 75 ) { + kdDebug() << "(K3bCloneTocReader) invalid track start: " + << (int)ft->pmin << "." + << (int)ft->psec << "." + << (int)ft->pframe << endl; + return; + } + } + } + else { + switch( ft->point ) { + case 0xa0: + if( ft->adr != 1 ) { + kdDebug() << "(K3bCloneTocReader) adr != 1" << endl; + return; + } + + // disk type in psec + if( ft->psec != 0x00 && ft->psec != 0x10 && ft->psec != 0x20 ) { + kdDebug() << "(K3bCloneTocReader) invalid disktype: " << ft->psec << endl; + return; + } + + if( ft->pmin != 1 ) { + kdDebug() << "(K3bCloneTocReader) first track number != 1 " << endl; + return; + } + + if( ft->pframe != 0x0 ) { + kdDebug() << "(K3bCloneTocReader) found data when there should be 0x0" << endl; + return; + } + break; + + case 0xa1: + if( ft->adr != 1 ) { + kdDebug() << "(K3bCloneTocReader) adr != 1" << endl; + return; + } + + if( !(ft->pmin >= 1) ) { + kdDebug() << "(K3bCloneTocReader) last track number needs to be >= 1." << endl; + return; + } + if( ft->psec != 0x0 || ft->pframe != 0x0 ) { + kdDebug() << "(K3bCloneTocReader) found data when there should be 0x0" << endl; + return; + } + break; + + case 0xa2: + if( ft->adr != 1 ) { + kdDebug() << "(K3bCloneTocReader) adr != 1" << endl; + return; + } + + // start of the leadout = size of the image + // substract 2 seconds since in cdrecord other than in K3b lba 0 = msf 2:00 + // (the cdrecord way is actually more accurate but we use k3b::Msf for many + // things and it is simpler this way.) + d->size = K3b::Msf( ft->pmin, ft->psec, ft->pframe ) - K3b::Msf( 0, 2, 0 ); + + // leadout... no check so far... + break; + + default: + if( ft->adr != 5 ) { + kdDebug() << "(K3bCloneTocReader) adr != 5" << endl; + return; + } + break; + } + } + } + + if( d->size.rawBytes() != K3b::filesize( imageFileName ) ) { + kdDebug() << "(K3bCloneTocReader) image file size invalid." << endl; + return; + } + + // ok, could be a cdrecord toc file + setValid(true); + } + else { + kdDebug() << "(K3bCloneTocReader) could not open file " << d->tocFile << endl; + } +} diff --git a/libk3b/jobs/k3bclonetocreader.h b/libk3b/jobs/k3bclonetocreader.h new file mode 100644 index 0000000..17e80d7 --- /dev/null +++ b/libk3b/jobs/k3bclonetocreader.h @@ -0,0 +1,45 @@ +/* + * + * $Id: k3bclonetocreader.h 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. + */ + +#ifndef _K3B_CLONETOC_FILE_PARSER_H_ +#define _K3B_CLONETOC_FILE_PARSER_H_ + +#include "k3bimagefilereader.h" + +#include <k3bmsf.h> + +#include "k3b_export.h" + + +/** + * Reads a cdrecord clone toc file and searches for the + * corresponding image file. + */ +class LIBK3B_EXPORT K3bCloneTocReader : public K3bImageFileReader +{ + public: + K3bCloneTocReader( const QString& filename = QString::null ); + ~K3bCloneTocReader(); + + const K3b::Msf& imageSize() const; + + protected: + void readFile(); + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/jobs/k3bdatatrackreader.cpp b/libk3b/jobs/k3bdatatrackreader.cpp new file mode 100644 index 0000000..8300ada --- /dev/null +++ b/libk3b/jobs/k3bdatatrackreader.cpp @@ -0,0 +1,515 @@ +/* + * + * $Id: k3bdatatrackreader.cpp 690529 2007-07-21 10:51:47Z 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 "k3bdatatrackreader.h" + +#include <k3blibdvdcss.h> +#include <k3bdevice.h> +#include <k3bdeviceglobals.h> +#include <k3btrack.h> +#include <k3bthread.h> +#include <k3bcore.h> + +#include <klocale.h> +#include <kdebug.h> + +#include <qfile.h> + +#include <unistd.h> + + + +// FIXME: determine max DMA buffer size +static int s_bufferSizeSectors = 10; + + +class K3bDataTrackReader::WorkThread : public K3bThread +{ +public: + WorkThread(); + ~WorkThread(); + + void init(); + void run(); + int read( unsigned char* buffer, unsigned long sector, unsigned int len ); + bool retryRead( unsigned char* buffer, unsigned long startSector, unsigned int len ); + bool setErrorRecovery( K3bDevice::Device* dev, int code ); + void cancel(); + + bool m_canceled; + bool m_ignoreReadErrors; + bool m_noCorrection; + int m_retries; + K3bDevice::Device* m_device; + K3b::Msf m_firstSector; + K3b::Msf m_lastSector; + K3b::Msf m_nextReadSector; + int m_fd; + QString m_imagePath; + int m_sectorSize; + bool m_useLibdvdcss; + K3bLibDvdCss* m_libcss; + + int m_oldErrorRecoveryMode; + + int m_errorSectorCount; + +private: + int m_usedSectorSize; +}; + + +K3bDataTrackReader::K3bDataTrackReader( K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bThreadJob( jh, parent, name ) +{ + m_thread = new WorkThread(); + setThread( m_thread ); +} + + +K3bDataTrackReader::WorkThread::WorkThread() + : K3bThread(), + m_canceled(false), + m_ignoreReadErrors(false), + m_noCorrection(false), + m_retries(10), + m_device(0), + m_fd(-1), + m_libcss(0) +{ +} + + +K3bDataTrackReader::WorkThread::~WorkThread() +{ + delete m_libcss; +} + + +void K3bDataTrackReader::WorkThread::init() +{ + m_canceled = false; +} + + +void K3bDataTrackReader::WorkThread::run() +{ + emitStarted(); + + if( !m_device->open() ) { + emitInfoMessage( i18n("Could not open device %1").arg(m_device->blockDeviceName()), K3bJob::ERROR ); + emitFinished(false); + return; + } + + // 1. determine sector size by checking the first sectors mode + // if impossible or MODE2 (mode2 formless) finish(false) + + m_useLibdvdcss = false; + m_usedSectorSize = m_sectorSize; + if( m_device->isDVD() ) { + m_usedSectorSize = MODE1; + + // + // In case of an encrypted VideoDVD we read with libdvdcss which takes care of decrypting the vobs + // + if( m_device->copyrightProtectionSystemType() == 1 ) { + + // close the device for libdvdcss + m_device->close(); + + kdDebug() << "(K3bDataTrackReader::WorkThread) found encrypted dvd. using libdvdcss." << endl; + + // open the libdvdcss stuff + if( !m_libcss ) + m_libcss = K3bLibDvdCss::create(); + if( !m_libcss ) { + emitInfoMessage( i18n("Unable to open libdvdcss."), K3bJob::ERROR ); + emitFinished(false); + return; + } + + if( !m_libcss->open(m_device) ) { + emitInfoMessage( i18n("Could not open device %1").arg(m_device->blockDeviceName()), K3bJob::ERROR ); + emitFinished(false); + return; + } + + emitInfoMessage( i18n("Retrieving all CSS keys. This might take a while."), K3bJob::INFO ); + if( !m_libcss->crackAllKeys() ) { + m_libcss->close(); + emitInfoMessage( i18n("Failed to retrieve all CSS keys."), K3bJob::ERROR ); + emitInfoMessage( i18n("Video DVD decryption failed."), K3bJob::ERROR ); + emitFinished(false); + return; + } + + m_useLibdvdcss = true; + } + } + else { + if( m_usedSectorSize == AUTO ) { + switch( m_device->getDataMode( m_firstSector ) ) { + case K3bDevice::Track::MODE1: + case K3bDevice::Track::DVD: + m_usedSectorSize = MODE1; + break; + case K3bDevice::Track::XA_FORM1: + m_usedSectorSize = MODE2FORM1; + break; + case K3bDevice::Track::XA_FORM2: + m_usedSectorSize = MODE2FORM2; + break; + case K3bDevice::Track::MODE2: + emitInfoMessage( i18n("No support for reading formless Mode2 sectors."), K3bJob::ERROR ); + default: + emitInfoMessage( i18n("Unsupported sector type."), K3bJob::ERROR ); + m_device->close(); + emitFinished(false); + return; + } + } + } + + emitInfoMessage( i18n("Reading with sector size %1.").arg(m_usedSectorSize), K3bJob::INFO ); + emitDebuggingOutput( "K3bDataTrackReader", + QString("reading sectors %1 to %2 with sector size %3. Length: %4 sectors, %5 bytes.") + .arg( m_firstSector.lba() ) + .arg( m_lastSector.lba() ) + .arg( m_usedSectorSize ) + .arg( m_lastSector.lba() - m_firstSector.lba() + 1 ) + .arg( Q_UINT64(m_usedSectorSize) * (Q_UINT64)(m_lastSector.lba() - m_firstSector.lba() + 1) ) ); + + QFile file; + if( m_fd == -1 ) { + file.setName( m_imagePath ); + if( !file.open( IO_WriteOnly ) ) { + m_device->close(); + if( m_useLibdvdcss ) + m_libcss->close(); + emitInfoMessage( i18n("Unable to open '%1' for writing.").arg(m_imagePath), K3bJob::ERROR ); + emitFinished( false ); + return; + } + } + + k3bcore->blockDevice( m_device ); + m_device->block( true ); + + // + // set the error recovery mode to 0x21 or 0x20 depending on m_ignoreReadErrors + // TODO: should we also set RC=1 in m_ignoreReadErrors mode (0x11 because TB is ignored) + // + setErrorRecovery( m_device, m_noCorrection ? 0x21 : 0x20 ); + + // + // Let the drive determine the optimal reading speed + // + m_device->setSpeed( 0xffff, 0xffff ); + + s_bufferSizeSectors = 128; + unsigned char* buffer = new unsigned char[m_usedSectorSize*s_bufferSizeSectors]; + while( s_bufferSizeSectors > 0 && read( buffer, m_firstSector.lba(), s_bufferSizeSectors ) < 0 ) { + kdDebug() << "(K3bDataTrackReader) determine max read sectors: " + << s_bufferSizeSectors << " too high." << endl; + s_bufferSizeSectors--; + } + kdDebug() << "(K3bDataTrackReader) determine max read sectors: " + << s_bufferSizeSectors << " is max." << endl; + + // s_bufferSizeSectors = K3bDevice::determineMaxReadingBufferSize( m_device, m_firstSector ); + if( s_bufferSizeSectors <= 0 ) { + emitInfoMessage( i18n("Error while reading sector %1.").arg(m_firstSector.lba()), K3bJob::ERROR ); + emitFinished(false); + m_device->block( false ); + k3bcore->unblockDevice( m_device ); + return; + } + + kdDebug() << "(K3bDataTrackReader) using buffer size of " << s_bufferSizeSectors << " blocks." << endl; + emitDebuggingOutput( "K3bDataTrackReader", QString("using buffer size of %1 blocks.").arg( s_bufferSizeSectors ) ); + + // 2. get it on + K3b::Msf currentSector = m_firstSector; + K3b::Msf totalReadSectors; + m_nextReadSector = 0; + m_errorSectorCount = 0; + bool writeError = false; + bool readError = false; + int lastPercent = 0; + unsigned long lastReadMb = 0; + int bufferLen = s_bufferSizeSectors*m_usedSectorSize; + while( !m_canceled && currentSector <= m_lastSector ) { + + int maxReadSectors = QMIN( bufferLen/m_usedSectorSize, m_lastSector.lba()-currentSector.lba()+1 ); + + int readSectors = read( buffer, + currentSector.lba(), + maxReadSectors ); + if( readSectors < 0 ) { + if( !retryRead( buffer, + currentSector.lba(), + maxReadSectors ) ) { + readError = true; + break; + } + else + readSectors = maxReadSectors; + } + + totalReadSectors += readSectors; + + int readBytes = readSectors * m_usedSectorSize; + + if( m_fd != -1 ) { + if( ::write( m_fd, reinterpret_cast<void*>(buffer), readBytes ) != readBytes ) { + kdDebug() << "(K3bDataTrackReader::WorkThread) error while writing to fd " << m_fd + << " current sector: " << (currentSector.lba()-m_firstSector.lba()) << endl; + emitDebuggingOutput( "K3bDataTrackReader", + QString("Error while writing to fd %1. Current sector is %2.") + .arg(m_fd).arg(currentSector.lba()-m_firstSector.lba()) ); + writeError = true; + break; + } + } + else { + if( file.writeBlock( reinterpret_cast<char*>(buffer), readBytes ) != readBytes ) { + kdDebug() << "(K3bDataTrackReader::WorkThread) error while writing to file " << m_imagePath + << " current sector: " << (currentSector.lba()-m_firstSector.lba()) << endl; + emitDebuggingOutput( "K3bDataTrackReader", + QString("Error while writing to file %1. Current sector is %2.") + .arg(m_imagePath).arg(currentSector.lba()-m_firstSector.lba()) ); + writeError = true; + break; + } + } + + currentSector += readSectors; + + int percent = 100 * (currentSector.lba() - m_firstSector.lba() + 1 ) / + (m_lastSector.lba() - m_firstSector.lba() + 1 ); + + if( percent > lastPercent ) { + lastPercent = percent; + emitPercent( percent ); + } + + unsigned long readMb = (currentSector.lba() - m_firstSector.lba() + 1) / 512; + if( readMb > lastReadMb ) { + lastReadMb = readMb; + emitProcessedSize( readMb, ( m_lastSector.lba() - m_firstSector.lba() + 1 ) / 512 ); + } + } + + if( m_errorSectorCount > 0 ) + emitInfoMessage( i18n("Ignored %n erroneous sector.", "Ignored a total of %n erroneous sectors.", m_errorSectorCount ), + K3bJob::ERROR ); + + // reset the error recovery mode + setErrorRecovery( m_device, m_oldErrorRecoveryMode ); + + m_device->block( false ); + k3bcore->unblockDevice( m_device ); + + // cleanup + if( m_useLibdvdcss ) + m_libcss->close(); + m_device->close(); + delete [] buffer; + + emitDebuggingOutput( "K3bDataTrackReader", + QString("Read a total of %1 sectors (%2 bytes)") + .arg(totalReadSectors.lba()) + .arg((Q_UINT64)totalReadSectors.lba()*(Q_UINT64)m_usedSectorSize) ); + + if( m_canceled ) + emitCanceled(); + + emitFinished( !m_canceled && !writeError && !readError ); +} + + +int K3bDataTrackReader::WorkThread::read( unsigned char* buffer, unsigned long sector, unsigned int len ) +{ + + // + // Encrypted DVD reading with libdvdcss + // + if( m_useLibdvdcss ) { + return m_libcss->readWrapped( reinterpret_cast<void*>(buffer), sector, len ); + } + + // + // Standard reading + // + else { + bool success = false; + // setErrorRecovery( m_device, m_ignoreReadErrors ? 0x21 : 0x20 ); + if( m_usedSectorSize == 2048 ) + success = m_device->read10( buffer, len*2048, sector, len ); + else + success = m_device->readCd( buffer, + len*m_usedSectorSize, + 0, // all sector types + false, // no dap + sector, + len, + false, // no sync + false, // no header + m_usedSectorSize != MODE1, // subheader + true, // user data + false, // no edc/ecc + 0, // no c2 error info... FIXME: should we check this?? + 0 // no subchannel data + ); + + if( success ) + return len; + else + return -1; + } +} + + +// here we read every single sector for itself to find the troubleing ones +bool K3bDataTrackReader::WorkThread::retryRead( unsigned char* buffer, unsigned long startSector, unsigned int len ) +{ + emitDebuggingOutput( "K3bDataTrackReader", QString( "Problem while reading. Retrying from sector %1.").arg(startSector) ); + emitInfoMessage( i18n("Problem while reading. Retrying from sector %1.").arg(startSector), K3bJob::WARNING ); + + int sectorsRead = -1; + bool success = true; + for( unsigned long sector = startSector; sector < startSector+len; ++sector ) { + int retry = m_retries; + while( !m_canceled && retry && (sectorsRead = read( &buffer[( sector - startSector ) * m_usedSectorSize], sector, 1 )) < 0 ) + --retry; + + success = ( sectorsRead > 0 ); + + if( m_canceled ) + return false; + + if( !success ) { + if( m_ignoreReadErrors ) { + emitInfoMessage( i18n("Ignoring read error in sector %1.").arg(sector), K3bJob::ERROR ); + emitDebuggingOutput( "K3bDataTrackReader", QString( "Ignoring read error in sector %1.").arg(sector) ); + + ++m_errorSectorCount; + // ::memset( &buffer[i], 0, 1 ); + success = true; + } + else { + emitInfoMessage( i18n("Error while reading sector %1.").arg(sector), K3bJob::ERROR ); + emitDebuggingOutput( "K3bDataTrackReader", QString( "Read error in sector %1.").arg(sector) ); + break; + } + } + } + + return success; +} + + +bool K3bDataTrackReader::WorkThread::setErrorRecovery( K3bDevice::Device* dev, int code ) +{ + unsigned char* data = 0; + unsigned int dataLen = 0; + if( !dev->modeSense( &data, dataLen, 0x01 ) ) + return false; + + // in MMC1 the page has 8 bytes (12 in MMC4 but we only need the first 3 anyway) + if( dataLen < 8+8 ) { + kdDebug() << "(K3bDataTrackReader) modepage 0x01 data too small: " << dataLen << endl; + delete [] data; + return false; + } + + m_oldErrorRecoveryMode = data[8+2]; + data[8+2] = code; + + if( m_oldErrorRecoveryMode != code ) + kdDebug() << "(K3bDataTrackReader) changing data recovery mode from " << m_oldErrorRecoveryMode << " to " << code << endl; + + bool success = dev->modeSelect( data, dataLen, true, false ); + + delete [] data; + + return success; +} + + +void K3bDataTrackReader::WorkThread::cancel() +{ + m_canceled = true; +} + + + + + +K3bDataTrackReader::~K3bDataTrackReader() +{ + delete m_thread; +} + + +void K3bDataTrackReader::setDevice( K3bDevice::Device* dev ) +{ + m_thread->m_device = dev; +} + + +void K3bDataTrackReader::setSectorRange( const K3b::Msf& start, const K3b::Msf& end ) +{ + m_thread->m_firstSector = start; + m_thread->m_lastSector = end; +} + + +void K3bDataTrackReader::setRetries( int r ) +{ + m_thread->m_retries = r; +} + + +void K3bDataTrackReader::setIgnoreErrors( bool b ) +{ + m_thread->m_ignoreReadErrors = b; +} + + +void K3bDataTrackReader::setNoCorrection( bool b ) +{ + m_thread->m_noCorrection = b; +} + + +void K3bDataTrackReader::writeToFd( int fd ) +{ + m_thread->m_fd = fd; +} + + +void K3bDataTrackReader::setImagePath( const QString& p ) +{ + m_thread->m_imagePath = p; + m_thread->m_fd = -1; +} + + +void K3bDataTrackReader::setSectorSize( SectorSize size ) +{ + m_thread->m_sectorSize = size; +} diff --git a/libk3b/jobs/k3bdatatrackreader.h b/libk3b/jobs/k3bdatatrackreader.h new file mode 100644 index 0000000..814c01c --- /dev/null +++ b/libk3b/jobs/k3bdatatrackreader.h @@ -0,0 +1,87 @@ +/* + * + * $Id: k3bdatatrackreader.h 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. + */ + +#ifndef _K3B_DATATRACK_READER_H_ +#define _K3B_DATATRACK_READER_H_ + + +#include <k3bthreadjob.h> +#include <k3bmsf.h> +#include <k3bglobals.h> + +namespace K3bDevice { + class Device; +} + + +/** + * This is a replacement for readcd. We need this since + * it is not possible to influence the sector size used + * by readcd and readcd is not very good to handle anyway. + * + * The sector size read is the following: + * @li Mode1: 2048 bytes (only user data) + * @li Mode2 Form1: 2056 bytes containing the subheader and the user data + * @li Mode2 Form2: 2332 bytes containing the subheader and the user data + * + * Formless Mode2 sectors will not be read. + */ +class K3bDataTrackReader : public K3bThreadJob +{ + public: + K3bDataTrackReader( K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bDataTrackReader(); + + enum SectorSize { + AUTO = 0, + MODE1 = K3b::SECTORSIZE_DATA_2048, + MODE2FORM1 = K3b::SECTORSIZE_DATA_2048_SUBHEADER, + MODE2FORM2 = K3b::SECTORSIZE_DATA_2324_SUBHEADER + }; + + void setSectorSize( SectorSize size ); + + void setDevice( K3bDevice::Device* ); + + /** + * @param start the first sector to be read + * @end the last sector to be read + */ + void setSectorRange( const K3b::Msf& start, const K3b::Msf& end ); + void setRetries( int ); + + /** + * If true unreadable sectors will be replaced by zero data to always + * maintain the track length. + */ + void setIgnoreErrors( bool b ); + + void setNoCorrection( bool b ); + + /** + * the data gets written directly into fd instead of the imagefile. + * Be aware that this only makes sense before starting the job. + * To disable just set fd to -1 + */ + void writeToFd( int fd ); + + void setImagePath( const QString& p ); + + private: + class WorkThread; + WorkThread* m_thread; +}; + +#endif diff --git a/libk3b/jobs/k3bdvdcopyjob.cpp b/libk3b/jobs/k3bdvdcopyjob.cpp new file mode 100644 index 0000000..96d727c --- /dev/null +++ b/libk3b/jobs/k3bdvdcopyjob.cpp @@ -0,0 +1,894 @@ +/* + * + * $Id: k3bdvdcopyjob.cpp 690529 2007-07-21 10:51:47Z 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 "k3bdvdcopyjob.h" +#include "k3blibdvdcss.h" + +#include <k3breadcdreader.h> +#include <k3bdatatrackreader.h> +#include <k3bexternalbinmanager.h> +#include <k3bdevice.h> +#include <k3bdeviceglobals.h> +#include <k3bdevicehandler.h> +#include <k3bdiskinfo.h> +#include <k3bglobals.h> +#include <k3bcore.h> +#include <k3bgrowisofswriter.h> +#include <k3bversion.h> +#include <k3biso9660.h> +#include <k3bfilesplitter.h> +#include <k3bchecksumpipe.h> +#include <k3bverificationjob.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kio/global.h> + +#include <qfile.h> +#include <qfileinfo.h> +#include <qapplication.h> + + +class K3bDvdCopyJob::Private +{ +public: + Private() + : doneCopies(0), + running(false), + canceled(false), + writerJob(0), + readcdReader(0), + dataTrackReader(0), + verificationJob(0), + usedWritingMode(0), + verifyData(false) { + outPipe.readFromIODevice( &imageFile ); + } + + int doneCopies; + + bool running; + bool readerRunning; + bool writerRunning; + bool canceled; + + K3bGrowisofsWriter* writerJob; + K3bReadcdReader* readcdReader; + K3bDataTrackReader* dataTrackReader; + K3bVerificationJob* verificationJob; + + K3bDevice::DiskInfo sourceDiskInfo; + + K3b::Msf lastSector; + + int usedWritingMode; + + K3bFileSplitter imageFile; + K3bChecksumPipe inPipe; + K3bActivePipe outPipe; + + bool verifyData; +}; + + +K3bDvdCopyJob::K3bDvdCopyJob( K3bJobHandler* hdl, QObject* parent, const char* name ) + : K3bBurnJob( hdl, parent, name ), + m_writerDevice(0), + m_readerDevice(0), + m_onTheFly(false), + m_removeImageFiles(false), + m_simulate(false), + m_speed(1), + m_copies(1), + m_onlyCreateImage(false), + m_ignoreReadErrors(false), + m_readRetries(128), + m_writingMode( K3b::WRITING_MODE_AUTO ) +{ + d = new Private(); +} + + +K3bDvdCopyJob::~K3bDvdCopyJob() +{ + delete d; +} + + +void K3bDvdCopyJob::start() +{ + jobStarted(); + emit burning(false); + + d->canceled = false; + d->running = true; + d->readerRunning = d->writerRunning = false; + + emit newTask( i18n("Checking Source Medium") ); + + if( m_onTheFly && + k3bcore->externalBinManager()->binObject( "growisofs" )->version < K3bVersion( 5, 12 ) ) { + m_onTheFly = false; + emit infoMessage( i18n("K3b does not support writing on-the-fly with growisofs %1.") + .arg(k3bcore->externalBinManager()->binObject( "growisofs" )->version), ERROR ); + emit infoMessage( i18n("Disabling on-the-fly writing."), INFO ); + } + + emit newSubTask( i18n("Waiting for source medium") ); + + // wait for a source disk + if( waitForMedia( m_readerDevice, + K3bDevice::STATE_COMPLETE|K3bDevice::STATE_INCOMPLETE, + K3bDevice::MEDIA_WRITABLE_DVD|K3bDevice::MEDIA_DVD_ROM ) < 0 ) { + emit canceled(); + d->running = false; + jobFinished( false ); + return; + } + + emit newSubTask( i18n("Checking source medium") ); + + connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::DISKINFO, m_readerDevice ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotDiskInfoReady(K3bDevice::DeviceHandler*)) ); +} + + +void K3bDvdCopyJob::slotDiskInfoReady( K3bDevice::DeviceHandler* dh ) +{ + if( d->canceled ) { + emit canceled(); + jobFinished(false); + d->running = false; + } + + d->sourceDiskInfo = dh->diskInfo(); + + if( dh->diskInfo().empty() || dh->diskInfo().diskState() == K3bDevice::STATE_NO_MEDIA ) { + emit infoMessage( i18n("No source medium found."), ERROR ); + jobFinished(false); + d->running = false; + } + else { + if( m_readerDevice->copyrightProtectionSystemType() == 1 ) { + emit infoMessage( i18n("Found encrypted DVD."), WARNING ); + // check for libdvdcss + bool haveLibdvdcss = false; + kdDebug() << "(K3bDvdCopyJob) trying to open libdvdcss." << endl; + if( K3bLibDvdCss* libcss = K3bLibDvdCss::create() ) { + kdDebug() << "(K3bDvdCopyJob) succeeded." << endl; + kdDebug() << "(K3bDvdCopyJob) dvdcss_open(" << m_readerDevice->blockDeviceName() << ") = " + << libcss->open(m_readerDevice) << endl; + haveLibdvdcss = true; + + delete libcss; + } + else + kdDebug() << "(K3bDvdCopyJob) failed." << endl; + + if( !haveLibdvdcss ) { + emit infoMessage( i18n("Cannot copy encrypted DVDs."), ERROR ); + d->running = false; + jobFinished( false ); + return; + } + } + + + // + // We cannot rely on the kernel to determine the size of the DVD for some reason + // On the other hand it is not always a good idea to rely on the size from the ISO9660 + // header since that may be wrong due to some buggy encoder or some boot code appended + // after creating the image. + // That is why we try our best to determine the size of the DVD. For DVD-ROM this is very + // easy since it has only one track. The same goes for single session DVD-R(W) and DVD+R. + // Multisession DVDs we will simply not copy. ;) + // For DVD+RW and DVD-RW in restricted overwrite mode we are left with no other choice but + // to use the ISO9660 header. + // + // On the other hand: in on-the-fly mode growisofs determines the size of the data to be written + // by looking at the ISO9660 header when writing in DAO mode. So in this case + // it would be best for us to do the same.... + // + // With growisofs 5.15 we have the option to specify the size of the image to be written in DAO mode. + // + + switch( dh->diskInfo().mediaType() ) { + case K3bDevice::MEDIA_DVD_ROM: + case K3bDevice::MEDIA_DVD_PLUS_R_DL: + case K3bDevice::MEDIA_DVD_R_DL: + case K3bDevice::MEDIA_DVD_R_DL_SEQ: + case K3bDevice::MEDIA_DVD_R_DL_JUMP: + if( !m_onlyCreateImage ) { + if( dh->diskInfo().numLayers() > 1 && + dh->diskInfo().size().mode1Bytes() > 4700372992LL ) { + if( !(m_writerDevice->type() & (K3bDevice::DEVICE_DVD_R_DL|K3bDevice::DEVICE_DVD_PLUS_R_DL)) ) { + emit infoMessage( i18n("The writer does not support writing Double Layer DVD."), ERROR ); + d->running = false; + jobFinished(false); + return; + } + // FIXME: check for growisofs 5.22 (or whatever version is needed) for DVD-R DL + else if( k3bcore->externalBinManager()->binObject( "growisofs" ) && + k3bcore->externalBinManager()->binObject( "growisofs" )->version < K3bVersion( 5, 20 ) ) { + emit infoMessage( i18n("Growisofs >= 5.20 is needed to write Double Layer DVD+R."), ERROR ); + d->running = false; + jobFinished(false); + return; + } + } + } + case K3bDevice::MEDIA_DVD_R: + case K3bDevice::MEDIA_DVD_R_SEQ: + case K3bDevice::MEDIA_DVD_RW: + case K3bDevice::MEDIA_DVD_RW_SEQ: + case K3bDevice::MEDIA_DVD_PLUS_R: + + if( dh->diskInfo().numSessions() > 1 ) { + emit infoMessage( i18n("K3b does not support copying multi-session DVDs."), ERROR ); + d->running = false; + jobFinished(false); + return; + } + + // growisofs only uses the size from the PVD for reserving + // writable space in DAO mode + // with version >= 5.15 growisofs supports specifying the size of the track + if( m_writingMode != K3b::DAO || !m_onTheFly || m_onlyCreateImage || + ( k3bcore->externalBinManager()->binObject( "growisofs" ) && + k3bcore->externalBinManager()->binObject( "growisofs" )->version >= K3bVersion( 5, 15, -1 ) ) ) { + d->lastSector = dh->toc().lastSector(); + break; + } + + // fallthrough + + case K3bDevice::MEDIA_DVD_PLUS_RW: + case K3bDevice::MEDIA_DVD_RW_OVWR: + { + emit infoMessage( i18n("K3b relies on the size saved in the ISO9660 header."), WARNING ); + emit infoMessage( i18n("This might result in a corrupt copy if the source was mastered with buggy software."), WARNING ); + + K3bIso9660 isoF( m_readerDevice, 0 ); + if( isoF.open() ) { + d->lastSector = ((long long)isoF.primaryDescriptor().logicalBlockSize*isoF.primaryDescriptor().volumeSpaceSize)/2048LL - 1; + } + else { + emit infoMessage( i18n("Unable to determine the ISO9660 filesystem size."), ERROR ); + jobFinished(false); + d->running = false; + return; + } + } + break; + + case K3bDevice::MEDIA_DVD_RAM: + emit infoMessage( i18n("K3b does not support copying DVD-RAM."), ERROR ); + jobFinished(false); + d->running = false; + return; + + default: + emit infoMessage( i18n("Unable to determine DVD media type."), ERROR ); + jobFinished(false); + d->running = false; + return; + } + + + if( !m_onTheFly ) { + // + // Check the image path + // + QFileInfo fi( m_imagePath ); + if( !fi.isFile() || + questionYesNo( i18n("Do you want to overwrite %1?").arg(m_imagePath), + i18n("File Exists") ) ) { + if( fi.isDir() ) + m_imagePath = K3b::findTempFile( "iso", m_imagePath ); + else if( !QFileInfo( m_imagePath.section( '/', 0, -2 ) ).isDir() ) { + emit infoMessage( i18n("Specified an unusable temporary path. Using default."), WARNING ); + m_imagePath = K3b::findTempFile( "iso" ); + } + // else the user specified a file in an existing dir + + emit infoMessage( i18n("Writing image file to %1.").arg(m_imagePath), INFO ); + emit newSubTask( i18n("Reading source medium.") ); + } + + // + // check free temp space + // + KIO::filesize_t imageSpaceNeeded = (KIO::filesize_t)(d->lastSector.lba()+1)*2048; + unsigned long avail, size; + QString pathToTest = m_imagePath.left( m_imagePath.findRev( '/' ) ); + if( !K3b::kbFreeOnFs( pathToTest, size, avail ) ) { + emit infoMessage( i18n("Unable to determine free space in temporary directory '%1'.").arg(pathToTest), ERROR ); + jobFinished(false); + d->running = false; + return; + } + else { + if( avail < imageSpaceNeeded/1024 ) { + emit infoMessage( i18n("Not enough space left in temporary directory."), ERROR ); + jobFinished(false); + d->running = false; + return; + } + } + + d->imageFile.setName( m_imagePath ); + if( !d->imageFile.open( IO_WriteOnly ) ) { + emit infoMessage( i18n("Unable to open '%1' for writing.").arg(m_imagePath), ERROR ); + jobFinished( false ); + d->running = false; + return; + } + } + + if( K3b::isMounted( m_readerDevice ) ) { + emit infoMessage( i18n("Unmounting source medium"), INFO ); + K3b::unmount( m_readerDevice ); + } + + if( m_onlyCreateImage || !m_onTheFly ) { + emit newTask( i18n("Creating DVD image") ); + } + else if( m_onTheFly && !m_onlyCreateImage ) { + if( waitForDvd() ) { + prepareWriter(); + if( m_simulate ) + emit newTask( i18n("Simulating DVD copy") ); + else if( m_copies > 1 ) + emit newTask( i18n("Writing DVD copy %1").arg(d->doneCopies+1) ); + else + emit newTask( i18n("Writing DVD copy") ); + + emit burning(true); + d->writerRunning = true; + d->writerJob->start(); + } + else { + if( d->canceled ) + emit canceled(); + jobFinished(false); + d->running = false; + return; + } + } + + prepareReader(); + d->readerRunning = true; + d->dataTrackReader->start(); + } +} + + +void K3bDvdCopyJob::cancel() +{ + if( d->running ) { + d->canceled = true; + if( d->readerRunning ) + d->dataTrackReader->cancel(); + if( d->writerRunning ) + d->writerJob->cancel(); + d->inPipe.close(); + d->outPipe.close(); + d->imageFile.close(); + } + else { + kdDebug() << "(K3bDvdCopyJob) not running." << endl; + } +} + + +void K3bDvdCopyJob::prepareReader() +{ + if( !d->dataTrackReader ) { + d->dataTrackReader = new K3bDataTrackReader( this ); + connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) ); + connect( d->dataTrackReader, SIGNAL(processedSize(int, int)), this, SLOT(slotReaderProcessedSize(int, int)) ); + connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotReaderFinished(bool)) ); + connect( d->dataTrackReader, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( d->dataTrackReader, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( d->dataTrackReader, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + } + + d->dataTrackReader->setDevice( m_readerDevice ); + d->dataTrackReader->setIgnoreErrors( m_ignoreReadErrors ); + d->dataTrackReader->setRetries( m_readRetries ); + d->dataTrackReader->setSectorRange( 0, d->lastSector ); + + if( m_onTheFly && !m_onlyCreateImage ) + d->inPipe.writeToFd( d->writerJob->fd(), true ); + else + d->inPipe.writeToIODevice( &d->imageFile ); + + d->inPipe.open( true ); + d->dataTrackReader->writeToFd( d->inPipe.in() ); +} + + +// ALWAYS CALL WAITFORDVD BEFORE PREPAREWRITER! +void K3bDvdCopyJob::prepareWriter() +{ + delete d->writerJob; + + d->writerJob = new K3bGrowisofsWriter( m_writerDevice, this ); + + connect( d->writerJob, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( d->writerJob, SIGNAL(percent(int)), this, SLOT(slotWriterProgress(int)) ); + connect( d->writerJob, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); + connect( d->writerJob, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); + connect( d->writerJob, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); + connect( d->writerJob, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); + connect( d->writerJob, SIGNAL(writeSpeed(int, int)), this, SIGNAL(writeSpeed(int, int)) ); + connect( d->writerJob, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) ); + // connect( d->writerJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); + connect( d->writerJob, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( d->writerJob, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + + // these do only make sense with DVD-R(W) + d->writerJob->setSimulate( m_simulate ); + d->writerJob->setBurnSpeed( m_speed ); + d->writerJob->setWritingMode( d->usedWritingMode ); + d->writerJob->setCloseDvd( true ); + + // + // In case the first layer size is not known let the + // split be determined by growisofs + // + if( d->sourceDiskInfo.numLayers() > 1 && + d->sourceDiskInfo.firstLayerSize() > 0 ) { + d->writerJob->setLayerBreak( d->sourceDiskInfo.firstLayerSize().lba() ); + } + else { + // this is only used in DAO mode with growisofs >= 5.15 + d->writerJob->setTrackSize( d->lastSector.lba()+1 ); + } + + d->writerJob->setImageToWrite( QString::null ); // write to stdin +} + + +void K3bDvdCopyJob::slotReaderProgress( int p ) +{ + if( !m_onTheFly || m_onlyCreateImage ) { + emit subPercent( p ); + + int bigParts = ( m_onlyCreateImage ? 1 : (m_simulate ? 2 : ( d->verifyData ? m_copies*2 : m_copies ) + 1 ) ); + emit percent( p/bigParts ); + } +} + + +void K3bDvdCopyJob::slotReaderProcessedSize( int p, int c ) +{ + if( !m_onTheFly || m_onlyCreateImage ) + emit processedSubSize( p, c ); + + if( m_onlyCreateImage ) + emit processedSize( p, c ); +} + + +void K3bDvdCopyJob::slotWriterProgress( int p ) +{ + int bigParts = ( m_simulate ? 1 : ( d->verifyData ? m_copies*2 : m_copies ) ) + ( m_onTheFly ? 0 : 1 ); + int doneParts = ( m_simulate ? 0 : ( d->verifyData ? d->doneCopies*2 : d->doneCopies ) ) + ( m_onTheFly ? 0 : 1 ); + emit percent( 100*doneParts/bigParts + p/bigParts ); + + emit subPercent( p ); +} + + +void K3bDvdCopyJob::slotVerificationProgress( int p ) +{ + int bigParts = ( m_simulate ? 1 : ( d->verifyData ? m_copies*2 : m_copies ) ) + ( m_onTheFly ? 0 : 1 ); + int doneParts = ( m_simulate ? 0 : ( d->verifyData ? d->doneCopies*2 : d->doneCopies ) ) + ( m_onTheFly ? 0 : 1 ) + 1; + emit percent( 100*doneParts/bigParts + p/bigParts ); +} + + +void K3bDvdCopyJob::slotReaderFinished( bool success ) +{ + d->readerRunning = false; + + d->inPipe.close(); + + // close the socket + // otherwise growisofs will never quit. + // FIXME: is it posiible to do this in a generic manner? + if( d->writerJob ) + d->writerJob->closeFd(); + + // already finished? + if( !d->running ) + return; + + if( d->canceled ) { + removeImageFiles(); + emit canceled(); + jobFinished(false); + d->running = false; + } + + if( success ) { + emit infoMessage( i18n("Successfully read source DVD."), SUCCESS ); + if( m_onlyCreateImage ) { + jobFinished(true); + d->running = false; + } + else { + if( m_writerDevice == m_readerDevice ) { + // eject the media (we do this blocking to know if it worked + // because if it did not it might happen that k3b overwrites a CD-RW + // source) + if( !m_readerDevice->eject() ) { + blockingInformation( i18n("K3b was unable to eject the source disk. Please do so manually.") ); + } + } + + if( !m_onTheFly ) { + if( waitForDvd() ) { + prepareWriter(); + if( m_copies > 1 ) + emit newTask( i18n("Writing DVD copy %1").arg(d->doneCopies+1) ); + else + emit newTask( i18n("Writing DVD copy") ); + + emit burning(true); + + d->writerRunning = true; + d->writerJob->start(); + d->outPipe.writeToFd( d->writerJob->fd(), true ); + d->outPipe.open( true ); + } + else { + if( m_removeImageFiles ) + removeImageFiles(); + if( d->canceled ) + emit canceled(); + jobFinished(false); + d->running = false; + } + } + } + } + else { + removeImageFiles(); + jobFinished(false); + d->running = false; + } +} + + +void K3bDvdCopyJob::slotWriterFinished( bool success ) +{ + d->writerRunning = false; + + d->outPipe.close(); + + // already finished? + if( !d->running ) + return; + + if( d->canceled ) { + if( m_removeImageFiles ) + removeImageFiles(); + emit canceled(); + jobFinished(false); + d->running = false; + } + + if( success ) { + emit infoMessage( i18n("Successfully written DVD copy %1.").arg(d->doneCopies+1), INFO ); + + if( d->verifyData && !m_simulate ) { + if( !d->verificationJob ) { + d->verificationJob = new K3bVerificationJob( this, this ); + connect( d->verificationJob, SIGNAL(infoMessage(const QString&, int)), + this, SIGNAL(infoMessage(const QString&, int)) ); + connect( d->verificationJob, SIGNAL(newTask(const QString&)), + this, SIGNAL(newSubTask(const QString&)) ); + connect( d->verificationJob, SIGNAL(percent(int)), + this, SLOT(slotVerificationProgress(int)) ); + connect( d->verificationJob, SIGNAL(percent(int)), + this, SIGNAL(subPercent(int)) ); + connect( d->verificationJob, SIGNAL(finished(bool)), + this, SLOT(slotVerificationFinished(bool)) ); + connect( d->verificationJob, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + + } + d->verificationJob->setDevice( m_writerDevice ); + d->verificationJob->addTrack( 1, d->inPipe.checksum(), d->lastSector+1 ); + + if( m_copies > 1 ) + emit newTask( i18n("Verifying DVD copy %1").arg(d->doneCopies+1) ); + else + emit newTask( i18n("Verifying DVD copy") ); + + emit burning( false ); + + d->verificationJob->start(); + } + + else if( ++d->doneCopies < m_copies ) { + + if ( !m_writerDevice->eject() ) { + blockingInformation( i18n("K3b was unable to eject the written disk. Please do so manually.") ); + } + + if( waitForDvd() ) { + prepareWriter(); + emit newTask( i18n("Writing DVD copy %1").arg(d->doneCopies+1) ); + + emit burning(true); + + d->writerRunning = true; + d->writerJob->start(); + } + else { + if( d->canceled ) + emit canceled(); + jobFinished(false); + d->running = false; + return; + } + + if( m_onTheFly ) { + prepareReader(); + d->readerRunning = true; + d->dataTrackReader->start(); + } + else { + d->outPipe.writeToFd( d->writerJob->fd(), true ); + d->outPipe.open( true ); + } + } + else { + if( m_removeImageFiles ) + removeImageFiles(); + d->running = false; + jobFinished(true); + } + } + else { + if( m_removeImageFiles ) + removeImageFiles(); + d->running = false; + jobFinished(false); + } +} + + +void K3bDvdCopyJob::slotVerificationFinished( bool success ) +{ + // we simply ignore the results from the verification, the verification + // job already emits a message + if( ++d->doneCopies < m_copies ) { + + if( waitForDvd() ) { + prepareWriter(); + emit newTask( i18n("Writing DVD copy %1").arg(d->doneCopies+1) ); + + emit burning(true); + + d->writerRunning = true; + d->writerJob->start(); + } + else { + if( d->canceled ) + emit canceled(); + jobFinished(false); + d->running = false; + return; + } + + if( m_onTheFly ) { + prepareReader(); + d->readerRunning = true; + d->dataTrackReader->start(); + } + else { + d->outPipe.writeToFd( d->writerJob->fd(), true ); + d->outPipe.open( true ); + } + } + else { + if( m_removeImageFiles ) + removeImageFiles(); + d->running = false; + jobFinished( success ); + } +} + + +// this is basically the same code as in K3bDvdJob... :( +// perhaps this should be moved to some K3bGrowisofsHandler which also parses the growisofs output? +bool K3bDvdCopyJob::waitForDvd() +{ + int mt = 0; + if( m_writingMode == K3b::WRITING_MODE_RES_OVWR ) // we treat DVD+R(W) as restricted overwrite media + mt = K3bDevice::MEDIA_DVD_RW_OVWR|K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_PLUS_R; + else + mt = K3bDevice::MEDIA_WRITABLE_DVD_SL; + + // + // in case the source is a double layer DVD we made sure above that the writer + // is capable of writing DVD+R-DL or DVD-R DL and here we wait for a DL DVD + // + if( d->sourceDiskInfo.numLayers() > 1 && + d->sourceDiskInfo.size().mode1Bytes() > 4700372992LL ) { + mt = K3bDevice::MEDIA_WRITABLE_DVD_DL; + } + + int m = waitForMedia( m_writerDevice, K3bDevice::STATE_EMPTY, mt ); + + if( m < 0 ) { + cancel(); + return false; + } + + if( m == 0 ) { + emit infoMessage( i18n("Forced by user. Growisofs will be called without further tests."), INFO ); + } + + else { + // ------------------------------- + // DVD Plus + // ------------------------------- + if( m & K3bDevice::MEDIA_DVD_PLUS_ALL ) { + + d->usedWritingMode = K3b::WRITING_MODE_RES_OVWR; + + if( m_simulate ) { + if( !questionYesNo( i18n("K3b does not support simulation with DVD+R(W) media. " + "Do you really want to continue? The media will actually be " + "written to."), + i18n("No Simulation with DVD+R(W)") ) ) { + cancel(); + return false; + } + +// m_simulate = false; + emit newTask( i18n("Writing DVD copy") ); + } + + if( m_writingMode != K3b::WRITING_MODE_AUTO && m_writingMode != K3b::WRITING_MODE_RES_OVWR ) + emit infoMessage( i18n("Writing mode ignored when writing DVD+R(W) media."), INFO ); + + if( m & K3bDevice::MEDIA_DVD_PLUS_RW ) + emit infoMessage( i18n("Writing DVD+RW."), INFO ); + else if( m & K3bDevice::MEDIA_DVD_PLUS_R_DL ) + emit infoMessage( i18n("Writing Double Layer DVD+R."), INFO ); + else + emit infoMessage( i18n("Writing DVD+R."), INFO ); + } + + // ------------------------------- + // DVD Minus + // ------------------------------- + else { + if( m_simulate && !m_writerDevice->dvdMinusTestwrite() ) { + if( !questionYesNo( i18n("Your writer (%1 %2) does not support simulation with DVD-R(W) media. " + "Do you really want to continue? The media will be written " + "for real.") + .arg(m_writerDevice->vendor()) + .arg(m_writerDevice->description()), + i18n("No Simulation with DVD-R(W)") ) ) { + cancel(); + return false; + } + +// m_simulate = false; + } + + // + // We do not default to DAO in onthefly mode since otherwise growisofs would + // use the size from the PVD to reserve space on the DVD and that can be bad + // if this size is wrong + // With growisofs 5.15 we have the option to specify the size of the image to be written in DAO mode. + // +// bool sizeWithDao = ( k3bcore->externalBinManager()->binObject( "growisofs" ) && +// k3bcore->externalBinManager()->binObject( "growisofs" )->version >= K3bVersion( 5, 15, -1 ) ); + + + // TODO: check for feature 0x21 + + if( m & K3bDevice::MEDIA_DVD_RW_OVWR ) { + emit infoMessage( i18n("Writing DVD-RW in restricted overwrite mode."), INFO ); + d->usedWritingMode = K3b::WRITING_MODE_RES_OVWR; + } + else if( m & (K3bDevice::MEDIA_DVD_RW_SEQ| + K3bDevice::MEDIA_DVD_RW) ) { + if( m_writingMode == K3b::DAO ) { +// ( m_writingMode == K3b::WRITING_MODE_AUTO && +// ( sizeWithDao || !m_onTheFly ) ) ) { + emit infoMessage( i18n("Writing DVD-RW in DAO mode."), INFO ); + d->usedWritingMode = K3b::DAO; + } + else { + emit infoMessage( i18n("Writing DVD-RW in incremental mode."), INFO ); + d->usedWritingMode = K3b::WRITING_MODE_INCR_SEQ; + } + } + else { + + // FIXME: DVD-R DL jump and stuff + + if( m_writingMode == K3b::WRITING_MODE_RES_OVWR ) + emit infoMessage( i18n("Restricted Overwrite is not possible with DVD-R media."), INFO ); + + if( m_writingMode == K3b::DAO ) { +// ( m_writingMode == K3b::WRITING_MODE_AUTO && +// ( sizeWithDao || !m_onTheFly ) ) ) { + emit infoMessage( i18n("Writing %1 in DAO mode.").arg( K3bDevice::mediaTypeString(m, true) ), INFO ); + d->usedWritingMode = K3b::DAO; + } + else { + emit infoMessage( i18n("Writing %1 in incremental mode.").arg( K3bDevice::mediaTypeString(m, true) ), INFO ); + d->usedWritingMode = K3b::WRITING_MODE_INCR_SEQ; + } + } + } + } + + return true; +} + + + +void K3bDvdCopyJob::removeImageFiles() +{ + if( QFile::exists( m_imagePath ) ) { + d->imageFile.remove(); + emit infoMessage( i18n("Removed image file %1").arg(m_imagePath), K3bJob::SUCCESS ); + } +} + + +QString K3bDvdCopyJob::jobDescription() const +{ + if( m_onlyCreateImage ) { + return i18n("Creating DVD Image"); + } + else { + if( m_onTheFly ) + return i18n("Copying DVD On-The-Fly"); + else + return i18n("Copying DVD"); + } +} + + +QString K3bDvdCopyJob::jobDetails() const +{ + return i18n("Creating 1 copy", + "Creating %n copies", + (m_simulate||m_onlyCreateImage) ? 1 : m_copies ); +} + + +void K3bDvdCopyJob::setVerifyData( bool b ) +{ + d->verifyData = b; +} + +#include "k3bdvdcopyjob.moc" diff --git a/libk3b/jobs/k3bdvdcopyjob.h b/libk3b/jobs/k3bdvdcopyjob.h new file mode 100644 index 0000000..91da4e9 --- /dev/null +++ b/libk3b/jobs/k3bdvdcopyjob.h @@ -0,0 +1,99 @@ +/* + * + * $Id: k3bdvdcopyjob.h 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. + */ + +#ifndef _K3B_DVD_COPY_JOB_H_ +#define _K3B_DVD_COPY_JOB_H_ + +#include <k3bjob.h> +#include "k3b_export.h" +#include <qstring.h> + + +namespace K3bDevice { + class Device; + class DeviceHandler; +} + + +class LIBK3B_EXPORT K3bDvdCopyJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bDvdCopyJob( K3bJobHandler* hdl, QObject* parent = 0, const char* name = 0 ); + ~K3bDvdCopyJob(); + + K3bDevice::Device* writer() const { return m_onlyCreateImage ? 0 : m_writerDevice; } + K3bDevice::Device* readingDevice() const { return m_readerDevice; } + + QString jobDescription() const; + QString jobDetails() const; + + public slots: + void start(); + void cancel(); + + void setWriterDevice( K3bDevice::Device* w ) { m_writerDevice = w; } + void setReaderDevice( K3bDevice::Device* w ) { m_readerDevice = w; } + void setImagePath( const QString& p ) { m_imagePath = p; } + void setRemoveImageFiles( bool b ) { m_removeImageFiles = b; } + void setOnlyCreateImage( bool b ) { m_onlyCreateImage = b; } + void setSimulate( bool b ) { m_simulate = b; } + void setOnTheFly( bool b ) { m_onTheFly = b; } + void setWriteSpeed( int s ) { m_speed = s; } + void setCopies( int c ) { m_copies = c; } + void setWritingMode( int w ) { m_writingMode = w; } + void setIgnoreReadErrors( bool b ) { m_ignoreReadErrors = b; } + void setReadRetries( int i ) { m_readRetries = i; } + void setVerifyData( bool b ); + + private slots: + void slotDiskInfoReady( K3bDevice::DeviceHandler* ); + void slotReaderProgress( int ); + void slotReaderProcessedSize( int, int ); + void slotWriterProgress( int ); + void slotReaderFinished( bool ); + void slotWriterFinished( bool ); + void slotVerificationFinished( bool ); + void slotVerificationProgress( int p ); + + private: + bool waitForDvd(); + void prepareReader(); + void prepareWriter(); + void removeImageFiles(); + + K3bDevice::Device* m_writerDevice; + K3bDevice::Device* m_readerDevice; + QString m_imagePath; + + bool m_onTheFly; + bool m_removeImageFiles; + + bool m_simulate; + int m_speed; + int m_copies; + bool m_onlyCreateImage; + bool m_ignoreReadErrors; + int m_readRetries; + + int m_writingMode; + + class Private; + Private* d; +}; + + +#endif diff --git a/libk3b/jobs/k3bdvdformattingjob.cpp b/libk3b/jobs/k3bdvdformattingjob.cpp new file mode 100644 index 0000000..732e404 --- /dev/null +++ b/libk3b/jobs/k3bdvdformattingjob.cpp @@ -0,0 +1,536 @@ +/* + * + * $Id: k3bdvdformattingjob.cpp 696897 2007-08-06 07:14:14Z 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 "k3bdvdformattingjob.h" + +#include <k3bglobals.h> +#include <k3bprocess.h> +#include <k3bdevice.h> +#include <k3bdeviceglobals.h> +#include <k3bdevicehandler.h> +#include <k3bdiskinfo.h> +#include <k3bexternalbinmanager.h> +#include <k3bcore.h> +#include <k3bversion.h> +#include <k3bglobalsettings.h> + +#include <klocale.h> +#include <kdebug.h> + +#include <qvaluelist.h> +#include <qregexp.h> + +#include <errno.h> +#include <string.h> + + +class K3bDvdFormattingJob::Private +{ +public: + Private() + : quick(false), + force(false), + mode(K3b::WRITING_MODE_AUTO), + device(0), + process(0), + dvdFormatBin(0), + lastProgressValue(0), + running(false), + forceNoEject(false) { + } + + bool quick; + bool force; + int mode; + + K3bDevice::Device* device; + K3bProcess* process; + const K3bExternalBin* dvdFormatBin; + + int lastProgressValue; + + bool success; + bool canceled; + bool running; + + bool forceNoEject; + + bool error; +}; + + +K3bDvdFormattingJob::K3bDvdFormattingJob( K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bBurnJob( jh, parent, name ) +{ + d = new Private; +} + + +K3bDvdFormattingJob::~K3bDvdFormattingJob() +{ + delete d->process; + delete d; +} + + +K3bDevice::Device* K3bDvdFormattingJob::writer() const +{ + return d->device; +} + + +void K3bDvdFormattingJob::setForceNoEject( bool b ) +{ + d->forceNoEject = b; +} + + +QString K3bDvdFormattingJob::jobDescription() const +{ + return i18n("Formatting DVD"); // Formatting DVD±RW +} + + +QString K3bDvdFormattingJob::jobDetails() const +{ + if( d->quick ) + return i18n("Quick Format"); + else + return QString::null; +} + + +void K3bDvdFormattingJob::start() +{ + d->canceled = false; + d->running = true; + d->error = false; + + jobStarted(); + + if( !d->device ) { + emit infoMessage( i18n("No device set"), ERROR ); + d->running = false; + jobFinished(false); + return; + } + + // FIXME: check the return value + if( K3b::isMounted( d->device ) ) { + emit infoMessage( i18n("Unmounting medium"), INFO ); + K3b::unmount( d->device ); + } + + // + // first wait for a dvd+rw or dvd-rw + // Be aware that an empty DVD-RW might be reformatted to another writing mode + // so we also wait for empty dvds + // + if( waitForMedia( d->device, + K3bDevice::STATE_COMPLETE|K3bDevice::STATE_INCOMPLETE|K3bDevice::STATE_EMPTY, + K3bDevice::MEDIA_WRITABLE_DVD, + i18n("Please insert a rewritable DVD medium into drive<p><b>%1 %2 (%3)</b>.") + .arg(d->device->vendor()).arg(d->device->description()).arg(d->device->devicename()) ) == -1 ) { + emit canceled(); + d->running = false; + jobFinished(false); + return; + } + + emit infoMessage( i18n("Checking media..."), INFO ); + emit newTask( i18n("Checking media") ); + + connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::NG_DISKINFO, d->device ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotDeviceHandlerFinished(K3bDevice::DeviceHandler*)) ); +} + + +void K3bDvdFormattingJob::start( const K3bDevice::DiskInfo& di ) +{ + d->canceled = false; + d->running = true; + + jobStarted(); + + startFormatting( di ); +} + + +void K3bDvdFormattingJob::cancel() +{ + if( d->running ) { + d->canceled = true; + if( d->process ) + d->process->kill(); + } + else { + kdDebug() << "(K3bDvdFormattingJob) not running." << endl; + } +} + + +void K3bDvdFormattingJob::setDevice( K3bDevice::Device* dev ) +{ + d->device = dev; +} + + +void K3bDvdFormattingJob::setMode( int m ) +{ + d->mode = m; +} + + +void K3bDvdFormattingJob::setQuickFormat( bool b ) +{ + d->quick = b; +} + + +void K3bDvdFormattingJob::setForce( bool b ) +{ + d->force = b; +} + + +void K3bDvdFormattingJob::slotStderrLine( const QString& line ) +{ +// * DVD±RW format utility by <appro@fy.chalmers.se>, version 4.4. +// * 4.7GB DVD-RW media in Sequential mode detected. +// * blanking 100.0| + +// * formatting 100.0| + + emit debuggingOutput( "dvd+rw-format", line ); + + // parsing for the -gui mode (since dvd+rw-format 4.6) + int pos = line.find( "blanking" ); + if( pos < 0 ) + pos = line.find( "formatting" ); + if( pos >= 0 ) { + pos = line.find( QRegExp( "\\d" ), pos ); + } + // parsing for \b\b... stuff + else if( !line.startsWith("*") ) { + pos = line.find( QRegExp( "\\d" ) ); + } + else if( line.startsWith( ":-(" ) ) { + if( line.startsWith( ":-( unable to proceed with format" ) ) { + d->error = true; + } + } + + if( pos >= 0 ) { + int endPos = line.find( QRegExp("[^\\d\\.]"), pos ) - 1; + bool ok; + int progress = (int)(line.mid( pos, endPos - pos ).toDouble(&ok)); + if( ok ) { + d->lastProgressValue = progress; + emit percent( progress ); + } + else { + kdDebug() << "(K3bDvdFormattingJob) parsing error: '" << line.mid( pos, endPos - pos ) << "'" << endl; + } + } +} + + +void K3bDvdFormattingJob::slotProcessFinished( KProcess* p ) +{ + if( d->canceled ) { + emit canceled(); + d->success = false; + } + else if( p->normalExit() ) { + if( !d->error && p->exitStatus() == 0 ) { + emit infoMessage( i18n("Formatting successfully completed"), K3bJob::SUCCESS ); + + if( d->lastProgressValue < 100 ) { + emit infoMessage( i18n("Do not be concerned with the progress stopping before 100%."), INFO ); + emit infoMessage( i18n("The formatting will continue in the background while writing."), INFO ); + } + + d->success = true; + } + else { + emit infoMessage( i18n("%1 returned an unknown error (code %2).").arg(d->dvdFormatBin->name()).arg(p->exitStatus()), + K3bJob::ERROR ); + emit infoMessage( i18n("Please send me an email with the last output."), K3bJob::ERROR ); + + d->success = false; + } + } + else { + emit infoMessage( i18n("%1 did not exit cleanly.").arg(d->dvdFormatBin->name()), + ERROR ); + d->success = false; + } + + if( d->forceNoEject || + !k3bcore->globalSettings()->ejectMedia() ) { + d->running = false; + jobFinished(d->success); + } + else { + emit infoMessage( i18n("Ejecting DVD..."), INFO ); + connect( K3bDevice::eject( d->device ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotEjectingFinished(K3bDevice::DeviceHandler*)) ); + } +} + + +void K3bDvdFormattingJob::slotEjectingFinished( K3bDevice::DeviceHandler* dh ) +{ + if( !dh->success() ) + emit infoMessage( i18n("Unable to eject media."), ERROR ); + + d->running = false; + jobFinished(d->success); +} + + +void K3bDvdFormattingJob::slotDeviceHandlerFinished( K3bDevice::DeviceHandler* dh ) +{ + if( d->canceled ) { + emit canceled(); + jobFinished(false); + d->running = false; + } + + if( dh->success() ) { + startFormatting( dh->diskInfo() ); + } + else { + emit infoMessage( i18n("Unable to determine media state."), ERROR ); + d->running = false; + jobFinished(false); + } +} + + +void K3bDvdFormattingJob::startFormatting( const K3bDevice::DiskInfo& diskInfo ) +{ + // + // Now check the media type: + // if DVD-RW: use d->mode + // emit warning if formatting is full and stuff + // + // in overwrite mode: emit info that progress might stop before 100% since formatting will continue + // in the background once the media gets rewritten (only DVD+RW?) + // + + // emit info about what kind of media has been found + + if( !(diskInfo.mediaType() & (K3bDevice::MEDIA_DVD_RW| + K3bDevice::MEDIA_DVD_RW_SEQ| + K3bDevice::MEDIA_DVD_RW_OVWR| + K3bDevice::MEDIA_DVD_PLUS_RW)) ) { + emit infoMessage( i18n("No rewritable DVD media found. Unable to format."), ERROR ); + d->running = false; + jobFinished(false); + return; + } + + + bool format = true; // do we need to format + bool blank = false; // blank is for DVD-RW sequential incremental + // DVD-RW restricted overwrite and DVD+RW uses the force option (or no option at all) + + + + // + // DVD+RW is quite easy to handle. There is only one possible mode and it is always recommended to not + // format it more than once but to overwrite it once it is formatted + // Once the initial formatting has been done it's always "appendable" (or "complete"???) + // + + + if( diskInfo.mediaType() == K3bDevice::MEDIA_DVD_PLUS_RW ) { + emit infoMessage( i18n("Found %1 media.").arg(K3bDevice::mediaTypeString(K3bDevice::MEDIA_DVD_PLUS_RW)), + INFO ); + + // mode is ignored + + if( diskInfo.empty() ) { + // + // The DVD+RW is blank and needs to be initially formatted + // + blank = false; + } + else { + emit infoMessage( i18n("No need to format %1 media more than once.") + .arg(K3bDevice::mediaTypeString(K3bDevice::MEDIA_DVD_PLUS_RW)), INFO ); + emit infoMessage( i18n("It may simply be overwritten."), INFO ); + + if( d->force ) { + emit infoMessage( i18n("Forcing formatting anyway."), INFO ); + emit infoMessage( i18n("It is not recommended to force formatting of DVD+RW media."), INFO ); + emit infoMessage( i18n("Already after 10-20 reformats the media might be unusable."), INFO ); + blank = false; + } + else { + format = false; + } + } + + if( format ) + emit newSubTask( i18n("Formatting DVD+RW") ); + } + + + + // + // DVD-RW has two modes: incremental sequential (the default which is also needed for DAO writing) + // and restricted overwrite which compares to the DVD+RW mode. + // + + else { // MEDIA_DVD_RW + emit infoMessage( i18n("Found %1 media.").arg(K3bDevice::mediaTypeString(K3bDevice::MEDIA_DVD_RW)), + INFO ); + + if( diskInfo.currentProfile() != K3bDevice::MEDIA_UNKNOWN ) { + emit infoMessage( i18n("Formatted in %1 mode.").arg(K3bDevice::mediaTypeString(diskInfo.currentProfile())), INFO ); + + + // + // Is it possible to have an empty DVD-RW in restricted overwrite mode???? I don't think so. + // + + if( diskInfo.empty() && + (d->mode == K3b::WRITING_MODE_AUTO || + (d->mode == K3b::WRITING_MODE_INCR_SEQ && + diskInfo.currentProfile() == K3bDevice::MEDIA_DVD_RW_SEQ) || + (d->mode == K3b::WRITING_MODE_RES_OVWR && + diskInfo.currentProfile() == K3bDevice::MEDIA_DVD_RW_OVWR) ) + ) { + emit infoMessage( i18n("Media is already empty."), INFO ); + if( d->force ) + emit infoMessage( i18n("Forcing formatting anyway."), INFO ); + else + format = false; + } + else if( diskInfo.currentProfile() == K3bDevice::MEDIA_DVD_RW_OVWR && + d->mode != K3b::WRITING_MODE_INCR_SEQ ) { + emit infoMessage( i18n("No need to format %1 media more than once.") + .arg(K3bDevice::mediaTypeString(diskInfo.currentProfile())), INFO ); + emit infoMessage( i18n("It may simply be overwritten."), INFO ); + + if( d->force ) + emit infoMessage( i18n("Forcing formatting anyway."), INFO ); + else + format = false; + } + + + if( format ) { + if( d->mode == K3b::WRITING_MODE_AUTO ) { + // just format in the same mode as the media is currently formatted + blank = (diskInfo.currentProfile() == K3bDevice::MEDIA_DVD_RW_SEQ); + } + else { + blank = (d->mode == K3b::WRITING_MODE_INCR_SEQ); + } + + emit newSubTask( i18n("Formatting" + " DVD-RW in %1 mode.").arg(K3bDevice::mediaTypeString( blank ? + K3bDevice::MEDIA_DVD_RW_SEQ : + K3bDevice::MEDIA_DVD_RW_OVWR )) ); + } + } + else { + emit infoMessage( i18n("Unable to determine the current formatting state of the DVD-RW media."), ERROR ); + d->running = false; + jobFinished(false); + return; + } + } + + + if( format ) { + delete d->process; + d->process = new K3bProcess(); + d->process->setRunPrivileged(true); + // d->process->setSuppressEmptyLines(false); + connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStderrLine(const QString&)) ); + connect( d->process, SIGNAL(processExited(KProcess*)), this, SLOT(slotProcessFinished(KProcess*)) ); + + d->dvdFormatBin = k3bcore->externalBinManager()->binObject( "dvd+rw-format" ); + if( !d->dvdFormatBin ) { + emit infoMessage( i18n("Could not find %1 executable.").arg("dvd+rw-format"), ERROR ); + d->running = false; + jobFinished(false); + return; + } + + if( !d->dvdFormatBin->copyright.isEmpty() ) + emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3").arg(d->dvdFormatBin->name()).arg(d->dvdFormatBin->version).arg(d->dvdFormatBin->copyright), INFO ); + + + *d->process << d->dvdFormatBin; + + if( d->dvdFormatBin->version >= K3bVersion( 4, 6 ) ) + *d->process << "-gui"; + + QString p; + if( blank ) + p = "-blank"; + else + p = "-force"; + if( !d->quick ) + p += "=full"; + + *d->process << p; + + *d->process << d->device->blockDeviceName(); + + // additional user parameters from config + const QStringList& params = d->dvdFormatBin->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *d->process << *it; + + kdDebug() << "***** dvd+rw-format parameters:\n"; + const QValueList<QCString>& args = d->process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << endl << flush; + emit debuggingOutput( "dvd+rw-format command:", s ); + + if( !d->process->start( KProcess::NotifyOnExit, KProcess::All ) ) { + // something went wrong when starting the program + // it "should" be the executable + kdDebug() << "(K3bDvdFormattingJob) could not start " << d->dvdFormatBin->path << endl; + emit infoMessage( i18n("Could not start %1.").arg(d->dvdFormatBin->name()), K3bJob::ERROR ); + d->running = false; + jobFinished(false); + } + else { + emit newTask( i18n("Formatting") ); + } + } + else { + // already formatted :) + d->running = false; + jobFinished(true); + } +} + + +#include "k3bdvdformattingjob.moc" diff --git a/libk3b/jobs/k3bdvdformattingjob.h b/libk3b/jobs/k3bdvdformattingjob.h new file mode 100644 index 0000000..10672cb --- /dev/null +++ b/libk3b/jobs/k3bdvdformattingjob.h @@ -0,0 +1,91 @@ +/* + * + * $Id: k3bdvdformattingjob.h 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. + */ + +#ifndef _K3B_DVD_FORMATTING_JOB_H_ +#define _K3B_DVD_FORMATTING_JOB_H_ + + +#include <k3bjob.h> +#include "k3b_export.h" + +class KProcess; +namespace K3bDevice { + class Device; + class DeviceHandler; +} + + +class LIBK3B_EXPORT K3bDvdFormattingJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bDvdFormattingJob( K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bDvdFormattingJob(); + + QString jobDescription() const; + QString jobDetails() const; + + K3bDevice::Device* writer() const; + + public slots: + void start(); + + /** + * Use this to force the start of the formatting without checking for a usable medium. + */ + void start( const K3bDevice::DiskInfo& ); + + void cancel(); + + void setDevice( K3bDevice::Device* ); + + /** + * One of: WRITING_MODE_INCR_SEQ, WRITING_MODE_RES_OVWR + * Ignored for DVD+RW + */ + void setMode( int ); + + /** + * Not all writers support this + */ + void setQuickFormat( bool ); + + /** + * @param b If true empty DVDs will also be formatted + */ + void setForce( bool b ); + + /** + * If set true the job ignores the global K3b setting + * and does not eject the CD-RW after finishing + */ + void setForceNoEject( bool ); + + private slots: + void slotStderrLine( const QString& ); + void slotProcessFinished( KProcess* ); + void slotDeviceHandlerFinished( K3bDevice::DeviceHandler* ); + void slotEjectingFinished( K3bDevice::DeviceHandler* ); + + private: + void startFormatting( const K3bDevice::DiskInfo& ); + + class Private; + Private* d; +}; + + +#endif diff --git a/libk3b/jobs/k3biso9660imagewritingjob.cpp b/libk3b/jobs/k3biso9660imagewritingjob.cpp new file mode 100644 index 0000000..1fb3871 --- /dev/null +++ b/libk3b/jobs/k3biso9660imagewritingjob.cpp @@ -0,0 +1,458 @@ +/* + * + * $Id: k3biso9660imagewritingjob.cpp 690187 2007-07-20 09:18:03Z 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 "k3biso9660imagewritingjob.h" +#include "k3bverificationjob.h" + +#include <k3bdevice.h> +#include <k3bdiskinfo.h> +#include <k3bdevicehandler.h> +#include <k3bcdrecordwriter.h> +#include <k3bcdrdaowriter.h> +#include <k3bgrowisofswriter.h> +#include <k3bglobals.h> +#include <k3bcore.h> +#include <k3bversion.h> +#include <k3bexternalbinmanager.h> +#include <k3bchecksumpipe.h> +#include <k3bfilesplitter.h> + +#include <kdebug.h> +#include <kconfig.h> +#include <klocale.h> +#include <ktempfile.h> +#include <kio/global.h> + +#include <qstring.h> +#include <qtextstream.h> +#include <qfile.h> +#include <qapplication.h> + + +class K3bIso9660ImageWritingJob::Private +{ +public: + K3bChecksumPipe checksumPipe; + K3bFileSplitter imageFile; +}; + + +K3bIso9660ImageWritingJob::K3bIso9660ImageWritingJob( K3bJobHandler* hdl ) + : K3bBurnJob( hdl ), + m_writingMode(K3b::WRITING_MODE_AUTO), + m_simulate(false), + m_device(0), + m_noFix(false), + m_speed(2), + m_dataMode(K3b::DATA_MODE_AUTO), + m_writer(0), + m_tocFile(0), + m_copies(1), + m_verifyJob(0) +{ + d = new Private; +} + +K3bIso9660ImageWritingJob::~K3bIso9660ImageWritingJob() +{ + delete m_tocFile; + delete d; +} + + +void K3bIso9660ImageWritingJob::start() +{ + m_canceled = m_finished = false; + m_currentCopy = 1; + + jobStarted(); + + if( m_simulate ) + m_verifyData = false; + + emit newTask( i18n("Preparing data") ); + + if( !QFile::exists( m_imagePath ) ) { + emit infoMessage( i18n("Could not find image %1").arg(m_imagePath), K3bJob::ERROR ); + jobFinished( false ); + return; + } + + unsigned long gb = K3b::imageFilesize( m_imagePath )/1024/1024; + + // very rough test but since most dvd images are 4,x or 8,x GB it should be enough + m_dvd = ( gb > 900 ); + + startWriting(); +} + + +void K3bIso9660ImageWritingJob::slotWriterJobFinished( bool success ) +{ + if( m_canceled ) { + m_finished = true; + emit canceled(); + jobFinished(false); + return; + } + + d->checksumPipe.close(); + + if( success ) { + if( !m_simulate && m_verifyData ) { + emit burning(false); + + // allright + // the writerJob should have emited the "simulation/writing successful" signal + + if( !m_verifyJob ) { + m_verifyJob = new K3bVerificationJob( this ); + connectSubJob( m_verifyJob, + SLOT(slotVerificationFinished(bool)), + true, + SLOT(slotVerificationProgress(int)), + SIGNAL(subPercent(int)) ); + } + m_verifyJob->setDevice( m_device ); + m_verifyJob->clear(); + m_verifyJob->addTrack( 1, d->checksumPipe.checksum(), K3b::imageFilesize( m_imagePath )/2048 ); + + if( m_copies == 1 ) + emit newTask( i18n("Verifying written data") ); + else + emit newTask( i18n("Verifying written copy %1 of %2").arg(m_currentCopy).arg(m_copies) ); + + m_verifyJob->start(); + } + else if( m_currentCopy >= m_copies ) { + m_finished = true; + jobFinished(true); + } + else { + m_currentCopy++; + startWriting(); + } + } + else { + m_finished = true; + jobFinished(false); + } +} + + +void K3bIso9660ImageWritingJob::slotVerificationFinished( bool success ) +{ + if( m_canceled ) { + m_finished = true; + emit canceled(); + jobFinished(false); + return; + } + + if( success && m_currentCopy < m_copies ) { + m_currentCopy++; + connect( K3bDevice::eject( m_device ), SIGNAL(finished(bool)), + this, SLOT(startWriting()) ); + return; + } + + k3bcore->config()->setGroup("General Options"); + if( !k3bcore->config()->readBoolEntry( "No cd eject", false ) ) + K3bDevice::eject( m_device ); + + m_finished = true; + jobFinished( success ); +} + + +void K3bIso9660ImageWritingJob::slotVerificationProgress( int p ) +{ + emit percent( (int)(100.0 / (double)m_copies * ( (double)(m_currentCopy-1) + 0.5 + (double)p/200.0 )) ); +} + + +void K3bIso9660ImageWritingJob::slotWriterPercent( int p ) +{ + emit subPercent( p ); + + if( m_verifyData ) + emit percent( (int)(100.0 / (double)m_copies * ( (double)(m_currentCopy-1) + ((double)p/200.0) )) ); + else + emit percent( (int)(100.0 / (double)m_copies * ( (double)(m_currentCopy-1) + ((double)p/100.0) )) ); +} + + +void K3bIso9660ImageWritingJob::slotNextTrack( int, int ) +{ + if( m_copies == 1 ) + emit newSubTask( i18n("Writing image") ); + else + emit newSubTask( i18n("Writing copy %1 of %2").arg(m_currentCopy).arg(m_copies) ); +} + + +void K3bIso9660ImageWritingJob::cancel() +{ + if( !m_finished ) { + m_canceled = true; + + if( m_writer ) + m_writer->cancel(); + if( m_verifyData && m_verifyJob ) + m_verifyJob->cancel(); + } +} + + +void K3bIso9660ImageWritingJob::startWriting() +{ + emit newSubTask( i18n("Waiting for medium") ); + + // we wait for the following: + // 1. if writing mode auto and writing app auto: all writable media types + // 2. if writing mode auto and writing app not growisofs: all writable cd types + // 3. if writing mode auto and writing app growisofs: all writable dvd types + // 4. if writing mode TAO or RAW: all writable cd types + // 5. if writing mode DAO and writing app auto: all writable cd types and DVD-R(W) + // 6. if writing mode DAO and writing app GROWISOFS: DVD-R(W) + // 7. if writing mode DAO and writing app CDRDAO or CDRECORD: all writable cd types + // 8. if writing mode WRITING_MODE_INCR_SEQ: DVD-R(W) + // 9. if writing mode WRITING_MODE_RES_OVWR: DVD-RW or DVD+RW + + int mt = 0; + if( m_writingMode == K3b::WRITING_MODE_AUTO ) { + if( writingApp() == K3b::DEFAULT ) { + if( m_dvd ) + mt = K3bDevice::MEDIA_WRITABLE_DVD; + else + mt = K3bDevice::MEDIA_WRITABLE_CD; + } + else if( writingApp() != K3b::GROWISOFS ) + mt = K3bDevice::MEDIA_WRITABLE_CD; + else + mt = K3bDevice::MEDIA_WRITABLE_DVD; + } + else if( m_writingMode == K3b::TAO || m_writingMode == K3b::RAW ) + mt = K3bDevice::MEDIA_WRITABLE_CD; + else if( m_writingMode == K3b::DAO ) { + if( writingApp() == K3b::DEFAULT ) { + if( m_dvd ) + mt = K3bDevice::MEDIA_WRITABLE_DVD; + else + mt = K3bDevice::MEDIA_WRITABLE_CD; + } + else if( writingApp() == K3b::GROWISOFS ) + mt = K3bDevice::MEDIA_WRITABLE_DVD; + else + mt = K3bDevice::MEDIA_WRITABLE_CD; + } + else if( m_writingMode == K3b::WRITING_MODE_RES_OVWR ) + mt = K3bDevice::MEDIA_DVD_PLUS_R|K3bDevice::MEDIA_DVD_PLUS_R_DL|K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_RW_OVWR; + else + mt = K3bDevice::MEDIA_WRITABLE_DVD; + + + // wait for the media + int media = waitForMedia( m_device, K3bDevice::STATE_EMPTY, mt ); + if( media < 0 ) { + m_finished = true; + emit canceled(); + jobFinished(false); + return; + } + + // we simply always calculate the checksum, thus making the code simpler + d->imageFile.close(); + d->imageFile.setName( m_imagePath ); + d->imageFile.open( IO_ReadOnly ); + d->checksumPipe.close(); + d->checksumPipe.readFromIODevice( &d->imageFile ); + + if( prepareWriter( media ) ) { + emit burning(true); + m_writer->start(); + d->checksumPipe.writeToFd( m_writer->fd(), true ); + d->checksumPipe.open( K3bChecksumPipe::MD5, true ); + } + else { + m_finished = true; + jobFinished(false); + } +} + + +bool K3bIso9660ImageWritingJob::prepareWriter( int mediaType ) +{ + if( mediaType == 0 ) { // media forced + // just to get it going... + if( writingApp() != K3b::GROWISOFS && !m_dvd ) + mediaType = K3bDevice::MEDIA_CD_R; + else + mediaType = K3bDevice::MEDIA_DVD_R; + } + + delete m_writer; + + if( mediaType == K3bDevice::MEDIA_CD_R || mediaType == K3bDevice::MEDIA_CD_RW ) { + int usedWritingMode = m_writingMode; + if( usedWritingMode == K3b::WRITING_MODE_AUTO ) { + // cdrecord seems to have problems when writing in mode2 in dao mode + // so with cdrecord we use TAO + if( m_noFix || m_dataMode == K3b::MODE2 || !m_device->dao() ) + usedWritingMode = K3b::TAO; + else + usedWritingMode = K3b::DAO; + } + + int usedApp = writingApp(); + if( usedApp == K3b::DEFAULT ) { + if( usedWritingMode == K3b::DAO && + ( m_dataMode == K3b::MODE2 || m_noFix ) ) + usedApp = K3b::CDRDAO; + else + usedApp = K3b::CDRECORD; + } + + + if( usedApp == K3b::CDRECORD ) { + K3bCdrecordWriter* writer = new K3bCdrecordWriter( m_device, this ); + + writer->setWritingMode( usedWritingMode ); + writer->setSimulate( m_simulate ); + writer->setBurnSpeed( m_speed ); + + if( m_noFix ) { + writer->addArgument("-multi"); + } + + if( (m_dataMode == K3b::DATA_MODE_AUTO && m_noFix) || + m_dataMode == K3b::MODE2 ) { + if( k3bcore->externalBinManager()->binObject("cdrecord") && + k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "xamix" ) ) + writer->addArgument( "-xa" ); + else + writer->addArgument( "-xa1" ); + } + else + writer->addArgument("-data"); + + // read from stdin + writer->addArgument( QString("-tsize=%1s").arg( K3b::imageFilesize( m_imagePath )/2048 ) )->addArgument( "-" ); + + m_writer = writer; + } + else { + // create cdrdao job + K3bCdrdaoWriter* writer = new K3bCdrdaoWriter( m_device, this ); + writer->setCommand( K3bCdrdaoWriter::WRITE ); + writer->setSimulate( m_simulate ); + writer->setBurnSpeed( m_speed ); + // multisession + writer->setMulti( m_noFix ); + + // now write the tocfile + delete m_tocFile; + m_tocFile = new KTempFile( QString::null, "toc" ); + m_tocFile->setAutoDelete(true); + + if( QTextStream* s = m_tocFile->textStream() ) { + if( (m_dataMode == K3b::DATA_MODE_AUTO && m_noFix) || + m_dataMode == K3b::MODE2 ) { + *s << "CD_ROM_XA" << "\n"; + *s << "\n"; + *s << "TRACK MODE2_FORM1" << "\n"; + } + else { + *s << "CD_ROM" << "\n"; + *s << "\n"; + *s << "TRACK MODE1" << "\n"; + } + *s << "DATAFILE \"-\" " << QString::number( K3b::imageFilesize( m_imagePath ) ) << "\n"; + + m_tocFile->close(); + } + else { + kdDebug() << "(K3bDataJob) could not write tocfile." << endl; + emit infoMessage( i18n("IO Error"), ERROR ); + return false; + } + + writer->setTocFile( m_tocFile->name() ); + + m_writer = writer; + } + } + else { // DVD + if( mediaType & K3bDevice::MEDIA_DVD_PLUS_ALL ) { + if( m_simulate ) { + if( questionYesNo( i18n("K3b does not support simulation with DVD+R(W) media. " + "Do you really want to continue? The media will be written " + "for real."), + i18n("No Simulation with DVD+R(W)") ) ) { + return false; + } + } + + m_simulate = false; + } + + K3bGrowisofsWriter* writer = new K3bGrowisofsWriter( m_device, this ); + writer->setSimulate( m_simulate ); + writer->setBurnSpeed( m_speed ); + writer->setWritingMode( m_writingMode == K3b::DAO ? K3b::DAO : 0 ); + writer->setImageToWrite( QString::null ); // read from stdin + writer->setCloseDvd( !m_noFix ); + writer->setTrackSize( K3b::imageFilesize( m_imagePath )/2048 ); + + m_writer = writer; + } + + connect( m_writer, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_writer, SIGNAL(nextTrack(int, int)), this, SLOT(slotNextTrack(int, int)) ); + connect( m_writer, SIGNAL(percent(int)), this, SLOT(slotWriterPercent(int)) ); + connect( m_writer, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); + connect( m_writer, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); + connect( m_writer, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); + connect( m_writer, SIGNAL(writeSpeed(int, int)), this, SIGNAL(writeSpeed(int, int)) ); + connect( m_writer, SIGNAL(finished(bool)), this, SLOT(slotWriterJobFinished(bool)) ); + connect( m_writer, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); + connect( m_writer, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( m_writer, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + + return true; +} + + +QString K3bIso9660ImageWritingJob::jobDescription() const +{ + if( m_simulate ) + return i18n("Simulating ISO9660 Image"); + else + return ( i18n("Burning ISO9660 Image") + + ( m_copies > 1 + ? i18n(" - %n Copy", " - %n Copies", m_copies) + : QString::null ) ); +} + + +QString K3bIso9660ImageWritingJob::jobDetails() const +{ + return m_imagePath.section("/", -1) + QString( " (%1)" ).arg(KIO::convertSize(K3b::filesize(KURL::fromPathOrURL(m_imagePath)))); +} + + +#include "k3biso9660imagewritingjob.moc" diff --git a/libk3b/jobs/k3biso9660imagewritingjob.h b/libk3b/jobs/k3biso9660imagewritingjob.h new file mode 100644 index 0000000..eceb6dc --- /dev/null +++ b/libk3b/jobs/k3biso9660imagewritingjob.h @@ -0,0 +1,98 @@ +/* + * + * $Id$ + * 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. + */ + + +#ifndef K3BISO9660_IMAGE_WRITING_JOB_H +#define K3BISO9660_IMAGE_WRITING_JOB_H + +#include <k3bjob.h> +#include "k3b_export.h" +class QString; +class K3bAbstractWriter; +class KTempFile; +namespace K3bDevice { + class Device; +} +class K3bVerificationJob; + + +/** + *@author Sebastian Trueg + */ +class LIBK3B_EXPORT K3bIso9660ImageWritingJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bIso9660ImageWritingJob( K3bJobHandler* ); + ~K3bIso9660ImageWritingJob(); + + K3bDevice::Device* writer() const { return m_device; }; + + QString jobDescription() const; + QString jobDetails() const; + + public slots: + void cancel(); + void start(); + + void setImagePath( const QString& path ) { m_imagePath = path; } + void setSpeed( int s ) { m_speed = s; } + void setBurnDevice( K3bDevice::Device* dev ) { m_device = dev; } + void setWritingMode( int mode ) { m_writingMode = mode; } + void setSimulate( bool b ) { m_simulate = b; } + void setNoFix( bool b ) { m_noFix = b; } + void setDataMode( int m ) { m_dataMode = m; } + void setVerifyData( bool b ) { m_verifyData = b; } + void setCopies( int c ) { m_copies = c; } + + protected slots: + void slotWriterJobFinished( bool ); + void slotVerificationFinished( bool ); + void slotVerificationProgress( int ); + void slotWriterPercent( int ); + void slotNextTrack( int, int ); + void startWriting(); + + private: + bool prepareWriter( int mediaType ); + + int m_writingMode; + bool m_simulate; + K3bDevice::Device* m_device; + bool m_noFix; + int m_speed; + int m_dataMode; + bool m_verifyData; + bool m_dvd; + + QString m_imagePath; + + K3bAbstractWriter* m_writer; + KTempFile* m_tocFile; + + bool m_canceled; + bool m_finished; + + int m_copies; + int m_currentCopy; + + K3bVerificationJob* m_verifyJob; + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/jobs/k3breadcdreader.cpp b/libk3b/jobs/k3breadcdreader.cpp new file mode 100644 index 0000000..d75eb63 --- /dev/null +++ b/libk3b/jobs/k3breadcdreader.cpp @@ -0,0 +1,335 @@ +/* + * + * $Id: k3breadcdreader.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 "k3breadcdreader.h" + +#include <k3bcore.h> +#include <k3bexternalbinmanager.h> +#include <k3bdevice.h> +#include <k3bdevicemanager.h> +#include <k3bprocess.h> +#include <k3bmsf.h> +#include <k3bglobals.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kconfig.h> + +#include <qregexp.h> +#include <qvaluelist.h> +#include <qstringlist.h> + + + +class K3bReadcdReader::Private +{ +public: + Private() + : process(0), + fdToWriteTo(-1), + canceled(false) { + } + + K3b::Msf firstSector, lastSector; + + K3bProcess* process; + const K3bExternalBin* readcdBinObject; + + int fdToWriteTo; + bool canceled; + + long blocksToRead; + int unreadableBlocks; + + int lastProgress; + int lastProcessedSize; +}; + + + +K3bReadcdReader::K3bReadcdReader( K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bJob( jh, parent, name ), + m_noCorr(false), + m_clone(false), + m_noError(false), + m_c2Scan(false), + m_speed(0), + m_retries(128) +{ + d = new Private(); +} + + +K3bReadcdReader::~K3bReadcdReader() +{ + delete d->process; + delete d; +} + + +bool K3bReadcdReader::active() const +{ + return (d->process ? d->process->isRunning() : false); +} + + +void K3bReadcdReader::writeToFd( int fd ) +{ + d->fdToWriteTo = fd; +} + + +void K3bReadcdReader::start() +{ + jobStarted(); + + d->blocksToRead = 1; + d->unreadableBlocks = 0; + d->lastProgress = 0; + d->lastProcessedSize = 0; + + // the first thing to do is to check for readcd + d->readcdBinObject = k3bcore->externalBinManager()->binObject( "readcd" ); + if( !d->readcdBinObject ) { + emit infoMessage( i18n("Could not find %1 executable.").arg("readcd"), ERROR ); + jobFinished(false); + return; + } + + // check if we have clone support if we need it + if( m_clone ) { + bool foundCloneSupport = false; + + if( !d->readcdBinObject->hasFeature( "clone" ) ) { + // search all readcd installations + K3bExternalProgram* readcdProgram = k3bcore->externalBinManager()->program( "readcd" ); + const QPtrList<K3bExternalBin>& readcdBins = readcdProgram->bins(); + for( QPtrListIterator<K3bExternalBin> it( readcdBins ); it.current(); ++it ) { + if( it.current()->hasFeature( "clone" ) ) { + d->readcdBinObject = it.current(); + emit infoMessage( i18n("Using readcd %1 instead of default version for clone support.").arg(d->readcdBinObject->version), INFO ); + foundCloneSupport = true; + break; + } + } + + if( !foundCloneSupport ) { + emit infoMessage( i18n("Could not find readcd executable with cloning support."), ERROR ); + jobFinished(false); + return; + } + } + } + + + // create the commandline + delete d->process; + d->process = new K3bProcess(); + connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); + connect( d->process, SIGNAL(processExited(KProcess*)), this, SLOT(slotProcessExited(KProcess*)) ); + + + *d->process << d->readcdBinObject; + + // display progress + *d->process << "-v"; + + // Again we assume the device to be set! + *d->process << QString("dev=%1").arg(K3b::externalBinDeviceParameter(m_readDevice, + d->readcdBinObject)); + if( m_speed > 0 ) + *d->process << QString("speed=%1").arg(m_speed); + + + // output + if( d->fdToWriteTo != -1 ) { + *d->process << "f=-"; + d->process->dupStdout( d->fdToWriteTo ); + } + else { + emit newTask( i18n("Writing image to %1.").arg(m_imagePath) ); + emit infoMessage( i18n("Writing image to %1.").arg(m_imagePath), INFO ); + *d->process << "f=" + m_imagePath; + } + + + if( m_noError ) + *d->process << "-noerror"; + if( m_clone ) { + *d->process << "-clone"; + // noCorr can only be used with cloning + if( m_noCorr ) + *d->process << "-nocorr"; + } + if( m_c2Scan ) + *d->process << "-c2scan"; + + *d->process << QString("retries=%1").arg(m_retries); + + // readcd does not read the last sector specified + if( d->firstSector < d->lastSector ) + *d->process << QString("sectors=%1-%2").arg(d->firstSector.lba()).arg(d->lastSector.lba()+1); + + // Joerg sais it is a Linux kernel bug, anyway, with the default value it does not work + *d->process << "ts=128k"; + + // additional user parameters from config + const QStringList& params = d->readcdBinObject->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *d->process << *it; + + + kdDebug() << "***** readcd parameters:\n"; + const QValueList<QCString>& args = d->process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << endl << flush; + + emit debuggingOutput("readcd command:", s); + + d->canceled = false; + + if( !d->process->start( KProcess::NotifyOnExit, KProcess::AllOutput) ) { + // something went wrong when starting the program + // it "should" be the executable + kdError() << "(K3bReadcdReader) could not start readcd" << endl; + emit infoMessage( i18n("Could not start readcd."), K3bJob::ERROR ); + jobFinished( false ); + } +} + + +void K3bReadcdReader::cancel() +{ + if( d->process ) { + if( d->process->isRunning() ) { + d->canceled = true; + d->process->kill(); + } + } +} + + +void K3bReadcdReader::slotStdLine( const QString& line ) +{ + emit debuggingOutput( "readcd", line ); + + int pos = -1; + + if( line.startsWith( "end:" ) ) { + bool ok; + d->blocksToRead = line.mid(4).toInt(&ok); + if( d->firstSector < d->lastSector ) + d->blocksToRead -= d->firstSector.lba(); + if( !ok ) + kdError() << "(K3bReadcdReader) blocksToRead parsing error in line: " + << line.mid(4) << endl; + } + + else if( line.startsWith( "addr:" ) ) { + bool ok; + long currentReadBlock = line.mid( 6, line.find("cnt")-7 ).toInt(&ok); + if( d->firstSector < d->lastSector ) + currentReadBlock -= d->firstSector.lba(); + if( ok ) { + int p = (int)(100.0 * (double)currentReadBlock / (double)d->blocksToRead); + if( p > d->lastProgress ) { + emit percent( p ); + d->lastProgress = p; + } + int ps = currentReadBlock*2/1024; + if( ps > d->lastProcessedSize ) { + emit processedSize( ps, d->blocksToRead*2/1024 ); + d->lastProcessedSize = ps; + } + } + else + kdError() << "(K3bReadcdReader) currentReadBlock parsing error in line: " + << line.mid( 6, line.find("cnt")-7 ) << endl; + } + + else if( line.contains("Cannot read source disk") ) { + emit infoMessage( i18n("Cannot read source disk."), ERROR ); + } + + else if( (pos = line.find("Retrying from sector")) >= 0 ) { + // parse the sector + pos += 21; + bool ok; + int problemSector = line.mid( pos, line.find( QRegExp("\\D"), pos )-pos ).toInt(&ok); + if( !ok ) { + kdError() << "(K3bReadcdReader) problemSector parsing error in line: " + << line.mid( pos, line.find( QRegExp("\\D"), pos )-pos ) << endl; + } + emit infoMessage( i18n("Retrying from sector %1.").arg(problemSector), INFO ); + } + + else if( (pos = line.find("Error on sector")) >= 0 ) { + d->unreadableBlocks++; + + pos += 16; + bool ok; + int problemSector = line.mid( pos, line.find( QRegExp("\\D"), pos )-pos ).toInt(&ok); + if( !ok ) { + kdError() << "(K3bReadcdReader) problemSector parsing error in line: " + << line.mid( pos, line.find( QRegExp("\\D"), pos )-pos ) << endl; + } + + if( line.contains( "not corrected") ) { + emit infoMessage( i18n("Uncorrected error in sector %1").arg(problemSector), ERROR ); + } + else { + emit infoMessage( i18n("Corrected error in sector %1").arg(problemSector), ERROR ); + } + } + + else { + kdDebug() << "(readcd) " << line << endl; + } +} + +void K3bReadcdReader::slotProcessExited( KProcess* p ) +{ + if( d->canceled ) { + emit canceled(); + jobFinished(false); + } + else if( p->normalExit() ) { + if( p->exitStatus() == 0 ) { + jobFinished( true ); + } + else { + emit infoMessage( i18n("%1 returned error: %2").arg("Readcd").arg(p->exitStatus()), ERROR ); + jobFinished( false ); + } + } + else { + emit infoMessage( i18n("Readcd exited abnormally."), ERROR ); + jobFinished( false ); + } +} + + +void K3bReadcdReader::setSectorRange( const K3b::Msf& first, const K3b::Msf& last ) +{ + d->firstSector = first; + d->lastSector = last; +} + +#include "k3breadcdreader.moc" + diff --git a/libk3b/jobs/k3breadcdreader.h b/libk3b/jobs/k3breadcdreader.h new file mode 100644 index 0000000..93ebce0 --- /dev/null +++ b/libk3b/jobs/k3breadcdreader.h @@ -0,0 +1,91 @@ +/* + * + * $Id: k3breadcdreader.h 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. + */ + + +#ifndef _K3B_READCD_READER_H_ +#define _K3B_READCD_READER_H_ + +#include <k3bjob.h> + + +class K3bProcess; +class KProcess; +class K3bExternalBin; +namespace K3bDevice { + class Device; +} +namespace K3b { + class Msf; +} + + +class K3bReadcdReader : public K3bJob +{ + Q_OBJECT + + public: + K3bReadcdReader( K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bReadcdReader(); + + bool active() const; + + public slots: + void start(); + void cancel(); + + void setReadDevice( K3bDevice::Device* dev ) { m_readDevice = dev; } + + /** 0 means MAX */ + void setReadSpeed( int s ) { m_speed = s; } + void setDisableCorrection( bool b ) { m_noCorr = b; } + + /** default: true */ + void setAbortOnError( bool b ) { m_noError = !b; } + void setC2Scan( bool b ) { m_c2Scan = b; } + void setClone( bool b ) { m_clone = b; } + void setRetries( int i ) { m_retries = i; } + + void setSectorRange( const K3b::Msf&, const K3b::Msf& ); + + void setImagePath( const QString& p ) { m_imagePath = p; } + + /** + * the data gets written directly into fd instead of the imagefile. + * Be aware that this only makes sense before starting the job. + * To disable just set fd to -1 + */ + void writeToFd( int fd ); + + private slots: + void slotStdLine( const QString& line ); + void slotProcessExited(KProcess*); + + private: + bool m_noCorr; + bool m_clone; + bool m_noError; + bool m_c2Scan; + int m_speed; + int m_retries; + + K3bDevice::Device* m_readDevice; + + QString m_imagePath; + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/jobs/k3bverificationjob.cpp b/libk3b/jobs/k3bverificationjob.cpp new file mode 100644 index 0000000..e73530e --- /dev/null +++ b/libk3b/jobs/k3bverificationjob.cpp @@ -0,0 +1,384 @@ +/* + * + * $Id: k3bisoimageverificationjob.cpp 597651 2006-10-21 08:04:01Z trueg $ + * Copyright (C) 2003-2007 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 "k3bverificationjob.h" + +#include <k3bdevice.h> +#include <k3bdevicehandler.h> +#include <k3bmd5job.h> +#include <k3bglobals.h> +#include <k3bdatatrackreader.h> +#include <k3bpipe.h> +#include <k3biso9660.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kio/global.h> +#include <kio/job.h> +#include <kio/netaccess.h> + +#include <qcstring.h> +#include <qapplication.h> +#include <qvaluelist.h> +#include <qpair.h> + + +class K3bVerificationJobTrackEntry +{ +public: + K3bVerificationJobTrackEntry() + : trackNumber(0) { + } + + K3bVerificationJobTrackEntry( int tn, const QCString& cs, const K3b::Msf& msf ) + : trackNumber(tn), + checksum(cs), + length(msf) { + } + + int trackNumber; + QCString checksum; + K3b::Msf length; +}; + + +class K3bVerificationJob::Private +{ +public: + Private() + : md5Job(0), + device(0), + dataTrackReader(0) { + } + + bool canceled; + K3bMd5Job* md5Job; + K3bDevice::Device* device; + + K3b::Msf grownSessionSize; + + QValueList<K3bVerificationJobTrackEntry> tracks; + int currentTrackIndex; + + K3bDevice::DiskInfo diskInfo; + K3bDevice::Toc toc; + + K3bDataTrackReader* dataTrackReader; + + K3b::Msf currentTrackSize; + K3b::Msf totalSectors; + K3b::Msf alreadyReadSectors; + + K3bPipe pipe; + + bool readSuccessful; + + bool mediumHasBeenReloaded; +}; + + +K3bVerificationJob::K3bVerificationJob( K3bJobHandler* hdl, QObject* parent, const char* name ) + : K3bJob( hdl, parent, name ) +{ + d = new Private(); + + d->md5Job = new K3bMd5Job( this ); + connect( d->md5Job, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( d->md5Job, SIGNAL(finished(bool)), this, SLOT(slotMd5JobFinished(bool)) ); + connect( d->md5Job, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); +} + + +K3bVerificationJob::~K3bVerificationJob() +{ + delete d; +} + + +void K3bVerificationJob::cancel() +{ + d->canceled = true; + if( d->md5Job && d->md5Job->active() ) + d->md5Job->cancel(); + if( d->dataTrackReader && d->dataTrackReader->active() ) + d->dataTrackReader->cancel(); +} + + +void K3bVerificationJob::addTrack( int trackNum, const QCString& checksum, const K3b::Msf& length ) +{ + d->tracks.append( K3bVerificationJobTrackEntry( trackNum, checksum, length ) ); +} + + +void K3bVerificationJob::clear() +{ + d->tracks.clear(); + d->grownSessionSize = 0; +} + + +void K3bVerificationJob::setDevice( K3bDevice::Device* dev ) +{ + d->device = dev; +} + + +void K3bVerificationJob::setGrownSessionSize( const K3b::Msf& s ) +{ + d->grownSessionSize = s; +} + + +void K3bVerificationJob::start() +{ + jobStarted(); + + d->canceled = false; + d->currentTrackIndex = 0; + d->alreadyReadSectors = 0; + + emit newTask( i18n("Checking medium") ); + + d->mediumHasBeenReloaded = false; + connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::DISKINFO, d->device ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotDiskInfoReady(K3bDevice::DeviceHandler*)) ); +} + + +void K3bVerificationJob::slotMediaReloaded( bool /*success*/ ) +{ + // we always need to wait for the medium. Otherwise the diskinfo below + // may run before the drive is ready! + waitForMedia( d->device, + K3bDevice::STATE_COMPLETE|K3bDevice::STATE_INCOMPLETE, + K3bDevice::MEDIA_WRITABLE ); + + d->mediumHasBeenReloaded = true; + + emit newTask( i18n("Checking medium") ); + + connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::DISKINFO, d->device ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotDiskInfoReady(K3bDevice::DeviceHandler*)) ); +} + + +void K3bVerificationJob::slotDiskInfoReady( K3bDevice::DeviceHandler* dh ) +{ + if( d->canceled ) { + emit canceled(); + jobFinished(false); + } + + d->diskInfo = dh->diskInfo(); + d->toc = dh->toc(); + d->totalSectors = 0; + + // just to be sure check if we actually have all the tracks + int i = 0; + for( QValueList<K3bVerificationJobTrackEntry>::iterator it = d->tracks.begin(); + it != d->tracks.end(); ++i, ++it ) { + + // 0 means "last track" + if( (*it).trackNumber == 0 ) + (*it).trackNumber = d->toc.count(); + + if( (int)d->toc.count() < (*it).trackNumber ) { + if ( d->mediumHasBeenReloaded ) { + emit infoMessage( i18n("Internal Error: Verification job improperly initialized (%1)") + .arg( "Specified track number not found on medium" ), ERROR ); + jobFinished( false ); + return; + } + else { + // many drives need to reload the medium to return to a proper state + emit newTask( i18n("Reloading the medium") ); + connect( K3bDevice::reload( d->device ), + SIGNAL(finished(bool)), + this, + SLOT(slotMediaReloaded(bool)) ); + return; + } + } + + d->totalSectors += trackLength( i ); + } + + readTrack( 0 ); +} + + +void K3bVerificationJob::readTrack( int trackIndex ) +{ + d->currentTrackIndex = trackIndex; + d->readSuccessful = true; + + d->currentTrackSize = trackLength( trackIndex ); + if( d->currentTrackSize == 0 ) { + jobFinished(false); + return; + } + + emit newTask( i18n("Verifying track %1").arg( d->tracks[trackIndex].trackNumber ) ); + + d->pipe.open(); + + if( d->toc[d->tracks[trackIndex].trackNumber-1].type() == K3bDevice::Track::DATA ) { + if( !d->dataTrackReader ) { + d->dataTrackReader = new K3bDataTrackReader( this ); + connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) ); + // connect( d->dataTrackReader, SIGNAL(processedSize(int, int)), this, SLOT(slotReaderProcessedSize(int, int)) ); + connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotReaderFinished(bool)) ); + connect( d->dataTrackReader, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( d->dataTrackReader, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( d->dataTrackReader, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + } + + d->dataTrackReader->setDevice( d->device ); + d->dataTrackReader->setIgnoreErrors( false ); + d->dataTrackReader->setSectorSize( K3bDataTrackReader::MODE1 ); + + // in case a session was grown the track size does not say anything about the verification data size + if( d->diskInfo.mediaType() & (K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_RW_OVWR) && + d->grownSessionSize > 0 ) { + K3bIso9660 isoF( d->device ); + if( isoF.open() ) { + int firstSector = isoF.primaryDescriptor().volumeSpaceSize - d->grownSessionSize.lba(); + d->dataTrackReader->setSectorRange( firstSector, + isoF.primaryDescriptor().volumeSpaceSize -1 ); + } + else { + emit infoMessage( i18n("Unable to determine the ISO9660 filesystem size."), ERROR ); + jobFinished( false ); + return; + } + } + else + d->dataTrackReader->setSectorRange( d->toc[d->tracks[trackIndex].trackNumber-1].firstSector(), + d->toc[d->tracks[trackIndex].trackNumber-1].firstSector() + d->currentTrackSize -1 ); + + d->md5Job->setMaxReadSize( d->currentTrackSize.mode1Bytes() ); + + d->dataTrackReader->writeToFd( d->pipe.in() ); + d->dataTrackReader->start(); + } + else { + // FIXME: handle audio tracks + } + + d->md5Job->setFd( d->pipe.out() ); + d->md5Job->start(); +} + + +void K3bVerificationJob::slotReaderProgress( int p ) +{ + emit subPercent( p ); + + emit percent( 100 * ( d->alreadyReadSectors.lba() + ( p*d->currentTrackSize.lba()/100 ) ) / d->totalSectors.lba() ); +} + + +void K3bVerificationJob::slotMd5JobFinished( bool success ) +{ + d->pipe.close(); + + if( success && !d->canceled && d->readSuccessful ) { + // compare the two sums + if( d->tracks[d->currentTrackIndex].checksum != d->md5Job->hexDigest() ) { + emit infoMessage( i18n("Written data in track %1 differs from original.").arg(d->tracks[d->currentTrackIndex].trackNumber), ERROR ); + jobFinished(false); + } + else { + emit infoMessage( i18n("Written data verified."), SUCCESS ); + ++d->currentTrackIndex; + if( d->currentTrackIndex < (int)d->tracks.count() ) + readTrack( d->currentTrackIndex ); + else + jobFinished(true); + } + } + else { + // The md5job emitted an error message. So there is no need to do this again + jobFinished(false); + } +} + + +void K3bVerificationJob::slotReaderFinished( bool success ) +{ + d->readSuccessful = success; + if( !d->readSuccessful ) + d->md5Job->cancel(); + else { + d->alreadyReadSectors += trackLength( d->currentTrackIndex ); + + // close the pipe and let the md5 job finish gracefully + d->pipe.closeIn(); + // d->md5Job->stop(); + } +} + + +K3b::Msf K3bVerificationJob::trackLength( int trackIndex ) +{ + K3b::Msf& trackSize = d->tracks[trackIndex].length; + const int& trackNum = d->tracks[trackIndex].trackNumber; + + if( trackSize == 0 ) { + trackSize = d->toc[trackNum-1].length(); + + if( d->diskInfo.mediaType() & (K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_RW_OVWR) ) { + K3bIso9660 isoF( d->device, d->toc[trackNum-1].firstSector().lba() ); + if( isoF.open() ) { + trackSize = isoF.primaryDescriptor().volumeSpaceSize; + } + else { + emit infoMessage( i18n("Unable to determine the ISO9660 filesystem size."), ERROR ); + return 0; + } + } + + // + // A data track recorded in TAO mode has two run-out blocks which cannot be read and contain + // zero data anyway. The problem is that I do not know of a valid method to determine if a track + // was written in TAO (the control nibble does definitely not work, I never saw one which did not + // equal 4). + // So the solution for now is to simply try to read the last sector of a data track. If this is not + // possible we assume it was written in TAO mode and reduce the length by 2 sectors + // + if( d->toc[trackNum-1].type() == K3bDevice::Track::DATA && + d->diskInfo.mediaType() & K3bDevice::MEDIA_CD_ALL ) { + // we try twice just to be sure + unsigned char buffer[2048]; + if( !d->device->read10( buffer, 2048, d->toc[trackNum-1].lastSector().lba(), 1 ) && + !d->device->read10( buffer, 2048, d->toc[trackNum-1].lastSector().lba(), 1 ) ) { + trackSize -= 2; + kdDebug() << "(K3bCdCopyJob) track " << trackNum << " probably TAO recorded." << endl; + } + } + } + + return trackSize; +} + + +#include "k3bverificationjob.moc" diff --git a/libk3b/jobs/k3bverificationjob.h b/libk3b/jobs/k3bverificationjob.h new file mode 100644 index 0000000..ad750ee --- /dev/null +++ b/libk3b/jobs/k3bverificationjob.h @@ -0,0 +1,92 @@ +/* + * + * $Id: k3bisoimageverificationjob.h 597651 2006-10-21 08:04:01Z 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. + */ + +#ifndef _K3B_VERIFICATION_JOB_H_ +#define _K3B_VERIFICATION_JOB_H_ + +#include <k3bjob.h> + +namespace K3bDevice { + class Device; + class DeviceHandler; +} + + +/** + * Generic verification job. Add tracks to be verified via addTrack. + * The job will then verifiy the tracks set against the set checksums. + * + * The different track types are handled as follows: + * \li Data/DVD tracks: Read the track with a 2048 bytes sector size. + * Tracks length on DVD+RW media will be read from the iso9660 + * descriptor. + * \li Audio tracks: Rip the track with a 2352 bytes sector size. + * In the case of audio tracks the job will not fail if the checksums + * differ becasue audio CD tracks do not contain error correction data. + * In this case only a warning will be emitted. + * + * Other sector sizes than 2048 bytes for data tracks are not supported yet, + * i.e. Video CDs cannot be verified. + * + * TAO written tracks have two run-out sectors that are not read. + * + * The VerificationJob will also reload the medium before starting. + */ +class K3bVerificationJob : public K3bJob +{ + Q_OBJECT + + public: + K3bVerificationJob( K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bVerificationJob(); + + public slots: + void start(); + void cancel(); + void setDevice( K3bDevice::Device* dev ); + + void clear(); + + /** + * Add a track to be verified. + * \param tracknum The number of the track. If \a tracknum is 0 + * the last track will be verified. + * \param length Set to override the track length from the TOC. This may be + * useful when writing to DVD+RW media and the iso descriptor does not + * contain the exact image size (as true for many commercial Video DVDs) + */ + void addTrack( int tracknum, const QCString& checksum, const K3b::Msf& length = K3b::Msf() ); + + /** + * Handle the special case of iso session growing + */ + void setGrownSessionSize( const K3b::Msf& ); + + private slots: + void slotMediaReloaded( bool success ); + void slotDiskInfoReady( K3bDevice::DeviceHandler* dh ); + void readTrack( int trackIndex ); + void slotMd5JobFinished( bool success ); + void slotReaderProgress( int p ); + void slotReaderFinished( bool success ); + + private: + K3b::Msf trackLength( int trackNum ); + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/jobs/k3bvideodvdtitledetectclippingjob.cpp b/libk3b/jobs/k3bvideodvdtitledetectclippingjob.cpp new file mode 100644 index 0000000..fdcc3a4 --- /dev/null +++ b/libk3b/jobs/k3bvideodvdtitledetectclippingjob.cpp @@ -0,0 +1,291 @@ +/* + * + * $Id: sourceheader 511311 2006-02-19 14:51:05Z trueg $ + * Copyright (C) 2006 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 "k3bvideodvdtitledetectclippingjob.h" + +#include <k3bexternalbinmanager.h> +#include <k3bprocess.h> +#include <k3bcore.h> +#include <k3bglobals.h> + +#include <klocale.h> +#include <kdebug.h> + + +static const int s_unrealisticHighClippingValue = 100000; + + +class K3bVideoDVDTitleDetectClippingJob::Private +{ +public: + const K3bExternalBin* usedTranscodeBin; + + K3bProcess* process; + + bool canceled; + + unsigned int currentChapter; + unsigned int currentFrames; + unsigned int totalChapters; + + int lastProgress; + int lastSubProgress; +}; + + + +K3bVideoDVDTitleDetectClippingJob::K3bVideoDVDTitleDetectClippingJob( K3bJobHandler* hdl, QObject* parent ) + : K3bJob( hdl, parent ), + m_clippingTop( 0 ), + m_clippingBottom( 0 ), + m_clippingLeft( 0 ), + m_clippingRight( 0 ), + m_lowPriority( true ) +{ + d = new Private; + d->process = 0; +} + + +K3bVideoDVDTitleDetectClippingJob::~K3bVideoDVDTitleDetectClippingJob() +{ + delete d->process; + delete d; +} + + +void K3bVideoDVDTitleDetectClippingJob::start() +{ + jobStarted(); + + d->canceled = false; + d->lastProgress = 0; + + // + // It seems as if the last chapter is often way too short + // + d->totalChapters = m_dvd[m_titleNumber-1].numPTTs(); + if( d->totalChapters > 1 && m_dvd[m_titleNumber-1][d->totalChapters-1].playbackTime().totalFrames() < 200 ) + d->totalChapters--; + + // initial values (some way to big value) + m_clippingTop = s_unrealisticHighClippingValue; + m_clippingBottom = s_unrealisticHighClippingValue; + m_clippingLeft = s_unrealisticHighClippingValue; + m_clippingRight = s_unrealisticHighClippingValue; + + d->usedTranscodeBin = k3bcore->externalBinManager()->binObject("transcode"); + if( !d->usedTranscodeBin ) { + emit infoMessage( i18n("%1 executable could not be found.").arg("transcode"), ERROR ); + jobFinished( false ); + return; + } + + if( d->usedTranscodeBin->version < K3bVersion( 1, 0, 0 ) ){ + emit infoMessage( i18n("%1 version %2 is too old.") + .arg("transcode") + .arg(d->usedTranscodeBin->version), ERROR ); + jobFinished( false ); + return; + } + + emit debuggingOutput( "Used versions", "transcode: " + d->usedTranscodeBin->version ); + + if( !d->usedTranscodeBin->copyright.isEmpty() ) + emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3") + .arg(d->usedTranscodeBin->name()) + .arg(d->usedTranscodeBin->version) + .arg(d->usedTranscodeBin->copyright), INFO ); + + emit newTask( i18n("Analysing Title %1 of Video DVD %2").arg(m_titleNumber).arg(m_dvd.volumeIdentifier()) ); + + startTranscode( 1 ); +} + + +void K3bVideoDVDTitleDetectClippingJob::startTranscode( int chapter ) +{ + d->currentChapter = chapter; + d->lastSubProgress = 0; + + // + // If we have only one chapter and it is not longer than 2 minutes (value guessed based on some test DVD) + // use the whole chapter + // + if( d->totalChapters == 1 ) + d->currentFrames = QMIN( 3000, QMAX( 1, m_dvd[m_titleNumber-1][d->currentChapter-1].playbackTime().totalFrames() ) ); + else + d->currentFrames = QMIN( 200, QMAX( 1, m_dvd[m_titleNumber-1][d->currentChapter-1].playbackTime().totalFrames() ) ); + + // + // prepare the process + // + delete d->process; + d->process = new K3bProcess(); + d->process->setSuppressEmptyLines(true); + d->process->setSplitStdout(true); + // connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotTranscodeStderr(const QString&)) ); + connect( d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotTranscodeStderr(const QString&)) ); + connect( d->process, SIGNAL(processExited(KProcess*)), this, SLOT(slotTranscodeExited(KProcess*)) ); + + // the executable + *d->process << d->usedTranscodeBin; + + // low priority + if( m_lowPriority ) + *d->process << "--nice" << "19"; + + // the input + *d->process << "-i" << m_dvd.device()->blockDeviceName(); + + // select the title number and chapter + *d->process << "-T" << QString("%1,%2").arg(m_titleNumber).arg(chapter); + + // null output + *d->process << "-y" << "null,null" << "-o" << "/dev/null"; + + // analyze the first 200 frames + *d->process << "-J" << QString("detectclipping=range=0-%1/5").arg(d->currentFrames); + + // also only decode the first 200 frames + *d->process << "-c" << QString("0-%1").arg(d->currentFrames+1); + + // additional user parameters from config + const QStringList& params = d->usedTranscodeBin->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *d->process << *it; + + // produce some debugging output + kdDebug() << "***** transcode parameters:\n"; + const QValueList<QCString>& args = d->process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << flush << endl; + emit debuggingOutput( d->usedTranscodeBin->name() + " command:", s); + + // start the process + if( !d->process->start( KProcess::NotifyOnExit, KProcess::All ) ) { + // something went wrong when starting the program + // it "should" be the executable + emit infoMessage( i18n("Could not start %1.").arg(d->usedTranscodeBin->name()), K3bJob::ERROR ); + jobFinished(false); + } + else { + emit newSubTask( i18n("Analysing Chapter %1 of %2").arg(chapter).arg(m_dvd[m_titleNumber-1].numPTTs()) ); + emit subPercent( 0 ); + } +} + + +void K3bVideoDVDTitleDetectClippingJob::cancel() +{ + d->canceled = true; + if( d->process && d->process->isRunning() ) + d->process->kill(); +} + + +void K3bVideoDVDTitleDetectClippingJob::slotTranscodeStderr( const QString& line ) +{ + emit debuggingOutput( "transcode", line ); + + // parse progress + // encoding frame [185], 24.02 fps, 93.0%, ETA: 0:00:00, ( 0| 0| 0) + if( line.startsWith( "encoding frame" ) ) { + int pos1 = line.find( '[', 15 ); + int pos2 = line.find( ']', pos1+1 ); + if( pos1 > 0 && pos2 > 0 ) { + bool ok; + int encodedFrames = line.mid( pos1+1, pos2-pos1-1 ).toInt( &ok ); + if( ok ) { + int progress = 100 * encodedFrames / d->currentFrames; + + if( progress > d->lastSubProgress ) { + d->lastSubProgress = progress; + emit subPercent( progress ); + } + + double part = 100.0 / (double)d->totalChapters; + + progress = (int)( ( (double)(d->currentChapter-1) * part ) + + ( (double)progress / (double)d->totalChapters ) + + 0.5 ); + + if( progress > d->lastProgress ) { + d->lastProgress = progress; + emit percent( progress ); + } + } + } + } + + // [detectclipping#0] valid area: X: 5..719 Y: 72..507 -> -j 72,6,68,0 + else if( line.startsWith( "[detectclipping" ) ) { + int pos = line.find( "-j" ); + if( pos > 0 ) { + QStringList values = QStringList::split( ',', line.mid( pos+3 ) ); + m_clippingTop = QMIN( m_clippingTop, values[0].toInt() ); + m_clippingLeft = QMIN( m_clippingLeft, values[1].toInt() ); + m_clippingBottom = QMIN( m_clippingBottom, values[2].toInt() ); + m_clippingRight = QMIN( m_clippingRight, values[3].toInt() ); + } + else + kdDebug() << "(K3bVideoDVDTitleDetectClippingJob) failed to parse line: " << line << endl; + } +} + + +void K3bVideoDVDTitleDetectClippingJob::slotTranscodeExited( KProcess* p ) +{ + switch( p->exitStatus() ) { + case 0: + d->currentChapter++; + if( d->currentChapter > d->totalChapters ) { + // + // check if we did set any values at all + // + if( m_clippingTop == s_unrealisticHighClippingValue ) + m_clippingTop = m_clippingLeft = m_clippingBottom = m_clippingRight = 0; + + if( d->totalChapters < m_dvd[m_titleNumber-1].numPTTs() ) + emit infoMessage( i18n("Ignoring last chapter due to its short playback time."), INFO ); + + jobFinished( true ); + } + else { + startTranscode( d->currentChapter ); + } + break; + + default: + // FIXME: error handling + + if( d->canceled ) { + emit canceled(); + } + else { + emit infoMessage( i18n("%1 returned an unknown error (code %2).") + .arg(d->usedTranscodeBin->name()).arg(p->exitStatus()), + K3bJob::ERROR ); + emit infoMessage( i18n("Please send me an email with the last output."), K3bJob::ERROR ); + } + + jobFinished( false ); + } +} + +#include "k3bvideodvdtitledetectclippingjob.moc" diff --git a/libk3b/jobs/k3bvideodvdtitledetectclippingjob.h b/libk3b/jobs/k3bvideodvdtitledetectclippingjob.h new file mode 100644 index 0000000..b13bbf8 --- /dev/null +++ b/libk3b/jobs/k3bvideodvdtitledetectclippingjob.h @@ -0,0 +1,106 @@ +/* + * + * $Id: sourceheader 511311 2006-02-19 14:51:05Z trueg $ + * Copyright (C) 2006 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. + */ + +#ifndef _K3B_VIDEODVD_TITLE_DETECTCLIPPING_JOB_H_ +#define _K3B_VIDEODVD_TITLE_DETECTCLIPPING_JOB_H_ + +#include <k3b_export.h> +#include <k3bjob.h> +#include <k3bvideodvd.h> + +class KProcess; + +/** + * Job to detect the clipping values for a Video DVD title. + */ +class LIBK3B_EXPORT K3bVideoDVDTitleDetectClippingJob : public K3bJob +{ + Q_OBJECT + + public: + K3bVideoDVDTitleDetectClippingJob( K3bJobHandler* hdl, QObject* parent ); + ~K3bVideoDVDTitleDetectClippingJob(); + + const K3bVideoDVD::VideoDVD& videoDVD() const { return m_dvd; } + int title() const { return m_titleNumber; } + bool lowPriority() const { return m_lowPriority; } + + /** + * Only valid after a successful completion of the job. + */ + int clippingTop() const { return m_clippingTop; } + + /** + * Only valid after a successful completion of the job. + */ + int clippingLeft() const { return m_clippingLeft; } + + /** + * Only valid after a successful completion of the job. + */ + int clippingBottom() const { return m_clippingBottom; } + + /** + * Only valid after a successful completion of the job. + */ + int clippingRight() const { return m_clippingRight; } + + public slots: + void start(); + void cancel(); + + /** + * The device containing the Video DVD + */ + void setVideoDVD( const K3bVideoDVD::VideoDVD& dvd ) { m_dvd = dvd; } + + /** + * Set the title number to be analysed + * + * The default value is 1, denoting the first title. + */ + void setTitle( int t ) { m_titleNumber = t; } + + /** + * If true the transcode processes will be run with a very low scheduling + * priority. + * + * The default is true. + */ + void setLowPriority( bool b ) { m_lowPriority = b; } + + private slots: + void slotTranscodeStderr( const QString& ); + void slotTranscodeExited( KProcess* ); + + private: + void startTranscode( int chapter ); + + K3bVideoDVD::VideoDVD m_dvd; + + int m_clippingTop; + int m_clippingBottom; + int m_clippingLeft; + int m_clippingRight; + + int m_titleNumber; + + bool m_lowPriority; + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/jobs/k3bvideodvdtitletranscodingjob.cpp b/libk3b/jobs/k3bvideodvdtitletranscodingjob.cpp new file mode 100644 index 0000000..9fec637 --- /dev/null +++ b/libk3b/jobs/k3bvideodvdtitletranscodingjob.cpp @@ -0,0 +1,583 @@ +/* + * + * $Id: sourceheader 511311 2006-02-19 14:51:05Z trueg $ + * Copyright (C) 2006 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 "k3bvideodvdtitletranscodingjob.h" + +#include <k3bexternalbinmanager.h> +#include <k3bprocess.h> +#include <k3bcore.h> +#include <k3bglobals.h> + +#include <klocale.h> +#include <kdebug.h> +#include <kstandarddirs.h> + +#include <qfile.h> +#include <qfileinfo.h> + + +class K3bVideoDVDTitleTranscodingJob::Private +{ +public: + const K3bExternalBin* usedTranscodeBin; + + K3bProcess* process; + + QString twoPassEncodingLogFile; + + int currentEncodingPass; + + bool canceled; + + int lastProgress; + int lastSubProgress; +}; + + + +K3bVideoDVDTitleTranscodingJob::K3bVideoDVDTitleTranscodingJob( K3bJobHandler* hdl, QObject* parent ) + : K3bJob( hdl, parent ), + m_clippingTop( 0 ), + m_clippingBottom( 0 ), + m_clippingLeft( 0 ), + m_clippingRight( 0 ), + m_width( 0 ), + m_height( 0 ), + m_titleNumber( 1 ), + m_audioStreamIndex( 0 ), + m_videoCodec( VIDEO_CODEC_FFMPEG_MPEG4 ), + m_audioCodec( AUDIO_CODEC_MP3 ), + m_videoBitrate( 1800 ), + m_audioBitrate( 128 ), + m_audioVBR( false ), + m_resampleAudio( false ), + m_twoPassEncoding( false ), + m_lowPriority( true ) +{ + d = new Private; + d->process = 0; +} + + +K3bVideoDVDTitleTranscodingJob::~K3bVideoDVDTitleTranscodingJob() +{ + delete d->process; + delete d; +} + + +void K3bVideoDVDTitleTranscodingJob::start() +{ + jobStarted(); + + d->canceled = false; + d->lastProgress = 0; + + d->usedTranscodeBin = k3bcore->externalBinManager()->binObject("transcode"); + if( !d->usedTranscodeBin ) { + emit infoMessage( i18n("%1 executable could not be found.").arg("transcode"), ERROR ); + jobFinished( false ); + return; + } + + if( d->usedTranscodeBin->version < K3bVersion( 1, 0, 0 ) ){ + emit infoMessage( i18n("%1 version %2 is too old.") + .arg("transcode") + .arg(d->usedTranscodeBin->version), ERROR ); + jobFinished( false ); + return; + } + + emit debuggingOutput( "Used versions", "transcode: " + d->usedTranscodeBin->version ); + + if( !d->usedTranscodeBin->copyright.isEmpty() ) + emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3") + .arg(d->usedTranscodeBin->name()) + .arg(d->usedTranscodeBin->version) + .arg(d->usedTranscodeBin->copyright), INFO ); + + // + // Let's take a look at the filename + // + if( m_filename.isEmpty() ) { + m_filename = K3b::findTempFile( "avi" ); + } + else { + // let's see if the directory exists and we can write to it + QFileInfo fileInfo( m_filename ); + QFileInfo dirInfo( fileInfo.dirPath() ); + if( !dirInfo.exists() && !KStandardDirs::makeDir( dirInfo.absFilePath() ) ) { + emit infoMessage( i18n("Unable to create folder '%1'").arg(dirInfo.filePath()), ERROR ); + return; + } + else if( !dirInfo.isDir() || !dirInfo.isWritable() ) { + emit infoMessage( i18n("Invalid filename: '%1'").arg(m_filename), ERROR ); + jobFinished( false ); + return; + } + } + + // + // Determine a log file for two-pass encoding + // + d->twoPassEncodingLogFile = K3b::findTempFile( "log" ); + + emit newTask( i18n("Transcoding title %1 from Video DVD %2").arg(m_titleNumber).arg(m_dvd.volumeIdentifier()) ); + + // + // Ok then, let's begin + // + startTranscode( m_twoPassEncoding ? 1 : 0 ); +} + + +void K3bVideoDVDTitleTranscodingJob::startTranscode( int pass ) +{ + d->currentEncodingPass = pass; + d->lastSubProgress = 0; + + QString videoCodecString; + switch( m_videoCodec ) { + case VIDEO_CODEC_XVID: + videoCodecString = "xvid"; + break; + + case VIDEO_CODEC_FFMPEG_MPEG4: + videoCodecString = "ffmpeg"; + break; + + default: + emit infoMessage( i18n("Invalid Video codec set: %1").arg(m_videoCodec), ERROR ); + jobFinished( false ); + return; + } + + QString audioCodecString; + switch( m_audioCodec ) { + case AUDIO_CODEC_MP3: + audioCodecString = "0x55"; + break; + + // ogg only works (as in: transcode does something) with .y <codec>,ogg + // but then the video is garbage (at least to xine and mplayer on my system) + // case AUDIO_CODEC_OGG_VORBIS: + // audioCodecString = "0xfffe"; + // break; + + case AUDIO_CODEC_AC3_STEREO: + case AUDIO_CODEC_AC3_PASSTHROUGH: + audioCodecString = "0x2000"; + break; + + default: + emit infoMessage( i18n("Invalid Audio codec set: %1").arg(m_audioCodec), ERROR ); + jobFinished( false ); + return; + } + + // + // prepare the process + // + delete d->process; + d->process = new K3bProcess(); + d->process->setSuppressEmptyLines(true); + d->process->setSplitStdout(true); + connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotTranscodeStderr(const QString&)) ); + connect( d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotTranscodeStderr(const QString&)) ); + connect( d->process, SIGNAL(processExited(KProcess*)), this, SLOT(slotTranscodeExited(KProcess*)) ); + + // the executable + *d->process << d->usedTranscodeBin; + + // low priority + if( m_lowPriority ) + *d->process << "--nice" << "19"; + + // we only need 100 steps, but to make sure we use 150 + if ( d->usedTranscodeBin->version.simplify() >= K3bVersion( 1, 1, 0 ) ) + *d->process << "--progress_meter" << "2" << "--progress_rate" << QString::number(m_dvd[m_titleNumber-1].playbackTime().totalFrames()/150); + else + *d->process << "--print_status" << QString::number(m_dvd[m_titleNumber-1].playbackTime().totalFrames()/150); + + // the input + *d->process << "-i" << m_dvd.device()->blockDeviceName(); + + // just to make sure + *d->process << "-x" << "dvd"; + + // select the title number + *d->process << "-T" << QString("%1,-1,1").arg( m_titleNumber ); + + // select the audio stream to extract + if ( m_dvd[m_titleNumber-1].numAudioStreams() > 0 ) + *d->process << "-a" << QString::number( m_audioStreamIndex ); + + // clipping + *d->process << "-j" << QString("%1,%2,%3,%4") + .arg(m_clippingTop) + .arg(m_clippingLeft) + .arg(m_clippingBottom) + .arg(m_clippingRight); + + // select the encoding type (single pass or two-pass) and the log file for two-pass encoding + // the latter is unused for pass = 0 + *d->process << "-R" << QString("%1,%2").arg( pass ).arg( d->twoPassEncodingLogFile ); + + // depending on the pass we use different options + if( pass != 1 ) { + // select video codec + *d->process << "-y" << videoCodecString; + + // select the audio codec to use + *d->process << "-N" << audioCodecString; + + if( m_audioCodec == AUDIO_CODEC_AC3_PASSTHROUGH ) { + // keep 5.1 sound + *d->process << "-A"; + } + else { + // audio quality settings + *d->process << "-b" << QString("%1,%2").arg(m_audioBitrate).arg(m_audioVBR ? 1 : 0); + + // resample audio stream to 44.1 khz + if( m_resampleAudio ) + *d->process << "-E" << "44100"; + } + + // the output filename + *d->process << "-o" << m_filename; + } + else { + // gather information about the video stream, ignore audio + *d->process << "-y" << QString("%1,null").arg( videoCodecString ); + + // we ignore the output from the first pass + *d->process << "-o" << "/dev/null"; + } + + // choose the ffmpeg codec + if( m_videoCodec == VIDEO_CODEC_FFMPEG_MPEG4 ) { + *d->process << "-F" << "mpeg4"; + } + + // video bitrate + *d->process << "-w" << QString::number( m_videoBitrate ); + + // video resizing + int usedWidth = m_width; + int usedHeight = m_height; + if( m_width == 0 || m_height == 0 ) { + // + // The "real" size of the video, considering anamorph encoding + // + int realHeight = m_dvd[m_titleNumber-1].videoStream().realPictureHeight(); + int readWidth = m_dvd[m_titleNumber-1].videoStream().realPictureWidth(); + + // + // The clipped size with the correct aspect ratio + // + int clippedHeight = realHeight - m_clippingTop - m_clippingBottom; + int clippedWidth = readWidth - m_clippingLeft - m_clippingRight; + + // + // Now simply resize the clipped video to the wanted size + // + if( usedWidth > 0 ) { + usedHeight = clippedHeight * usedWidth / clippedWidth; + } + else { + if( usedHeight == 0 ) { + // + // This is the default case in which both m_width and m_height are 0. + // The result will be a size of clippedWidth x clippedHeight + // + usedHeight = clippedHeight; + } + usedWidth = clippedWidth * usedHeight / clippedHeight; + } + } + + // + // Now make sure both width and height are multiple of 16 the simple way + // + usedWidth -= usedWidth%16; + usedHeight -= usedHeight%16; + + // we only give information about the resizing of the video once + if( pass < 2 ) + emit infoMessage( i18n("Resizing picture of title %1 to %2x%3").arg(m_titleNumber).arg(usedWidth).arg(usedHeight), INFO ); + *d->process << "-Z" << QString("%1x%2").arg(usedWidth).arg(usedHeight); + + // additional user parameters from config + const QStringList& params = d->usedTranscodeBin->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *d->process << *it; + + // produce some debugging output + kdDebug() << "***** transcode parameters:\n"; + const QValueList<QCString>& args = d->process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << flush << endl; + emit debuggingOutput( d->usedTranscodeBin->name() + " command:", s); + + // start the process + if( !d->process->start( KProcess::NotifyOnExit, KProcess::All ) ) { + // something went wrong when starting the program + // it "should" be the executable + emit infoMessage( i18n("Could not start %1.").arg(d->usedTranscodeBin->name()), K3bJob::ERROR ); + jobFinished(false); + } + else { + if( pass == 0 ) + emit newSubTask( i18n("Single-pass Encoding") ); + else if( pass == 1 ) + emit newSubTask( i18n("Two-pass Encoding: First Pass") ); + else + emit newSubTask( i18n("Two-pass Encoding: Second Pass") ); + + emit subPercent( 0 ); + } +} + + +void K3bVideoDVDTitleTranscodingJob::cancel() +{ + // FIXME: do not cancel before one frame has been encoded. transcode seems to hang then + // find a way to determine all subprocess ids to kill all of them + d->canceled = true; + if( d->process && d->process->isRunning() ) + d->process->kill(); +} + + +void K3bVideoDVDTitleTranscodingJob::cleanup( bool success ) +{ + if( QFile::exists( d->twoPassEncodingLogFile ) ) { + QFile::remove( d->twoPassEncodingLogFile ); + } + + if( !success && QFile::exists( m_filename ) ) { + emit infoMessage( i18n("Removing incomplete video file '%1'").arg(m_filename), INFO ); + QFile::remove( m_filename ); + } +} + + +void K3bVideoDVDTitleTranscodingJob::slotTranscodeStderr( const QString& line ) +{ + emit debuggingOutput( "transcode", line ); + + // parse progress + // encoding frames [000000-000144], 27.58 fps, EMT: 0:00:05, ( 0| 0| 0) + if( line.startsWith( "encoding frame" ) ) { + int pos1 = line.find( '-', 15 ); + int pos2 = line.find( ']', pos1+1 ); + if( pos1 > 0 && pos2 > 0 ) { + bool ok; + int encodedFrames = line.mid( pos1+1, pos2-pos1-1 ).toInt( &ok ); + if( ok ) { + int progress = 100 * encodedFrames / m_dvd[m_titleNumber-1].playbackTime().totalFrames(); + + if( progress > d->lastSubProgress ) { + d->lastSubProgress = progress; + emit subPercent( progress ); + } + + if( m_twoPassEncoding ) { + progress /= 2; + if( d->currentEncodingPass == 2 ) + progress += 50; + } + + if( progress > d->lastProgress ) { + d->lastProgress = progress; + emit percent( progress ); + } + } + } + } +} + + +void K3bVideoDVDTitleTranscodingJob::slotTranscodeExited( KProcess* p ) +{ + if( d->canceled ) { + emit canceled(); + cleanup( false ); + jobFinished( false ); + } + else if( p->normalExit() ) { + switch( p->exitStatus() ) { + case 0: + if( d->currentEncodingPass == 1 ) { + emit percent( 50 ); + // start second encoding pass + startTranscode( 2 ); + } + else { + emit percent( 100 ); + cleanup( true ); + jobFinished( true ); + } + break; + + default: + // FIXME: error handling + + emit infoMessage( i18n("%1 returned an unknown error (code %2).") + .arg(d->usedTranscodeBin->name()).arg(p->exitStatus()), + K3bJob::ERROR ); + emit infoMessage( i18n("Please send me an email with the last output."), K3bJob::ERROR ); + + cleanup( false ); + jobFinished( false ); + } + } + else { + cleanup( false ); + emit infoMessage( i18n("Execution of %1 failed.").arg("transcode"), ERROR ); + emit infoMessage( i18n("Please consult the debugging output for details."), ERROR ); + jobFinished( false ); + } +} + + +void K3bVideoDVDTitleTranscodingJob::setClipping( int top, int left, int bottom, int right ) +{ + m_clippingTop = top; + m_clippingLeft = left; + m_clippingBottom = bottom; + m_clippingRight = right; + + // + // transcode seems unable to handle different clipping values for left and right + // + m_clippingLeft = m_clippingRight = QMIN( m_clippingRight, m_clippingLeft ); +} + + +void K3bVideoDVDTitleTranscodingJob::setSize( int width, int height ) +{ + m_width = width; + m_height = height; +} + + +QString K3bVideoDVDTitleTranscodingJob::audioCodecString( K3bVideoDVDTitleTranscodingJob::AudioCodec codec ) +{ + switch( codec ) { + case AUDIO_CODEC_AC3_STEREO: + return i18n("AC3 (Stereo)"); + case AUDIO_CODEC_AC3_PASSTHROUGH: + return i18n("AC3 (Pass-through)"); + case AUDIO_CODEC_MP3: + return i18n("MPEG1 Layer III"); + default: + return "unknown audio codec"; + } +} + + +QString K3bVideoDVDTitleTranscodingJob::videoCodecString( K3bVideoDVDTitleTranscodingJob::VideoCodec codec ) +{ + switch( codec ) { + case VIDEO_CODEC_FFMPEG_MPEG4: + return i18n("MPEG4 (FFMPEG)"); + case VIDEO_CODEC_XVID: + return i18n("XviD"); + default: + return "unknown video codec"; + } +} + + +QString K3bVideoDVDTitleTranscodingJob::videoCodecDescription( K3bVideoDVDTitleTranscodingJob::VideoCodec codec ) +{ + switch( codec ) { + case VIDEO_CODEC_FFMPEG_MPEG4: + return i18n("FFmpeg is an open-source project trying to support most video and audio codecs used " + "these days. Its subproject libavcodec forms the basis for multimedia players such as " + "xine or mplayer.") + + "<br>" + + i18n("FFmpeg contains an implementation of the MPEG-4 video encoding standard which produces " + "high quality results."); + case VIDEO_CODEC_XVID: + return i18n("XviD is a free and open source MPEG-4 video codec. XviD was created by a group of " + "volunteer programmers after the OpenDivX source was closed in July 2001.") + + "<br>" + + i18n("XviD features MPEG-4 Advanced Profile settings such as b-frames, global " + "and quarter pixel motion compensation, lumi masking, trellis quantization, and " + "H.263, MPEG and custom quantization matrices.") + + "<br>" + + i18n("XviD is a primary competitor of DivX (XviD being DivX spelled backwards). " + "While DivX is closed source and may only run on Windows, Mac OS and Linux, " + "XviD is open source and can potentially run on any platform.") + + "<br><em>" + + i18n("(Description taken from the Wikipedia article)") + + "</em>"; + default: + return "unknown video codec"; + } +} + + +QString K3bVideoDVDTitleTranscodingJob::audioCodecDescription( K3bVideoDVDTitleTranscodingJob::AudioCodec codec ) +{ + static QString s_ac3General = i18n("AC3, better known as Dolby Digital is standardized as ATSC A/52. " + "It contains up to 6 total channels of sound."); + switch( codec ) { + case AUDIO_CODEC_AC3_STEREO: + return s_ac3General + + "<br>" + i18n("With this setting K3b will create a two-channel stereo " + "Dolby Digital audio stream."); + case AUDIO_CODEC_AC3_PASSTHROUGH: + return s_ac3General + + "<br>" + i18n("With this setting K3b will use the Dolby Digital audio stream " + "from the source DVD without changing it.") + + "<br>" + i18n("Use this setting to preserve 5.1 channel sound from the DVD."); + case AUDIO_CODEC_MP3: + return i18n("MPEG1 Layer III is better known as MP3 and is the most used lossy audio format.") + + "<br>" + i18n("With this setting K3b will create a two-channel stereo MPEG1 Layer III audio stream."); + default: + return "unknown audio codec"; + } +} + + +bool K3bVideoDVDTitleTranscodingJob::transcodeBinaryHasSupportFor( K3bVideoDVDTitleTranscodingJob::VideoCodec codec, const K3bExternalBin* bin ) +{ + static char* s_codecFeatures[] = { "xvid", "ffmpeg" }; + if( !bin ) + bin = k3bcore->externalBinManager()->binObject("transcode"); + if( !bin ) + return false; + return bin->hasFeature( QString::fromLatin1( s_codecFeatures[(int)codec] ) ); +} + + +bool K3bVideoDVDTitleTranscodingJob::transcodeBinaryHasSupportFor( K3bVideoDVDTitleTranscodingJob::AudioCodec codec, const K3bExternalBin* bin ) +{ + static char* s_codecFeatures[] = { "lame", "ac3", "ac3" }; + if( !bin ) + bin = k3bcore->externalBinManager()->binObject("transcode"); + if( !bin ) + return false; + return bin->hasFeature( QString::fromLatin1( s_codecFeatures[(int)codec] ) ); +} + +#include "k3bvideodvdtitletranscodingjob.moc" diff --git a/libk3b/jobs/k3bvideodvdtitletranscodingjob.h b/libk3b/jobs/k3bvideodvdtitletranscodingjob.h new file mode 100644 index 0000000..77a48b5 --- /dev/null +++ b/libk3b/jobs/k3bvideodvdtitletranscodingjob.h @@ -0,0 +1,275 @@ +/* + * + * $Id: sourceheader 511311 2006-02-19 14:51:05Z trueg $ + * Copyright (C) 2006 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. + */ + +#ifndef _K3B_VIDEODVD_TITLE_TRANSCODING_JOB_H_ +#define _K3B_VIDEODVD_TITLE_TRANSCODING_JOB_H_ + +#include <k3b_export.h> +#include <k3bjob.h> +#include <k3bvideodvd.h> + +class KProcess; +class K3bExternalBin; + + +/** + * The K3bVideoDVDTitleTranscodingJob rips a Video DVD title directly + * from the medium and transcodes it on-the-fly to, for example, an XviD video + * + * For now only one audio stream is supported. + */ +class LIBK3B_EXPORT K3bVideoDVDTitleTranscodingJob : public K3bJob +{ + Q_OBJECT + + public: + K3bVideoDVDTitleTranscodingJob( K3bJobHandler* hdl, QObject* parent ); + ~K3bVideoDVDTitleTranscodingJob(); + + /** + * The video codecs supported by this job. + */ + enum VideoCodec { + VIDEO_CODEC_XVID, + VIDEO_CODEC_FFMPEG_MPEG4, + VIDEO_CODEC_NUM_ENTRIES /**< Do not use this as a codec. */ + }; + + /** + * The audio codecs supported by this job. + */ + enum AudioCodec { + AUDIO_CODEC_MP3, + /* AUDIO_CODEC_OGG_VORBIS,*/ + AUDIO_CODEC_AC3_STEREO, + AUDIO_CODEC_AC3_PASSTHROUGH, + AUDIO_CODEC_NUM_ENTRIES /**< Do not use this as a codec. */ + }; + + const K3bVideoDVD::VideoDVD& videoDVD() const { return m_dvd; } + int title() const { return m_titleNumber; } + int audioStream() const { return m_audioStreamIndex; } + int clippingTop() const { return m_clippingTop; } + int clippingLeft() const { return m_clippingLeft; } + int clippingBottom() const { return m_clippingBottom; } + int clippingRight() const { return m_clippingRight; } + int height() const { return m_height; } + int width() const { return m_width; } + const QString& filename() { return m_filename; } + VideoCodec videoCodec() const { return m_videoCodec; } + int videoBitrate() const { return m_videoBitrate; } + bool twoPassEncoding() const { return m_twoPassEncoding; } + AudioCodec audioCodec() const { return m_audioCodec; } + int audioBitrate() const { return m_audioBitrate; } + bool audioVBR() const { return m_audioVBR; } + bool resampleAudioTo44100() const { return m_resampleAudio; } + bool lowPriority() const { return m_lowPriority; } + + /** + * \param bin If 0 the default binary from K3bCore will be used + */ + static bool transcodeBinaryHasSupportFor( VideoCodec codec, const K3bExternalBin* bin = 0 ); + + /** + * \param bin If 0 the default binary from K3bCore will be used + */ + static bool transcodeBinaryHasSupportFor( AudioCodec codec, const K3bExternalBin* bin = 0 ); + + static QString videoCodecString( VideoCodec ); + static QString audioCodecString( AudioCodec ); + + static QString videoCodecDescription( VideoCodec ); + static QString audioCodecDescription( AudioCodec ); + + public slots: + void start(); + void cancel(); + + /** + * The device containing the Video DVD + */ + void setVideoDVD( const K3bVideoDVD::VideoDVD& dvd ) { m_dvd = dvd; } + + /** + * Set the title number to be transcoded + * + * The default value is 1, denoting the first title. + */ + void setTitle( int t ) { m_titleNumber = t; } + + /** + * Set the audio stream to use. + * + * For now K3b does not support encoding multiple audio streams + * in one video file. + * + * The default value is 0, meaning that the first audio stream will + * be encoded. + */ + void setAudioStream( int i ) { m_audioStreamIndex = i; } + + /** + * Set the clipping values for the Video title. + * The clipping will be applied before the transcoding. + * + * For now it is not possible to use different clipping values for left + * and right as transcode cannot handle this. Thus, the job uses the + * smaller value for both the left and right clipping. + * + * The default is to not clip the video. + */ + void setClipping( int top, int left, int bottom, int right ); + + /** + * The size of the resulting transcoded video. + * + * The default is to automatically adjust the size (width=height=0), which + * essentially means that anamorph encoded source material will be resized + * according to its aspect ratio. + * + * It is also possible to set just the width or just the height and leave + * the other value to 0 which will then be determined automatically. + * + * The clipping values will be taken into account if at least one value + * is determined automatically. + * + * The width and height values have to be a multiple of 16. If it is not, + * they will be changed accordingly. + * + * FIXME: GET INFORMATION: why a multiple of 16 and not 8 or 32? + */ + void setSize( int width, int height ); + + /** + * The filename to write the resulting video to. + * + * The default is some automatically generated filename + * in the default K3b temp directory. + */ + void setFilename( const QString& name ) { m_filename = name; } + + /** + * Set the video codec used to encode the video title. + * + * The default is VIDEO_CODEC_FFMPEG_MPEG4 + */ + void setVideoCodec( VideoCodec codec ) { m_videoCodec = codec; } + + /** + * Set the bitrate used to encode the video. + * + * The default is 1800 + */ + void setVideoBitrate( int bitrate ) { m_videoBitrate = bitrate; } + + /** + * Set if the job should use two-pass encoding to improve + * the quality of the resulting video. + * + * The default is false. + */ + void setTwoPassEncoding( bool b ) { m_twoPassEncoding = b; } + + /** + * Set the audio codec used to encode the audio stream + * in the video title. + * + * The default is AUDIO_CODEC_MP3 + */ + void setAudioCodec( AudioCodec codec ) { m_audioCodec = codec; } + + /** + * Set the bitrate used to encode the audio stream. + * + * The default is 128 + * + * In case of the AC3 codec the bitrate can be some value between 32 and 640. + * + * For the AC3 passthrough mode the bitrate is ignored. + */ + void setAudioBitrate( int bitrate ) { m_audioBitrate = bitrate; } + + /** + * Set if the audio stream should be encoded with a variable bitrate. + * + * The default is false. + * + * For the AC3 passthrough mode the bitrate is ignored. + */ + void setAudioVBR( bool vbr ) { m_audioVBR = vbr; } + + /** + * Set if the audio data should be resampled to 44100 Hz/s + * + * The default is false. + * + * For the AC3 passthrough mode this is ignored. + */ + void setResampleAudioTo44100( bool b ) { m_resampleAudio = b; } + + /** + * If true the transcode processes will be run with a very low scheduling + * priority. + * + * The default is true. + */ + void setLowPriority( bool b ) { m_lowPriority = b; } + + private slots: + void slotTranscodeStderr( const QString& ); + void slotTranscodeExited( KProcess* ); + + private: + /** + * \param 0 - single pass encoding + * 1 - two pass encoding/first pass + * 2 - two pass encoding/second pass + */ + void startTranscode( int pass ); + + void cleanup( bool success ); + + K3bVideoDVD::VideoDVD m_dvd; + + QString m_filename; + + int m_clippingTop; + int m_clippingBottom; + int m_clippingLeft; + int m_clippingRight; + + int m_width; + int m_height; + + int m_titleNumber; + int m_audioStreamIndex; + + VideoCodec m_videoCodec; + AudioCodec m_audioCodec; + + int m_videoBitrate; + int m_audioBitrate; + bool m_audioVBR; + + bool m_resampleAudio; + bool m_twoPassEncoding; + + bool m_lowPriority; + + class Private; + Private* d; +}; + +#endif |