summaryrefslogtreecommitdiffstats
path: root/libk3b/projects/audiocd
diff options
context:
space:
mode:
Diffstat (limited to 'libk3b/projects/audiocd')
-rw-r--r--libk3b/projects/audiocd/Makefile.am35
-rw-r--r--libk3b/projects/audiocd/k3baudiocdtrackdrag.cpp109
-rw-r--r--libk3b/projects/audiocd/k3baudiocdtrackdrag.h50
-rw-r--r--libk3b/projects/audiocd/k3baudiocdtracksource.cpp262
-rw-r--r--libk3b/projects/audiocd/k3baudiocdtracksource.h99
-rw-r--r--libk3b/projects/audiocd/k3baudiodatasource.cpp210
-rw-r--r--libk3b/projects/audiocd/k3baudiodatasource.h168
-rw-r--r--libk3b/projects/audiocd/k3baudiodatasourceiterator.cpp71
-rw-r--r--libk3b/projects/audiocd/k3baudiodatasourceiterator.h61
-rw-r--r--libk3b/projects/audiocd/k3baudiodoc.cpp1127
-rw-r--r--libk3b/projects/audiocd/k3baudiodoc.h263
-rw-r--r--libk3b/projects/audiocd/k3baudiofile.cpp112
-rw-r--r--libk3b/projects/audiocd/k3baudiofile.h85
-rw-r--r--libk3b/projects/audiocd/k3baudioimager.cpp203
-rw-r--r--libk3b/projects/audiocd/k3baudioimager.h59
-rw-r--r--libk3b/projects/audiocd/k3baudiojob.cpp864
-rw-r--r--libk3b/projects/audiocd/k3baudiojob.h107
-rw-r--r--libk3b/projects/audiocd/k3baudiojobtempdata.cpp132
-rw-r--r--libk3b/projects/audiocd/k3baudiojobtempdata.h64
-rw-r--r--libk3b/projects/audiocd/k3baudiomaxspeedjob.cpp224
-rw-r--r--libk3b/projects/audiocd/k3baudiomaxspeedjob.h43
-rw-r--r--libk3b/projects/audiocd/k3baudionormalizejob.cpp205
-rw-r--r--libk3b/projects/audiocd/k3baudionormalizejob.h63
-rw-r--r--libk3b/projects/audiocd/k3baudiotrack.cpp628
-rw-r--r--libk3b/projects/audiocd/k3baudiotrack.h214
-rw-r--r--libk3b/projects/audiocd/k3baudiozerodata.cpp115
-rw-r--r--libk3b/projects/audiocd/k3baudiozerodata.h55
27 files changed, 5628 insertions, 0 deletions
diff --git a/libk3b/projects/audiocd/Makefile.am b/libk3b/projects/audiocd/Makefile.am
new file mode 100644
index 0000000..60942cc
--- /dev/null
+++ b/libk3b/projects/audiocd/Makefile.am
@@ -0,0 +1,35 @@
+AM_CPPFLAGS= -I$(srcdir)/../../../src \
+ -I$(srcdir)/.. \
+ -I$(srcdir)/../../core \
+ -I$(srcdir)/../../plugin \
+ -I$(srcdir)/../../cddb \
+ -I$(srcdir)/../../../libk3bdevice \
+ -I$(srcdir)/../../tools $(all_includes)
+
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libaudio.la
+
+libaudio_la_SOURCES = k3baudiojob.cpp \
+ k3baudiotrack.cpp \
+ k3baudiodoc.cpp \
+ k3baudiofile.cpp \
+ k3baudiozerodata.cpp \
+ k3baudiodatasource.cpp \
+ k3baudionormalizejob.cpp \
+ k3baudiojobtempdata.cpp \
+ k3baudioimager.cpp \
+ k3baudiomaxspeedjob.cpp \
+ k3baudiocdtracksource.cpp \
+ k3baudiocdtrackdrag.cpp \
+ k3baudiodatasourceiterator.cpp
+
+include_HEADERS = k3baudiodoc.h \
+ k3baudiojob.h \
+ k3baudiocdtrackdrag.h \
+ k3baudiotrack.h \
+ k3baudiodatasource.h \
+ k3baudiofile.h \
+ k3baudiozerodata.h \
+ k3baudiocdtracksource.h \
+ k3baudiodatasourceiterator.h
diff --git a/libk3b/projects/audiocd/k3baudiocdtrackdrag.cpp b/libk3b/projects/audiocd/k3baudiocdtrackdrag.cpp
new file mode 100644
index 0000000..8429f25
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiocdtrackdrag.cpp
@@ -0,0 +1,109 @@
+/*
+ *
+ * $Id: k3baudiocdtrackdrag.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 "k3baudiocdtrackdrag.h"
+
+#include <k3bdevice.h>
+#include <k3bdevicemanager.h>
+#include <k3btoc.h>
+#include <k3btrack.h>
+#include <k3bcore.h>
+
+#include <qdatastream.h>
+#include <qcstring.h>
+
+
+// FIXME: multiple tracks
+K3bAudioCdTrackDrag::K3bAudioCdTrackDrag( const K3bDevice::Toc& toc, const QValueList<int>& cdTrackNumbers,
+ const K3bCddbResultEntry& cddb,
+ K3bDevice::Device* lastDev, QWidget* dragSource, const char* name )
+ : QStoredDrag( "k3b/audio_track_drag", dragSource, name ),
+ m_toc(toc),
+ m_cdTrackNumbers(cdTrackNumbers),
+ m_cddb(cddb),
+ m_device(lastDev)
+{
+ QByteArray data;
+ QDataStream s( data, IO_WriteOnly );
+ s << (unsigned int)toc.count();
+ for( K3bDevice::Toc::const_iterator it = toc.begin(); it != toc.end(); ++it ) {
+ const K3bDevice::Track& track = *it;
+ s << track.firstSector().lba() << track.lastSector().lba();
+ }
+ QTextStream t( s.device() );
+ t << cddb.cdArtist << endl
+ << cddb.cdTitle << endl;
+ for( unsigned int i = 0; i < toc.count(); ++i ) {
+ t << cddb.artists[i] << endl
+ << cddb.titles[i] << endl;
+ }
+
+ s << (unsigned int)cdTrackNumbers.count();
+
+ for( QValueList<int>::const_iterator it = cdTrackNumbers.begin();
+ it != cdTrackNumbers.end(); ++it )
+ s << *it;
+
+ if( lastDev )
+ t << lastDev->blockDeviceName() << endl;
+ else
+ t << endl;
+
+ // TODO: the rest
+ setEncodedData( data );
+}
+
+
+bool K3bAudioCdTrackDrag::decode( const QMimeSource* e,
+ K3bDevice::Toc& toc, QValueList<int>& trackNumbers,
+ K3bCddbResultEntry& cddb, K3bDevice::Device** dev )
+{
+ QByteArray data = e->encodedData( "k3b/audio_track_drag" );
+
+ QDataStream s( data, IO_ReadOnly );
+
+ unsigned int trackCnt;
+ s >> trackCnt;
+ for( unsigned int i = 0; i < trackCnt; ++i ) {
+ int fs, ls;
+ s >> fs;
+ s >> ls;
+ toc.append( K3bDevice::Track( fs, ls, K3bDevice::Track::AUDIO ) );
+ }
+
+ QTextStream t( s.device() );
+ cddb.artists.clear();
+ cddb.titles.clear();
+ cddb.cdArtist = t.readLine();
+ cddb.cdTitle = t.readLine();
+ for( unsigned int i = 0; i < trackCnt; ++i ) {
+ cddb.artists.append( t.readLine() );
+ cddb.titles.append( t.readLine() );
+ }
+
+ s >> trackCnt;
+ trackNumbers.clear();
+ for( unsigned int i = 0; i < trackCnt; ++i ) {
+ int trackNumber = 0;
+ s >> trackNumber;
+ trackNumbers.append( trackNumber );
+ }
+
+ QString devName = t.readLine();
+ if( dev && !devName.isEmpty() )
+ *dev = k3bcore->deviceManager()->findDevice( devName );
+
+ return true;
+}
diff --git a/libk3b/projects/audiocd/k3baudiocdtrackdrag.h b/libk3b/projects/audiocd/k3baudiocdtrackdrag.h
new file mode 100644
index 0000000..3148466
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiocdtrackdrag.h
@@ -0,0 +1,50 @@
+/*
+ *
+ * $Id: k3baudiocdtrackdrag.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_CDTRACK_DRAG_H_
+#define _K3B_AUDIO_CDTRACK_DRAG_H_
+
+#include <qdragobject.h>
+#include <qcstring.h>
+#include <qvaluelist.h>
+
+#include <k3btoc.h>
+#include <k3bcddbresult.h>
+#include <k3bdevice.h>
+#include "k3b_export.h"
+
+class LIBK3B_EXPORT K3bAudioCdTrackDrag : public QStoredDrag
+{
+ public:
+ K3bAudioCdTrackDrag( const K3bDevice::Toc& toc, const QValueList<int>& cdTrackNumbers, const K3bCddbResultEntry& cddb,
+ K3bDevice::Device* lastDev = 0, QWidget* dragSource = 0, const char* name = 0 );
+
+ const K3bDevice::Toc& toc() const { return m_toc; }
+ const QValueList<int>& cdTrackNumbers() const { return m_cdTrackNumbers; }
+ const K3bCddbResultEntry& cddbEntry() const { return m_cddb; }
+
+ bool provides( const char* mimetype ) const { return !qstrcmp( mimetype, "k3b/audio_track_drag" ); }
+
+ static bool canDecode( const QMimeSource* s ) { return s->provides( "k3b/audio_track_drag" ); }
+ static bool decode( const QMimeSource* s, K3bDevice::Toc&, QValueList<int>& trackNumbers, K3bCddbResultEntry&, K3bDevice::Device** dev = 0 );
+
+ private:
+ K3bDevice::Toc m_toc;
+ QValueList<int> m_cdTrackNumbers;
+ K3bCddbResultEntry m_cddb;
+ K3bDevice::Device* m_device;
+};
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudiocdtracksource.cpp b/libk3b/projects/audiocd/k3baudiocdtracksource.cpp
new file mode 100644
index 0000000..b61e865
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiocdtracksource.cpp
@@ -0,0 +1,262 @@
+/*
+ *
+ * $Id: k3baudiocdtracksource.cpp 676194 2007-06-16 08:59:19Z 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 "k3baudiocdtracksource.h"
+#include "k3baudiotrack.h"
+#include "k3baudiodoc.h"
+
+#include <k3bthreadwidget.h>
+#include <k3btoc.h>
+#include <k3bdevice.h>
+#include <k3bdevicemanager.h>
+#include <k3bcdparanoialib.h>
+#include <k3bdeviceselectiondialog.h>
+#include <k3bcore.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+
+K3bAudioCdTrackSource::K3bAudioCdTrackSource( const K3bDevice::Toc& toc, int cdTrackNumber,
+ const K3bCddbResultEntry& cddb, K3bDevice::Device* dev )
+ : K3bAudioDataSource(),
+ m_discId( toc.discId() ),
+ m_length( toc[cdTrackNumber-1].length() ),
+ m_toc( toc ),
+ m_cdTrackNumber( cdTrackNumber ),
+ m_cddbEntry( cddb ),
+ m_lastUsedDevice( dev ),
+ m_cdParanoiaLib( 0 ),
+ m_initialized( false )
+{
+}
+
+
+K3bAudioCdTrackSource::K3bAudioCdTrackSource( unsigned int discid, const K3b::Msf& length, int cdTrackNumber,
+ const QString& artist, const QString& title,
+ const QString& cdArtist, const QString& cdTitle )
+ : K3bAudioDataSource(),
+ m_discId( discid ),
+ m_length( length ),
+ m_cdTrackNumber( cdTrackNumber ),
+ m_lastUsedDevice( 0 ),
+ m_cdParanoiaLib( 0 ),
+ m_initialized( false )
+{
+ for( int i = 1; i < cdTrackNumber; ++i ) {
+ m_cddbEntry.titles.append( QString::null );
+ m_cddbEntry.artists.append( QString::null );
+ }
+ m_cddbEntry.titles.append( title );
+ m_cddbEntry.artists.append( artist );
+ m_cddbEntry.cdTitle = cdTitle;
+ m_cddbEntry.cdArtist = cdArtist;
+}
+
+
+K3bAudioCdTrackSource::K3bAudioCdTrackSource( const K3bAudioCdTrackSource& source )
+ : K3bAudioDataSource( source ),
+ m_discId( source.m_discId ),
+ m_toc( source.m_toc ),
+ m_cdTrackNumber( source.m_cdTrackNumber ),
+ m_cddbEntry( source.m_cddbEntry ),
+ m_lastUsedDevice( source.m_lastUsedDevice ),
+ m_cdParanoiaLib( 0 ),
+ m_initialized( false )
+{
+}
+
+
+K3bAudioCdTrackSource::~K3bAudioCdTrackSource()
+{
+ closeParanoia();
+ delete m_cdParanoiaLib;
+}
+
+
+bool K3bAudioCdTrackSource::initParanoia()
+{
+ if( !m_initialized ) {
+ if( !m_cdParanoiaLib )
+ m_cdParanoiaLib = K3bCdparanoiaLib::create();
+
+ if( m_cdParanoiaLib ) {
+ m_lastUsedDevice = searchForAudioCD();
+
+ // ask here for the cd since searchForAudioCD() may also be called from outside
+ if( !m_lastUsedDevice ) {
+ // could not find the CD, so ask for it
+ QString s = i18n("Please insert Audio CD %1%2")
+ .arg(m_discId, 0, 16)
+ .arg(m_cddbEntry.cdTitle.isEmpty() || m_cddbEntry.cdArtist.isEmpty()
+ ? QString::null
+ : " (" + m_cddbEntry.cdArtist + " - " + m_cddbEntry.cdTitle + ")");
+
+ while( K3bDevice::Device* dev = K3bThreadWidget::selectDevice( track()->doc()->view(), s ) ) {
+ if( searchForAudioCD( dev ) ) {
+ m_lastUsedDevice = dev;
+ break;
+ }
+ }
+ }
+
+ // user canceled
+ if( !m_lastUsedDevice )
+ return false;
+
+ k3bcore->blockDevice( m_lastUsedDevice );
+
+ if( m_toc.isEmpty() )
+ m_toc = m_lastUsedDevice->readToc();
+
+ if( !m_cdParanoiaLib->initParanoia( m_lastUsedDevice, m_toc ) ) {
+ k3bcore->unblockDevice( m_lastUsedDevice );
+ return false;
+ }
+
+ if( doc() ) {
+ m_cdParanoiaLib->setParanoiaMode( doc()->audioRippingParanoiaMode() );
+ m_cdParanoiaLib->setNeverSkip( !doc()->audioRippingIgnoreReadErrors() );
+ m_cdParanoiaLib->setMaxRetries( doc()->audioRippingRetries() );
+ }
+
+ m_cdParanoiaLib->initReading( m_toc[m_cdTrackNumber-1].firstSector().lba() + startOffset().lba() + m_position.lba(),
+ m_toc[m_cdTrackNumber-1].firstSector().lba() + lastSector().lba() );
+
+ // we only block during the initialization because we cannot determine the end of the reading process :(
+ k3bcore->unblockDevice( m_lastUsedDevice );
+
+ m_initialized = true;
+ kdDebug() << "(K3bAudioCdTrackSource) initialized." << endl;
+ }
+ }
+
+ return m_initialized;
+}
+
+
+void K3bAudioCdTrackSource::closeParanoia()
+{
+ if( m_cdParanoiaLib && m_initialized ) {
+ m_cdParanoiaLib->close();
+ }
+ m_initialized = false;
+}
+
+
+K3bDevice::Device* K3bAudioCdTrackSource::searchForAudioCD() const
+{
+ kdDebug() << "(K3bAudioCdTrackSource::searchForAudioCD()" << endl;
+ // first try the saved device
+ if( m_lastUsedDevice && searchForAudioCD( m_lastUsedDevice ) )
+ return m_lastUsedDevice;
+
+ const QPtrList<K3bDevice::Device>& devices = k3bcore->deviceManager()->readingDevices();
+ for( QPtrListIterator<K3bDevice::Device> it(devices); *it; ++it ) {
+ if( searchForAudioCD( *it ) ) {
+ return *it;
+ }
+ }
+
+ kdDebug() << "(K3bAudioCdTrackSource::searchForAudioCD) failed." << endl;
+
+ return 0;
+}
+
+
+bool K3bAudioCdTrackSource::searchForAudioCD( K3bDevice::Device* dev ) const
+{
+ kdDebug() << "(K3bAudioCdTrackSource::searchForAudioCD(" << dev->description() << ")" << endl;
+ K3bDevice::Toc toc = dev->readToc();
+ return ( toc.discId() == m_discId );
+}
+
+
+void K3bAudioCdTrackSource::setDevice( K3bDevice::Device* dev )
+{
+ if( dev && dev != m_lastUsedDevice ) {
+ m_lastUsedDevice = dev;
+ if( m_initialized ) {
+ }
+ }
+}
+
+
+K3b::Msf K3bAudioCdTrackSource::originalLength() const
+{
+ return m_length;
+}
+
+
+bool K3bAudioCdTrackSource::seek( const K3b::Msf& msf )
+{
+ // HACK: to reinitialize every time we restart the decoding
+ if( msf == 0 && m_cdParanoiaLib )
+ closeParanoia();
+
+ m_position = msf;
+
+ if( m_cdParanoiaLib )
+ m_cdParanoiaLib->initReading( m_toc[m_cdTrackNumber-1].firstSector().lba() + startOffset().lba() + m_position.lba(),
+ m_toc[m_cdTrackNumber-1].firstSector().lba() + lastSector().lba() );
+
+ return true;
+}
+
+
+int K3bAudioCdTrackSource::read( char* data, unsigned int )
+{
+ if( initParanoia() ) {
+ int status = 0;
+ char* buf = m_cdParanoiaLib->read( &status, 0, false /* big endian */ );
+ if( status == K3bCdparanoiaLib::S_OK ) {
+ if( buf == 0 ) {
+ // done
+ closeParanoia();
+ return 0;
+ }
+ else {
+ ++m_position;
+ ::memcpy( data, buf, CD_FRAMESIZE_RAW );
+ return CD_FRAMESIZE_RAW;
+ }
+ }
+ else {
+ // in case the reading fails we go back to "not initialized"
+ closeParanoia();
+ return -1;
+ }
+ }
+ else
+ return -1;
+}
+
+
+QString K3bAudioCdTrackSource::type() const
+{
+ return i18n("CD Track");
+}
+
+
+QString K3bAudioCdTrackSource::sourceComment() const
+{
+ return i18n("Track %1 from Audio CD %2").arg(m_cdTrackNumber).arg(m_discId,0,16);
+}
+
+
+K3bAudioDataSource* K3bAudioCdTrackSource::copy() const
+{
+ return new K3bAudioCdTrackSource( *this );
+}
diff --git a/libk3b/projects/audiocd/k3baudiocdtracksource.h b/libk3b/projects/audiocd/k3baudiocdtracksource.h
new file mode 100644
index 0000000..6eaaa5b
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiocdtracksource.h
@@ -0,0 +1,99 @@
+/*
+ *
+ * $Id: k3baudiocdtracksource.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_CD_TRACK_SOURCE_H_
+#define _K3B_AUDIO_CD_TRACK_SOURCE_H_
+
+#include "k3baudiodatasource.h"
+
+#include <k3btoc.h>
+#include <k3bcddbresult.h>
+
+#include "k3b_export.h"
+
+
+namespace K3bDevice {
+ class Device;
+}
+class K3bCdparanoiaLib;
+
+
+/**
+ * Audio data source which reads it's data directly from an audio CD.
+ *
+ * Be aware that since GUI elements are not allowed in sources (other thread)
+ * the source relies on the audio CD being inserted before any read operations.
+ * It will search all available devices for the CD starting with the last used drive.
+ */
+class LIBK3B_EXPORT K3bAudioCdTrackSource : public K3bAudioDataSource
+{
+ public:
+ /**
+ * Default constructor to create a new source.
+ */
+ K3bAudioCdTrackSource( const K3bDevice::Toc& toc, int cdTrackNumber, const K3bCddbResultEntry& cddb,
+ K3bDevice::Device* dev = 0 );
+
+ /**
+ * Constructor to create sources when loading from a project file without toc information
+ */
+ K3bAudioCdTrackSource( unsigned int discid, const K3b::Msf& length, int cdTrackNumber,
+ const QString& artist, const QString& title,
+ const QString& cdartist, const QString& cdtitle );
+ K3bAudioCdTrackSource( const K3bAudioCdTrackSource& );
+ ~K3bAudioCdTrackSource();
+
+ unsigned int discId() const { return m_discId; }
+ int cdTrackNumber() const { return m_cdTrackNumber; }
+ const K3bCddbResultEntry& metaInfo() const { return m_cddbEntry; }
+
+ K3b::Msf originalLength() const;
+ bool seek( const K3b::Msf& );
+ int read( char* data, unsigned int max );
+ QString type() const;
+ QString sourceComment() const;
+ K3bAudioDataSource* copy() const;
+
+ /**
+ * Searches for the corresponding Audio CD and returns the device in which it has
+ * been found or 0 if it could not be found.
+ */
+ K3bDevice::Device* searchForAudioCD() const;
+
+ /**
+ * Set the device the source should start to look for the CD.
+ */
+ void setDevice( K3bDevice::Device* dev );
+
+ private:
+ bool initParanoia();
+ void closeParanoia();
+ bool searchForAudioCD( K3bDevice::Device* ) const;
+
+ unsigned int m_discId;
+ K3b::Msf m_length;
+ K3bDevice::Toc m_toc;
+ int m_cdTrackNumber;
+ K3bCddbResultEntry m_cddbEntry;
+
+ // ripping
+ // we only save the device we last saw the CD in
+ K3bDevice::Device* m_lastUsedDevice;
+ K3bCdparanoiaLib* m_cdParanoiaLib;
+ K3b::Msf m_position;
+ bool m_initialized;
+};
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudiodatasource.cpp b/libk3b/projects/audiocd/k3baudiodatasource.cpp
new file mode 100644
index 0000000..0f705f4
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiodatasource.cpp
@@ -0,0 +1,210 @@
+/*
+ *
+ * $Id: k3baudiodatasource.cpp 619556 2007-01-03 17:38:12Z trueg $
+ * Copyright (C) 2004 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 "k3baudiodatasource.h"
+#include "k3baudiotrack.h"
+#include "k3baudiodoc.h"
+
+
+K3bAudioDataSource::K3bAudioDataSource()
+ : m_track(0),
+ m_prev(0),
+ m_next(0)
+{
+}
+
+
+K3bAudioDataSource::K3bAudioDataSource( const K3bAudioDataSource& source )
+ : m_track( 0 ),
+ m_prev( 0 ),
+ m_next( 0 ),
+ m_startOffset( source.m_startOffset ),
+ m_endOffset( source.m_endOffset )
+{
+}
+
+
+K3bAudioDataSource::~K3bAudioDataSource()
+{
+ take();
+}
+
+
+K3bAudioDoc* K3bAudioDataSource::doc() const
+{
+ if( m_track )
+ return m_track->doc();
+ else
+ return 0;
+}
+
+
+K3bAudioDataSource* K3bAudioDataSource::take()
+{
+ // if we do not have a track we are not in any list
+ if( m_track ) {
+ if( !m_prev )
+ m_track->setFirstSource( m_next );
+
+ if( m_prev )
+ m_prev->m_next = m_next;
+ if( m_next )
+ m_next->m_prev = m_prev;
+
+ m_prev = m_next = 0;
+
+ emitChange();
+ m_track = 0;
+ }
+
+ return this;
+}
+
+
+void K3bAudioDataSource::moveAfter( K3bAudioDataSource* source )
+{
+ // cannot create a list outside a track!
+ if( !source->track() )
+ return;
+
+ if( source == this )
+ return;
+
+ // remove this from the list
+ take();
+
+ K3bAudioDataSource* oldNext = source->m_next;
+
+ // set track as prev
+ source->m_next = this;
+ m_prev = source;
+
+ // set oldNext as next
+ if( oldNext )
+ oldNext->m_prev = this;
+ m_next = oldNext;
+
+ m_track = source->track();
+ emitChange();
+}
+
+
+void K3bAudioDataSource::moveAhead( K3bAudioDataSource* source )
+{
+ // cannot create a list outside a track!
+ if( !source->track() )
+ return;
+
+ if( source == this )
+ return;
+
+ // remove this from the list
+ take();
+
+ K3bAudioDataSource* oldPrev = source->m_prev;
+
+ // set track as next
+ m_next = source;
+ source->m_prev = this;
+
+ // set oldPrev as prev
+ m_prev = oldPrev;
+ if( oldPrev )
+ oldPrev->m_next = this;
+
+ m_track = source->track();
+
+ if( !m_prev )
+ m_track->setFirstSource( this );
+
+ emitChange();
+}
+
+
+void K3bAudioDataSource::emitChange()
+{
+ if( m_track )
+ m_track->sourceChanged( this );
+}
+
+
+K3bAudioDataSource* K3bAudioDataSource::split( const K3b::Msf& pos )
+{
+ if( pos < length() ) {
+ K3bAudioDataSource* s = copy();
+ s->setStartOffset( startOffset() + pos );
+ s->setEndOffset( endOffset() );
+ setEndOffset( startOffset() + pos );
+ s->moveAfter( this );
+ emitChange();
+ return s;
+ }
+ else
+ return 0;
+}
+
+
+K3b::Msf K3bAudioDataSource::lastSector() const
+{
+ if( endOffset() > 0 )
+ return endOffset()-1;
+ else
+ return originalLength()-1;
+}
+
+
+K3b::Msf K3bAudioDataSource::length() const
+{
+ if( originalLength() == 0 )
+ return 0;
+ else if( lastSector() < m_startOffset )
+ return 1;
+ else
+ return lastSector() - m_startOffset + 1;
+}
+
+
+void K3bAudioDataSource::setStartOffset( const K3b::Msf& msf )
+{
+ m_startOffset = msf;
+ fixupOffsets();
+ emitChange();
+}
+
+
+void K3bAudioDataSource::setEndOffset( const K3b::Msf& msf )
+{
+ m_endOffset = msf;
+ fixupOffsets();
+ emitChange();
+}
+
+
+void K3bAudioDataSource::fixupOffsets()
+{
+ // no length available yet
+ if( originalLength() == 0 )
+ return;
+
+ if( startOffset() >= originalLength() ) {
+ setStartOffset( 0 );
+ }
+ if( endOffset() > originalLength() ) {
+ setEndOffset( 0 ); // whole source
+ }
+ if( endOffset() > 0 && endOffset() <= startOffset() ) {
+ setEndOffset( startOffset() );
+ }
+}
diff --git a/libk3b/projects/audiocd/k3baudiodatasource.h b/libk3b/projects/audiocd/k3baudiodatasource.h
new file mode 100644
index 0000000..d12fd10
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiodatasource.h
@@ -0,0 +1,168 @@
+/*
+ *
+ * $Id: k3baudiodatasource.h 619556 2007-01-03 17:38:12Z trueg $
+ * Copyright (C) 2004 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_DATA_SOURCE_H_
+#define _K3B_AUDIO_DATA_SOURCE_H_
+
+#include <k3bmsf.h>
+#include "k3b_export.h"
+class K3bAudioTrack;
+class K3bAudioDoc;
+
+
+/**
+ * An AudioDataSource has an original length which represents the maximum amount of audio
+ * sectors this source can provide (in special cases this is not true, see K3bAudioZeroData).
+ *
+ * It is possible to just use a portion of that data by changing the startOffset and endOffset.
+ * This will change the actual length of the data provided by this source through the read method.
+ *
+ * Sources are part of a list which can be traversed via the prev() and next() methods. This list
+ * is part of a K3bAudioTrack which in turn is part of a list which is owned by a K3bAudioDoc.
+ *
+ * The list may be modified with the take(), moveAfter(), and moveAhead() methods. The source takes
+ * care of fixing the list and notifying the track about the change (It is also possible to move sources
+ * from one track to the other).
+ *
+ * When a source is deleted it automatically removes itself from it's list.
+ */
+class LIBK3B_EXPORT K3bAudioDataSource
+{
+ friend class K3bAudioTrack;
+
+ public:
+ K3bAudioDataSource();
+
+ /**
+ * Create en identical copy except that the copy will not be in any list.
+ */
+ K3bAudioDataSource( const K3bAudioDataSource& );
+ virtual ~K3bAudioDataSource();
+
+ /**
+ * The original length of the source is the maximum data which is available
+ * when startOffset is 0 this is the max for endOffset
+ *
+ * Be aware that this may change (see K3bAudioZeroData)
+ */
+ virtual K3b::Msf originalLength() const = 0;
+
+ /**
+ * The default implementation returns the originalLength modified by startOffset and endOffset
+ */
+ virtual K3b::Msf length() const;
+
+ /**
+ * @return The raw size in pcm samples (16bit, 44800 kHz, stereo)
+ */
+ KIO::filesize_t size() const { return length().audioBytes(); }
+
+ virtual bool seek( const K3b::Msf& ) = 0;
+
+ /**
+ * Read data from the source.
+ */
+ virtual int read( char* data, unsigned int max ) = 0;
+
+ /**
+ * Type of the data in readable form.
+ */
+ virtual QString type() const = 0;
+
+ /**
+ * The source in readable form (this is the filename for files)
+ */
+ virtual QString sourceComment() const = 0;
+
+ /**
+ * Used in case an error occurred. For now this is used if the
+ * decoder was not able to decode an audiofile
+ */
+ virtual bool isValid() const { return true; }
+
+ /**
+ * The doc the source is currently a part of or null.
+ */
+ K3bAudioDoc* doc() const;
+ K3bAudioTrack* track() const { return m_track; }
+
+ K3bAudioDataSource* prev() const { return m_prev; }
+ K3bAudioDataSource* next() const { return m_next; }
+
+ K3bAudioDataSource* take();
+
+ void moveAfter( K3bAudioDataSource* track );
+ void moveAhead( K3bAudioDataSource* track );
+
+ /**
+ * Set the start offset from the beginning of the source's originalLength.
+ */
+ virtual void setStartOffset( const K3b::Msf& );
+
+ /**
+ * Set the end offset from the beginning of the file. The endOffset sector
+ * is not included in the data.
+ * The maximum value is originalLength() which means to use all data.
+ * 0 means the same as originalLength().
+ * This has to be bigger than the start offset.
+ */
+ virtual void setEndOffset( const K3b::Msf& );
+
+ virtual const K3b::Msf& startOffset() const { return m_startOffset; }
+
+ /**
+ * The end offset. It is the first sector not included in the data.
+ * If 0 the last sector is determined by the originalLength
+ */
+ virtual const K3b::Msf& endOffset() const { return m_endOffset; }
+
+ /**
+ * Get the last used sector in the source.
+ * The default implementation uses originalLength() and endOffset()
+ */
+ virtual K3b::Msf lastSector() const;
+
+ /**
+ * Create a copy of this source which is not part of a list
+ */
+ virtual K3bAudioDataSource* copy() const = 0;
+
+ /**
+ * Split the source at position pos and return the splitted source
+ * on success.
+ * The new source will be moved after this source.
+ *
+ * The default implementation uses copy() to create a new source instance
+ */
+ virtual K3bAudioDataSource* split( const K3b::Msf& pos );
+
+ protected:
+ /**
+ * Informs the parent track about changes.
+ */
+ void emitChange();
+
+ private:
+ void fixupOffsets();
+
+ K3bAudioTrack* m_track;
+ K3bAudioDataSource* m_prev;
+ K3bAudioDataSource* m_next;
+
+ K3b::Msf m_startOffset;
+ K3b::Msf m_endOffset;
+};
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudiodatasourceiterator.cpp b/libk3b/projects/audiocd/k3baudiodatasourceiterator.cpp
new file mode 100644
index 0000000..81e0a59
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiodatasourceiterator.cpp
@@ -0,0 +1,71 @@
+/*
+ *
+ * $Id: sourceheader,v 1.3 2005/01/19 13:03:46 trueg Exp $
+ * 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 "k3baudiodatasourceiterator.h"
+#include "k3baudiodoc.h"
+#include "k3baudiotrack.h"
+#include "k3baudiodatasource.h"
+
+
+K3bAudioDataSourceIterator::K3bAudioDataSourceIterator( K3bAudioDoc* doc )
+ : m_doc( doc )
+{
+ first();
+}
+
+
+K3bAudioDataSource* K3bAudioDataSourceIterator::current() const
+{
+ return m_currentSource;
+}
+
+
+K3bAudioDataSource* K3bAudioDataSourceIterator::next()
+{
+ m_currentSource = m_currentSource->next();
+ if( !m_currentSource ) {
+ m_currentTrack = m_currentTrack->next();
+ if( m_currentTrack )
+ m_currentSource = m_currentTrack->firstSource();
+ }
+
+ return m_currentSource;
+}
+
+
+bool K3bAudioDataSourceIterator::hasNext() const
+{
+ if( !m_currentSource )
+ return false;
+ if( m_currentSource->next() )
+ return true;
+ if( m_currentTrack->next() )
+ return true;
+
+ return false;
+}
+
+
+K3bAudioDataSource* K3bAudioDataSourceIterator::first()
+{
+ m_currentTrack = m_doc->firstTrack();
+
+ if( m_currentTrack )
+ m_currentSource = m_currentTrack->firstSource();
+ else
+ m_currentSource = 0;
+
+ return m_currentSource;
+}
diff --git a/libk3b/projects/audiocd/k3baudiodatasourceiterator.h b/libk3b/projects/audiocd/k3baudiodatasourceiterator.h
new file mode 100644
index 0000000..7a0ce59
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiodatasourceiterator.h
@@ -0,0 +1,61 @@
+/*
+ *
+ * $Id: sourceheader,v 1.3 2005/01/19 13:03:46 trueg Exp $
+ * 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_DATA_SOURCE_ITERATOR_H_
+#define _K3B_AUDIO_DATA_SOURCE_ITERATOR_H_
+#include "k3b_export.h"
+class K3bAudioDataSource;
+class K3bAudioTrack;
+class K3bAudioDoc;
+
+
+/**
+ * This Iterator iterates over the sources in an audio project
+ *
+ * Be aware that this iterator does not properly update when the doc
+ * changes. A manual update can be issued with first(). This is becasue
+ * an update would either involve slots (this being a QObject) which is
+ * too much overhead or the AudioDoc would need to have knowledge of all
+ * the iterators which is also overhead that would be overkill.
+ */
+class LIBK3B_EXPORT K3bAudioDataSourceIterator
+{
+ public:
+ /**
+ * This will place the iterator on the first source just like first() does.
+ */
+ explicit K3bAudioDataSourceIterator( K3bAudioDoc* );
+
+ K3bAudioDataSource* current() const;
+
+ bool hasNext() const;
+
+ /**
+ * \return the next source or 0 if at end.
+ */
+ K3bAudioDataSource* next();
+
+ /**
+ * Reset the iterator
+ */
+ K3bAudioDataSource* first();
+
+ private:
+ K3bAudioDoc* m_doc;
+ K3bAudioTrack* m_currentTrack;
+ K3bAudioDataSource* m_currentSource;
+};
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudiodoc.cpp b/libk3b/projects/audiocd/k3baudiodoc.cpp
new file mode 100644
index 0000000..e7661ba
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiodoc.cpp
@@ -0,0 +1,1127 @@
+/*
+ *
+ * $Id: k3baudiodoc.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 <k3bglobals.h>
+#include "k3baudiodoc.h"
+#include "k3baudiotrack.h"
+#include "k3baudiojob.h"
+#include "k3baudiofile.h"
+#include "k3baudiozerodata.h"
+#include "k3baudiocdtracksource.h"
+
+#include <k3bcuefileparser.h>
+#include <k3bcdtextvalidator.h>
+#include <k3bcore.h>
+#include <k3baudiodecoder.h>
+
+
+// QT-includes
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qdatastream.h>
+#include <qdir.h>
+#include <qdom.h>
+#include <qdatetime.h>
+#include <qtextstream.h>
+#include <qsemaphore.h>
+
+// KDE-includes
+#include <kprocess.h>
+#include <kurl.h>
+#include <kapplication.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kio/global.h>
+#include <kdebug.h>
+
+#include <iostream>
+
+
+class K3bAudioDoc::Private
+{
+public:
+ Private() {
+ cdTextValidator = new K3bCdTextValidator();
+ }
+
+ ~Private() {
+ delete cdTextValidator;
+ }
+
+ K3bCdTextValidator* cdTextValidator;
+};
+
+
+K3bAudioDoc::K3bAudioDoc( QObject* parent )
+ : K3bDoc( parent ),
+ m_firstTrack(0),
+ m_lastTrack(0)
+{
+ d = new Private;
+ m_docType = AUDIO;
+}
+
+K3bAudioDoc::~K3bAudioDoc()
+{
+ // delete all tracks
+ int i = 1;
+ int cnt = numOfTracks();
+ while( m_firstTrack ) {
+ kdDebug() << "(K3bAudioDoc::~K3bAudioDoc) deleting track " << i << " of " << cnt << endl;
+ delete m_firstTrack->take();
+ kdDebug() << "(K3bAudioDoc::~K3bAudioDoc) deleted." << endl;
+ ++i;
+ }
+
+ delete d;
+}
+
+bool K3bAudioDoc::newDocument()
+{
+ // delete all tracks
+ while( m_firstTrack )
+ delete m_firstTrack->take();
+
+ m_normalize = false;
+ m_hideFirstTrack = false;
+ m_cdText = false;
+ m_cdTextData.clear();
+ m_audioRippingParanoiaMode = 0;
+ m_audioRippingRetries = 5;
+ m_audioRippingIgnoreReadErrors = true;
+
+ return K3bDoc::newDocument();
+}
+
+
+QString K3bAudioDoc::name() const
+{
+ if( !m_cdTextData.title().isEmpty() )
+ return m_cdTextData.title();
+ else
+ return K3bDoc::name();
+}
+
+
+K3bAudioTrack* K3bAudioDoc::firstTrack() const
+{
+ return m_firstTrack;
+}
+
+
+K3bAudioTrack* K3bAudioDoc::lastTrack() const
+{
+ return m_lastTrack;
+}
+
+
+// this one is called by K3bAudioTrack to update the list
+void K3bAudioDoc::setFirstTrack( K3bAudioTrack* track )
+{
+ m_firstTrack = track;
+}
+
+// this one is called by K3bAudioTrack to update the list
+void K3bAudioDoc::setLastTrack( K3bAudioTrack* track )
+{
+ m_lastTrack = track;
+}
+
+
+KIO::filesize_t K3bAudioDoc::size() const
+{
+ // This is not really correct but what the user expects ;)
+ return length().mode1Bytes();
+}
+
+
+K3b::Msf K3bAudioDoc::length() const
+{
+ K3b::Msf length = 0;
+ K3bAudioTrack* track = m_firstTrack;
+ while( track ) {
+ length += track->length();
+ track = track->next();
+ }
+
+ return length;
+}
+
+
+void K3bAudioDoc::addUrls( const KURL::List& urls )
+{
+ // make sure we add them at the end even if urls are in the queue
+ addTracks( urls, 99 );
+}
+
+
+void K3bAudioDoc::addTracks( const KURL::List& urls, uint position )
+{
+ KURL::List allUrls = extractUrlList( K3b::convertToLocalUrls(urls) );
+ KURL::List::iterator end( allUrls.end());
+ for( KURL::List::iterator it = allUrls.begin(); it != end; it++, position++ ) {
+ KURL& url = *it;
+ if( url.path().right(3).lower() == "cue" ) {
+ // try adding a cue file
+ if( K3bAudioTrack* newAfter = importCueFile( url.path(), getTrack(position) ) ) {
+ position = newAfter->trackNumber();
+ continue;
+ }
+ }
+
+ if( K3bAudioTrack* track = createTrack( url ) ) {
+ addTrack( track, position );
+
+ K3bAudioDecoder* dec = static_cast<K3bAudioFile*>( track->firstSource() )->decoder();
+ track->setTitle( dec->metaInfo( K3bAudioDecoder::META_TITLE ) );
+ track->setArtist( dec->metaInfo( K3bAudioDecoder::META_ARTIST ) );
+ track->setSongwriter( dec->metaInfo( K3bAudioDecoder::META_SONGWRITER ) );
+ track->setComposer( dec->metaInfo( K3bAudioDecoder::META_COMPOSER ) );
+ track->setCdTextMessage( dec->metaInfo( K3bAudioDecoder::META_COMMENT ) );
+ }
+ }
+
+ emit changed();
+
+ informAboutNotFoundFiles();
+}
+
+
+KURL::List K3bAudioDoc::extractUrlList( const KURL::List& urls )
+{
+ KURL::List allUrls = urls;
+ KURL::List urlsFromPlaylist;
+ KURL::List::iterator it = allUrls.begin();
+ while( it != allUrls.end() ) {
+
+ const KURL& url = *it;
+ QFileInfo fi( url.path() );
+
+ if( !url.isLocalFile() ) {
+ kdDebug() << url.path() << " no local file" << endl;
+ it = allUrls.remove( it );
+ m_notFoundFiles.append( url );
+ }
+ else if( !fi.exists() ) {
+ it = allUrls.remove( it );
+ kdDebug() << url.path() << " not found" << endl;
+ m_notFoundFiles.append( url );
+ }
+ else if( fi.isDir() ) {
+ it = allUrls.remove( it );
+ // add all files in the dir
+ QDir dir(fi.filePath());
+ QStringList entries = dir.entryList( QDir::Files );
+ KURL::List::iterator oldIt = it;
+ // add all files into the list after the current item
+ for( QStringList::iterator dirIt = entries.begin();
+ dirIt != entries.end(); ++dirIt )
+ it = allUrls.insert( oldIt, KURL::fromPathOrURL( dir.absPath() + "/" + *dirIt ) );
+ }
+ else if( readPlaylistFile( url, urlsFromPlaylist ) ) {
+ it = allUrls.remove( it );
+ KURL::List::iterator oldIt = it;
+ // add all files into the list after the current item
+ for( KURL::List::iterator dirIt = urlsFromPlaylist.begin();
+ dirIt != urlsFromPlaylist.end(); ++dirIt )
+ it = allUrls.insert( oldIt, *dirIt );
+ }
+ else
+ ++it;
+ }
+
+ return allUrls;
+}
+
+
+bool K3bAudioDoc::readPlaylistFile( const KURL& url, KURL::List& playlist )
+{
+ // check if the file is a m3u playlist
+ // and if so add all listed files
+
+ QFile f( url.path() );
+ if( !f.open( IO_ReadOnly ) )
+ return false;
+
+ QTextStream t( &f );
+ char buf[7];
+ t.readRawBytes( buf, 7 );
+ if( QString::fromLatin1( buf, 7 ) != "#EXTM3U" )
+ return false;
+
+ // skip the first line
+ t.readLine();
+
+ // read the file
+ while( !t.atEnd() ) {
+ QString line = t.readLine();
+ if( line[0] != '#' ) {
+ KURL mp3url;
+ // relative paths
+ if( line[0] != '/' )
+ mp3url.setPath( url.directory(false) + line );
+ else
+ mp3url.setPath( line );
+
+ playlist.append( mp3url );
+ }
+ }
+
+ return true;
+}
+
+
+void K3bAudioDoc::addSources( K3bAudioTrack* parent,
+ const KURL::List& urls,
+ K3bAudioDataSource* sourceAfter )
+{
+ kdDebug() << "(K3bAudioDoc::addSources( " << parent << ", "
+ << urls.first().path() << ", "
+ << sourceAfter << " )" << endl;
+ KURL::List allUrls = extractUrlList( urls );
+ KURL::List::const_iterator end(allUrls.end());
+ for( KURL::List::const_iterator it = allUrls.begin(); it != end; ++it ) {
+ if( K3bAudioFile* file = createAudioFile( *it ) ) {
+ if( sourceAfter )
+ file->moveAfter( sourceAfter );
+ else
+ file->moveAhead( parent->firstSource() );
+ sourceAfter = file;
+ }
+ }
+
+ informAboutNotFoundFiles();
+ kdDebug() << "(K3bAudioDoc::addSources) finished." << endl;
+}
+
+
+K3bAudioTrack* K3bAudioDoc::importCueFile( const QString& cuefile, K3bAudioTrack* after, K3bAudioDecoder* decoder )
+{
+ if( !after )
+ after = m_lastTrack;
+
+ kdDebug() << "(K3bAudioDoc::importCueFile( " << cuefile << ", " << after << ")" << endl;
+ K3bCueFileParser parser( cuefile );
+ if( parser.isValid() && parser.toc().contentType() == K3bDevice::AUDIO ) {
+
+ kdDebug() << "(K3bAudioDoc::importCueFile) parsed with image: " << parser.imageFilename() << endl;
+
+ // global cd-text
+ if( !parser.cdText().title().isEmpty() )
+ setTitle( parser.cdText().title() );
+ if( !parser.cdText().performer().isEmpty() )
+ setPerformer( parser.cdText().performer() );
+
+ bool reused = true;
+ if( !decoder )
+ decoder = getDecoderForUrl( KURL::fromPathOrURL(parser.imageFilename()), &reused );
+
+ if( decoder ) {
+ if( !reused )
+ decoder->analyseFile();
+
+ 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( decoder, this );
+ newFile->setStartOffset( track.firstSector() );
+ newFile->setEndOffset( track.lastSector()+1 );
+
+ K3bAudioTrack* newTrack = new K3bAudioTrack( this );
+ newTrack->addSource( newFile );
+ newTrack->moveAfter( after );
+
+ // we do not know the length of the source yet so we have to force the index value
+ if( track.index0() > 0 )
+ newTrack->m_index0Offset = track.length() - track.index0();
+ else
+ newTrack->m_index0Offset = 0;
+
+ // 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);
+
+ return after;
+ }
+ }
+ return 0;
+}
+
+
+K3bAudioDecoder* K3bAudioDoc::getDecoderForUrl( const KURL& url, bool* reused )
+{
+ K3bAudioDecoder* decoder = 0;
+
+ // check if we already have a proper decoder
+ if( m_decoderPresenceMap.contains( url.path() ) ) {
+ decoder = m_decoderPresenceMap[url.path()];
+ *reused = true;
+ }
+ else if( (decoder = K3bAudioDecoderFactory::createDecoder( url )) ) {
+ kdDebug() << "(K3bAudioDoc) using " << decoder->className()
+ << " for decoding of " << url.path() << endl;
+
+ decoder->setFilename( url.path() );
+ *reused = false;
+ }
+
+ return decoder;
+}
+
+
+K3bAudioFile* K3bAudioDoc::createAudioFile( const KURL& url )
+{
+ if( !QFile::exists( url.path() ) ) {
+ m_notFoundFiles.append( url.path() );
+ kdDebug() << "(K3bAudioDoc) could not find file " << url.path() << endl;
+ return 0;
+ }
+
+ bool reused;
+ K3bAudioDecoder* decoder = getDecoderForUrl( url, &reused );
+ if( decoder ) {
+ if( !reused )
+ decoder->analyseFile();
+ return new K3bAudioFile( decoder, this );
+ }
+ else {
+ m_unknownFileFormatFiles.append( url.path() );
+ kdDebug() << "(K3bAudioDoc) unknown file type in file " << url.path() << endl;
+ return 0;
+ }
+}
+
+
+K3bAudioTrack* K3bAudioDoc::createTrack( const KURL& url )
+{
+ kdDebug() << "(K3bAudioDoc::createTrack( " << url.path() << " )" << endl;
+ if( K3bAudioFile* file = createAudioFile( url ) ) {
+ K3bAudioTrack* newTrack = new K3bAudioTrack( this );
+ newTrack->setFirstSource( file );
+ return newTrack;
+ }
+ else
+ return 0;
+}
+
+
+void K3bAudioDoc::addTrack( const KURL& url, uint position )
+{
+ addTracks( KURL::List(url), position );
+}
+
+
+
+K3bAudioTrack* K3bAudioDoc::getTrack( unsigned int trackNum )
+{
+ K3bAudioTrack* track = m_firstTrack;
+ unsigned int i = 1;
+ while( track ) {
+ if( i == trackNum )
+ return track;
+ track = track->next();
+ ++i;
+ }
+
+ return 0;
+}
+
+
+void K3bAudioDoc::addTrack( K3bAudioTrack* track, uint position )
+{
+ kdDebug() << "(K3bAudioDoc::addTrack( " << track << ", " << position << " )" << endl;
+ track->m_parent = this;
+ if( !m_firstTrack )
+ m_firstTrack = m_lastTrack = track;
+ else if( position == 0 )
+ track->moveAhead( m_firstTrack );
+ else {
+ K3bAudioTrack* after = getTrack( position );
+ if( after )
+ track->moveAfter( after );
+ else
+ track->moveAfter( m_lastTrack ); // just to be sure it's anywhere...
+ }
+
+ emit changed();
+}
+
+
+void K3bAudioDoc::removeTrack( K3bAudioTrack* track )
+{
+ delete track;
+}
+
+
+void K3bAudioDoc::moveTrack( K3bAudioTrack* track, K3bAudioTrack* after )
+{
+ track->moveAfter( after );
+}
+
+
+QString K3bAudioDoc::typeString() const
+{
+ return "audio";
+}
+
+
+bool K3bAudioDoc::loadDocumentData( QDomElement* root )
+{
+ newDocument();
+
+ // we will parse the dom-tree and create a K3bAudioTrack for all entries immediately
+ // this should not take long and so not block the gui
+
+ QDomNodeList nodes = root->childNodes();
+
+ for( uint i = 0; i < nodes.count(); i++ ) {
+
+ QDomElement e = nodes.item(i).toElement();
+
+ if( e.isNull() )
+ return false;
+
+ if( e.nodeName() == "general" ) {
+ if( !readGeneralDocumentData( e ) )
+ return false;
+ }
+
+ else if( e.nodeName() == "normalize" )
+ setNormalize( e.text() == "yes" );
+
+ else if( e.nodeName() == "hide_first_track" )
+ setHideFirstTrack( e.text() == "yes" );
+
+ else if( e.nodeName() == "audio_ripping" ) {
+ QDomNodeList ripNodes = e.childNodes();
+ for( uint j = 0; j < ripNodes.length(); j++ ) {
+ if( ripNodes.item(j).nodeName() == "paranoia_mode" )
+ setAudioRippingParanoiaMode( ripNodes.item(j).toElement().text().toInt() );
+ else if( ripNodes.item(j).nodeName() == "read_retries" )
+ setAudioRippingRetries( ripNodes.item(j).toElement().text().toInt() );
+ else if( ripNodes.item(j).nodeName() == "ignore_read_errors" )
+ setAudioRippingIgnoreReadErrors( ripNodes.item(j).toElement().text() == "yes" );
+ }
+ }
+
+ // parse cd-text
+ else if( e.nodeName() == "cd-text" ) {
+ if( !e.hasAttribute( "activated" ) )
+ return false;
+
+ writeCdText( e.attributeNode( "activated" ).value() == "yes" );
+
+ QDomNodeList cdTextNodes = e.childNodes();
+ for( uint j = 0; j < cdTextNodes.length(); j++ ) {
+ if( cdTextNodes.item(j).nodeName() == "title" )
+ setTitle( cdTextNodes.item(j).toElement().text() );
+
+ else if( cdTextNodes.item(j).nodeName() == "artist" )
+ setArtist( cdTextNodes.item(j).toElement().text() );
+
+ else if( cdTextNodes.item(j).nodeName() == "arranger" )
+ setArranger( cdTextNodes.item(j).toElement().text() );
+
+ else if( cdTextNodes.item(j).nodeName() == "songwriter" )
+ setSongwriter( cdTextNodes.item(j).toElement().text() );
+
+ else if( cdTextNodes.item(j).nodeName() == "composer" )
+ setComposer( cdTextNodes.item(j).toElement().text() );
+
+ else if( cdTextNodes.item(j).nodeName() == "disc_id" )
+ setDisc_id( cdTextNodes.item(j).toElement().text() );
+
+ else if( cdTextNodes.item(j).nodeName() == "upc_ean" )
+ setUpc_ean( cdTextNodes.item(j).toElement().text() );
+
+ else if( cdTextNodes.item(j).nodeName() == "message" )
+ setCdTextMessage( cdTextNodes.item(j).toElement().text() );
+ }
+ }
+
+ else if( e.nodeName() == "contents" ) {
+
+ QDomNodeList contentNodes = e.childNodes();
+
+ for( uint j = 0; j< contentNodes.length(); j++ ) {
+
+ QDomElement trackElem = contentNodes.item(j).toElement();
+
+ // first of all we need a track
+ K3bAudioTrack* track = new K3bAudioTrack();
+
+
+ // backwards compatibility
+ // -----------------------------------------------------------------------------------------------------
+ QDomAttr oldUrlAttr = trackElem.attributeNode( "url" );
+ if( !oldUrlAttr.isNull() ) {
+ if( K3bAudioFile* file =
+ createAudioFile( KURL::fromPathOrURL( oldUrlAttr.value() ) ) ) {
+ track->addSource( file );
+ }
+ }
+ // -----------------------------------------------------------------------------------------------------
+
+
+ QDomNodeList trackNodes = trackElem.childNodes();
+ for( uint trackJ = 0; trackJ < trackNodes.length(); trackJ++ ) {
+
+ if( trackNodes.item(trackJ).nodeName() == "sources" ) {
+ QDomNodeList sourcesNodes = trackNodes.item(trackJ).childNodes();
+ for( unsigned int sourcesIndex = 0; sourcesIndex < sourcesNodes.length(); sourcesIndex++ ) {
+ QDomElement sourceElem = sourcesNodes.item(sourcesIndex).toElement();
+ if( sourceElem.nodeName() == "file" ) {
+ if( K3bAudioFile* file =
+ createAudioFile( KURL::fromPathOrURL( sourceElem.attributeNode( "url" ).value() ) ) ) {
+ file->setStartOffset( K3b::Msf::fromString( sourceElem.attributeNode( "start_offset" ).value() ) );
+ file->setEndOffset( K3b::Msf::fromString( sourceElem.attributeNode( "end_offset" ).value() ) );
+ track->addSource( file );
+ }
+ }
+ else if( sourceElem.nodeName() == "silence" ) {
+ K3bAudioZeroData* zero = new K3bAudioZeroData();
+ zero->setLength( K3b::Msf::fromString( sourceElem.attributeNode( "length" ).value() ) );
+ track->addSource( zero );
+ }
+ else if( sourceElem.nodeName() == "cdtrack" ) {
+ K3b::Msf length = K3b::Msf::fromString( sourceElem.attributeNode( "length" ).value() );
+ int titlenum = 0;
+ unsigned int discid = 0;
+ QString title, artist, cdTitle, cdArtist;
+
+ QDomNodeList cdTrackSourceNodes = sourceElem.childNodes();
+ for( unsigned int cdTrackSourceIndex = 0; cdTrackSourceIndex < cdTrackSourceNodes.length(); ++cdTrackSourceIndex ) {
+ QDomElement cdTrackSourceItemElem = cdTrackSourceNodes.item(cdTrackSourceIndex).toElement();
+ if( cdTrackSourceItemElem.nodeName() == "title_number" )
+ titlenum = cdTrackSourceItemElem.text().toInt();
+ else if( cdTrackSourceItemElem.nodeName() == "disc_id" )
+ discid = cdTrackSourceItemElem.text().toUInt( 0, 16 );
+ else if( cdTrackSourceItemElem.nodeName() == "title" )
+ title = cdTrackSourceItemElem.text().toInt();
+ else if( cdTrackSourceItemElem.nodeName() == "artist" )
+ artist = cdTrackSourceItemElem.text().toInt();
+ else if( cdTrackSourceItemElem.nodeName() == "cdtitle" )
+ cdTitle = cdTrackSourceItemElem.text().toInt();
+ else if( cdTrackSourceItemElem.nodeName() == "cdartist" )
+ cdArtist = cdTrackSourceItemElem.text().toInt();
+ }
+
+ if( discid != 0 && titlenum > 0 ) {
+ K3bAudioCdTrackSource* cdtrack = new K3bAudioCdTrackSource( discid, length, titlenum,
+ artist, title,
+ cdArtist, cdTitle );
+ cdtrack->setStartOffset( K3b::Msf::fromString( sourceElem.attributeNode( "start_offset" ).value() ) );
+ cdtrack->setEndOffset( K3b::Msf::fromString( sourceElem.attributeNode( "end_offset" ).value() ) );
+ track->addSource( cdtrack );
+ }
+ else {
+ kdDebug() << "(K3bAudioDoc) invalid cdtrack source." << endl;
+ return false;
+ }
+ }
+ else {
+ kdDebug() << "(K3bAudioDoc) unknown source type: " << sourceElem.nodeName() << endl;
+ return false;
+ }
+ }
+ }
+
+ // load cd-text
+ else if( trackNodes.item(trackJ).nodeName() == "cd-text" ) {
+ QDomNodeList cdTextNodes = trackNodes.item(trackJ).childNodes();
+ for( uint trackCdTextJ = 0; trackCdTextJ < cdTextNodes.length(); trackCdTextJ++ ) {
+ if( cdTextNodes.item(trackCdTextJ).nodeName() == "title" )
+ track->setTitle( cdTextNodes.item(trackCdTextJ).toElement().text() );
+
+ else if( cdTextNodes.item(trackCdTextJ).nodeName() == "artist" )
+ track->setArtist( cdTextNodes.item(trackCdTextJ).toElement().text() );
+
+ else if( cdTextNodes.item(trackCdTextJ).nodeName() == "arranger" )
+ track->setArranger( cdTextNodes.item(trackCdTextJ).toElement().text() );
+
+ else if( cdTextNodes.item(trackCdTextJ).nodeName() == "songwriter" )
+ track->setSongwriter( cdTextNodes.item(trackCdTextJ).toElement().text() );
+
+ else if( cdTextNodes.item(trackCdTextJ).nodeName() == "composer" )
+ track->setComposer( cdTextNodes.item(trackCdTextJ).toElement().text() );
+
+ else if( cdTextNodes.item(trackCdTextJ).nodeName() == "isrc" )
+ track->setIsrc( cdTextNodes.item(trackCdTextJ).toElement().text() );
+
+ else if( cdTextNodes.item(trackCdTextJ).nodeName() == "message" )
+ track->setCdTextMessage( cdTextNodes.item(trackCdTextJ).toElement().text() );
+ }
+ }
+
+ else if( trackNodes.item(trackJ).nodeName() == "index0" )
+ track->setIndex0( K3b::Msf::fromString( trackNodes.item(trackJ).toElement().text() ) );
+
+ // TODO: load other indices
+
+ // load options
+ else if( trackNodes.item(trackJ).nodeName() == "copy_protection" )
+ track->setCopyProtection( trackNodes.item(trackJ).toElement().text() == "yes" );
+
+ else if( trackNodes.item(trackJ).nodeName() == "pre_emphasis" )
+ track->setPreEmp( trackNodes.item(trackJ).toElement().text() == "yes" );
+ }
+
+ // add the track
+ if( track->numberSources() > 0 )
+ addTrack( track, 99 ); // append to the end // TODO improve
+ else {
+ kdDebug() << "(K3bAudioDoc) no sources. deleting track " << track << endl;
+ delete track;
+ }
+ }
+ }
+ }
+
+ informAboutNotFoundFiles();
+
+ setModified(false);
+
+ return true;
+}
+
+bool K3bAudioDoc::saveDocumentData( QDomElement* docElem )
+{
+ QDomDocument doc = docElem->ownerDocument();
+ saveGeneralDocumentData( docElem );
+
+ // add normalize
+ QDomElement normalizeElem = doc.createElement( "normalize" );
+ normalizeElem.appendChild( doc.createTextNode( normalize() ? "yes" : "no" ) );
+ docElem->appendChild( normalizeElem );
+
+ // add hide track
+ QDomElement hideFirstTrackElem = doc.createElement( "hide_first_track" );
+ hideFirstTrackElem.appendChild( doc.createTextNode( hideFirstTrack() ? "yes" : "no" ) );
+ docElem->appendChild( hideFirstTrackElem );
+
+ // save the audio cd ripping settings
+ // paranoia mode, read retries, and ignore read errors
+ // ------------------------------------------------------------
+ QDomElement ripMain = doc.createElement( "audio_ripping" );
+ docElem->appendChild( ripMain );
+
+ QDomElement ripElem = doc.createElement( "paranoia_mode" );
+ ripElem.appendChild( doc.createTextNode( QString::number( audioRippingParanoiaMode() ) ) );
+ ripMain.appendChild( ripElem );
+
+ ripElem = doc.createElement( "read_retries" );
+ ripElem.appendChild( doc.createTextNode( QString::number( audioRippingRetries() ) ) );
+ ripMain.appendChild( ripElem );
+
+ ripElem = doc.createElement( "ignore_read_errors" );
+ ripElem.appendChild( doc.createTextNode( audioRippingIgnoreReadErrors() ? "yes" : "no" ) );
+ ripMain.appendChild( ripElem );
+ // ------------------------------------------------------------
+
+ // save disc cd-text
+ // -------------------------------------------------------------
+ QDomElement cdTextMain = doc.createElement( "cd-text" );
+ cdTextMain.setAttribute( "activated", cdText() ? "yes" : "no" );
+ QDomElement cdTextElem = doc.createElement( "title" );
+ cdTextElem.appendChild( doc.createTextNode( (title())) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "artist" );
+ cdTextElem.appendChild( doc.createTextNode( (artist())) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "arranger" );
+ cdTextElem.appendChild( doc.createTextNode( (arranger())) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "songwriter" );
+ cdTextElem.appendChild( doc.createTextNode( (songwriter())) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "composer" );
+ cdTextElem.appendChild( doc.createTextNode( composer()) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "disc_id" );
+ cdTextElem.appendChild( doc.createTextNode( (disc_id())) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "upc_ean" );
+ cdTextElem.appendChild( doc.createTextNode( (upc_ean())) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "message" );
+ cdTextElem.appendChild( doc.createTextNode( (cdTextMessage())) );
+ cdTextMain.appendChild( cdTextElem );
+
+ docElem->appendChild( cdTextMain );
+ // -------------------------------------------------------------
+
+ // save the tracks
+ // -------------------------------------------------------------
+ QDomElement contentsElem = doc.createElement( "contents" );
+
+ for( K3bAudioTrack* track = firstTrack(); track != 0; track = track->next() ) {
+
+ QDomElement trackElem = doc.createElement( "track" );
+
+ // add sources
+ QDomElement sourcesParent = doc.createElement( "sources" );
+
+ for( K3bAudioDataSource* source = track->firstSource(); source; source = source->next() ) {
+ // TODO: save a source element with a type attribute and start- and endoffset
+ // then distict between the different source types.
+ if( K3bAudioFile* file = dynamic_cast<K3bAudioFile*>(source) ) {
+ QDomElement sourceElem = doc.createElement( "file" );
+ sourceElem.setAttribute( "url", file->filename() );
+ sourceElem.setAttribute( "start_offset", file->startOffset().toString() );
+ sourceElem.setAttribute( "end_offset", file->endOffset().toString() );
+ sourcesParent.appendChild( sourceElem );
+ }
+ else if( K3bAudioZeroData* zero = dynamic_cast<K3bAudioZeroData*>(source) ) {
+ QDomElement sourceElem = doc.createElement( "silence" );
+ sourceElem.setAttribute( "length", zero->length().toString() );
+ sourcesParent.appendChild( sourceElem );
+ }
+ else if( K3bAudioCdTrackSource* cdTrack = dynamic_cast<K3bAudioCdTrackSource*>(source) ) {
+ QDomElement sourceElem = doc.createElement( "cdtrack" );
+ sourceElem.setAttribute( "length", cdTrack->originalLength().toString() );
+ sourceElem.setAttribute( "start_offset", cdTrack->startOffset().toString() );
+ sourceElem.setAttribute( "end_offset", cdTrack->endOffset().toString() );
+
+ QDomElement subElem = doc.createElement( "title_number" );
+ subElem.appendChild( doc.createTextNode( QString::number(cdTrack->cdTrackNumber()) ) );
+ sourceElem.appendChild( subElem );
+
+ subElem = doc.createElement( "disc_id" );
+ subElem.appendChild( doc.createTextNode( QString::number(cdTrack->discId(), 16) ) );
+ sourceElem.appendChild( subElem );
+
+ subElem = doc.createElement( "title" );
+ subElem.appendChild( doc.createTextNode( cdTrack->metaInfo().titles[cdTrack->cdTrackNumber()-1] ) );
+ sourceElem.appendChild( subElem );
+
+ subElem = doc.createElement( "artist" );
+ subElem.appendChild( doc.createTextNode( cdTrack->metaInfo().artists[cdTrack->cdTrackNumber()-1] ) );
+ sourceElem.appendChild( subElem );
+
+ subElem = doc.createElement( "cdtitle" );
+ subElem.appendChild( doc.createTextNode( cdTrack->metaInfo().cdTitle ) );
+ sourceElem.appendChild( subElem );
+
+ subElem = doc.createElement( "cdartist" );
+ subElem.appendChild( doc.createTextNode( cdTrack->metaInfo().cdArtist ) );
+ sourceElem.appendChild( subElem );
+
+ sourcesParent.appendChild( sourceElem );
+ }
+ else {
+ kdError() << "(K3bAudioDoc) saving sources other than file or zero not supported yet." << endl;
+ return false;
+ }
+ }
+ trackElem.appendChild( sourcesParent );
+
+ // index 0
+ QDomElement index0Elem = doc.createElement( "index0" );
+ index0Elem.appendChild( doc.createTextNode( track->index0().toString() ) );
+ trackElem.appendChild( index0Elem );
+
+ // TODO: other indices
+
+ // add cd-text
+ cdTextMain = doc.createElement( "cd-text" );
+ cdTextElem = doc.createElement( "title" );
+ cdTextElem.appendChild( doc.createTextNode( (track->title())) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "artist" );
+ cdTextElem.appendChild( doc.createTextNode( (track->artist())) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "arranger" );
+ cdTextElem.appendChild( doc.createTextNode( (track->arranger()) ) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "songwriter" );
+ cdTextElem.appendChild( doc.createTextNode( (track->songwriter()) ) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "composer" );
+ cdTextElem.appendChild( doc.createTextNode( (track->composer()) ) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "isrc" );
+ cdTextElem.appendChild( doc.createTextNode( ( track->isrc()) ) );
+ cdTextMain.appendChild( cdTextElem );
+
+ cdTextElem = doc.createElement( "message" );
+ cdTextElem.appendChild( doc.createTextNode( (track->cdTextMessage()) ) );
+ cdTextMain.appendChild( cdTextElem );
+
+ trackElem.appendChild( cdTextMain );
+
+ // add copy protection
+ QDomElement copyElem = doc.createElement( "copy_protection" );
+ copyElem.appendChild( doc.createTextNode( track->copyProtection() ? "yes" : "no" ) );
+ trackElem.appendChild( copyElem );
+
+ // add pre emphasis
+ copyElem = doc.createElement( "pre_emphasis" );
+ copyElem.appendChild( doc.createTextNode( track->preEmp() ? "yes" : "no" ) );
+ trackElem.appendChild( copyElem );
+
+ contentsElem.appendChild( trackElem );
+ }
+ // -------------------------------------------------------------
+
+ docElem->appendChild( contentsElem );
+
+ return true;
+}
+
+
+int K3bAudioDoc::numOfTracks() const
+{
+ return ( m_lastTrack ? m_lastTrack->trackNumber() : 0 );
+}
+
+
+K3bBurnJob* K3bAudioDoc::newBurnJob( K3bJobHandler* hdl, QObject* parent )
+{
+ return new K3bAudioJob( this, hdl, parent );
+}
+
+
+void K3bAudioDoc::informAboutNotFoundFiles()
+{
+ if( !m_notFoundFiles.isEmpty() ) {
+ QStringList l;
+ for( KURL::List::const_iterator it = m_notFoundFiles.begin();
+ it != m_notFoundFiles.end(); ++it )
+ l.append( (*it).path() );
+ KMessageBox::informationList( qApp->activeWindow(),
+ i18n("Could not find the following files:"),
+ l,
+ i18n("Not Found") );
+
+ m_notFoundFiles.clear();
+ }
+ if( !m_unknownFileFormatFiles.isEmpty() ) {
+ QStringList l;
+ for( KURL::List::const_iterator it = m_unknownFileFormatFiles.begin();
+ it != m_unknownFileFormatFiles.end(); ++it )
+ l.append( (*it).path() );
+ KMessageBox::informationList( qApp->activeWindow(),
+ i18n("<p>Unable to handle the following files due to an unsupported format:"
+ "<p>You may manually convert these audio files to wave using another "
+ "application supporting the audio format and then add the wave files "
+ "to the K3b project."),
+ l,
+ i18n("Unsupported Format") );
+
+ m_unknownFileFormatFiles.clear();
+ }
+}
+
+
+
+void K3bAudioDoc::removeCorruptTracks()
+{
+// K3bAudioTrack* track = m_tracks->first();
+// while( track ) {
+// if( track->status() != 0 ) {
+// removeTrack(track);
+// track = m_tracks->current();
+// }
+// else
+// track = m_tracks->next();
+// }
+}
+
+
+void K3bAudioDoc::slotTrackChanged( K3bAudioTrack* track )
+{
+ kdDebug() << "(K3bAudioDoc::slotTrackChanged " << track << endl;
+ setModified( true );
+ // if the track is empty now we simply delete it
+ if( track->firstSource() ) {
+ emit trackChanged(track);
+ emit changed();
+ }
+ else {
+ kdDebug() << "(K3bAudioDoc::slotTrackChanged) track " << track << " empty. Deleting." << endl;
+ delete track; // this will emit the proper signal
+ }
+ kdDebug() << "(K3bAudioDoc::slotTrackChanged done" << track << endl;
+}
+
+
+void K3bAudioDoc::slotTrackRemoved( K3bAudioTrack* track )
+{
+ setModified( true );
+ emit trackRemoved(track);
+ emit changed();
+}
+
+
+void K3bAudioDoc::increaseDecoderUsage( K3bAudioDecoder* decoder )
+{
+ kdDebug() << "(K3bAudioDoc::increaseDecoderUsage)" << endl;
+ if( !m_decoderUsageCounterMap.contains( decoder ) ) {
+ m_decoderUsageCounterMap[decoder] = 1;
+ m_decoderPresenceMap[decoder->filename()] = decoder;
+ }
+ else
+ m_decoderUsageCounterMap[decoder]++;
+ kdDebug() << "(K3bAudioDoc::increaseDecoderUsage) finished" << endl;
+}
+
+
+void K3bAudioDoc::decreaseDecoderUsage( K3bAudioDecoder* decoder )
+{
+ m_decoderUsageCounterMap[decoder]--;
+ if( m_decoderUsageCounterMap[decoder] <= 0 ) {
+ m_decoderUsageCounterMap.erase(decoder);
+ m_decoderPresenceMap.erase(decoder->filename());
+ delete decoder;
+ }
+}
+
+
+K3bDevice::CdText K3bAudioDoc::cdTextData() const
+{
+ K3bDevice::CdText text( m_cdTextData );
+ text.reserve( numOfTracks() );
+ K3bAudioTrack* track = firstTrack();
+ while( track ) {
+ text.append( track->cdText() );
+
+ track = track->next();
+ }
+ return text;
+}
+
+
+K3bDevice::Toc K3bAudioDoc::toToc() const
+{
+ K3bDevice::Toc toc;
+
+ // FIXME: add MCN
+
+ K3bAudioTrack* track = firstTrack();
+ K3b::Msf pos = 0;
+ while( track ) {
+ toc.append( track->toCdTrack() );
+ track = track->next();
+ }
+
+ return toc;
+}
+
+
+void K3bAudioDoc::setTitle( const QString& v )
+{
+ m_cdTextData.setTitle( v );
+ emit changed();
+}
+
+
+void K3bAudioDoc::setArtist( const QString& v )
+{
+ setPerformer( v );
+}
+
+
+void K3bAudioDoc::setPerformer( const QString& v )
+{
+ QString s( v );
+ d->cdTextValidator->fixup( s );
+ m_cdTextData.setPerformer( s );
+ emit changed();
+}
+
+
+void K3bAudioDoc::setDisc_id( const QString& v )
+{
+ QString s( v );
+ d->cdTextValidator->fixup( s );
+ m_cdTextData.setDiscId( s );
+ emit changed();
+}
+
+
+void K3bAudioDoc::setArranger( const QString& v )
+{
+ QString s( v );
+ d->cdTextValidator->fixup( s );
+ m_cdTextData.setArranger( s );
+ emit changed();
+}
+
+
+void K3bAudioDoc::setSongwriter( const QString& v )
+{
+ QString s( v );
+ d->cdTextValidator->fixup( s );
+ m_cdTextData.setSongwriter( s );
+ emit changed();
+}
+
+
+void K3bAudioDoc::setComposer( const QString& v )
+{
+ QString s( v );
+ d->cdTextValidator->fixup( s );
+ m_cdTextData.setComposer( s );
+ emit changed();
+}
+
+
+void K3bAudioDoc::setUpc_ean( const QString& v )
+{
+ QString s( v );
+ d->cdTextValidator->fixup( s );
+ m_cdTextData.setUpcEan( s );
+ emit changed();
+}
+
+
+void K3bAudioDoc::setCdTextMessage( const QString& v )
+{
+ QString s( v );
+ d->cdTextValidator->fixup( s );
+ m_cdTextData.setMessage( s );
+ emit changed();
+}
+
+#include "k3baudiodoc.moc"
diff --git a/libk3b/projects/audiocd/k3baudiodoc.h b/libk3b/projects/audiocd/k3baudiodoc.h
new file mode 100644
index 0000000..87a512e
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiodoc.h
@@ -0,0 +1,263 @@
+/*
+ *
+ * $Id: k3baudiodoc.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 K3BAUDIODOC_H
+#define K3BAUDIODOC_H
+
+#include <k3bdoc.h>
+
+#include <k3bcdtext.h>
+#include <k3btoc.h>
+
+#include <qptrlist.h>
+#include <qfile.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qdatetime.h>
+#include <qtextstream.h>
+#include "k3b_export.h"
+#include <kurl.h>
+
+class K3bApp;
+class K3bAudioTrack;
+class QWidget;
+class QTimer;
+class QDomDocument;
+class QDomElement;
+class K3bThreadJob;
+class KConfig;
+class K3bAudioDataSource;
+class K3bAudioDecoder;
+class K3bAudioFile;
+
+/**Document class for an audio project.
+ *@author Sebastian Trueg
+ */
+
+class LIBK3B_EXPORT K3bAudioDoc : public K3bDoc
+{
+ Q_OBJECT
+
+ friend class K3bMixedDoc;
+ friend class K3bAudioTrack;
+ friend class K3bAudioFile;
+
+ public:
+ K3bAudioDoc( QObject* );
+ ~K3bAudioDoc();
+
+ QString name() const;
+
+ bool newDocument();
+
+ bool hideFirstTrack() const { return m_hideFirstTrack; }
+ int numOfTracks() const;
+
+ bool normalize() const { return m_normalize; }
+
+ K3bAudioTrack* firstTrack() const;
+ K3bAudioTrack* lastTrack() const;
+
+ /**
+ * Slow.
+ * \return the K3bAudioTrack with track number trackNum starting at 1 or 0 if trackNum > numOfTracks()
+ */
+ K3bAudioTrack* getTrack( unsigned int trackNum );
+
+ /**
+ * Creates a new audiofile inside this doc which has no track yet.
+ */
+ K3bAudioFile* createAudioFile( const KURL& url );
+
+ /** get the current size of the project */
+ KIO::filesize_t size() const;
+ K3b::Msf length() const;
+
+ // CD-Text
+ bool cdText() const { return m_cdText; }
+ const QString& title() const { return m_cdTextData.title(); }
+ const QString& artist() const { return m_cdTextData.performer(); }
+ const QString& disc_id() const { return m_cdTextData.discId(); }
+ const QString& arranger() const { return m_cdTextData.arranger(); }
+ const QString& songwriter() const { return m_cdTextData.songwriter(); }
+ const QString& composer() const { return m_cdTextData.composer(); }
+ const QString& upc_ean() const { return m_cdTextData.upcEan(); }
+ const QString& cdTextMessage() const { return m_cdTextData.message(); }
+
+ /**
+ * Create complete CD-Text including the tracks' data.
+ */
+ K3bDevice::CdText cdTextData() const;
+
+ int audioRippingParanoiaMode() const { return m_audioRippingParanoiaMode; }
+ int audioRippingRetries() const { return m_audioRippingRetries; }
+ bool audioRippingIgnoreReadErrors() const { return m_audioRippingIgnoreReadErrors; }
+
+ /**
+ * Represent the structure of the doc as CD Table of Contents.
+ */
+ K3bDevice::Toc toToc() const;
+
+ K3bBurnJob* newBurnJob( K3bJobHandler*, QObject* parent = 0 );
+
+ /**
+ * Shows dialogs.
+ */
+ void informAboutNotFoundFiles();
+
+ /**
+ * returns the new after track, ie. the the last added track or null if
+ * the import failed.
+ *
+ * This is a blocking method.
+ *
+ * \param cuefile The Cuefile to be imported
+ * \param after The track after which the new tracks should be inserted
+ * \param decoder The decoder to be used for the new tracks. If 0 a new one will be created.
+ *
+ * BE AWARE THAT THE DECODER HAS TO FIT THE AUDIO FILE IN THE CUE.
+ */
+ K3bAudioTrack* importCueFile( const QString& cuefile, K3bAudioTrack* after, K3bAudioDecoder* decoder = 0 );
+
+ /**
+ * Create a decoder for a specific url. If another AudioFileSource with this
+ * url is already part of this project the associated decoder is returned.
+ *
+ * In the first case the decoder will not be initialized yet (K3bAudioDecoder::analyseFile
+ * is not called yet).
+ *
+ * \param url The url for which a decoder is requested.
+ * \param reused If not null this variable is set to true if the decoder is already in
+ * use and K3bAudioDecoder::analyseFile() does not have to be called anymore.
+ */
+ K3bAudioDecoder* getDecoderForUrl( const KURL& url, bool* reused = 0 );
+
+ static bool readPlaylistFile( const KURL& url, KURL::List& playlist );
+
+ public slots:
+ void addUrls( const KURL::List& );
+ void addTrack( const KURL&, uint );
+ void addTracks( const KURL::List&, uint );
+ /**
+ * Adds a track without any testing
+ *
+ * Slow because it uses getTrack.
+ */
+ void addTrack( K3bAudioTrack* track, uint position = 0 );
+
+ void addSources( K3bAudioTrack* parent, const KURL::List& urls, K3bAudioDataSource* sourceAfter = 0 );
+
+ void removeTrack( K3bAudioTrack* );
+ void moveTrack( K3bAudioTrack* track, K3bAudioTrack* after );
+
+ void setHideFirstTrack( bool b ) { m_hideFirstTrack = b; }
+ void setNormalize( bool b ) { m_normalize = b; }
+
+ // CD-Text
+ void writeCdText( bool b ) { m_cdText = b; }
+ void setTitle( const QString& v );
+ void setArtist( const QString& v );
+ void setPerformer( const QString& v );
+ void setDisc_id( const QString& v );
+ void setArranger( const QString& v );
+ void setSongwriter( const QString& v );
+ void setComposer( const QString& v );
+ void setUpc_ean( const QString& v );
+ void setCdTextMessage( const QString& v );
+
+ // Audio-CD Ripping
+ void setAudioRippingParanoiaMode( int i ) { m_audioRippingParanoiaMode = i; }
+ void setAudioRippingRetries( int r ) { m_audioRippingRetries = r; }
+ void setAudioRippingIgnoreReadErrors( bool b ) { m_audioRippingIgnoreReadErrors = b; }
+
+ void removeCorruptTracks();
+
+ private slots:
+ void slotTrackChanged( K3bAudioTrack* );
+ void slotTrackRemoved( K3bAudioTrack* );
+
+ signals:
+ void trackChanged( K3bAudioTrack* );
+ void trackRemoved( K3bAudioTrack* );
+
+ protected:
+ /** reimplemented from K3bDoc */
+ bool loadDocumentData( QDomElement* );
+ /** reimplemented from K3bDoc */
+ bool saveDocumentData( QDomElement* );
+
+ QString typeString() const;
+
+ private:
+ // the stuff for adding files
+ // ---------------------------------------------------------
+ K3bAudioTrack* createTrack( const KURL& url );
+
+ /**
+ * Handle directories and M3u files
+ */
+ KURL::List extractUrlList( const KURL::List& urls );
+ // ---------------------------------------------------------
+
+ /**
+ * Used by K3bAudioTrack to update the track list
+ */
+ void setFirstTrack( K3bAudioTrack* track );
+ /**
+ * Used by K3bAudioTrack to update the track list
+ */
+ void setLastTrack( K3bAudioTrack* track );
+
+ /**
+ * Used by K3bAudioFile to tell the doc that it does not need the decoder anymore.
+ */
+ void decreaseDecoderUsage( K3bAudioDecoder* );
+ void increaseDecoderUsage( K3bAudioDecoder* );
+
+ K3bAudioTrack* m_firstTrack;
+ K3bAudioTrack* m_lastTrack;
+
+ bool m_hideFirstTrack;
+ bool m_normalize;
+
+ KURL::List m_notFoundFiles;
+ KURL::List m_unknownFileFormatFiles;
+
+ // CD-Text
+ // --------------------------------------------------
+ K3bDevice::CdText m_cdTextData;
+ bool m_cdText;
+ // --------------------------------------------------
+
+ // Audio ripping
+ int m_audioRippingParanoiaMode;
+ int m_audioRippingRetries;
+ bool m_audioRippingIgnoreReadErrors;
+
+ //
+ // decoder housekeeping
+ // --------------------------------------------------
+ // used to check if we may delete a decoder
+ QMap<K3bAudioDecoder*, int> m_decoderUsageCounterMap;
+ // used to check if we already have a decoder for a specific file
+ QMap<QString, K3bAudioDecoder*> m_decoderPresenceMap;
+
+ class Private;
+ Private* d;
+};
+
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudiofile.cpp b/libk3b/projects/audiocd/k3baudiofile.cpp
new file mode 100644
index 0000000..2011e73
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiofile.cpp
@@ -0,0 +1,112 @@
+/*
+ *
+ * $Id: k3baudiofile.cpp 619556 2007-01-03 17:38:12Z trueg $
+ * Copyright (C) 2004 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 "k3baudiofile.h"
+#include "k3baudiodoc.h"
+#include "k3baudiotrack.h"
+
+#include <k3baudiodecoder.h>
+
+
+K3bAudioFile::K3bAudioFile( K3bAudioDecoder* dec, K3bAudioDoc* doc )
+ : K3bAudioDataSource(),
+ m_doc(doc),
+ m_decoder(dec),
+ m_decodedData(0)
+{
+ // FIXME: somehow make it possible to switch docs
+ doc->increaseDecoderUsage( m_decoder );
+}
+
+
+K3bAudioFile::K3bAudioFile( const K3bAudioFile& file )
+ : K3bAudioDataSource( file ),
+ m_doc( file.m_doc ),
+ m_decoder( file.m_decoder ),
+ m_decodedData(0)
+{
+ m_doc->increaseDecoderUsage( m_decoder );
+}
+
+
+K3bAudioFile::~K3bAudioFile()
+{
+ m_doc->decreaseDecoderUsage( m_decoder );
+}
+
+
+QString K3bAudioFile::type() const
+{
+ return m_decoder->fileType();
+}
+
+
+QString K3bAudioFile::sourceComment() const
+{
+ return m_decoder->filename().section( "/", -1 );
+}
+
+
+const QString& K3bAudioFile::filename() const
+{
+ return m_decoder->filename();
+}
+
+
+bool K3bAudioFile::isValid() const
+{
+ return m_decoder->isValid();
+}
+
+
+K3b::Msf K3bAudioFile::originalLength() const
+{
+ return m_decoder->length();
+}
+
+
+bool K3bAudioFile::seek( const K3b::Msf& msf )
+{
+ // this is valid once the decoder has been initialized.
+ if( startOffset() + msf <= lastSector() &&
+ m_decoder->seek( startOffset() + msf ) ) {
+ m_decodedData = msf.audioBytes();
+ return true;
+ }
+ else
+ return false;
+}
+
+
+int K3bAudioFile::read( char* data, unsigned int max )
+{
+ // here we can trust on the decoder to always provide enough data
+ // see if we decode too much
+ if( max + m_decodedData > length().audioBytes() )
+ max = length().audioBytes() - m_decodedData;
+
+ int read = m_decoder->decode( data, max );
+
+ if( read > 0 )
+ m_decodedData += read;
+
+ return read;
+}
+
+
+K3bAudioDataSource* K3bAudioFile::copy() const
+{
+ return new K3bAudioFile( *this );
+}
diff --git a/libk3b/projects/audiocd/k3baudiofile.h b/libk3b/projects/audiocd/k3baudiofile.h
new file mode 100644
index 0000000..83f75eb
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiofile.h
@@ -0,0 +1,85 @@
+/*
+ *
+ * $Id: k3baudiofile.h 619556 2007-01-03 17:38:12Z trueg $
+ * Copyright (C) 2004 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_FILE_H_
+#define _K3B_AUDIO_FILE_H_
+
+#include "k3baudiodatasource.h"
+
+#include <k3bmsf.h>
+#include <kurl.h>
+#include "k3b_export.h"
+
+class K3bAudioDecoder;
+class K3bAudioTrack;
+
+
+/**
+ * The K3bAudioFile is the most important audio data source. It gets its data
+ * from an audio file and uses a K3bAudioDecoder to decode this data.
+ *
+ * Be aware that it is currently not possible to change the doc of an AudioFile.
+ * The reason for this is the decoder sharing which is in place to allow gapless
+ * splitting of audio files into several tracks.
+ *
+ * \see K3bAudioDoc::createDecoderForUrl
+ */
+class LIBK3B_EXPORT K3bAudioFile : public K3bAudioDataSource
+{
+ public:
+ /**
+ * The AudioFile registers itself with the doc. This is part of the
+ * decoder handling facility in K3bAudioDoc which reuses the same decoder
+ * for sources with the same url.
+ *
+ * Use K3bAudioDoc::getDecoderForUrl to create a decoder.
+ */
+ K3bAudioFile( K3bAudioDecoder*, K3bAudioDoc* );
+ K3bAudioFile( const K3bAudioFile& );
+
+ /**
+ * The AudioFile deregisters itself from the doc. If it was the last file
+ * to use the decoder the doc will take care of deleting it.
+ */
+ ~K3bAudioFile();
+
+ const QString& filename() const;
+
+ /**
+ * The complete length of the file used by this source.
+ */
+ K3b::Msf originalLength() const;
+
+ QString type() const;
+ QString sourceComment() const;
+
+ bool isValid() const;
+
+ K3bAudioDecoder* decoder() const { return m_decoder; }
+
+ bool seek( const K3b::Msf& );
+
+ int read( char* data, unsigned int max );
+
+ K3bAudioDataSource* copy() const;
+
+ private:
+ K3bAudioDoc* m_doc;
+ K3bAudioDecoder* m_decoder;
+
+ unsigned long long m_decodedData;
+};
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudioimager.cpp b/libk3b/projects/audiocd/k3baudioimager.cpp
new file mode 100644
index 0000000..b8a7a11
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudioimager.cpp
@@ -0,0 +1,203 @@
+/*
+ *
+ * $Id: k3baudioimager.cpp 619556 2007-01-03 17:38:12Z trueg $
+ * Copyright (C) 2004 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 "k3baudioimager.h"
+#include "k3baudiodoc.h"
+#include "k3baudiotrack.h"
+#include "k3baudiodatasource.h"
+
+#include <k3bthread.h>
+#include <k3bwavefilewriter.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <qfile.h>
+
+#include <unistd.h>
+
+
+class K3bAudioImager::WorkThread : public K3bThread
+{
+public:
+ WorkThread( K3bAudioDoc* doc );
+
+ void run();
+
+ void cancel();
+
+ bool m_canceled;
+ int m_fd;
+ QStringList m_imageNames;
+ K3bAudioImager::ErrorType lastError;
+
+private:
+ K3bAudioDoc* m_doc;
+};
+
+
+K3bAudioImager::WorkThread::WorkThread( K3bAudioDoc* doc )
+ : K3bThread(),
+ m_canceled(false),
+ m_fd(-1),
+ m_doc(doc)
+{
+}
+
+
+void K3bAudioImager::WorkThread::run()
+{
+ m_canceled = false;
+
+ emitStarted();
+
+ lastError = K3bAudioImager::ERROR_UNKNOWN;
+
+ //
+ //
+ //
+ QStringList::iterator imageFileIt = m_imageNames.begin();
+ K3bWaveFileWriter waveFileWriter;
+
+ K3bAudioTrack* track = m_doc->firstTrack();
+ int trackNumber = 1;
+ unsigned long long totalSize = m_doc->length().audioBytes();
+ unsigned long long totalRead = 0;
+ char buffer[2352 * 10];
+
+ while( track ) {
+
+ emitNextTrack( trackNumber, m_doc->numOfTracks() );
+
+ //
+ // Seek to the beginning of the track
+ //
+ if( !track->seek(0) ) {
+ emitInfoMessage( i18n("Unable to seek in track %1.").arg(trackNumber), K3bJob::ERROR );
+ emitFinished(false);
+ return;
+ }
+
+ //
+ // Initialize the reading
+ //
+ int read = 0;
+ unsigned long long trackRead = 0;
+
+ //
+ // Create the image file
+ //
+ if( m_fd == -1 ) {
+ if( !waveFileWriter.open( *imageFileIt ) ) {
+ emitInfoMessage( i18n("Could not open %1 for writing").arg(*imageFileIt), K3bJob::ERROR );
+ emitFinished(false);
+ return;
+ }
+ }
+
+ //
+ // Read data from the track
+ //
+ while( (read = track->read( buffer, sizeof(buffer) )) > 0 ) {
+ if( m_fd == -1 ) {
+ waveFileWriter.write( buffer, read, K3bWaveFileWriter::BigEndian );
+ }
+ else {
+ if( ::write( m_fd, reinterpret_cast<void*>(buffer), read ) != read ) {
+ kdDebug() << "(K3bAudioImager::WorkThread) writing to fd " << m_fd << " failed." << endl;
+ lastError = K3bAudioImager::ERROR_FD_WRITE;
+ emitFinished(false);
+ return;
+ }
+ }
+
+ if( m_canceled ) {
+ emitCanceled();
+ emitFinished(false);
+ return;
+ }
+
+ //
+ // Emit progress
+ //
+ totalRead += read;
+ trackRead += read;
+
+ emitSubPercent( 100*trackRead/track->length().audioBytes() );
+ emitPercent( 100*totalRead/totalSize );
+ emitProcessedSubSize( trackRead/1024/1024, track->length().audioBytes()/1024/1024 );
+ emitProcessedSize( totalRead/1024/1024, totalSize/1024/1024 );
+ }
+
+ if( read < 0 ) {
+ emitInfoMessage( i18n("Error while decoding track %1.").arg(trackNumber), K3bJob::ERROR );
+ kdDebug() << "(K3bAudioImager::WorkThread) read error on track " << trackNumber
+ << " at pos " << K3b::Msf(trackRead/2352) << endl;
+ lastError = K3bAudioImager::ERROR_DECODING_TRACK;
+ emitFinished(false);
+ return;
+ }
+
+ track = track->next();
+ trackNumber++;
+ imageFileIt++;
+ }
+
+ emitFinished(true);
+}
+
+
+void K3bAudioImager::WorkThread::cancel()
+{
+ m_canceled = true;
+}
+
+
+
+
+K3bAudioImager::K3bAudioImager( K3bAudioDoc* doc, K3bJobHandler* jh, QObject* parent, const char* name )
+ : K3bThreadJob( jh, parent, name ),
+ m_doc(doc)
+{
+ m_thread = new WorkThread(doc);
+ setThread( m_thread );
+}
+
+
+K3bAudioImager::~K3bAudioImager()
+{
+ delete m_thread;
+}
+
+
+void K3bAudioImager::writeToFd( int fd )
+{
+ m_thread->m_fd = fd;
+}
+
+
+void K3bAudioImager::setImageFilenames( const QStringList& p )
+{
+ m_thread->m_imageNames = p;
+ m_thread->m_fd = -1;
+}
+
+
+K3bAudioImager::ErrorType K3bAudioImager::lastErrorType() const
+{
+ return m_thread->lastError;
+}
+
+#include "k3baudioimager.moc"
diff --git a/libk3b/projects/audiocd/k3baudioimager.h b/libk3b/projects/audiocd/k3baudioimager.h
new file mode 100644
index 0000000..df4ae7a
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudioimager.h
@@ -0,0 +1,59 @@
+/*
+ *
+ * $Id: k3baudioimager.h 619556 2007-01-03 17:38:12Z trueg $
+ * Copyright (C) 2004 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_IMAGER_H_
+#define _K3B_AUDIO_IMAGER_H_
+
+#include <k3bthreadjob.h>
+
+class K3bAudioDoc;
+
+class K3bAudioImager : public K3bThreadJob
+{
+ Q_OBJECT
+
+ public:
+ K3bAudioImager( K3bAudioDoc*, K3bJobHandler*, QObject* parent = 0, const char* name = 0 );
+ ~K3bAudioImager();
+
+ /**
+ * 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 );
+
+ /**
+ * Image path. Should be an empty directory or a non-existing
+ * directory in which case it will be created.
+ */
+ void setImageFilenames( const QStringList& p );
+
+ enum ErrorType {
+ ERROR_FD_WRITE,
+ ERROR_DECODING_TRACK,
+ ERROR_UNKNOWN
+ };
+
+ ErrorType lastErrorType() const;
+
+ private:
+ K3bAudioDoc* m_doc;
+
+ class WorkThread;
+ WorkThread* m_thread;
+};
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudiojob.cpp b/libk3b/projects/audiocd/k3baudiojob.cpp
new file mode 100644
index 0000000..c2e62c2
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiojob.cpp
@@ -0,0 +1,864 @@
+/*
+ *
+ * $Id: k3baudiojob.cpp 690212 2007-07-20 11:02:13Z 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 "k3baudiojob.h"
+
+#include "k3baudioimager.h"
+#include <k3baudiodoc.h>
+#include "k3baudiotrack.h"
+#include "k3baudiodatasource.h"
+#include "k3baudionormalizejob.h"
+#include "k3baudiojobtempdata.h"
+#include "k3baudiomaxspeedjob.h"
+#include "k3baudiocdtracksource.h"
+#include "k3baudiofile.h"
+#include <k3bdevicemanager.h>
+#include <k3bdevicehandler.h>
+#include <k3bdevice.h>
+#include <k3bcdtext.h>
+#include <k3bmsf.h>
+#include <k3bglobals.h>
+#include <k3bexternalbinmanager.h>
+#include <k3bcore.h>
+#include <k3bcdrecordwriter.h>
+#include <k3bcdrdaowriter.h>
+#include <k3btocfilewriter.h>
+#include <k3binffilewriter.h>
+
+#include <qfile.h>
+#include <qvaluevector.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <ktempfile.h>
+#include <kstringhandler.h>
+
+
+
+static QString createNonExistingFilesString( const QValueList<K3bAudioFile*>& items, unsigned int max )
+{
+ QString s;
+ unsigned int cnt = 0;
+ for( QValueList<K3bAudioFile*>::const_iterator it = items.begin();
+ it != items.end(); ++it ) {
+
+ s += KStringHandler::csqueeze( (*it)->filename(), 60 );
+
+ ++cnt;
+ if( cnt >= max || it == items.end() )
+ break;
+
+ s += "<br>";
+ }
+
+ if( items.count() > max )
+ s += "...";
+
+ return s;
+}
+
+
+
+class K3bAudioJob::Private
+{
+ public:
+ Private()
+ : copies(1),
+ copiesDone(0) {
+ }
+
+ int copies;
+ int copiesDone;
+ int usedSpeed;
+
+ bool useCdText;
+ bool maxSpeed;
+
+ bool zeroPregap;
+ bool less4Sec;
+};
+
+
+K3bAudioJob::K3bAudioJob( K3bAudioDoc* doc, K3bJobHandler* hdl, QObject* parent )
+ : K3bBurnJob( hdl, parent ),
+ m_doc( doc ),
+ m_normalizeJob(0),
+ m_maxSpeedJob(0)
+{
+ d = new Private;
+
+ m_audioImager = new K3bAudioImager( m_doc, this, this );
+ connect( m_audioImager, SIGNAL(infoMessage(const QString&, int)),
+ this, SIGNAL(infoMessage(const QString&, int)) );
+ connect( m_audioImager, SIGNAL(percent(int)),
+ this, SLOT(slotAudioDecoderPercent(int)) );
+ connect( m_audioImager, SIGNAL(subPercent(int)),
+ this, SLOT(slotAudioDecoderSubPercent(int)) );
+ connect( m_audioImager, SIGNAL(finished(bool)),
+ this, SLOT(slotAudioDecoderFinished(bool)) );
+ connect( m_audioImager, SIGNAL(nextTrack(int, int)),
+ this, SLOT(slotAudioDecoderNextTrack(int, int)) );
+
+ m_writer = 0;
+ m_tempData = new K3bAudioJobTempData( m_doc, this );
+}
+
+
+K3bAudioJob::~K3bAudioJob()
+{
+ delete d;
+}
+
+
+K3bDevice::Device* K3bAudioJob::writer() const
+{
+ if( m_doc->onlyCreateImages() )
+ return 0; // no writer needed -> no blocking on K3bBurnJob
+ else
+ return m_doc->burner();
+}
+
+
+K3bDoc* K3bAudioJob::doc() const
+{
+ return m_doc;
+}
+
+
+void K3bAudioJob::start()
+{
+ jobStarted();
+
+ m_written = true;
+ m_canceled = false;
+ m_errorOccuredAndAlreadyReported = false;
+ d->copies = m_doc->copies();
+ d->copiesDone = 0;
+ d->useCdText = m_doc->cdText();
+ d->usedSpeed = m_doc->speed();
+ d->maxSpeed = false;
+
+ if( m_doc->dummy() )
+ d->copies = 1;
+
+ emit newTask( i18n("Preparing data") );
+
+ //
+ // Check if all files exist
+ //
+ QValueList<K3bAudioFile*> nonExistingFiles;
+ K3bAudioTrack* track = m_doc->firstTrack();
+ while( track ) {
+ K3bAudioDataSource* source = track->firstSource();
+ while( source ) {
+ if( K3bAudioFile* file = dynamic_cast<K3bAudioFile*>( source ) ) {
+ if( !QFile::exists( file->filename() ) )
+ nonExistingFiles.append( file );
+ }
+ source = source->next();
+ }
+ track = track->next();
+ }
+ if( !nonExistingFiles.isEmpty() ) {
+ if( questionYesNo( "<p>" + i18n("The following files could not be found. Do you want to remove them from the "
+ "project and continue without adding them to the image?") +
+ "<p>" + createNonExistingFilesString( nonExistingFiles, 10 ),
+ i18n("Warning"),
+ i18n("Remove missing files and continue"),
+ i18n("Cancel and go back") ) ) {
+ for( QValueList<K3bAudioFile*>::const_iterator it = nonExistingFiles.begin();
+ it != nonExistingFiles.end(); ++it ) {
+ delete *it;
+ }
+ }
+ else {
+ m_canceled = true;
+ emit canceled();
+ jobFinished(false);
+ return;
+ }
+ }
+
+ //
+ // Make sure the project is not empty
+ //
+ if( m_doc->numOfTracks() == 0 ) {
+ emit infoMessage( i18n("Please add files to your project first."), ERROR );
+ jobFinished(false);
+ return;
+ }
+
+ if( m_doc->onTheFly() && !checkAudioSources() ) {
+ emit infoMessage( i18n("Unable to write on-the-fly with these audio sources."), WARNING );
+ m_doc->setOnTheFly(false);
+ }
+
+
+ // we don't need this when only creating image and it is possible
+ // that the burn device is null
+ if( !m_doc->onlyCreateImages() ) {
+
+ //
+ // there are a lot of writers out there which produce coasters
+ // in dao mode if the CD contains pregaps of length 0 (or maybe already != 2 secs?)
+ //
+ // Also most writers do not accept cuesheets with tracks smaller than 4 seconds (a violation
+ // of the red book standard) in DAO mode.
+ //
+ d->zeroPregap = false;
+ d->less4Sec = false;
+ track = m_doc->firstTrack();
+ while( track ) {
+ if( track->postGap() == 0 && track->next() != 0 ) // the last track's postgap is always 0
+ d->zeroPregap = true;
+
+ if( track->length() < K3b::Msf( 0, 4, 0 ) )
+ d->less4Sec = true;
+
+ track = track->next();
+ }
+
+ // determine writing mode
+ if( m_doc->writingMode() == K3b::WRITING_MODE_AUTO ) {
+ //
+ // DAO is always the first choice
+ // RAW second and TAO last
+ // there are none-DAO writers that are supported by cdrdao
+ //
+ // older cdrecord versions do not support the -shorttrack option in RAW writing mode
+ //
+ if( !writer()->dao() && writingApp() == K3b::CDRECORD ) {
+ if(!writer()->supportsRawWriting() &&
+ ( !d->less4Sec || k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "short-track-raw" ) ) )
+ m_usedWritingMode = K3b::RAW;
+ else
+ m_usedWritingMode = K3b::TAO;
+ }
+ else {
+ if( (d->zeroPregap||d->less4Sec) && writer()->supportsRawWriting() ) {
+ m_usedWritingMode = K3b::RAW;
+ if( d->less4Sec )
+ emit infoMessage( i18n("Tracklengths below 4 seconds violate the Red Book standard."), WARNING );
+ }
+ else
+ m_usedWritingMode = K3b::DAO;
+ }
+ }
+ else
+ m_usedWritingMode = m_doc->writingMode();
+
+ bool cdrecordOnTheFly = false;
+ bool cdrecordCdText = false;
+ if( k3bcore->externalBinManager()->binObject("cdrecord") ) {
+ cdrecordOnTheFly = k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "audio-stdin" );
+ cdrecordCdText = k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "cdtext" );
+ }
+
+ // determine writing app
+ if( writingApp() == K3b::DEFAULT ) {
+ if( m_usedWritingMode == K3b::DAO ) {
+ // there are none-DAO writers that are supported by cdrdao
+ if( !writer()->dao() ||
+ ( !cdrecordOnTheFly && m_doc->onTheFly() ) ||
+ ( d->useCdText && !cdrecordCdText ) ||
+ m_doc->hideFirstTrack() )
+ m_usedWritingApp = K3b::CDRDAO;
+ else
+ m_usedWritingApp = K3b::CDRECORD;
+ }
+ else
+ m_usedWritingApp = K3b::CDRECORD;
+ }
+ else
+ m_usedWritingApp = writingApp();
+
+ // on-the-fly writing with cdrecord >= 2.01a13
+ if( m_usedWritingApp == K3b::CDRECORD &&
+ m_doc->onTheFly() &&
+ !cdrecordOnTheFly ) {
+ emit infoMessage( i18n("On-the-fly writing with cdrecord < 2.01a13 not supported."), ERROR );
+ m_doc->setOnTheFly(false);
+ }
+
+ if( m_usedWritingApp == K3b::CDRECORD &&
+ d->useCdText ) {
+ if( !cdrecordCdText ) {
+ emit infoMessage( i18n("Cdrecord %1 does not support CD-Text writing.")
+ .arg(k3bcore->externalBinManager()->binObject("cdrecord")->version), ERROR );
+ d->useCdText = false;
+ }
+ else if( m_usedWritingMode == K3b::TAO ) {
+ emit infoMessage( i18n("It is not possible to write CD-Text in TAO mode."), WARNING );
+ d->useCdText = false;
+ }
+ }
+ }
+
+
+ if( !m_doc->onlyCreateImages() && m_doc->onTheFly() ) {
+ if( m_doc->speed() == 0 ) {
+ // try to determine the max possible speed
+ emit newSubTask( i18n("Determining maximum writing speed") );
+ if( !m_maxSpeedJob ) {
+ m_maxSpeedJob = new K3bAudioMaxSpeedJob( m_doc, this, this );
+ connect( m_maxSpeedJob, SIGNAL(percent(int)),
+ this, SIGNAL(subPercent(int)) );
+ connect( m_maxSpeedJob, SIGNAL(finished(bool)),
+ this, SLOT(slotMaxSpeedJobFinished(bool)) );
+ }
+ m_maxSpeedJob->start();
+ return;
+ }
+ else {
+ if( !prepareWriter() ) {
+ cleanupAfterError();
+ jobFinished(false);
+ return;
+ }
+
+ if( startWriting() ) {
+
+ // now the writer is running and we can get it's stdin
+ // we only use this method when writing on-the-fly since
+ // we cannot easily change the audioDecode fd while it's working
+ // which we would need to do since we write into several
+ // image files.
+ m_audioImager->writeToFd( m_writer->fd() );
+ }
+ else {
+ // startWriting() already did the cleanup
+ return;
+ }
+ }
+ }
+ else {
+ emit burning(false);
+ emit infoMessage( i18n("Creating image files in %1").arg(m_doc->tempDir()), INFO );
+ emit newTask( i18n("Creating image files") );
+ m_tempData->prepareTempFileNames( doc()->tempDir() );
+ QStringList filenames;
+ for( int i = 1; i <= m_doc->numOfTracks(); ++i )
+ filenames += m_tempData->bufferFileName( i );
+ m_audioImager->setImageFilenames( filenames );
+ }
+
+ m_audioImager->start();
+}
+
+
+void K3bAudioJob::slotMaxSpeedJobFinished( bool success )
+{
+ d->maxSpeed = success;
+ if( !success )
+ emit infoMessage( i18n("Unable to determine maximum speed for some reason. Ignoring."), WARNING );
+
+ // now start the writing
+ // same code as above. See the commecnts there
+ if( !prepareWriter() ) {
+ cleanupAfterError();
+ jobFinished(false);
+ return;
+ }
+
+ if( startWriting() )
+ m_audioImager->writeToFd( m_writer->fd() );
+
+ m_audioImager->start();
+}
+
+
+void K3bAudioJob::cancel()
+{
+ m_canceled = true;
+
+ if( m_maxSpeedJob )
+ m_maxSpeedJob->cancel();
+
+ if( m_writer )
+ m_writer->cancel();
+
+ m_audioImager->cancel();
+ emit infoMessage( i18n("Writing canceled."), K3bJob::ERROR );
+ removeBufferFiles();
+ emit canceled();
+ jobFinished(false);
+}
+
+
+void K3bAudioJob::slotWriterFinished( bool success )
+{
+ if( m_canceled || m_errorOccuredAndAlreadyReported )
+ return;
+
+ if( !success ) {
+ cleanupAfterError();
+ jobFinished(false);
+ return;
+ }
+ else {
+ d->copiesDone++;
+
+ if( d->copiesDone == d->copies ) {
+ if( m_doc->onTheFly() || m_doc->removeImages() )
+ removeBufferFiles();
+
+ jobFinished(true);
+ }
+ else {
+ K3bDevice::eject( m_doc->burner() );
+
+ if( startWriting() ) {
+ if( m_doc->onTheFly() ) {
+ // now the writer is running and we can get it's stdin
+ // we only use this method when writing on-the-fly since
+ // we cannot easily change the audioDecode fd while it's working
+ // which we would need to do since we write into several
+ // image files.
+ m_audioImager->writeToFd( m_writer->fd() );
+ m_audioImager->start();
+ }
+ }
+ }
+ }
+}
+
+
+void K3bAudioJob::slotAudioDecoderFinished( bool success )
+{
+ if( m_canceled || m_errorOccuredAndAlreadyReported )
+ return;
+
+ if( !success ) {
+ if( m_audioImager->lastErrorType() == K3bAudioImager::ERROR_FD_WRITE ) {
+ // this means that the writer job failed so let's use the error handling there.
+ return;
+ }
+
+ emit infoMessage( i18n("Error while decoding audio tracks."), ERROR );
+ cleanupAfterError();
+ jobFinished(false);
+ return;
+ }
+
+ if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) {
+
+ emit infoMessage( i18n("Successfully decoded all tracks."), SUCCESS );
+
+ if( m_doc->normalize() ) {
+ normalizeFiles();
+ }
+ else if( !m_doc->onlyCreateImages() ) {
+ if( !prepareWriter() ) {
+ cleanupAfterError();
+ jobFinished(false);
+ }
+ else
+ startWriting();
+ }
+ else {
+ jobFinished(true);
+ }
+ }
+}
+
+
+void K3bAudioJob::slotAudioDecoderNextTrack( int t, int tt )
+{
+ if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) {
+ K3bAudioTrack* track = m_doc->getTrack(t);
+ emit newSubTask( i18n("Decoding audio track %1 of %2%3")
+ .arg(t)
+ .arg(tt)
+ .arg( track->title().isEmpty() || track->artist().isEmpty()
+ ? QString::null
+ : " (" + track->artist() + " - " + track->title() + ")" ) );
+ }
+}
+
+
+bool K3bAudioJob::prepareWriter()
+{
+ delete m_writer;
+
+ if( m_usedWritingApp == K3b::CDRECORD ) {
+
+ if( !writeInfFiles() ) {
+ kdDebug() << "(K3bAudioJob) could not write inf-files." << endl;
+ emit infoMessage( i18n("IO Error. Most likely no space left on harddisk."), ERROR );
+
+ return false;
+ }
+
+ K3bCdrecordWriter* writer = new K3bCdrecordWriter( m_doc->burner(), this, this );
+
+ writer->setWritingMode( m_usedWritingMode );
+ writer->setSimulate( m_doc->dummy() );
+ writer->setBurnSpeed( d->usedSpeed );
+
+ writer->addArgument( "-useinfo" );
+
+ if( d->useCdText ) {
+ writer->setRawCdText( m_doc->cdTextData().rawPackData() );
+ }
+
+ // add all the audio tracks
+ writer->addArgument( "-audio" );
+
+ // we only need to pad in one case. cdrecord < 2.01.01a03 cannot handle shorttrack + raw
+ if( d->less4Sec ) {
+ if( m_usedWritingMode == K3b::RAW &&
+ !k3bcore->externalBinManager()->binObject( "cdrecord" )->hasFeature( "short-track-raw" ) ) {
+ writer->addArgument( "-pad" );
+ }
+ else {
+ // Allow tracks shorter than 4 seconds
+ writer->addArgument( "-shorttrack" );
+ }
+ }
+
+ K3bAudioTrack* track = m_doc->firstTrack();
+ while( track ) {
+ if( m_doc->onTheFly() ) {
+ // this is only supported by cdrecord versions >= 2.01a13
+ writer->addArgument( QFile::encodeName( m_tempData->infFileName( track ) ) );
+ }
+ else {
+ writer->addArgument( QFile::encodeName( m_tempData->bufferFileName( track ) ) );
+ }
+ track = track->next();
+ }
+
+ m_writer = writer;
+ }
+ else {
+ if( !writeTocFile() ) {
+ kdDebug() << "(K3bDataJob) could not write tocfile." << endl;
+ emit infoMessage( i18n("IO Error"), ERROR );
+
+ return false;
+ }
+
+ // create the writer
+ // create cdrdao job
+ K3bCdrdaoWriter* writer = new K3bCdrdaoWriter( m_doc->burner(), this, this );
+ writer->setCommand( K3bCdrdaoWriter::WRITE );
+ writer->setSimulate( m_doc->dummy() );
+ writer->setBurnSpeed( d->usedSpeed );
+ writer->setTocFile( m_tempData->tocFileName() );
+
+ m_writer = writer;
+ }
+
+ connect( m_writer, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) );
+ connect( m_writer, SIGNAL(percent(int)), this, SLOT(slotWriterJobPercent(int)) );
+ connect( m_writer, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) );
+ connect( m_writer, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) );
+ connect( m_writer, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) );
+ connect( m_writer, SIGNAL(nextTrack(int, int)), this, SLOT(slotWriterNextTrack(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(slotWriterFinished(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;
+}
+
+
+void K3bAudioJob::slotWriterNextTrack( int t, int tt )
+{
+ K3bAudioTrack* track = m_doc->getTrack(t);
+ // t is in range 1..tt
+ if( m_doc->hideFirstTrack() )
+ track = m_doc->getTrack(t+1);
+ emit newSubTask( i18n("Writing track %1 of %2%3")
+ .arg(t)
+ .arg(tt)
+ .arg( track->title().isEmpty() || track->artist().isEmpty()
+ ? QString::null
+ : " (" + track->artist() + " - " + track->title() + ")" ) );
+}
+
+
+void K3bAudioJob::slotWriterJobPercent( int p )
+{
+ double totalTasks = d->copies;
+ double tasksDone = d->copiesDone;
+ if( m_doc->normalize() ) {
+ totalTasks+=1.0;
+ tasksDone+=1.0;
+ }
+ if( !m_doc->onTheFly() ) {
+ totalTasks+=1.0;
+ tasksDone+=1.0;
+ }
+
+ emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) );
+}
+
+
+void K3bAudioJob::slotAudioDecoderPercent( int p )
+{
+ if( m_doc->onlyCreateImages() ) {
+ if( m_doc->normalize() )
+ emit percent( p/2 );
+ else
+ emit percent( p );
+ }
+ else if( !m_doc->onTheFly() ) {
+ double totalTasks = d->copies;
+ double tasksDone = d->copiesDone; // =0 when creating an image
+ if( m_doc->normalize() ) {
+ totalTasks+=1.0;
+ }
+ if( !m_doc->onTheFly() ) {
+ totalTasks+=1.0;
+ }
+
+ emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) );
+ }
+}
+
+
+void K3bAudioJob::slotAudioDecoderSubPercent( int p )
+{
+ // when writing on the fly the writer produces the subPercent
+ if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) {
+ emit subPercent( p );
+ }
+}
+
+
+bool K3bAudioJob::startWriting()
+{
+ if( m_doc->dummy() )
+ emit newTask( i18n("Simulating") );
+ else if( d->copies > 1 )
+ emit newTask( i18n("Writing Copy %1").arg(d->copiesDone+1) );
+ else
+ emit newTask( i18n("Writing") );
+
+
+ emit newSubTask( i18n("Waiting for media") );
+ if( waitForMedia( m_doc->burner() ) < 0 ) {
+ cancel();
+ return false;
+ }
+
+ // just to be sure we did not get canceled during the async discWaiting
+ if( m_canceled )
+ return false;
+
+ // in case we determined the max possible writing speed we have to reset the speed on the writer job
+ // here since an inserted media is necessary
+ // the Max speed job will compare the max speed value with the supported values of the writer
+ if( d->maxSpeed )
+ m_writer->setBurnSpeed( m_maxSpeedJob->maxSpeed() );
+
+ emit burning(true);
+ m_writer->start();
+ return true;
+}
+
+
+void K3bAudioJob::cleanupAfterError()
+{
+ m_errorOccuredAndAlreadyReported = true;
+ m_audioImager->cancel();
+
+ if( m_writer )
+ m_writer->cancel();
+
+ // remove the temp files
+ removeBufferFiles();
+}
+
+
+void K3bAudioJob::removeBufferFiles()
+{
+ if ( !m_doc->onTheFly() ) {
+ emit infoMessage( i18n("Removing temporary files."), INFO );
+ }
+
+ // removes buffer images and temp toc or inf files
+ m_tempData->cleanup();
+}
+
+
+void K3bAudioJob::normalizeFiles()
+{
+ if( !m_normalizeJob ) {
+ m_normalizeJob = new K3bAudioNormalizeJob( this, this );
+
+ connect( m_normalizeJob, SIGNAL(infoMessage(const QString&, int)),
+ this, SIGNAL(infoMessage(const QString&, int)) );
+ connect( m_normalizeJob, SIGNAL(percent(int)), this, SLOT(slotNormalizeProgress(int)) );
+ connect( m_normalizeJob, SIGNAL(subPercent(int)), this, SLOT(slotNormalizeSubProgress(int)) );
+ connect( m_normalizeJob, SIGNAL(finished(bool)), this, SLOT(slotNormalizeJobFinished(bool)) );
+ connect( m_normalizeJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) );
+ connect( m_normalizeJob, SIGNAL(debuggingOutput(const QString&, const QString&)),
+ this, SIGNAL(debuggingOutput(const QString&, const QString&)) );
+ }
+
+ // add all the files
+ // TODO: we may need to split the wave files and put them back together!
+ QValueVector<QString> files;
+ K3bAudioTrack* track = m_doc->firstTrack();
+ while( track ) {
+ files.append( m_tempData->bufferFileName(track) );
+ track = track->next();
+ }
+
+ m_normalizeJob->setFilesToNormalize( files );
+
+ emit newTask( i18n("Normalizing volume levels") );
+ m_normalizeJob->start();
+}
+
+void K3bAudioJob::slotNormalizeJobFinished( bool success )
+{
+ if( m_canceled || m_errorOccuredAndAlreadyReported )
+ return;
+
+ if( success ) {
+ if( m_doc->onlyCreateImages() ) {
+ jobFinished(true);
+ }
+ else {
+ // start the writing
+ if( !prepareWriter() ) {
+ cleanupAfterError();
+ jobFinished(false);
+ }
+ else
+ startWriting();
+ }
+ }
+ else {
+ cleanupAfterError();
+ jobFinished(false);
+ }
+}
+
+void K3bAudioJob::slotNormalizeProgress( int p )
+{
+ double totalTasks = d->copies+2.0;
+ double tasksDone = 1; // the decoding has been finished
+
+ emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) );
+}
+
+
+void K3bAudioJob::slotNormalizeSubProgress( int p )
+{
+ emit subPercent( p );
+}
+
+
+bool K3bAudioJob::writeTocFile()
+{
+ K3bTocFileWriter tocWriter;
+ tocWriter.setData( m_doc->toToc() );
+ tocWriter.setHideFirstTrack( m_doc->hideFirstTrack() );
+ if( d->useCdText )
+ tocWriter.setCdText( m_doc->cdTextData() );
+ if( !m_doc->onTheFly() ) {
+ QStringList filenames;
+ for( int i = 1; i <= m_doc->numOfTracks(); ++i )
+ filenames += m_tempData->bufferFileName( i );
+ tocWriter.setFilenames( filenames );
+ }
+ return tocWriter.save( m_tempData->tocFileName() );
+}
+
+
+bool K3bAudioJob::writeInfFiles()
+{
+ K3bInfFileWriter infFileWriter;
+ K3bAudioTrack* track = m_doc->firstTrack();
+ while( track ) {
+
+ infFileWriter.setTrack( track->toCdTrack() );
+ infFileWriter.setTrackNumber( track->trackNumber() );
+ if( !m_doc->onTheFly() )
+ infFileWriter.setBigEndian( false );
+
+ if( !infFileWriter.save( m_tempData->infFileName(track) ) )
+ return false;
+
+ track = track->next();
+ }
+ return true;
+}
+
+
+// checks if the doc contains sources from an audio cd which cannot be read on-the-fly
+bool K3bAudioJob::checkAudioSources()
+{
+ K3bAudioTrack* track = m_doc->firstTrack();
+ K3bAudioDataSource* source = track->firstSource();
+
+ while( source ) {
+
+ if( K3bAudioCdTrackSource* cdSource = dynamic_cast<K3bAudioCdTrackSource*>(source) ) {
+ //
+ // If which cases we cannot wite on-the-fly:
+ // 1. the writing device contains one of the audio cds
+ // 2. Well, one of the cds is missing
+ //
+ K3bDevice::Device* dev = cdSource->searchForAudioCD();
+ if( !dev || dev == writer() )
+ return false;
+ else
+ cdSource->setDevice( dev );
+ }
+
+ // next source
+ source = source->next();
+ if( !source ) {
+ track = track->next();
+ if( track )
+ source = track->firstSource();
+ }
+ }
+
+ return true;
+}
+
+
+QString K3bAudioJob::jobDescription() const
+{
+ return i18n("Writing Audio CD")
+ + ( m_doc->title().isEmpty()
+ ? QString::null
+ : QString( " (%1)" ).arg(m_doc->title()) );
+}
+
+
+QString K3bAudioJob::jobDetails() const
+{
+ return ( i18n( "1 track (%1 minutes)",
+ "%n tracks (%1 minutes)",
+ m_doc->numOfTracks() ).arg(m_doc->length().toString())
+ + ( m_doc->copies() > 1 && !m_doc->dummy()
+ ? i18n(" - %n copy", " - %n copies", m_doc->copies())
+ : QString::null ) );
+}
+
+#include "k3baudiojob.moc"
diff --git a/libk3b/projects/audiocd/k3baudiojob.h b/libk3b/projects/audiocd/k3baudiojob.h
new file mode 100644
index 0000000..af37639
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiojob.h
@@ -0,0 +1,107 @@
+/*
+ *
+ * $Id: k3baudiojob.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 K3BAUDIOJOB_H
+#define K3BAUDIOJOB_H
+
+#include <k3bjob.h>
+
+
+class K3bAudioDoc;
+class K3bAudioImager;
+class QFile;
+class QDataStream;
+class K3bAbstractWriter;
+class KTempFile;
+class K3bCdrecordWriter;
+class K3bAudioNormalizeJob;
+class K3bAudioJobTempData;
+class K3bDevice::Device;
+class K3bAudioMaxSpeedJob;
+
+/**
+ *@author Sebastian Trueg
+ */
+class K3bAudioJob : public K3bBurnJob
+{
+ Q_OBJECT
+
+ public:
+ K3bAudioJob( K3bAudioDoc*, K3bJobHandler*, QObject* parent = 0 );
+ ~K3bAudioJob();
+
+ K3bDoc* doc() const;
+ K3bDevice::Device* writer() const;
+
+ QString jobDescription() const;
+ QString jobDetails() const;
+
+ public slots:
+ void cancel();
+ void start();
+
+ protected slots:
+ // writer slots
+ void slotWriterFinished( bool success );
+ void slotWriterNextTrack(int, int);
+ void slotWriterJobPercent(int);
+
+ // audiodecoder slots
+ void slotAudioDecoderFinished( bool );
+ void slotAudioDecoderNextTrack( int, int );
+ void slotAudioDecoderPercent(int);
+ void slotAudioDecoderSubPercent( int );
+
+ // normalizing slots
+ void slotNormalizeJobFinished( bool );
+ void slotNormalizeProgress( int );
+ void slotNormalizeSubProgress( int );
+
+ // max speed
+ void slotMaxSpeedJobFinished( bool );
+
+ private:
+ bool prepareWriter();
+ bool startWriting();
+ void cleanupAfterError();
+ void removeBufferFiles();
+ void normalizeFiles();
+ bool writeTocFile();
+ bool writeInfFiles();
+ bool checkAudioSources();
+
+ K3bAudioDoc* m_doc;
+ K3bAudioImager* m_audioImager;
+ K3bAbstractWriter* m_writer;
+ K3bAudioNormalizeJob* m_normalizeJob;
+ K3bAudioJobTempData* m_tempData;
+ K3bAudioMaxSpeedJob* m_maxSpeedJob;
+
+ KTempFile* m_tocFile;
+
+ bool m_canceled;
+ bool m_errorOccuredAndAlreadyReported;
+
+ bool m_written;
+
+ int m_usedWritingApp;
+ int m_usedWritingMode;
+
+ class Private;
+ Private* d;
+};
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudiojobtempdata.cpp b/libk3b/projects/audiocd/k3baudiojobtempdata.cpp
new file mode 100644
index 0000000..af98c2e
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiojobtempdata.cpp
@@ -0,0 +1,132 @@
+/*
+ *
+ * $Id: k3baudiojobtempdata.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 "k3baudiojobtempdata.h"
+#include "k3baudiodoc.h"
+#include "k3baudiotrack.h"
+#include <k3bglobals.h>
+#include <k3bversion.h>
+#include <k3bmsf.h>
+#include <k3bcore.h>
+
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qvaluevector.h>
+
+#include <kdebug.h>
+
+
+class K3bAudioJobTempData::Private
+{
+public:
+ Private( K3bAudioDoc* _doc )
+ : doc(_doc) {
+ }
+
+ QValueVector<QString> bufferFiles;
+ QValueVector<QString> infFiles;
+ QString tocFile;
+
+ K3bAudioDoc* doc;
+};
+
+
+K3bAudioJobTempData::K3bAudioJobTempData( K3bAudioDoc* doc, QObject* parent, const char* name )
+ : QObject( parent, name )
+{
+ d = new Private( doc );
+}
+
+
+K3bAudioJobTempData::~K3bAudioJobTempData()
+{
+ delete d;
+}
+
+
+const QString& K3bAudioJobTempData::bufferFileName( int track )
+{
+ if( (int)d->bufferFiles.count() < track )
+ prepareTempFileNames();
+ return d->bufferFiles.at(track-1);
+}
+
+const QString& K3bAudioJobTempData::bufferFileName( K3bAudioTrack* track )
+{
+ return bufferFileName( track->trackNumber() );
+}
+
+
+const QString& K3bAudioJobTempData::tocFileName()
+{
+ if( d->tocFile.isEmpty() )
+ prepareTempFileNames();
+ return d->tocFile;
+}
+
+
+const QString& K3bAudioJobTempData::infFileName( int track )
+{
+ if( (int)d->infFiles.count() < track )
+ prepareTempFileNames();
+ return d->infFiles.at( track - 1 );
+}
+
+const QString& K3bAudioJobTempData::infFileName( K3bAudioTrack* track )
+{
+ return infFileName( track->trackNumber() );
+}
+
+
+K3bAudioDoc* K3bAudioJobTempData::doc() const
+{
+ return d->doc;
+}
+
+
+void K3bAudioJobTempData::prepareTempFileNames( const QString& path )
+{
+ d->bufferFiles.clear();
+ d->infFiles.clear();
+
+ QString prefix = K3b::findUniqueFilePrefix( "k3b_audio_", path ) + "_";
+
+ for( int i = 0; i < d->doc->numOfTracks(); i++ ) {
+ d->bufferFiles.append( prefix + QString::number( i+1 ).rightJustify( 2, '0' ) + ".wav" );
+ d->infFiles.append( prefix + QString::number( i+1 ).rightJustify( 2, '0' ) + ".inf" );
+ }
+
+ d->tocFile = prefix + ".toc";
+}
+
+
+void K3bAudioJobTempData::cleanup()
+{
+ for( uint i = 0; i < d->infFiles.count(); ++i ) {
+ if( QFile::exists( d->infFiles[i] ) )
+ QFile::remove( d->infFiles[i] );
+ }
+
+ for( uint i = 0; i < d->bufferFiles.count(); ++i ) {
+ if( QFile::exists( d->bufferFiles[i] ) )
+ QFile::remove( d->bufferFiles[i] );
+ }
+
+ if( QFile::exists( d->tocFile ) )
+ QFile::remove( d->tocFile );
+}
+
+
+#include "k3baudiojobtempdata.moc"
diff --git a/libk3b/projects/audiocd/k3baudiojobtempdata.h b/libk3b/projects/audiocd/k3baudiojobtempdata.h
new file mode 100644
index 0000000..72b753f
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiojobtempdata.h
@@ -0,0 +1,64 @@
+/*
+ *
+ * $Id: k3baudiojobtempdata.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_AUDIO_JOB_TEMPDATA_H_
+#define _K3B_AUDIO_JOB_TEMPDATA_H_
+
+#include <qobject.h>
+#include <k3bmsf.h>
+
+class K3bAudioTrack;
+class K3bAudioDoc;
+class QTextStream;
+
+
+class K3bAudioJobTempData : public QObject
+{
+ Q_OBJECT
+
+ public:
+ K3bAudioJobTempData( K3bAudioDoc* doc, QObject* parent = 0, const char* name = 0 );
+ ~K3bAudioJobTempData();
+
+ const QString& bufferFileName( int track );
+ const QString& bufferFileName( K3bAudioTrack* track );
+
+ const QString& infFileName( int track );
+ const QString& infFileName( K3bAudioTrack* track );
+
+ const QString& tocFileName();
+
+ K3bAudioDoc* doc() const;
+
+ /**
+ * use this if you want
+ * a specific directory
+ * it defaults to the default K3b temp dir
+ */
+ void prepareTempFileNames( const QString& path = QString::null );
+
+ /**
+ * remove all temp files (this does not include the audio buffer files
+ * since these are not created and thus not handled by the K3bAudioJobTempData)
+ */
+ void cleanup();
+
+ private:
+ class Private;
+ Private* d;
+};
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudiomaxspeedjob.cpp b/libk3b/projects/audiocd/k3baudiomaxspeedjob.cpp
new file mode 100644
index 0000000..975ab89
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiomaxspeedjob.cpp
@@ -0,0 +1,224 @@
+/*
+ *
+ * $Id: k3baudiomaxspeedjob.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 "k3baudiomaxspeedjob.h"
+#include "k3baudiotrack.h"
+#include "k3baudiodatasource.h"
+#include "k3baudiodoc.h"
+#include "k3baudiocdtracksource.h"
+#include "k3baudiodatasourceiterator.h"
+
+#include <k3bdevice.h>
+
+#include <k3bthread.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qdatetime.h>
+
+
+class K3bAudioMaxSpeedJob::WorkThread : public K3bThread
+{
+public:
+ WorkThread( K3bAudioDoc* doc );
+ ~WorkThread();
+
+ void run();
+
+ int speedTest( K3bAudioDataSource* source );
+ void cancel();
+ int maxSpeedByMedia() const;
+
+ int maxSpeed;
+
+private:
+ K3bAudioDoc* m_doc;
+ bool m_canceled;
+ char* m_buffer;
+};
+
+
+K3bAudioMaxSpeedJob::WorkThread::WorkThread( K3bAudioDoc* doc )
+ : K3bThread(),
+ m_doc(doc),
+ m_canceled(false)
+{
+ m_buffer = new char[2352*10];
+}
+
+
+K3bAudioMaxSpeedJob::WorkThread::~WorkThread()
+{
+ delete [] m_buffer;
+}
+
+
+void K3bAudioMaxSpeedJob::WorkThread::run()
+{
+ kdDebug() << k_funcinfo << endl;
+ m_canceled = false;
+
+ emitStarted();
+
+ K3bAudioDataSourceIterator it( m_doc );
+
+ // count sources for minimal progress info
+ int numSources = 0;
+ int sourcesDone = 0;
+ while( it.current() ) {
+ ++numSources;
+ it.next();
+ }
+
+ bool success = true;
+ maxSpeed = 175*1000;
+ it.first();
+
+ while( it.current() && !m_canceled ) {
+ if( !it.current()->seek(0) ) {
+ kdDebug() << "(K3bAudioMaxSpeedJob) seek failed." << endl;
+ success = false;
+ break;
+ }
+
+ // read some data
+ int speed = speedTest( it.current() );
+
+ ++sourcesDone;
+ emitPercent( 100*numSources/sourcesDone );
+
+ if( speed < 0 ) {
+ success = false;
+ break;
+ }
+ else if( speed > 0 ) {
+ // update the max speed
+ maxSpeed = QMIN( maxSpeed, speed );
+ }
+
+ it.next();
+ }
+
+ if( m_canceled ) {
+ emitCanceled();
+ success = false;
+ }
+
+ if( success )
+ kdDebug() << "(K3bAudioMaxSpeedJob) max speed: " << maxSpeed << endl;
+
+ emitFinished( success );
+}
+
+// returns the amount of data read from this source
+int K3bAudioMaxSpeedJob::WorkThread::speedTest( K3bAudioDataSource* source )
+{
+ //
+ // in case of an audio track source we only test when the cd is inserted since asking the user would
+ // confuse him a lot.
+ //
+ // FIXME: there is still the problem of the spin up time.
+ //
+ if( K3bAudioCdTrackSource* cdts = dynamic_cast<K3bAudioCdTrackSource*>( source ) ) {
+ if( K3bDevice::Device* dev = cdts->searchForAudioCD() ) {
+ cdts->setDevice( dev );
+ }
+ else {
+ kdDebug() << "(K3bAudioMaxSpeedJob) ignoring audio cd track source." << endl;
+ return 0;
+ }
+ }
+
+ QTime t;
+ int dataRead = 0;
+ int r = 0;
+
+ // start the timer
+ t.start();
+
+ // read ten seconds of audio data. This is some value which seemed about right. :)
+ while( dataRead < 2352*75*10 && (r = source->read( m_buffer, 2352*10 )) > 0 ) {
+ dataRead += r;
+ }
+
+ // elapsed millisec
+ int usedT = t.elapsed();
+
+ if( r < 0 ) {
+ kdDebug() << "(K3bAudioMaxSpeedJob) read failure." << endl;
+ return -1;
+ }
+
+ // KB/sec (add 1 millisecond to avoid division by 0)
+ int throughput = (dataRead*1000+usedT)/(usedT+1)/1024;
+ kdDebug() << "(K3bAudioMaxSpeedJob) throughput: " << throughput
+ << " (" << dataRead << "/" << usedT << ")" << endl;
+
+
+ return throughput;
+}
+
+
+void K3bAudioMaxSpeedJob::WorkThread::cancel()
+{
+ kdDebug() << k_funcinfo << endl;
+ m_canceled = true;
+}
+
+
+int K3bAudioMaxSpeedJob::WorkThread::maxSpeedByMedia() const
+{
+ int s = 0;
+
+ QValueList<int> speeds = m_doc->burner()->determineSupportedWriteSpeeds();
+ // simply use what we have and let the writer decide if the speeds are empty
+ if( !speeds.isEmpty() ) {
+ // start with the highest speed and go down the list until we are below our max
+ QValueListIterator<int> it = speeds.end();
+ --it;
+ while( *it > maxSpeed && it != speeds.begin() )
+ --it;
+
+ // this is the first valid speed or the lowest supported one
+ s = *it;
+ kdDebug() << "(K3bAudioMaxSpeedJob) using speed factor: " << (s/175) << endl;
+ }
+
+ return s;
+}
+
+
+
+
+K3bAudioMaxSpeedJob::K3bAudioMaxSpeedJob( K3bAudioDoc* doc, K3bJobHandler* jh, QObject* parent, const char* name )
+ : K3bThreadJob( jh, parent, name )
+{
+ m_thread = new WorkThread( doc );
+ setThread( m_thread );
+}
+
+
+K3bAudioMaxSpeedJob::~K3bAudioMaxSpeedJob()
+{
+ delete m_thread;
+}
+
+
+int K3bAudioMaxSpeedJob::maxSpeed() const
+{
+ return m_thread->maxSpeedByMedia();
+}
+#include "k3baudiomaxspeedjob.moc"
diff --git a/libk3b/projects/audiocd/k3baudiomaxspeedjob.h b/libk3b/projects/audiocd/k3baudiomaxspeedjob.h
new file mode 100644
index 0000000..876bc7f
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiomaxspeedjob.h
@@ -0,0 +1,43 @@
+/*
+ *
+ * $Id: k3baudiomaxspeedjob.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_MAX_SPEED_JOB_H_
+#define _K3B_AUDIO_MAX_SPEED_JOB_H_
+
+#include <k3bthreadjob.h>
+
+class K3bAudioDoc;
+
+
+class K3bAudioMaxSpeedJob : public K3bThreadJob
+{
+ Q_OBJECT
+
+ public:
+ K3bAudioMaxSpeedJob( K3bAudioDoc* doc, K3bJobHandler*, QObject* parent = 0, const char* name = 0 );
+ ~K3bAudioMaxSpeedJob();
+
+ /**
+ * KB/sec
+ * Only valid if the job finished successfully.
+ */
+ int maxSpeed() const;
+
+ private:
+ class WorkThread;
+ WorkThread* m_thread;
+};
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudionormalizejob.cpp b/libk3b/projects/audiocd/k3baudionormalizejob.cpp
new file mode 100644
index 0000000..782712b
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudionormalizejob.cpp
@@ -0,0 +1,205 @@
+/*
+ *
+ * $Id: k3baudionormalizejob.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 "k3baudionormalizejob.h"
+#include <k3bexternalbinmanager.h>
+#include <k3bprocess.h>
+#include <k3bcore.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+
+
+K3bAudioNormalizeJob::K3bAudioNormalizeJob( K3bJobHandler* hdl, QObject* parent, const char* name )
+ : K3bJob( hdl, parent, name ),
+ m_process(0)
+{
+}
+
+
+K3bAudioNormalizeJob::~K3bAudioNormalizeJob()
+{
+ if( m_process )
+ delete m_process;
+}
+
+
+void K3bAudioNormalizeJob::start()
+{
+ m_canceled = false;
+ m_currentAction = COMPUTING_LEVELS;
+ m_currentTrack = 1;
+
+ jobStarted();
+
+ if( m_process )
+ delete m_process;
+
+ m_process = new K3bProcess();
+ connect( m_process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStdLine(const QString&)) );
+ connect( m_process, SIGNAL(processExited(KProcess*)), this, SLOT(slotProcessExited(KProcess*)) );
+
+ const K3bExternalBin* bin = k3bcore->externalBinManager()->binObject( "normalize-audio" );
+
+ if( !bin ) {
+ emit infoMessage( i18n("Could not find normalize-audio executable."), ERROR );
+ jobFinished(false);
+ return;
+ }
+
+ if( !bin->copyright.isEmpty() )
+ emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3").arg(bin->name()).arg(bin->version).arg(bin->copyright), INFO );
+
+ // create the commandline
+ *m_process << bin;
+
+ // additional user parameters from config
+ const QStringList& params = bin->userParameters();
+ for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it )
+ *m_process << *it;
+
+ // end the options
+ *m_process << "--";
+
+ // add the files
+ for( uint i = 0; i < m_files.count(); ++i )
+ *m_process << m_files[i];
+
+ // now start the process
+ if( !m_process->start( KProcess::NotifyOnExit, KProcess::AllOutput ) ) {
+ // something went wrong when starting the program
+ // it "should" be the executable
+ kdDebug() << "(K3bAudioNormalizeJob) could not start normalize-audio" << endl;
+ emit infoMessage( i18n("Could not start normalize-audio."), K3bJob::ERROR );
+ jobFinished(false);
+ }
+}
+
+
+void K3bAudioNormalizeJob::cancel()
+{
+ m_canceled = true;
+
+ if( m_process )
+ if( m_process->isRunning() ) {
+ m_process->kill();
+ }
+}
+
+
+void K3bAudioNormalizeJob::slotStdLine( const QString& line )
+{
+ // percent, subPercent, newTask (compute level and adjust)
+
+ // emit newSubTask( i18n("Normalizing track %1 of %2 (%3)").arg(t).arg(tt).arg(m_files.at(t-1)) );
+
+ emit debuggingOutput( "normalize-audio", line );
+
+ // wenn "% done" drin:
+ // wenn ein --% drin ist, so beginnt ein neuer track
+ // sonst prozent parsen "batch xxx" ist der fortschritt der action
+ // also ev. den batch fortschritt * 1/2
+
+ if( line.startsWith( "Applying adjustment" ) ) {
+ if( m_currentAction == COMPUTING_LEVELS ) {
+ // starting the adjustment with track 1
+ m_currentTrack = 1;
+ m_currentAction = ADJUSTING_LEVELS;
+ }
+ }
+
+ else if( line.contains( "already normalized" ) ) {
+ // no normalization necessary for the current track
+ emit infoMessage( i18n("Track %1 is already normalized.").arg(m_currentTrack), INFO );
+ m_currentTrack++;
+ }
+
+ else if( line.contains( "--% done") ) {
+ if( m_currentAction == ADJUSTING_LEVELS ) {
+ emit newTask( i18n("Adjusting volume level for track %1 of %2").arg(m_currentTrack).arg(m_files.count()) );
+ kdDebug() << "(K3bAudioNormalizeJob) adjusting level for track "
+ << m_currentTrack
+ << " "
+ << m_files.at(m_currentTrack-1)
+ << endl;
+ }
+ else {
+ emit newTask( i18n("Computing level for track %1 of %2").arg(m_currentTrack).arg(m_files.count()) );
+ kdDebug() << "(K3bAudioNormalizeJob) computing level for track "
+ << m_currentTrack
+ << " "
+ << m_files.at(m_currentTrack-1)
+ << endl;
+ }
+
+ m_currentTrack++;
+ }
+
+ else if( int pos = line.find( "% done" ) > 0 ) {
+ // parse progress: "XXX% done" and "batch XXX% done"
+ pos -= 3;
+ bool ok;
+ // TODO: do not use fixed values
+ // track progress starts at position 19 in version 0.7.6
+ int p = line.mid( 19, 3 ).toInt(&ok);
+ if( ok )
+ emit subPercent( p );
+ else
+ kdDebug() << "(K3bAudioNormalizeJob) subPercent parsing error at pos "
+ << 19 << " in line '" << line.mid( 19, 3 ) << "'" << endl;
+
+ // batch progress starts at position 50 in version 0.7.6
+ p = line.mid( 50, 3 ).toInt(&ok);
+ if( ok && m_currentAction == COMPUTING_LEVELS )
+ emit percent( (int)((double)p/2.0) );
+ else if( ok && m_currentAction == ADJUSTING_LEVELS )
+ emit percent( 50 + (int)((double)p/2.0) );
+ else
+ kdDebug() << "(K3bAudioNormalizeJob) percent parsing error at pos "
+ << 50 << " in line '" << line.mid( 50, 3 ) << "'" << endl;
+
+ }
+}
+
+
+void K3bAudioNormalizeJob::slotProcessExited( KProcess* p )
+{
+ if( p->normalExit() ) {
+ switch( p->exitStatus() ) {
+ case 0:
+ emit infoMessage( i18n("Successfully normalized all tracks."), SUCCESS );
+ jobFinished(true);
+ break;
+ default:
+ if( !m_canceled ) {
+ emit infoMessage( i18n("%1 returned an unknown error (code %2).").arg("normalize-audio").arg(p->exitStatus()),
+ K3bJob::ERROR );
+ emit infoMessage( i18n("Please send me an email with the last output."), K3bJob::ERROR );
+ emit infoMessage( i18n("Error while normalizing tracks."), ERROR );
+ }
+ else
+ emit canceled();
+ jobFinished(false);
+ break;
+ }
+ }
+ else {
+ emit infoMessage( i18n("%1 did not exit cleanly.").arg("Normalize"), K3bJob::ERROR );
+ jobFinished( false );
+ }
+}
+
+#include "k3baudionormalizejob.moc"
diff --git a/libk3b/projects/audiocd/k3baudionormalizejob.h b/libk3b/projects/audiocd/k3baudionormalizejob.h
new file mode 100644
index 0000000..e56086b
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudionormalizejob.h
@@ -0,0 +1,63 @@
+/*
+ *
+ * $Id: k3baudionormalizejob.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_AUDIO_NORMALIZE_JOB_H_
+#define _K3B_AUDIO_NORMALIZE_JOB_H_
+
+
+#include <k3bjob.h>
+
+#include <qvaluevector.h>
+
+class K3bProcess;
+class KProcess;
+
+
+class K3bAudioNormalizeJob : public K3bJob
+{
+ Q_OBJECT
+
+ public:
+ K3bAudioNormalizeJob( K3bJobHandler*, QObject* parent = 0, const char* name = 0 );
+ ~K3bAudioNormalizeJob();
+
+ public slots:
+ void start();
+ void cancel();
+
+ void setFilesToNormalize( const QValueVector<QString>& files ) { m_files = files; }
+
+ private slots:
+ void slotStdLine( const QString& line );
+ void slotProcessExited( KProcess* p );
+
+ private:
+ K3bProcess* m_process;
+
+ QValueVector<QString> m_files;
+ bool m_canceled;
+
+ enum Action {
+ COMPUTING_LEVELS,
+ ADJUSTING_LEVELS
+ };
+
+ int m_currentAction;
+ int m_currentTrack;
+};
+
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudiotrack.cpp b/libk3b/projects/audiocd/k3baudiotrack.cpp
new file mode 100644
index 0000000..a1d12e4
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiotrack.cpp
@@ -0,0 +1,628 @@
+/*
+ *
+ * $Id: k3baudiotrack.cpp 620139 2007-01-05 11:59:05Z 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 "k3baudiotrack.h"
+#include "k3baudiodoc.h"
+#include "k3baudiodatasource.h"
+
+#include <k3baudiodecoder.h>
+#include <k3bcore.h>
+#include <k3bcdtextvalidator.h>
+
+#include <qstring.h>
+
+#include <kdebug.h>
+
+
+
+class K3bAudioTrack::Private
+{
+public:
+ Private() {
+ cdTextValidator = new K3bCdTextValidator();
+ }
+
+ ~Private() {
+ delete cdTextValidator;
+ }
+
+ K3bCdTextValidator* cdTextValidator;
+};
+
+
+K3bAudioTrack::K3bAudioTrack()
+ : m_parent(0),
+ m_copy(false),
+ m_preEmp(false),
+ m_index0Offset(150),
+ m_prev(0),
+ m_next(0),
+ m_firstSource(0),
+ m_currentSource(0),
+ m_alreadyReadBytes(0),
+ m_currentlyDeleting(false)
+{
+ d = new Private;
+}
+
+
+K3bAudioTrack::K3bAudioTrack( K3bAudioDoc* parent )
+ : m_parent(parent),
+ m_copy(false),
+ m_preEmp(false),
+ m_index0Offset(150),
+ m_prev(0),
+ m_next(0),
+ m_firstSource(0),
+ m_currentSource(0),
+ m_alreadyReadBytes(0),
+ m_currentlyDeleting(false)
+{
+ d = new Private;
+}
+
+
+K3bAudioTrack::~K3bAudioTrack()
+{
+ kdDebug() << "(K3bAudioTrack::~K3bAudioTrack) " << this << endl;
+ //
+ // It is crucial that we do not emit the changed signal here because otherwise
+ // the doc will delete us again once we are empty!
+ //
+ m_currentlyDeleting = true;
+
+ // fix the list
+ take();
+
+ kdDebug() << "(K3bAudioTrack::~K3bAudioTrack) deleting sources." << endl;
+
+ // delete all sources
+ while( m_firstSource )
+ delete m_firstSource->take();
+
+ kdDebug() << "(K3bAudioTrack::~K3bAudioTrack) finished" << endl;
+
+ delete d;
+}
+
+
+void K3bAudioTrack::emitChanged()
+{
+ if( m_parent )
+ m_parent->slotTrackChanged( this );
+}
+
+
+void K3bAudioTrack::setArtist( const QString& a )
+{
+ setPerformer( a );
+}
+
+
+void K3bAudioTrack::setPerformer( const QString& a )
+{
+ QString s( a );
+ d->cdTextValidator->fixup( s );
+ m_cdText.setPerformer(s);
+ emitChanged();
+}
+
+
+void K3bAudioTrack::setTitle( const QString& t )
+{
+ QString s( t );
+ d->cdTextValidator->fixup( s );
+ m_cdText.setTitle(s);
+ emitChanged();
+}
+
+
+void K3bAudioTrack::setArranger( const QString& t )
+{
+ QString s( t );
+ d->cdTextValidator->fixup( s );
+ m_cdText.setArranger(s);
+ emitChanged();
+}
+
+
+void K3bAudioTrack::setSongwriter( const QString& t )
+{
+ QString s( t );
+ d->cdTextValidator->fixup( s );
+ m_cdText.setSongwriter(s);
+ emitChanged();
+}
+
+
+void K3bAudioTrack::setComposer( const QString& t )
+{
+ QString s( t );
+ d->cdTextValidator->fixup( s );
+ m_cdText.setComposer(s);
+ emitChanged();
+}
+
+
+void K3bAudioTrack::setIsrc( const QString& t )
+{
+ m_cdText.setIsrc(t);
+ emitChanged();
+}
+
+
+void K3bAudioTrack::setCdTextMessage( const QString& t )
+{
+ QString s( t );
+ d->cdTextValidator->fixup( s );
+ m_cdText.setMessage(s);
+ emitChanged();
+}
+
+
+void K3bAudioTrack::setCdText( const K3bDevice::TrackCdText& cdtext )
+{
+ m_cdText = cdtext;
+ emitChanged();
+}
+
+
+K3bAudioDataSource* K3bAudioTrack::lastSource() const
+{
+ K3bAudioDataSource* s = m_firstSource;
+ while( s && s->next() )
+ s = s->next();
+ return s;
+}
+
+
+bool K3bAudioTrack::inList() const
+{
+ if( doc() )
+ return ( doc()->firstTrack() == this || m_prev != 0 );
+ else
+ return false;
+}
+
+
+K3b::Msf K3bAudioTrack::length() const
+{
+ K3b::Msf length;
+ K3bAudioDataSource* source = m_firstSource;
+ while( source ) {
+ length += source->length();
+ source = source->next();
+ }
+ return length;
+}
+
+
+KIO::filesize_t K3bAudioTrack::size() const
+{
+ return length().audioBytes();
+}
+
+
+unsigned int K3bAudioTrack::trackNumber() const
+{
+ if( m_prev )
+ return m_prev->trackNumber() + 1;
+ else
+ return 1;
+}
+
+
+K3b::Msf K3bAudioTrack::index0() const
+{
+ // we save the index0Offset as length of the resulting pregap
+ // this way the length of the track does not need to be ready
+ // when creating the track.
+ return length() - m_index0Offset;
+}
+
+
+K3b::Msf K3bAudioTrack::postGap() const
+{
+ if( next() )
+ return m_index0Offset;
+ else
+ return 0;
+}
+
+
+void K3bAudioTrack::setIndex0( const K3b::Msf& msf )
+{
+ if( msf == 0 )
+ m_index0Offset = 0;
+ else
+ m_index0Offset = length() - msf;
+}
+
+
+K3bAudioTrack* K3bAudioTrack::take()
+{
+ if( inList() ) {
+ if( !m_prev )
+ doc()->setFirstTrack( m_next );
+ if( !m_next )
+ doc()->setLastTrack( m_prev );
+
+ if( m_prev )
+ m_prev->m_next = m_next;
+ if( m_next )
+ m_next->m_prev = m_prev;
+
+ m_prev = m_next = 0;
+
+ // remove from doc
+ if( m_parent )
+ m_parent->slotTrackRemoved(this);
+ m_parent = 0;
+ }
+
+ return this;
+}
+
+
+void K3bAudioTrack::moveAfter( K3bAudioTrack* track )
+{
+ kdDebug() << "(K3bAudioTrack::moveAfter( " << track << " )" << endl;
+ if( !track ) {
+ if( !doc() ) {
+ kdDebug() << "(K3bAudioTrack::moveAfter) no parent set" << endl;
+ return;
+ }
+
+ // make sure we do not mess up the list
+ if( doc()->lastTrack() )
+ moveAfter( doc()->lastTrack() );
+ else {
+ doc()->setFirstTrack( take() );
+ doc()->setLastTrack( this );
+ }
+ }
+ else if( track == this ) {
+ kdDebug() << "(K3bAudioTrack::moveAfter) trying to move this after this." << endl;
+ return;
+ }
+ else {
+ // remove this from the list
+ take();
+
+ // set the new parent doc
+ m_parent = track->doc();
+
+ K3bAudioTrack* oldNext = track->m_next;
+
+ // set track as prev
+ track->m_next = this;
+ m_prev = track;
+
+ // set oldNext as next
+ if( oldNext )
+ oldNext->m_prev = this;
+ m_next = oldNext;
+
+ if( !m_prev )
+ doc()->setFirstTrack( this );
+ if( !m_next )
+ doc()->setLastTrack( this );
+ }
+
+ emitChanged();
+}
+
+
+void K3bAudioTrack::moveAhead( K3bAudioTrack* track )
+{
+ if( !track ) {
+ if( !doc() ) {
+ kdDebug() << "(K3bAudioTrack::moveAfter) no parent set" << endl;
+ return;
+ }
+
+ // make sure we do not mess up the list
+ if( doc()->firstTrack() )
+ moveAhead( doc()->firstTrack() );
+ else {
+ doc()->setFirstTrack( take() );
+ doc()->setLastTrack( this );
+ }
+ }
+ else if( track == this ) {
+ kdDebug() << "(K3bAudioTrack::moveAhead) trying to move this ahead of this." << endl;
+ return;
+ }
+ else {
+ // remove this from the list
+ take();
+
+ // set the new parent doc
+ m_parent = track->doc();
+
+ K3bAudioTrack* oldPrev = track->m_prev;
+
+ // set track as next
+ m_next = track;
+ track->m_prev = this;
+
+ // set oldPrev as prev
+ m_prev = oldPrev;
+ if( oldPrev )
+ oldPrev->m_next = this;
+
+ if( !m_prev )
+ doc()->setFirstTrack( this );
+ if( !m_next )
+ doc()->setLastTrack( this );
+ }
+
+ emitChanged();
+}
+
+
+void K3bAudioTrack::merge( K3bAudioTrack* trackToMerge, K3bAudioDataSource* sourceAfter )
+{
+ kdDebug() << "(K3bAudioTrack::merge) " << trackToMerge << " into " << this << endl;
+ if( this == trackToMerge ) {
+ kdDebug() << "(K3bAudioTrack::merge) trying to merge this with this." << endl;
+ return;
+ }
+
+ // remove the track to merge to make sure it does not get deleted by the doc too early
+ trackToMerge->take();
+
+ // in case we prepend all of trackToMerge's sources
+ if( !sourceAfter ) {
+ kdDebug() << "(K3bAudioTrack::merge) merging " << trackToMerge->firstSource() << endl;
+ if( m_firstSource ) {
+ trackToMerge->firstSource()->moveAhead( m_firstSource );
+ }
+ else {
+ addSource( trackToMerge->firstSource()->take() );
+ }
+ sourceAfter = m_firstSource;
+ }
+
+ kdDebug() << "(K3bAudioTrack::merge) now merge the other sources." << endl;
+ // now merge all sources into this track
+ while( trackToMerge->firstSource() ) {
+ K3bAudioDataSource* s = trackToMerge->firstSource();
+ kdDebug() << "(K3bAudioTrack::merge) merging source " << s << " from track " << s->track() << " into track "
+ << this << " after source " << sourceAfter << endl;
+ s->moveAfter( sourceAfter );
+ sourceAfter = s;
+ }
+
+ // TODO: should we also merge the indices?
+
+ // now we can safely delete the track we merged
+ delete trackToMerge;
+
+ kdDebug() << "(K3bAudioTrack::merge) finished" << endl;
+
+ emitChanged();
+}
+
+
+void K3bAudioTrack::setFirstSource( K3bAudioDataSource* source )
+{
+ // reset the reading stuff since this might be a completely new source list
+ m_currentSource = 0;
+ m_alreadyReadBytes = 0;
+
+ m_firstSource = source;
+ while( source ) {
+ source->m_track = this;
+ source = source->next();
+ }
+
+ emitChanged();
+}
+
+
+void K3bAudioTrack::addSource( K3bAudioDataSource* source )
+{
+ if( !source )
+ return;
+
+ K3bAudioDataSource* s = m_firstSource;
+ while( s && s->next() )
+ s = s->next();
+ if( s )
+ source->moveAfter( s );
+ else
+ setFirstSource( source->take() );
+}
+
+
+void K3bAudioTrack::sourceChanged( K3bAudioDataSource* )
+{
+ if( m_currentlyDeleting )
+ return;
+
+ // TODO: update indices
+
+ if( m_index0Offset > length() )
+ m_index0Offset = length()-1;
+
+ emitChanged();
+}
+
+
+int K3bAudioTrack::numberSources() const
+{
+ K3bAudioDataSource* source = m_firstSource;
+ int i = 0;
+ while( source ) {
+ source = source->next();
+ ++i;
+ }
+ return i;
+}
+
+
+bool K3bAudioTrack::seek( const K3b::Msf& msf )
+{
+ K3bAudioDataSource* source = m_firstSource;
+
+ K3b::Msf pos;
+ while( source && pos + source->length() < msf ) {
+ pos += source->length();
+ source = source->next();
+ }
+
+ if( source ) {
+ m_currentSource = source;
+ m_alreadyReadBytes = msf.audioBytes();
+ return source->seek( msf - pos );
+ }
+ else
+ return false;
+}
+
+
+int K3bAudioTrack::read( char* data, unsigned int max )
+{
+ if( !m_currentSource ) {
+ m_currentSource = m_firstSource;
+ if( m_currentSource )
+ m_currentSource->seek(0);
+ m_alreadyReadBytes = 0;
+ }
+
+ int readData = m_currentSource->read( data, max );
+ if( readData == 0 ) {
+ m_currentSource = m_currentSource->next();
+ if( m_currentSource ) {
+ m_currentSource->seek(0);
+ return read( data, max ); // read from next source
+ }
+ }
+
+ m_alreadyReadBytes += readData;
+
+ return readData;
+}
+
+
+K3bAudioTrack* K3bAudioTrack::copy() const
+{
+ K3bAudioTrack* track = new K3bAudioTrack();
+
+ track->m_copy = m_copy;
+ track->m_preEmp = m_preEmp;
+ track->m_index0Offset = m_index0Offset;
+ track->m_cdText = m_cdText;
+ K3bAudioDataSource* source = m_firstSource;
+ while( source ) {
+ track->addSource( source->copy() );
+ source = source->next();
+ }
+
+ return track;
+}
+
+
+K3bAudioTrack* K3bAudioTrack::split( const K3b::Msf& pos )
+{
+ if( pos < length() ) {
+ // search the source
+ // pos will be the first sector of the new track
+ K3b::Msf currentPos;
+ K3bAudioDataSource* source = firstSource();
+ while( source && currentPos + source->length() <= pos ) {
+ currentPos += source->length();
+ source = source->next();
+ }
+
+ K3bAudioDataSource* splitSource = 0;
+ if( currentPos > 0 && currentPos == pos ) {
+ // no need to split a source
+ splitSource = source;
+ }
+ else {
+ splitSource = source->split( pos - currentPos );
+ }
+
+ // the new track should include all sources from splitSource and below
+ K3bAudioTrack* splitTrack = new K3bAudioTrack();
+ splitTrack->m_cdText = m_cdText;
+ source = splitSource;
+ while( source ) {
+ K3bAudioDataSource* addSource = source;
+ source = source->next();
+ splitTrack->addSource( addSource );
+ }
+
+ kdDebug() << "(K3bAudioTrack) moving track " << splitTrack << " after this (" << this << ") with parent " << doc() << endl;
+ splitTrack->moveAfter( this );
+
+ return splitTrack;
+ }
+ else
+ return 0;
+}
+
+
+K3bDevice::Track K3bAudioTrack::toCdTrack() const
+{
+ if( !inList() )
+ return K3bDevice::Track();
+
+ K3b::Msf firstSector;
+ K3bAudioTrack* track = doc()->firstTrack();
+ while( track != this ) {
+ firstSector += track->length();
+ track = track->next();
+ }
+
+ K3bDevice::Track cdTrack( firstSector,
+ firstSector + length() - 1,
+ K3bDevice::Track::AUDIO );
+
+ // FIXME: auch im audiotrack copy permitted
+ cdTrack.setCopyPermitted( !copyProtection() );
+ cdTrack.setPreEmphasis( preEmp() );
+
+ // FIXME: add indices != 0
+
+ // no index 0 for the last track. Or should we allow this???
+ if( doc()->lastTrack() != this )
+ cdTrack.setIndex0( index0() );
+
+ // FIXME: convert to QCString
+ // cdTrack.setIsrc( isrc() );
+
+ return cdTrack;
+}
+
+
+void K3bAudioTrack::debug()
+{
+ kdDebug() << "Track " << this << endl
+ << " Prev: " << m_prev << endl
+ << " Next: " << m_next << endl
+ << " Sources:" << endl;
+ K3bAudioDataSource* s = m_firstSource;
+ while( s ) {
+ kdDebug() << " " << s << " - Prev: " << s->prev() << " Next: " << s->next() << endl;
+ s = s->next();
+ }
+}
+
+
+
diff --git a/libk3b/projects/audiocd/k3baudiotrack.h b/libk3b/projects/audiocd/k3baudiotrack.h
new file mode 100644
index 0000000..ab0ee1b
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiotrack.h
@@ -0,0 +1,214 @@
+/*
+ *
+ * $Id: k3baudiotrack.h 620139 2007-01-05 11:59:05Z 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 K3BAUDIOTRACK_H
+#define K3BAUDIOTRACK_H
+
+#include <qstring.h>
+#include <qfileinfo.h>
+#include <qfile.h>
+#include <qptrlist.h>
+
+#include <kio/global.h>
+
+#include <k3bmsf.h>
+
+#include <k3bcdtext.h>
+#include <k3btrack.h>
+#include "k3b_export.h"
+
+class K3bAudioDecoder;
+class K3bAudioDataSource;
+class K3bAudioDoc;
+
+
+/**
+ * @author Sebastian Trueg
+ */
+class LIBK3B_EXPORT K3bAudioTrack
+{
+ friend class K3bAudioDataSource;
+ friend class K3bAudioDoc;
+
+ public:
+ K3bAudioTrack();
+ K3bAudioTrack( K3bAudioDoc* parent );
+ ~K3bAudioTrack();
+
+ K3bAudioDoc* doc() const { return m_parent; }
+
+ K3bDevice::Track toCdTrack() const;
+
+ /**
+ * @return length of track in frames
+ */
+ K3b::Msf length() const;
+ KIO::filesize_t size() const;
+
+ const QString& artist() const { return m_cdText.performer(); }
+ const QString& performer() const { return m_cdText.performer(); }
+ const QString& title() const { return m_cdText.title(); }
+ const QString& arranger() const { return m_cdText.arranger(); }
+ const QString& songwriter() const { return m_cdText.songwriter(); }
+ const QString& composer() const { return m_cdText.composer(); }
+ const QString& isrc() const { return m_cdText.isrc(); }
+ const QString& cdTextMessage() const { return m_cdText.message(); }
+ const K3bDevice::TrackCdText& cdText() const { return m_cdText; }
+
+ bool copyProtection() const { return m_copy; }
+ bool preEmp() const { return m_preEmp; }
+
+ /**
+ * @obsolete use setPerformer
+ **/
+ void setArtist( const QString& a );
+ void setPerformer( const QString& a );
+ void setTitle( const QString& t );
+ void setArranger( const QString& t );
+ void setSongwriter( const QString& t );
+ void setComposer( const QString& t );
+ void setIsrc( const QString& t );
+ void setCdTextMessage( const QString& t );
+
+ void setCdText( const K3bDevice::TrackCdText& cdtext );
+
+ void setPreEmp( bool b ) { m_preEmp = b; emitChanged(); }
+ void setCopyProtection( bool b ) { m_copy = b; emitChanged(); }
+
+ K3b::Msf index0() const;
+ /**
+ * The length of the postgap, ie. the number of blocks with index0.
+ * This is always 0 for the last track.
+ */
+ K3b::Msf postGap() const;
+ void setIndex0( const K3b::Msf& );
+
+ /**
+ * \return The track number starting at 1.
+ */
+ unsigned int trackNumber() const;
+
+ /**
+ * Remove this track from the list and return it.
+ */
+ K3bAudioTrack* take();
+
+ /**
+ * Move this track after @p track.
+ * If @p track is null this track will be merged into the beginning
+ * of the docs list.
+ */
+ void moveAfter( K3bAudioTrack* track );
+
+ /**
+ * Move this track ahead of @p track.
+ * If @p track is null this track will be appended to the end
+ * of the docs list.
+ */
+ void moveAhead( K3bAudioTrack* track );
+
+ /**
+ * Merge @p trackToMerge into this one.
+ */
+ void merge( K3bAudioTrack* trackToMerge, K3bAudioDataSource* sourceAfter = 0 );
+
+ K3bAudioTrack* prev() const { return m_prev; }
+ K3bAudioTrack* next() const { return m_next; }
+
+ /**
+ * Use with care.
+ */
+ void setFirstSource( K3bAudioDataSource* source );
+ K3bAudioDataSource* firstSource() const { return m_firstSource; }
+ K3bAudioDataSource* lastSource() const;
+ int numberSources() const;
+
+ /**
+ * Append source to the end of the sources list.
+ */
+ void addSource( K3bAudioDataSource* source );
+
+ bool seek( const K3b::Msf& );
+
+ /**
+ * Read data from the track.
+ *
+ * @return number of read bytes
+ */
+ int read( char* data, unsigned int max );
+
+ /**
+ * called by K3bAudioDataSource because of the lack of signals
+ */
+ void sourceChanged( K3bAudioDataSource* );
+
+ /**
+ * Create a copy of this track containing copies of all the sources
+ * but not being part of some list.
+ */
+ K3bAudioTrack* copy() const;
+
+ /**
+ * Split the track at position pos and return the splitted track
+ * on success.
+ * The new track will be moved after this track.
+ *
+ * \param pos The position at which to split. \a pos will be the
+ * first frame in the new track.
+ */
+ K3bAudioTrack* split( const K3b::Msf& pos );
+
+ /**
+ * Is this track in a list
+ */
+ bool inList() const;
+
+ private:
+ /**
+ * Tells the doc that the track has changed
+ */
+ void emitChanged();
+
+ void debug();
+
+ K3bAudioDoc* m_parent;
+
+ /** copy protection */
+ bool m_copy;
+ bool m_preEmp;
+
+ K3b::Msf m_index0Offset;
+
+ K3bDevice::TrackCdText m_cdText;
+
+ // list
+ K3bAudioTrack* m_prev;
+ K3bAudioTrack* m_next;
+
+ K3bAudioDataSource* m_firstSource;
+
+
+ K3bAudioDataSource* m_currentSource;
+ long long m_alreadyReadBytes;
+
+ bool m_currentlyDeleting;
+
+ class Private;
+ Private* d;
+};
+
+
+#endif
diff --git a/libk3b/projects/audiocd/k3baudiozerodata.cpp b/libk3b/projects/audiocd/k3baudiozerodata.cpp
new file mode 100644
index 0000000..f5c985d
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiozerodata.cpp
@@ -0,0 +1,115 @@
+/*
+ *
+ * $Id: k3baudiozerodata.cpp 619556 2007-01-03 17:38:12Z trueg $
+ * Copyright (C) 2004 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 "k3baudiozerodata.h"
+#include "k3baudiotrack.h"
+
+#include <klocale.h>
+
+#include <string.h>
+
+
+K3bAudioZeroData::K3bAudioZeroData( const K3b::Msf& len )
+ : K3bAudioDataSource(),
+ m_length(len),
+ m_writtenData(0)
+{
+}
+
+
+K3bAudioZeroData::K3bAudioZeroData( const K3bAudioZeroData& zero )
+ : K3bAudioDataSource( zero ),
+ m_length( zero.m_length ),
+ m_writtenData( 0 )
+{
+}
+
+
+K3bAudioZeroData::~K3bAudioZeroData()
+{
+}
+
+
+void K3bAudioZeroData::setLength( const K3b::Msf& msf )
+{
+ if( msf > 0 )
+ m_length = msf;
+ else
+ m_length = 1; // 1 frame
+
+ m_writtenData = 0;
+
+ emitChange();
+}
+
+
+QString K3bAudioZeroData::type() const
+{
+ return i18n("Silence");
+}
+
+
+QString K3bAudioZeroData::sourceComment() const
+{
+ return QString::null;
+}
+
+
+bool K3bAudioZeroData::seek( const K3b::Msf& msf )
+{
+ if( msf < length() ) {
+ m_writtenData = msf.audioBytes();
+ return true;
+ }
+ else
+ return false;
+}
+
+
+int K3bAudioZeroData::read( char* data, unsigned int max )
+{
+ if( m_writtenData + max > length().audioBytes() )
+ max = length().audioBytes() - m_writtenData;
+
+ m_writtenData += max;
+
+ ::memset( data, 0, max );
+
+ return max;
+}
+
+
+K3bAudioDataSource* K3bAudioZeroData::copy() const
+{
+ return new K3bAudioZeroData( *this );
+}
+
+
+void K3bAudioZeroData::setStartOffset( const K3b::Msf& pos )
+{
+ if( pos >= length() )
+ setLength( 1 );
+ else
+ setLength( length() - pos );
+}
+
+
+void K3bAudioZeroData::setEndOffset( const K3b::Msf& pos )
+{
+ if( pos < 1 )
+ setLength( 1 );
+ else
+ setLength( pos );
+}
diff --git a/libk3b/projects/audiocd/k3baudiozerodata.h b/libk3b/projects/audiocd/k3baudiozerodata.h
new file mode 100644
index 0000000..8cb2911
--- /dev/null
+++ b/libk3b/projects/audiocd/k3baudiozerodata.h
@@ -0,0 +1,55 @@
+/*
+ *
+ * $Id: k3baudiozerodata.h 619556 2007-01-03 17:38:12Z trueg $
+ * Copyright (C) 2004 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_ZERO_DATA_H_
+#define _K3B_AUDIO_ZERO_DATA_H_
+
+#include "k3baudiodatasource.h"
+#include "k3b_export.h"
+
+class LIBK3B_EXPORT K3bAudioZeroData : public K3bAudioDataSource
+{
+ public:
+ K3bAudioZeroData( const K3b::Msf& msf = 150 );
+ K3bAudioZeroData( const K3bAudioZeroData& );
+ ~K3bAudioZeroData();
+
+ K3b::Msf originalLength() const { return m_length; }
+ void setLength( const K3b::Msf& msf );
+
+ QString type() const;
+ QString sourceComment() const;
+
+ bool seek( const K3b::Msf& );
+ int read( char* data, unsigned int max );
+
+ K3bAudioDataSource* copy() const;
+
+ /**
+ * Only changes the length
+ */
+ void setStartOffset( const K3b::Msf& );
+
+ /**
+ * Only changes the length
+ */
+ void setEndOffset( const K3b::Msf& );
+
+ private:
+ K3b::Msf m_length;
+ unsigned long long m_writtenData;
+};
+
+#endif