diff options
Diffstat (limited to 'libk3b/projects/audiocd')
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 |