summaryrefslogtreecommitdiffstats
path: root/libk3b/jobs
diff options
context:
space:
mode:
Diffstat (limited to 'libk3b/jobs')
-rw-r--r--libk3b/jobs/Makefile.am43
-rw-r--r--libk3b/jobs/k3baudiocuefilewritingjob.cpp272
-rw-r--r--libk3b/jobs/k3baudiocuefilewritingjob.h79
-rw-r--r--libk3b/jobs/k3baudiosessionreadingjob.cpp278
-rw-r--r--libk3b/jobs/k3baudiosessionreadingjob.h75
-rw-r--r--libk3b/jobs/k3bbinimagewritingjob.cpp234
-rw-r--r--libk3b/jobs/k3bbinimagewritingjob.h79
-rw-r--r--libk3b/jobs/k3bblankingjob.cpp176
-rw-r--r--libk3b/jobs/k3bblankingjob.h71
-rw-r--r--libk3b/jobs/k3bcdcopyjob.cpp1213
-rw-r--r--libk3b/jobs/k3bcdcopyjob.h117
-rw-r--r--libk3b/jobs/k3bcdda2wavreader.cpp254
-rw-r--r--libk3b/jobs/k3bcdda2wavreader.h70
-rw-r--r--libk3b/jobs/k3bclonejob.cpp375
-rw-r--r--libk3b/jobs/k3bclonejob.h99
-rw-r--r--libk3b/jobs/k3bclonetocreader.cpp235
-rw-r--r--libk3b/jobs/k3bclonetocreader.h45
-rw-r--r--libk3b/jobs/k3bdatatrackreader.cpp515
-rw-r--r--libk3b/jobs/k3bdatatrackreader.h87
-rw-r--r--libk3b/jobs/k3bdvdcopyjob.cpp894
-rw-r--r--libk3b/jobs/k3bdvdcopyjob.h99
-rw-r--r--libk3b/jobs/k3bdvdformattingjob.cpp536
-rw-r--r--libk3b/jobs/k3bdvdformattingjob.h91
-rw-r--r--libk3b/jobs/k3biso9660imagewritingjob.cpp458
-rw-r--r--libk3b/jobs/k3biso9660imagewritingjob.h98
-rw-r--r--libk3b/jobs/k3breadcdreader.cpp335
-rw-r--r--libk3b/jobs/k3breadcdreader.h91
-rw-r--r--libk3b/jobs/k3bverificationjob.cpp384
-rw-r--r--libk3b/jobs/k3bverificationjob.h92
-rw-r--r--libk3b/jobs/k3bvideodvdtitledetectclippingjob.cpp291
-rw-r--r--libk3b/jobs/k3bvideodvdtitledetectclippingjob.h106
-rw-r--r--libk3b/jobs/k3bvideodvdtitletranscodingjob.cpp583
-rw-r--r--libk3b/jobs/k3bvideodvdtitletranscodingjob.h275
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