diff options
Diffstat (limited to 'libk3b/projects')
141 files changed, 29208 insertions, 0 deletions
diff --git a/libk3b/projects/Makefile.am b/libk3b/projects/Makefile.am new file mode 100644 index 0000000..1a07372 --- /dev/null +++ b/libk3b/projects/Makefile.am @@ -0,0 +1,32 @@ +AM_CPPFLAGS= -I$(srcdir)/../../src -I$(srcdir)/../core -I$(srcdir)/../plugin -I$(srcdir)/../../libk3bdevice -I$(srcdir)/../tools -I$(srcdir)/datacd -I$(srcdir)/audiocd -I$(srcdir)/mixedcd -I$(srcdir)/movixcd -I$(srcdir)/movixdvd -I$(srcdir)/datadvd -I$(srcdir)/videocd -I$(srcdir)/videodvd $(all_includes) + +noinst_LTLIBRARIES = libk3bproject.la + +libk3bproject_la_LIBADD = ./datacd/libdata.la ./datadvd/libdvd.la ./mixedcd/libmixed.la ./videocd/libvcd.la ./videodvd/libvideodvd.la ./movixcd/libmovix.la ./movixdvd/libmovixdvd.la ./audiocd/libaudio.la + +libk3bproject_la_LDFLAGS = $(all_libraries) + +libk3bproject_la_SOURCES = k3babstractwriter.cpp \ + k3bgrowisofswriter.cpp \ + k3bgrowisofshandler.cpp \ + k3bdoc.cpp \ + k3bcdrdaowriter.cpp \ + k3bcdrecordwriter.cpp \ + k3binffilewriter.cpp \ + k3btocfilewriter.cpp \ + k3bimagefilereader.cpp \ + k3bcuefileparser.cpp \ + k3bpipebuffer.cpp + +include_HEADERS = k3bdoc.h \ + k3bgrowisofswriter.h \ + k3bcdrdaowriter.h \ + k3bcdrecordwriter.h \ + k3binffilewriter.h \ + k3btocfilewriter.h \ + k3bcuefileparser.h \ + k3bimagefilereader.h + +SUBDIRS = datacd audiocd mixedcd movixcd videocd datadvd movixdvd videodvd + +METASOURCES = AUTO 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 diff --git a/libk3b/projects/datacd/Makefile.am b/libk3b/projects/datacd/Makefile.am new file mode 100644 index 0000000..dea5cb2 --- /dev/null +++ b/libk3b/projects/datacd/Makefile.am @@ -0,0 +1,35 @@ +AM_CPPFLAGS= -I$(srcdir)/.. \ + -I$(srcdir)/../../core \ + -I$(srcdir)/../../plugin \ + -I$(srcdir)/../../../libk3bdevice \ + -I$(srcdir)/../../../src \ + -I$(srcdir)/../../tools \ + -I$(srcdir)/../../jobs \ + -I$(srcdir)/../.. \ + $(all_includes) + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libdata.la + +libdata_la_SOURCES = k3bdatajob.cpp \ + k3bdatadoc.cpp \ + k3bdataitem.cpp \ + k3bdiritem.cpp \ + k3bfileitem.cpp \ + k3bisoimager.cpp \ + k3bmsinfofetcher.cpp \ + k3bbootitem.cpp \ + k3bisooptions.cpp \ + k3bfilecompilationsizehandler.cpp \ + k3bsessionimportitem.cpp \ + k3bmkisofshandler.cpp \ + k3bdatapreparationjob.cpp + +include_HEADERS = k3bdatadoc.h \ + k3bdatajob.h \ + k3bdataitem.h \ + k3bdiritem.h \ + k3bfileitem.h \ + k3bbootitem.h \ + k3bisooptions.h diff --git a/libk3b/projects/datacd/k3bbootitem.cpp b/libk3b/projects/datacd/k3bbootitem.cpp new file mode 100644 index 0000000..e94830e --- /dev/null +++ b/libk3b/projects/datacd/k3bbootitem.cpp @@ -0,0 +1,58 @@ +/* + * + * $Id: k3bbootitem.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 "k3bbootitem.h" +#include "k3bdatadoc.h" +#include "k3bdiritem.h" + +#include <klocale.h> + +#include <qptrlist.h> + + +K3bBootItem::K3bBootItem( const QString& fileName, K3bDataDoc* doc, K3bDirItem* dir, const QString& k3bName ) + : K3bFileItem( fileName, doc, dir, k3bName, FILE|BOOT_IMAGE ), + m_noBoot(false), + m_bootInfoTable(false), + m_loadSegment(0), + m_loadSize(0), + m_imageType(FLOPPY) +{ + setExtraInfo( i18n("El Torito Boot image") ); +} + + +K3bBootItem::K3bBootItem( const K3bBootItem& item ) + : K3bFileItem( item ), + m_noBoot( item.m_noBoot ), + m_bootInfoTable( item.m_bootInfoTable ), + m_loadSegment( item.m_loadSegment ), + m_loadSize( item.m_loadSize ), + m_imageType( item.m_imageType ), + m_tempPath( item.m_tempPath ) +{ +} + + +K3bBootItem::~K3bBootItem() +{ + take(); +} + + +K3bDataItem* K3bBootItem::copy() const +{ + return new K3bBootItem( *this ); +} diff --git a/libk3b/projects/datacd/k3bbootitem.h b/libk3b/projects/datacd/k3bbootitem.h new file mode 100644 index 0000000..9dd8704 --- /dev/null +++ b/libk3b/projects/datacd/k3bbootitem.h @@ -0,0 +1,66 @@ +/* + * + * $Id: k3bbootitem.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_BOOT_ITEM_H_ +#define _K3B_BOOT_ITEM_H_ + +#include "k3bfileitem.h" + +class K3bBootItem : public K3bFileItem +{ + public: + K3bBootItem( const QString& fileName, K3bDataDoc* doc, K3bDirItem* dir, const QString& k3bName = 0 ); + K3bBootItem( const K3bBootItem& ); + ~K3bBootItem(); + + K3bDataItem* copy() const; + + bool isHideable() const { return false; } + + bool isBootItem() const { return true; } + + enum imageType { FLOPPY, HARDDISK, NONE }; + + void setNoBoot( bool b ) { m_noBoot = b; } + void setBootInfoTable( bool b ) { m_bootInfoTable = b; } + void setLoadSegment( int s ) { m_loadSegment = s; } + void setLoadSize( int s ) { m_loadSize = s; } + void setImageType( int t ) { m_imageType = t; } + + void setTempPath( const QString& p ) { m_tempPath = p; } + + bool noBoot() const { return m_noBoot; } + bool bootInfoTable() const { return m_bootInfoTable; } + int loadSegment() const { return m_loadSegment; } + int loadSize() const { return m_loadSize; } + int imageType() const { return m_imageType; } + + /** + * mkisofs changes boot images on disk. That is why the iso imager + * buffers them and saves the path to the buffered copy here. + */ + const QString& tempPath() const { return m_tempPath; } + + private: + bool m_noBoot; + bool m_bootInfoTable; + int m_loadSegment; + int m_loadSize; + int m_imageType; + + QString m_tempPath; +}; + +#endif diff --git a/libk3b/projects/datacd/k3bdatadoc.cpp b/libk3b/projects/datacd/k3bdatadoc.cpp new file mode 100644 index 0000000..d12c8d2 --- /dev/null +++ b/libk3b/projects/datacd/k3bdatadoc.cpp @@ -0,0 +1,1376 @@ +/* + * + * $Id: k3bdatadoc.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 "k3bdatadoc.h" +#include "k3bfileitem.h" +#include "k3bdiritem.h" +#include "k3bsessionimportitem.h" +#include "k3bdatajob.h" +#include "k3bbootitem.h" +#include "k3bspecialdataitem.h" +#include "k3bfilecompilationsizehandler.h" +#include "k3bmkisofshandler.h" +#include <k3bcore.h> +#include <k3bglobals.h> +#include <k3bmsf.h> +#include <k3biso9660.h> +#include <k3bdevicehandler.h> +#include <k3bdevice.h> +#include <k3btoc.h> +#include <k3btrack.h> +#include <k3bmultichoicedialog.h> +#include <k3bvalidators.h> + +#include <qdir.h> +#include <qstring.h> +#include <qfileinfo.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qtimer.h> +#include <qdom.h> +#include <qptrlist.h> + +#include <kstandarddirs.h> +#include <kurl.h> +#include <kstatusbar.h> +#include <klocale.h> +#include <kinputdialog.h> +#include <kmessagebox.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kprogress.h> +#include <kconfig.h> +#include <kapplication.h> + + +#include <string.h> +#include <stdlib.h> +#include <ctype.h> + + +/** + * There are two ways to fill a data project with files and folders: + * \li Use the addUrl and addUrls methods + * \li or create your own K3bDirItems and K3bFileItems. The doc will be properly updated + * by the constructors of the items. + */ +K3bDataDoc::K3bDataDoc( QObject* parent ) + : K3bDoc( parent ) +{ + m_root = 0; + + m_sizeHandler = new K3bFileCompilationSizeHandler(); +} + +K3bDataDoc::~K3bDataDoc() +{ + delete m_root; + delete m_sizeHandler; + // delete m_oldSessionSizeHandler; +} + + +bool K3bDataDoc::newDocument() +{ + clearImportedSession(); + + m_bootCataloge = 0; + m_oldSessionSize = 0; + m_bExistingItemsReplaceAll = m_bExistingItemsIgnoreAll = false; + + if( m_root ) { + while( m_root->children().getFirst() ) + removeItem( m_root->children().getFirst() ); + } + else + m_root = new K3bRootItem( this ); + + m_sizeHandler->clear(); + + m_multisessionMode = AUTO; + m_dataMode = K3b::DATA_MODE_AUTO; + + m_isoOptions = K3bIsoOptions(); + + return K3bDoc::newDocument(); +} + + +QString K3bDataDoc::name() const +{ + return m_isoOptions.volumeID(); +} + + +void K3bDataDoc::setIsoOptions( const K3bIsoOptions& o ) +{ + m_isoOptions = o; + emit changed(); +} + + +void K3bDataDoc::setVolumeID( const QString& v ) +{ + m_isoOptions.setVolumeID( v ); + emit changed(); +} + + +void K3bDataDoc::addUrls( const KURL::List& urls ) +{ + addUrls( urls, root() ); +} + + +void K3bDataDoc::addUrls( const KURL::List& l, K3bDirItem* dir ) +{ + if( !dir ) + dir = root(); + + KURL::List urls = K3b::convertToLocalUrls(l); + + for( KURL::List::ConstIterator it = urls.begin(); it != urls.end(); ++it ) { + const KURL& url = *it; + QFileInfo f( url.path() ); + QString k3bname = f.absFilePath().section( "/", -1 ); + + // filenames cannot end in backslashes (mkisofs problem. See comments in k3bisoimager.cpp (escapeGraftPoint())) + while( k3bname[k3bname.length()-1] == '\\' ) + k3bname.truncate( k3bname.length()-1 ); + + // backup dummy name + if( k3bname.isEmpty() ) + k3bname = "1"; + + K3bDirItem* newDirItem = 0; + + // rename the new item if an item with that name already exists + int cnt = 0; + bool ok = false; + while( !ok ) { + ok = true; + QString name( k3bname ); + if( cnt > 0 ) + name += QString("_%1").arg(cnt); + if( K3bDataItem* oldItem = dir->find( name ) ) { + if( f.isDir() && oldItem->isDir() ) { + // ok, just reuse the dir + newDirItem = static_cast<K3bDirItem*>(oldItem); + } + // directories cannot replace files in an old session (I think) + // and also directories can for sure never be replaced (only be reused as above) + // so we always rename if the old item is a dir. + else if( !oldItem->isFromOldSession() || + f.isDir() || + oldItem->isDir() ) { + ++cnt; + ok = false; + } + } + } + if( cnt > 0 ) + k3bname += QString("_%1").arg(cnt); + + // QFileInfo::exists and QFileInfo::isReadable return false for broken symlinks :( + if( f.isDir() && !f.isSymLink() ) { + if( !newDirItem ) { + newDirItem = new K3bDirItem( k3bname, this, dir ); + newDirItem->setLocalPath( url.path() ); // HACK: see k3bdiritem.h + } + + // recursively add all the files in the directory + QStringList dlist = QDir( f.absFilePath() ).entryList( QDir::All|QDir::System|QDir::Hidden ); + dlist.remove("."); + dlist.remove(".."); + KURL::List newUrls; + for( QStringList::Iterator it = dlist.begin(); it != dlist.end(); ++it ) + newUrls.append( KURL::fromPathOrURL( f.absFilePath() + "/" + *it ) ); + addUrls( newUrls, newDirItem ); + } + else if( f.isSymLink() || f.isFile() ) + (void)new K3bFileItem( url.path(), this, dir, k3bname ); + } + + emit changed(); + + setModified( true ); +} + + +bool K3bDataDoc::nameAlreadyInDir( const QString& name, K3bDirItem* dir ) +{ + if( !dir ) + return false; + else + return ( dir->find( name ) != 0 ); +} + + +K3bDirItem* K3bDataDoc::addEmptyDir( const QString& name, K3bDirItem* parent ) +{ + K3bDirItem* item = new K3bDirItem( name, this, parent ); + + setModified( true ); + + return item; +} + + +KIO::filesize_t K3bDataDoc::size() const +{ + if( m_isoOptions.doNotCacheInodes() ) + return root()->blocks().mode1Bytes() + m_oldSessionSize; + else + return m_sizeHandler->blocks( m_isoOptions.followSymbolicLinks() || + !m_isoOptions.createRockRidge() ).mode1Bytes() + m_oldSessionSize; +} + + +KIO::filesize_t K3bDataDoc::burningSize() const +{ + return size() - m_oldSessionSize; //m_oldSessionSizeHandler->size(); +} + + +K3b::Msf K3bDataDoc::length() const +{ + // 1 block consists of 2048 bytes real data + // and 1 block equals to 1 audio frame + // so this is the way to calculate: + + return K3b::Msf( size() / 2048 ); +} + + +K3b::Msf K3bDataDoc::burningLength() const +{ + return K3b::Msf( burningSize() / 2048 ); +} + + +QString K3bDataDoc::typeString() const +{ + return QString::fromLatin1("data"); +} + + +bool K3bDataDoc::loadDocumentData( QDomElement* rootElem ) +{ + if( !root() ) + newDocument(); + + QDomNodeList nodes = rootElem->childNodes(); + + if( nodes.item(0).nodeName() != "general" ) { + kdDebug() << "(K3bDataDoc) could not find 'general' section." << endl; + return false; + } + if( !readGeneralDocumentData( nodes.item(0).toElement() ) ) + return false; + + + // parse options + // ----------------------------------------------------------------- + if( nodes.item(1).nodeName() != "options" ) { + kdDebug() << "(K3bDataDoc) could not find 'options' section." << endl; + return false; + } + if( !loadDocumentDataOptions( nodes.item(1).toElement() ) ) + return false; + // ----------------------------------------------------------------- + + + + // parse header + // ----------------------------------------------------------------- + if( nodes.item(2).nodeName() != "header" ) { + kdDebug() << "(K3bDataDoc) could not find 'header' section." << endl; + return false; + } + if( !loadDocumentDataHeader( nodes.item(2).toElement() ) ) + return false; + // ----------------------------------------------------------------- + + + + // parse files + // ----------------------------------------------------------------- + if( nodes.item(3).nodeName() != "files" ) { + kdDebug() << "(K3bDataDoc) could not find 'files' section." << endl; + return false; + } + + if( m_root == 0 ) + m_root = new K3bRootItem( this ); + + QDomNodeList filesList = nodes.item(3).childNodes(); + for( uint i = 0; i < filesList.count(); i++ ) { + + QDomElement e = filesList.item(i).toElement(); + if( !loadDataItem( e, root() ) ) + return false; + } + + // ----------------------------------------------------------------- + + // + // Old versions of K3b do not properly save the boot catalog location + // and name. So to ensure we have one around even if loading an old project + // file we create a default one here. + // + if( !m_bootImages.isEmpty() && !m_bootCataloge ) + createBootCatalogeItem( m_bootImages.first()->parent() ); + + + informAboutNotFoundFiles(); + + return true; +} + + +bool K3bDataDoc::loadDocumentDataOptions( QDomElement elem ) +{ + QDomNodeList headerList = elem.childNodes(); + for( uint i = 0; i < headerList.count(); i++ ) { + + QDomElement e = headerList.item(i).toElement(); + if( e.isNull() ) + return false; + + if( e.nodeName() == "rock_ridge") + m_isoOptions.setCreateRockRidge( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "joliet") + m_isoOptions.setCreateJoliet( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "udf") + m_isoOptions.setCreateUdf( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "joliet_allow_103_characters") + m_isoOptions.setJolietLong( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_allow_lowercase") + m_isoOptions.setISOallowLowercase( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_allow_period_at_begin") + m_isoOptions.setISOallowPeriodAtBegin( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_allow_31_char") + m_isoOptions.setISOallow31charFilenames( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_omit_version_numbers") + m_isoOptions.setISOomitVersionNumbers( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_omit_trailing_period") + m_isoOptions.setISOomitTrailingPeriod( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_max_filename_length") + m_isoOptions.setISOmaxFilenameLength( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_relaxed_filenames") + m_isoOptions.setISOrelaxedFilenames( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_no_iso_translate") + m_isoOptions.setISOnoIsoTranslate( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_allow_multidot") + m_isoOptions.setISOallowMultiDot( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_untranslated_filenames") + m_isoOptions.setISOuntranslatedFilenames( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "follow_symbolic_links") + m_isoOptions.setFollowSymbolicLinks( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "create_trans_tbl") + m_isoOptions.setCreateTRANS_TBL( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "hide_trans_tbl") + m_isoOptions.setHideTRANS_TBL( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "iso_level") + m_isoOptions.setISOLevel( e.text().toInt() ); + + else if( e.nodeName() == "discard_symlinks") + m_isoOptions.setDiscardSymlinks( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "discard_broken_symlinks") + m_isoOptions.setDiscardBrokenSymlinks( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "preserve_file_permissions") + m_isoOptions.setPreserveFilePermissions( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "force_input_charset") + m_isoOptions.setForceInputCharset( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "input_charset") + m_isoOptions.setInputCharset( e.text() ); + + else if( e.nodeName() == "do_not_cache_inodes" ) + m_isoOptions.setDoNotCacheInodes( e.attributeNode( "activated" ).value() == "yes" ); + + else if( e.nodeName() == "whitespace_treatment" ) { + if( e.text() == "strip" ) + m_isoOptions.setWhiteSpaceTreatment( K3bIsoOptions::strip ); + else if( e.text() == "extended" ) + m_isoOptions.setWhiteSpaceTreatment( K3bIsoOptions::extended ); + else if( e.text() == "extended" ) + m_isoOptions.setWhiteSpaceTreatment( K3bIsoOptions::replace ); + else + m_isoOptions.setWhiteSpaceTreatment( K3bIsoOptions::noChange ); + } + + else if( e.nodeName() == "whitespace_replace_string") + m_isoOptions.setWhiteSpaceTreatmentReplaceString( e.text() ); + + else if( e.nodeName() == "data_track_mode" ) { + if( e.text() == "mode1" ) + m_dataMode = K3b::MODE1; + else if( e.text() == "mode2" ) + m_dataMode = K3b::MODE2; + else + m_dataMode = K3b::DATA_MODE_AUTO; + } + + else if( e.nodeName() == "multisession" ) { + QString mode = e.text(); + if( mode == "start" ) + setMultiSessionMode( START ); + else if( mode == "continue" ) + setMultiSessionMode( CONTINUE ); + else if( mode == "finish" ) + setMultiSessionMode( FINISH ); + else if( mode == "none" ) + setMultiSessionMode( NONE ); + else + setMultiSessionMode( AUTO ); + } + + else if( e.nodeName() == "verify_data" ) + setVerifyData( e.attributeNode( "activated" ).value() == "yes" ); + + else + kdDebug() << "(K3bDataDoc) unknown option entry: " << e.nodeName() << endl; + } + + return true; +} + + +bool K3bDataDoc::loadDocumentDataHeader( QDomElement headerElem ) +{ + QDomNodeList headerList = headerElem.childNodes(); + for( uint i = 0; i < headerList.count(); i++ ) { + + QDomElement e = headerList.item(i).toElement(); + if( e.isNull() ) + return false; + + if( e.nodeName() == "volume_id" ) + m_isoOptions.setVolumeID( e.text() ); + + else if( e.nodeName() == "application_id" ) + m_isoOptions.setApplicationID( e.text() ); + + else if( e.nodeName() == "publisher" ) + m_isoOptions.setPublisher( e.text() ); + + else if( e.nodeName() == "preparer" ) + m_isoOptions.setPreparer( e.text() ); + + else if( e.nodeName() == "volume_set_id" ) + m_isoOptions.setVolumeSetId( e.text() ); + + else if( e.nodeName() == "volume_set_size" ) + m_isoOptions.setVolumeSetSize( e.text().toInt() ); + + else if( e.nodeName() == "volume_set_number" ) + m_isoOptions.setVolumeSetNumber( e.text().toInt() ); + + else if( e.nodeName() == "system_id" ) + m_isoOptions.setSystemId( e.text() ); + + else + kdDebug() << "(K3bDataDoc) unknown header entry: " << e.nodeName() << endl; + } + + return true; +} + + +bool K3bDataDoc::loadDataItem( QDomElement& elem, K3bDirItem* parent ) +{ + K3bDataItem* newItem = 0; + + if( elem.nodeName() == "file" ) { + QDomElement urlElem = elem.firstChild().toElement(); + if( urlElem.isNull() ) { + kdDebug() << "(K3bDataDoc) file-element without url!" << endl; + return false; + } + + QFileInfo f( urlElem.text() ); + + // We canot use exists() here since this always disqualifies broken symlinks + if( !f.isFile() && !f.isSymLink() ) + m_notFoundFiles.append( urlElem.text() ); + + // broken symlinks are not readable according to QFileInfo which is wrong in our case + else if( f.isFile() && !f.isReadable() ) + m_noPermissionFiles.append( urlElem.text() ); + + else if( !elem.attribute( "bootimage" ).isEmpty() ) { + K3bBootItem* bootItem = new K3bBootItem( urlElem.text(), + this, + parent, + elem.attributeNode( "name" ).value() ); + if( elem.attribute( "bootimage" ) == "floppy" ) + bootItem->setImageType( K3bBootItem::FLOPPY ); + else if( elem.attribute( "bootimage" ) == "harddisk" ) + bootItem->setImageType( K3bBootItem::HARDDISK ); + else + bootItem->setImageType( K3bBootItem::NONE ); + bootItem->setNoBoot( elem.attribute( "no_boot" ) == "yes" ); + bootItem->setBootInfoTable( elem.attribute( "boot_info_table" ) == "yes" ); + bootItem->setLoadSegment( elem.attribute( "load_segment" ).toInt() ); + bootItem->setLoadSize( elem.attribute( "load_size" ).toInt() ); + + newItem = bootItem; + } + + else { + newItem = new K3bFileItem( urlElem.text(), + this, + parent, + elem.attributeNode( "name" ).value() ); + } + } + else if( elem.nodeName() == "special" ) { + if( elem.attributeNode( "type" ).value() == "boot cataloge" ) + createBootCatalogeItem( parent )->setK3bName( elem.attributeNode( "name" ).value() ); + } + else if( elem.nodeName() == "directory" ) { + // This is for the VideoDVD project which already contains the *_TS folders + K3bDirItem* newDirItem = 0; + if( K3bDataItem* item = parent->find( elem.attributeNode( "name" ).value() ) ) { + if( item->isDir() ) { + newDirItem = static_cast<K3bDirItem*>(item); + } + else { + kdError() << "(K3bDataDoc) INVALID DOCUMENT: item " << item->k3bPath() << " saved twice" << endl; + return false; + } + } + + if( !newDirItem ) + newDirItem = new K3bDirItem( elem.attributeNode( "name" ).value(), this, parent ); + QDomNodeList childNodes = elem.childNodes(); + for( uint i = 0; i < childNodes.count(); i++ ) { + + QDomElement e = childNodes.item(i).toElement(); + if( !loadDataItem( e, newDirItem ) ) + return false; + } + + newItem = newDirItem; + } + else { + kdDebug() << "(K3bDataDoc) wrong tag in files-section: " << elem.nodeName() << endl; + return false; + } + + // load the sort weight + if( newItem ) + newItem->setSortWeight( elem.attribute( "sort_weight", "0" ).toInt() ); + + return true; +} + + +bool K3bDataDoc::saveDocumentData( QDomElement* docElem ) +{ + QDomDocument doc = docElem->ownerDocument(); + + saveGeneralDocumentData( docElem ); + + // all options + // ---------------------------------------------------------------------- + QDomElement optionsElem = doc.createElement( "options" ); + saveDocumentDataOptions( optionsElem ); + docElem->appendChild( optionsElem ); + // ---------------------------------------------------------------------- + + // the header stuff + // ---------------------------------------------------------------------- + QDomElement headerElem = doc.createElement( "header" ); + saveDocumentDataHeader( headerElem ); + docElem->appendChild( headerElem ); + + + // now do the "real" work: save the entries + // ---------------------------------------------------------------------- + QDomElement topElem = doc.createElement( "files" ); + + QPtrListIterator<K3bDataItem> it( root()->children() ); + for( ; it.current(); ++it ) { + saveDataItem( it.current(), &doc, &topElem ); + } + + docElem->appendChild( topElem ); + // ---------------------------------------------------------------------- + + return true; +} + + +void K3bDataDoc::saveDocumentDataOptions( QDomElement& optionsElem ) +{ + QDomDocument doc = optionsElem.ownerDocument(); + + QDomElement topElem = doc.createElement( "rock_ridge" ); + topElem.setAttribute( "activated", isoOptions().createRockRidge() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "joliet" ); + topElem.setAttribute( "activated", isoOptions().createJoliet() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "udf" ); + topElem.setAttribute( "activated", isoOptions().createUdf() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "joliet_allow_103_characters" ); + topElem.setAttribute( "activated", isoOptions().jolietLong() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_allow_lowercase" ); + topElem.setAttribute( "activated", isoOptions().ISOallowLowercase() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_allow_period_at_begin" ); + topElem.setAttribute( "activated", isoOptions().ISOallowPeriodAtBegin() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_allow_31_char" ); + topElem.setAttribute( "activated", isoOptions().ISOallow31charFilenames() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_omit_version_numbers" ); + topElem.setAttribute( "activated", isoOptions().ISOomitVersionNumbers() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_omit_trailing_period" ); + topElem.setAttribute( "activated", isoOptions().ISOomitTrailingPeriod() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_max_filename_length" ); + topElem.setAttribute( "activated", isoOptions().ISOmaxFilenameLength() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_relaxed_filenames" ); + topElem.setAttribute( "activated", isoOptions().ISOrelaxedFilenames() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_no_iso_translate" ); + topElem.setAttribute( "activated", isoOptions().ISOnoIsoTranslate() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_allow_multidot" ); + topElem.setAttribute( "activated", isoOptions().ISOallowMultiDot() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_untranslated_filenames" ); + topElem.setAttribute( "activated", isoOptions().ISOuntranslatedFilenames() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "follow_symbolic_links" ); + topElem.setAttribute( "activated", isoOptions().followSymbolicLinks() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "create_trans_tbl" ); + topElem.setAttribute( "activated", isoOptions().createTRANS_TBL() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "hide_trans_tbl" ); + topElem.setAttribute( "activated", isoOptions().hideTRANS_TBL() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "iso_level" ); + topElem.appendChild( doc.createTextNode( QString::number(isoOptions().ISOLevel()) ) ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "discard_symlinks" ); + topElem.setAttribute( "activated", isoOptions().discardSymlinks() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "discard_broken_symlinks" ); + topElem.setAttribute( "activated", isoOptions().discardBrokenSymlinks() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "preserve_file_permissions" ); + topElem.setAttribute( "activated", isoOptions().preserveFilePermissions() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "force_input_charset" ); + topElem.setAttribute( "activated", isoOptions().forceInputCharset() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "do_not_cache_inodes" ); + topElem.setAttribute( "activated", isoOptions().doNotCacheInodes() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "input_charset" ); + topElem.appendChild( doc.createTextNode( isoOptions().inputCharset() ) ); + optionsElem.appendChild( topElem ); + + + topElem = doc.createElement( "whitespace_treatment" ); + switch( isoOptions().whiteSpaceTreatment() ) { + case K3bIsoOptions::strip: + topElem.appendChild( doc.createTextNode( "strip" ) ); + break; + case K3bIsoOptions::extended: + topElem.appendChild( doc.createTextNode( "extended" ) ); + break; + case K3bIsoOptions::replace: + topElem.appendChild( doc.createTextNode( "replace" ) ); + break; + default: + topElem.appendChild( doc.createTextNode( "noChange" ) ); + break; + } + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "whitespace_replace_string" ); + topElem.appendChild( doc.createTextNode( isoOptions().whiteSpaceTreatmentReplaceString() ) ); + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "data_track_mode" ); + if( m_dataMode == K3b::MODE1 ) + topElem.appendChild( doc.createTextNode( "mode1" ) ); + else if( m_dataMode == K3b::MODE2 ) + topElem.appendChild( doc.createTextNode( "mode2" ) ); + else + topElem.appendChild( doc.createTextNode( "auto" ) ); + optionsElem.appendChild( topElem ); + + + // save multisession + topElem = doc.createElement( "multisession" ); + switch( m_multisessionMode ) { + case START: + topElem.appendChild( doc.createTextNode( "start" ) ); + break; + case CONTINUE: + topElem.appendChild( doc.createTextNode( "continue" ) ); + break; + case FINISH: + topElem.appendChild( doc.createTextNode( "finish" ) ); + break; + case NONE: + topElem.appendChild( doc.createTextNode( "none" ) ); + break; + default: + topElem.appendChild( doc.createTextNode( "auto" ) ); + break; + } + optionsElem.appendChild( topElem ); + + topElem = doc.createElement( "verify_data" ); + topElem.setAttribute( "activated", verifyData() ? "yes" : "no" ); + optionsElem.appendChild( topElem ); + // ---------------------------------------------------------------------- +} + + +void K3bDataDoc::saveDocumentDataHeader( QDomElement& headerElem ) +{ + QDomDocument doc = headerElem.ownerDocument(); + + QDomElement topElem = doc.createElement( "volume_id" ); + topElem.appendChild( doc.createTextNode( isoOptions().volumeID() ) ); + headerElem.appendChild( topElem ); + + topElem = doc.createElement( "volume_set_id" ); + topElem.appendChild( doc.createTextNode( isoOptions().volumeSetId() ) ); + headerElem.appendChild( topElem ); + + topElem = doc.createElement( "volume_set_size" ); + topElem.appendChild( doc.createTextNode( QString::number(isoOptions().volumeSetSize()) ) ); + headerElem.appendChild( topElem ); + + topElem = doc.createElement( "volume_set_number" ); + topElem.appendChild( doc.createTextNode( QString::number(isoOptions().volumeSetNumber()) ) ); + headerElem.appendChild( topElem ); + + topElem = doc.createElement( "system_id" ); + topElem.appendChild( doc.createTextNode( isoOptions().systemId() ) ); + headerElem.appendChild( topElem ); + + topElem = doc.createElement( "application_id" ); + topElem.appendChild( doc.createTextNode( isoOptions().applicationID() ) ); + headerElem.appendChild( topElem ); + + topElem = doc.createElement( "publisher" ); + topElem.appendChild( doc.createTextNode( isoOptions().publisher() ) ); + headerElem.appendChild( topElem ); + + topElem = doc.createElement( "preparer" ); + topElem.appendChild( doc.createTextNode( isoOptions().preparer() ) ); + headerElem.appendChild( topElem ); + // ---------------------------------------------------------------------- +} + + +void K3bDataDoc::saveDataItem( K3bDataItem* item, QDomDocument* doc, QDomElement* parent ) +{ + if( K3bFileItem* fileItem = dynamic_cast<K3bFileItem*>( item ) ) { + if( m_oldSession.contains( fileItem ) ) { + kdDebug() << "(K3bDataDoc) ignoring fileitem " << fileItem->k3bName() << " from old session while saving..." << endl; + } + else { + QDomElement topElem = doc->createElement( "file" ); + topElem.setAttribute( "name", fileItem->k3bName() ); + QDomElement subElem = doc->createElement( "url" ); + subElem.appendChild( doc->createTextNode( fileItem->localPath() ) ); + topElem.appendChild( subElem ); + + if( item->sortWeight() != 0 ) + topElem.setAttribute( "sort_weight", QString::number(item->sortWeight()) ); + + parent->appendChild( topElem ); + + // add boot options as attributes to preserve compatibility to older K3b versions + if( K3bBootItem* bootItem = dynamic_cast<K3bBootItem*>( fileItem ) ) { + if( bootItem->imageType() == K3bBootItem::FLOPPY ) + topElem.setAttribute( "bootimage", "floppy" ); + else if( bootItem->imageType() == K3bBootItem::HARDDISK ) + topElem.setAttribute( "bootimage", "harddisk" ); + else + topElem.setAttribute( "bootimage", "none" ); + + topElem.setAttribute( "no_boot", bootItem->noBoot() ? "yes" : "no" ); + topElem.setAttribute( "boot_info_table", bootItem->bootInfoTable() ? "yes" : "no" ); + topElem.setAttribute( "load_segment", QString::number( bootItem->loadSegment() ) ); + topElem.setAttribute( "load_size", QString::number( bootItem->loadSize() ) ); + } + } + } + else if( item == m_bootCataloge ) { + QDomElement topElem = doc->createElement( "special" ); + topElem.setAttribute( "name", m_bootCataloge->k3bName() ); + topElem.setAttribute( "type", "boot cataloge" ); + + parent->appendChild( topElem ); + } + else if( K3bDirItem* dirItem = dynamic_cast<K3bDirItem*>( item ) ) { + QDomElement topElem = doc->createElement( "directory" ); + topElem.setAttribute( "name", dirItem->k3bName() ); + + if( item->sortWeight() != 0 ) + topElem.setAttribute( "sort_weight", QString::number(item->sortWeight()) ); + + QPtrListIterator<K3bDataItem> it( dirItem->children() ); + for( ; it.current(); ++it ) { + saveDataItem( it.current(), doc, &topElem ); + } + + parent->appendChild( topElem ); + } +} + + +void K3bDataDoc::removeItem( K3bDataItem* item ) +{ + if( !item ) + return; + + if( item->isRemoveable() ) { + delete item; + } + else + kdDebug() << "(K3bDataDoc) tried to remove non-removable entry!" << endl; +} + + +void K3bDataDoc::itemRemovedFromDir( K3bDirItem*, K3bDataItem* removedItem ) +{ + // update the project size + if( !removedItem->isFromOldSession() ) + m_sizeHandler->removeFile( removedItem ); + + // update the boot item list + if( removedItem->isBootItem() ) { + m_bootImages.removeRef( static_cast<K3bBootItem*>( removedItem ) ); + if( m_bootImages.isEmpty() ) { + delete m_bootCataloge; + m_bootCataloge = 0; + } + } + + emit itemRemoved( removedItem ); + emit changed(); +} + + +void K3bDataDoc::itemAddedToDir( K3bDirItem*, K3bDataItem* item ) +{ + // update the project size + if( !item->isFromOldSession() ) + m_sizeHandler->addFile( item ); + + // update the boot item list + if( item->isBootItem() ) + m_bootImages.append( static_cast<K3bBootItem*>( item ) ); + + emit itemAdded( item ); + emit changed(); +} + + +void K3bDataDoc::moveItem( K3bDataItem* item, K3bDirItem* newParent ) +{ + if( !item || !newParent ) { + kdDebug() << "(K3bDataDoc) item or parentitem was NULL while moving." << endl; + return; + } + + if( !item->isMoveable() ) { + kdDebug() << "(K3bDataDoc) item is not movable! " << endl; + return; + } + + item->reparent( newParent ); +} + + +void K3bDataDoc::moveItems( QPtrList<K3bDataItem> itemList, K3bDirItem* newParent ) +{ + if( !newParent ) { + kdDebug() << "(K3bDataDoc) tried to move items to nowhere...!" << endl; + return; + } + + QPtrListIterator<K3bDataItem> it( itemList ); + for( ; it.current(); ++it ) { + // check if newParent is subdir of item + if( K3bDirItem* dirItem = dynamic_cast<K3bDirItem*>( it.current() ) ) { + if( dirItem->isSubItem( newParent ) ) { + continue; + } + } + + if( it.current()->isMoveable() ) + it.current()->reparent( newParent ); + } +} + + +K3bBurnJob* K3bDataDoc::newBurnJob( K3bJobHandler* hdl, QObject* parent ) +{ + return new K3bDataJob( this, hdl, parent ); +} + + +QString K3bDataDoc::treatWhitespace( const QString& path ) +{ + + // TODO: + // It could happen that two files with different names + // will have the same name after the treatment + // Perhaps we should add a number at the end or something + // similar (s.a.) + + + if( isoOptions().whiteSpaceTreatment() != K3bIsoOptions::noChange ) { + QString result = path; + + if( isoOptions().whiteSpaceTreatment() == K3bIsoOptions::replace ) { + result.replace( ' ', isoOptions().whiteSpaceTreatmentReplaceString() ); + } + else if( isoOptions().whiteSpaceTreatment() == K3bIsoOptions::strip ) { + result.remove( ' ' ); + } + else if( isoOptions().whiteSpaceTreatment() == K3bIsoOptions::extended ) { + result.truncate(0); + for( uint i = 0; i < path.length(); i++ ) { + if( path[i] == ' ' ) { + if( path[i+1] != ' ' ) + result.append( path[++i].upper() ); + } + else + result.append( path[i] ); + } + } + + kdDebug() << "(K3bDataDoc) converted " << path << " to " << result << endl; + return result; + } + else + return path; +} + + +void K3bDataDoc::prepareFilenames() +{ + m_needToCutFilenames = false; + m_needToCutFilenameItems.clear(); + + // + // if joliet is used cut the names and rename if necessary + // 64 characters for standard joliet and 103 characters for long joliet names + // + // Rockridge supports the full 255 UNIX chars and in case Rockridge is disabled we leave + // it to mkisofs for now since handling all the options to alter the ISO9660 standard it just + // too much. + // + + K3bDataItem* item = root(); + unsigned int maxlen = ( isoOptions().jolietLong() ? 103 : 64 ); + while( (item = item->nextSibling()) ) { + item->setWrittenName( treatWhitespace( item->k3bName() ) ); + + if( isoOptions().createJoliet() && item->writtenName().length() > maxlen ) { + m_needToCutFilenames = true; + item->setWrittenName( K3b::cutFilename( item->writtenName(), maxlen ) ); + m_needToCutFilenameItems.append( item ); + } + + // TODO: check the Joliet charset + } + + // + // 3. check if a directory contains items with the same name + // + prepareFilenamesInDir( root() ); +} + + +void K3bDataDoc::prepareFilenamesInDir( K3bDirItem* dir ) +{ + if( !dir ) + return; + + QPtrList<K3bDataItem> sortedChildren; + QPtrListIterator<K3bDataItem> it( dir->children() ); + + for( it.toLast(); it.current(); --it ) { + K3bDataItem* item = it.current(); + + if( item->isDir() ) + prepareFilenamesInDir( dynamic_cast<K3bDirItem*>( item ) ); + + // insertion sort + unsigned int i = 0; + while( i < sortedChildren.count() && item->writtenName() > sortedChildren.at(i)->writtenName() ) + ++i; + + sortedChildren.insert( i, item ); + } + + + if( isoOptions().createJoliet() || isoOptions().createRockRidge() ) { + QPtrList<K3bDataItem> sameNameList; + while( !sortedChildren.isEmpty() ) { + + sameNameList.clear(); + + do { + sameNameList.append( sortedChildren.first() ); + sortedChildren.removeFirst(); + } while( !sortedChildren.isEmpty() && + sortedChildren.first()->writtenName() == sameNameList.first()->writtenName() ); + + if( sameNameList.count() > 1 ) { + // now we need to rename the items + unsigned int maxlen = 255; + if( isoOptions().createJoliet() ) { + if( isoOptions().jolietLong() ) + maxlen = 103; + else + maxlen = 64; + } + + int cnt = 1; + for( QPtrListIterator<K3bDataItem> it( sameNameList ); + it.current(); ++it ) { + it.current()->setWrittenName( K3b::appendNumberToFilename( it.current()->writtenName(), cnt++, maxlen ) ); + } + } + } + } +} + + +void K3bDataDoc::informAboutNotFoundFiles() +{ + if( !m_notFoundFiles.isEmpty() ) { + KMessageBox::informationList( qApp->activeWindow(), i18n("Could not find the following files:"), + m_notFoundFiles, i18n("Not Found") ); + m_notFoundFiles.clear(); + } + + if( !m_noPermissionFiles.isEmpty() ) { + KMessageBox::informationList( qApp->activeWindow(), i18n("No permission to read the following files:"), + m_noPermissionFiles, i18n("No Read Permission") ); + + m_noPermissionFiles.clear(); + } +} + + +void K3bDataDoc::setMultiSessionMode( K3bDataDoc::MultiSessionMode mode ) +{ + if( m_multisessionMode == NONE || m_multisessionMode == START ) + clearImportedSession(); + + m_multisessionMode = mode; +} + + +bool K3bDataDoc::importSession( K3bDevice::Device* device ) +{ + K3bDevice::DiskInfo diskInfo = device->diskInfo(); + // DVD+RW media is reported as non-appendable + if( !diskInfo.appendable() && + !(diskInfo.mediaType() & (K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_RW_OVWR)) ) + return false; + + K3bDevice::Toc toc = device->readToc(); + if( toc.isEmpty() || + toc.last().type() != K3bDevice::Track::DATA ) + return false; + + long startSec = toc.last().firstSector().lba(); + K3bIso9660 iso( device, startSec ); + + if( iso.open() ) { + // remove previously imported sessions + clearImportedSession(); + + // set multisession option + if( m_multisessionMode != FINISH && m_multisessionMode != AUTO ) + m_multisessionMode = CONTINUE; + + // since in iso9660 it is possible that two files share it's data + // simply summing the file sizes could result in wrong values + // that's why we use the size from the toc. This is more accurate + // anyway since there might be files overwritten or removed + m_oldSessionSize = toc.last().lastSector().mode1Bytes(); + + kdDebug() << "(K3bDataDoc) imported session size: " << KIO::convertSize(m_oldSessionSize) << endl; + + // the track size for DVD+RW media and DVD-RW Overwrite media has nothing to do with the filesystem + // size. in that case we need to use the filesystem's size (which is ok since it's one track anyway, + // no real multisession) + if( diskInfo.mediaType() & (K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_RW_OVWR) ) { + m_oldSessionSize = iso.primaryDescriptor().volumeSpaceSize + * iso.primaryDescriptor().logicalBlockSize; + } + + // import some former settings + m_isoOptions.setCreateRockRidge( iso.firstRRDirEntry() != 0 ); + m_isoOptions.setCreateJoliet( iso.firstJolietDirEntry() != 0 ); + m_isoOptions.setVolumeID( iso.primaryDescriptor().volumeId ); + // TODO: also import some other pd fields + + const K3bIso9660Directory* rootDir = iso.firstRRDirEntry(); + // Jörg Schilling says that it is impossible to import the joliet tree for multisession +// if( !rootDir ) +// rootDir = iso.firstJolietDirEntry(); + if( !rootDir ) + rootDir = iso.firstIsoDirEntry(); + + if( rootDir ) { + createSessionImportItems( rootDir, root() ); + emit changed(); + return true; + } + else { + kdDebug() << "(K3bDataDoc::importSession) Could not find primary volume desc." << endl; + return false; + } + } + else { + kdDebug() << "(K3bDataDoc) unable to read toc." << endl; + return false; + } +} + + +void K3bDataDoc::createSessionImportItems( const K3bIso9660Directory* importDir, K3bDirItem* parent ) +{ + Q_ASSERT(importDir); + QStringList entries = importDir->entries(); + entries.remove( "." ); + entries.remove( ".." ); + for( QStringList::const_iterator it = entries.begin(); + it != entries.end(); ++it ) { + const K3bIso9660Entry* entry = importDir->entry( *it ); + K3bDataItem* oldItem = parent->find( entry->name() ); + if( entry->isDirectory() ) { + K3bDirItem* dir = 0; + if( oldItem && oldItem->isDir() ) { + dir = (K3bDirItem*)oldItem; + } + else { + // we overwrite without warning! + if( oldItem ) + removeItem( oldItem ); + dir = new K3bDirItem( entry->name(), this, parent ); + } + + dir->setRemoveable(false); + dir->setRenameable(false); + dir->setMoveable(false); + dir->setHideable(false); + dir->setWriteToCd(false); + dir->setExtraInfo( i18n("From previous session") ); + m_oldSession.append( dir ); + + createSessionImportItems( static_cast<const K3bIso9660Directory*>(entry), dir ); + } + else { + const K3bIso9660File* file = static_cast<const K3bIso9660File*>(entry); + + // we overwrite without warning! + if( oldItem ) + removeItem( oldItem ); + + K3bSessionImportItem* item = new K3bSessionImportItem( file, this, parent ); + item->setExtraInfo( i18n("From previous session") ); + m_oldSession.append( item ); + } + } +} + + +void K3bDataDoc::clearImportedSession() +{ + // m_oldSessionSizeHandler->clear(); + m_oldSessionSize = 0; + m_oldSession.setAutoDelete(false); + K3bDataItem* item = m_oldSession.first(); + while( !m_oldSession.isEmpty() ) { + if( item == 0 ) + item = m_oldSession.first(); + + if( item->isDir() ) { + K3bDirItem* dir = (K3bDirItem*)item; + if( dir->numDirs() + dir->numFiles() == 0 ) { + // this imported dir is not needed anymore + // since it is empty + m_oldSession.remove(); + delete dir; + } + else { + for( QPtrListIterator<K3bDataItem> it( dir->children() ); it.current(); ++it ) { + if( !m_oldSession.contains(it.current()) ) { + m_oldSession.remove(); + // now the dir becomes a totally normal dir + dir->setRemoveable(true); + dir->setRenameable(true); + dir->setMoveable(true); + dir->setHideable(true); + dir->setWriteToCd(true); + dir->setExtraInfo( "" ); + break; + } + } + } + } + else { + m_oldSession.remove(); + delete item; + } + + item = m_oldSession.next(); + } + + m_multisessionMode = AUTO; + + emit changed(); +} + + +K3bDirItem* K3bDataDoc::bootImageDir() +{ + K3bDataItem* b = m_root->find( "boot" ); + if( !b ) { + b = new K3bDirItem( "boot", this, m_root ); + setModified( true ); + } + + // if we cannot create the dir because there is a file named boot just use the root dir + if( !b->isDir() ) + return m_root; + else + return static_cast<K3bDirItem*>(b); +} + + +K3bBootItem* K3bDataDoc::createBootItem( const QString& filename, K3bDirItem* dir ) +{ + if( !dir ) + dir = bootImageDir(); + + K3bBootItem* boot = new K3bBootItem( filename, this, dir ); + + if( !m_bootCataloge ) + createBootCatalogeItem(dir); + + return boot; +} + + +K3bDataItem* K3bDataDoc::createBootCatalogeItem( K3bDirItem* dir ) +{ + if( !m_bootCataloge ) { + QString newName = "boot.catalog"; + int i = 0; + while( dir->alreadyInDirectory( "boot.catalog" ) ) { + ++i; + newName = QString( "boot%1.catalog" ).arg(i); + } + + K3bSpecialDataItem* b = new K3bSpecialDataItem( this, 0, dir, newName ); + m_bootCataloge = b; + m_bootCataloge->setRemoveable(false); + m_bootCataloge->setHideable(false); + m_bootCataloge->setWriteToCd(false); + m_bootCataloge->setExtraInfo( i18n("El Torito boot catalog file") ); + b->setMimeType( i18n("Boot catalog") ); + } + else + m_bootCataloge->reparent( dir ); + + return m_bootCataloge; +} + + +QValueList<K3bDataItem*> K3bDataDoc::findItemByLocalPath( const QString& path ) const +{ + Q_UNUSED( path ); + return QValueList<K3bDataItem*>(); +} + + +bool K3bDataDoc::sessionImported() const +{ + return !m_oldSession.isEmpty(); +} + +#include "k3bdatadoc.moc" diff --git a/libk3b/projects/datacd/k3bdatadoc.h b/libk3b/projects/datacd/k3bdatadoc.h new file mode 100644 index 0000000..e09177a --- /dev/null +++ b/libk3b/projects/datacd/k3bdatadoc.h @@ -0,0 +1,297 @@ +/* + * + * $Id: k3bdatadoc.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 K3BDATADOC_H +#define K3BDATADOC_H + +#include <k3bdoc.h> +#include <k3bdataitem.h> + +#include "k3bisooptions.h" + +#include <qptrlist.h> +#include <qfileinfo.h> +#include <qstringlist.h> + +#include <kurl.h> +#include <kio/global.h> +#include "k3b_export.h" + +class K3bDataItem; +class K3bRootItem; +class K3bDirItem; +class K3bFileItem; +class K3bJob; +class K3bBootItem; +class K3bFileCompilationSizeHandler; + +class KProgressDialog; +//class K3bView; +class KConfig; +class QString; +class QStringList; +class QWidget; +class QDomDocument; +class QDomElement; +class K3bIso9660Directory; + +namespace K3bDevice { + class Device; + class DeviceHandler; +} + + +/** + *@author Sebastian Trueg + */ + +class LIBK3B_EXPORT K3bDataDoc : public K3bDoc +{ + Q_OBJECT + + public: + K3bDataDoc( QObject* parent = 0 ); + virtual ~K3bDataDoc(); + + virtual int type() const { return DATA; } + virtual QString typeString() const; + + virtual QString name() const; + + enum MultiSessionMode { + /** + * Let the K3bDataJob decide if to close the CD or not. + * The decision is based on the state of the inserted media + * (appendable/closed), the size of the project (will it fill + * up the CD?), and the free space on the inserted media. + */ + AUTO, + NONE, + START, + CONTINUE, + FINISH + }; + + K3bRootItem* root() const { return m_root; } + + virtual bool newDocument(); + virtual KIO::filesize_t size() const; + + /** + * This is used for multisession where size() also returnes the imported session's size + */ + virtual KIO::filesize_t burningSize() const; + virtual K3b::Msf length() const; + virtual K3b::Msf burningLength() const; + + /** + * Simply deletes the item if it is removable (meaning isRemovable() returns true. + * Be aware that you can remove items simply by deleting them even if isRemovable() + * returns false. + */ + void removeItem( K3bDataItem* item ); + + /** + * Simply calls reparent. + */ + void moveItem( K3bDataItem* item, K3bDirItem* newParent ); + void moveItems( QPtrList<K3bDataItem> itemList, K3bDirItem* newParent ); + + K3bDirItem* addEmptyDir( const QString& name, K3bDirItem* parent ); + + QString treatWhitespace( const QString& ); + + virtual K3bBurnJob* newBurnJob( K3bJobHandler* hdl, QObject* parent = 0 ); + + MultiSessionMode multiSessionMode() const { return m_multisessionMode; } + void setMultiSessionMode( MultiSessionMode mode ); + + int dataMode() const { return m_dataMode; } + void setDataMode( int m ) { m_dataMode = m; } + + void setVerifyData( bool b ) { m_verifyData = b; } + bool verifyData() const { return m_verifyData; } + + static bool nameAlreadyInDir( const QString&, K3bDirItem* ); + + /** + * Most of the options that map to the mkisofs parameters are grouped + * together in the K3bIsoOptions class to allow easy saving to and loading + * from a KConfig object. + */ + const K3bIsoOptions& isoOptions() const { return m_isoOptions; } + void setIsoOptions( const K3bIsoOptions& ); + + const QPtrList<K3bBootItem>& bootImages() { return m_bootImages; } + K3bDataItem* bootCataloge() { return m_bootCataloge; } + + K3bDirItem* bootImageDir(); + + /** + * Create a boot item and also create a boot cataloge file in case none + * exists in the project. + * + * Calling this method has the same effect like creating a new K3bBootItem + * instance manually and then calling createBootCatalogeItem. + * + * \return The new boot item on success or 0 in case a file with the same + * name already exists. + */ + K3bBootItem* createBootItem( const QString& filename, K3bDirItem* bootDir = 0 ); + + /** + * Create a new boot catalog item. + * For now this is not called automatically for internal reasons. + * + * Call this if you create boot items manually instead of using createBootItem. + * + * The boot catalog is automatically deleted once the last boot item is removed + * from the doc. + * + * \return The new boot catalog item or the old one if it already exists. + */ + K3bDataItem* createBootCatalogeItem( K3bDirItem* bootDir ); + + /** + * This will prepare the filenames as written to the image. + * These filenames are saved in K3bDataItem::writtenName + */ + void prepareFilenames(); + + /** + * Returns true if filenames need to be cut due to the limitations of Joliet. + * + * This is only valid after a call to @p prepareFilenames() + */ + bool needToCutFilenames() const { return m_needToCutFilenames; } + + const QValueList<K3bDataItem*>& needToCutFilenameItems() const { return m_needToCutFilenameItems; } + + /** + * Imports a session into the project. This will create K3bSessionImportItems + * and properly set the imported session size. + * Some settings will be adjusted to the imported session (joliet, rr). + * + * Be aware that this method is blocking. + * + * \return true if the old session was successfully imported, false if no + * session could be found. + * + * \see clearImportedSession() + */ + bool importSession( K3bDevice::Device* ); + + bool sessionImported() const; + + /** + * Searches for an item by it's local path. + * + * NOT IMPLEMENTED YET! + * + * \return The items that correspond to the specified local path. + */ + QValueList<K3bDataItem*> findItemByLocalPath( const QString& path ) const; + + public slots: + virtual void addUrls( const KURL::List& urls ); + + /** + * Add urls syncroneously + * This method adds files recursively including symlinks, hidden, and system files. + * If a file already exists the new file's name will be appended a number. + */ + virtual void addUrls( const KURL::List& urls, K3bDirItem* dir ); + + void clearImportedSession(); + + /** + * Just a convience method to prevent using setIsoOptions for this + * often used value. + */ + void setVolumeID( const QString& ); + + signals: + void itemRemoved( K3bDataItem* ); + void itemAdded( K3bDataItem* ); + + protected: + /** reimplemented from K3bDoc */ + virtual bool loadDocumentData( QDomElement* root ); + /** reimplemented from K3bDoc */ + virtual bool saveDocumentData( QDomElement* ); + + void saveDocumentDataOptions( QDomElement& optionsElem ); + void saveDocumentDataHeader( QDomElement& headerElem ); + bool loadDocumentDataOptions( QDomElement optionsElem ); + bool loadDocumentDataHeader( QDomElement optionsElem ); + + K3bFileCompilationSizeHandler* m_sizeHandler; + + // K3bFileCompilationSizeHandler* m_oldSessionSizeHandler; + KIO::filesize_t m_oldSessionSize; + + private: + void prepareFilenamesInDir( K3bDirItem* dir ); + void createSessionImportItems( const K3bIso9660Directory*, K3bDirItem* parent ); + + /** + * used by K3bDirItem to inform about removed items. + */ + void itemRemovedFromDir( K3bDirItem* parent, K3bDataItem* removedItem ); + void itemAddedToDir( K3bDirItem* parent, K3bDataItem* addedItem ); + + /** + * load recursivly + */ + bool loadDataItem( QDomElement& e, K3bDirItem* parent ); + /** + * save recursivly + */ + void saveDataItem( K3bDataItem* item, QDomDocument* doc, QDomElement* parent ); + + void informAboutNotFoundFiles(); + + QStringList m_notFoundFiles; + QStringList m_noPermissionFiles; + + K3bRootItem* m_root; + + int m_dataMode; + + bool m_verifyData; + + KIO::filesize_t m_size; + + K3bIsoOptions m_isoOptions; + + MultiSessionMode m_multisessionMode; + QPtrList<K3bDataItem> m_oldSession; + + // boot cd stuff + K3bDataItem* m_bootCataloge; + QPtrList<K3bBootItem> m_bootImages; + + bool m_bExistingItemsReplaceAll; + bool m_bExistingItemsIgnoreAll; + + bool m_needToCutFilenames; + QValueList<K3bDataItem*> m_needToCutFilenameItems; + + friend class K3bMixedDoc; + friend class K3bDirItem; +}; + +#endif diff --git a/libk3b/projects/datacd/k3bdataitem.cpp b/libk3b/projects/datacd/k3bdataitem.cpp new file mode 100644 index 0000000..6f2a861 --- /dev/null +++ b/libk3b/projects/datacd/k3bdataitem.cpp @@ -0,0 +1,264 @@ +/* + * + * $Id: k3bdataitem.cpp 659634 2007-04-30 14:51:32Z 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 "k3bdataitem.h" +#include "k3bdiritem.h" +#include "k3bdatadoc.h" +#include <kdebug.h> + +#include <math.h> + + +class K3bDataItem::Private +{ +public: + int flags; +}; + + +K3bDataItem::K3bDataItem( K3bDataDoc* doc, K3bDataItem* parent, int flags ) + : m_bHideOnRockRidge(false), + m_bHideOnJoliet(false), + m_bRemoveable(true), + m_bRenameable(true), + m_bMovable(true), + m_bHideable(true), + m_bWriteToCd(true), + m_sortWeight(0) +{ + d = new Private; + d->flags = flags; + + m_doc = doc; + m_bHideOnRockRidge = m_bHideOnJoliet = false; + + if( parent ) + m_parentDir = parent->getDirItem(); + else + m_parentDir = 0; +} + + +K3bDataItem::K3bDataItem( const K3bDataItem& item ) + : m_k3bName( item.m_k3bName ), + m_doc( 0 ), + m_parentDir( 0 ), + m_bHideOnRockRidge( item.m_bHideOnRockRidge ), + m_bHideOnJoliet( item.m_bHideOnJoliet ), + m_bRemoveable( item.m_bRemoveable ), + m_bRenameable( item.m_bRenameable ), + m_bMovable( item.m_bMovable ), + m_bHideable( item.m_bHideable ), + m_bWriteToCd( item.m_bWriteToCd ), + m_extraInfo( item.m_extraInfo ), + m_sortWeight( item.m_sortWeight ) +{ + d = new Private; + d->flags = item.d->flags; +} + + +K3bDataItem::~K3bDataItem() +{ + delete d; +} + + +void K3bDataItem::setFlags( int flags ) +{ + d->flags = flags; +} + + +bool K3bDataItem::isBootItem() const +{ + return d->flags & BOOT_IMAGE; +} + + +KIO::filesize_t K3bDataItem::size() const +{ + return itemSize( m_doc + ? m_doc->isoOptions().followSymbolicLinks() || + !m_doc->isoOptions().createRockRidge() + : false ); +} + + +K3b::Msf K3bDataItem::blocks() const +{ + return itemBlocks( m_doc + ? m_doc->isoOptions().followSymbolicLinks() || + !m_doc->isoOptions().createRockRidge() + : false ); +} + + +K3b::Msf K3bDataItem::itemBlocks( bool followSymbolicLinks ) const +{ + return (long)::ceil( (double)itemSize( followSymbolicLinks ) / 2048.0 ); +} + + +void K3bDataItem::setK3bName( const QString& name ) { + if ( name != m_k3bName ) { + // test for not-allowed characters + if( name.contains('/') ) { + kdDebug() << "(K3bDataItem) name contained invalid characters!" << endl; + return; + } + + if( parent() ) { + K3bDataItem* item = parent()->find( name ); + if( item && item != this ) { + kdDebug() << "(K3bDataItem) item with that name already exists." << endl; + return; + } + } + + m_k3bName = name; + m_doc->setModified(); +} +} + + +const QString& K3bDataItem::k3bName() const +{ + return m_k3bName; +} + + +K3bDataItem* K3bDataItem::take() +{ + if( parent() ) + parent()->takeDataItem( this ); + + return this; +} + + +QString K3bDataItem::k3bPath() const +{ + if( !getParent() ) + return QString::null; // the root item is the only one not having a parent + else if( isDir() ) + return getParent()->k3bPath() + k3bName() + "/"; + else + return getParent()->k3bPath() + k3bName(); +} + + +QString K3bDataItem::writtenPath() const +{ + if( !getParent() ) + return QString::null; // the root item is the only one not having a parent + else if( isDir() ) + return getParent()->writtenPath() + writtenName() + "/"; + else + return getParent()->writtenPath() + writtenName(); +} + + +QString K3bDataItem::iso9660Path() const +{ + if( !getParent() ) + return QString::null; // the root item is the only one not having a parent + else if( isDir() ) + return getParent()->iso9660Path() + iso9660Name() + "/"; + else + return getParent()->iso9660Path() + iso9660Name(); +} + + +K3bDataItem* K3bDataItem::nextSibling() const +{ + K3bDataItem* item = const_cast<K3bDataItem*>(this); // urg, but we know that we don't mess with it, so... + K3bDirItem* parentItem = getParent(); + + while( parentItem ) { + if( K3bDataItem* i = parentItem->nextChild( item ) ) + return i; + + item = parentItem; + parentItem = item->getParent(); + } + + return 0; +} + + +void K3bDataItem::reparent( K3bDirItem* newParent ) +{ + // addDataItem will do all the stuff including taking this + newParent->addDataItem( this ); +} + + +bool K3bDataItem::hideOnRockRidge() const +{ + if( !isHideable() ) + return false; + if( getParent() ) + return m_bHideOnRockRidge || getParent()->hideOnRockRidge(); + else + return m_bHideOnRockRidge; +} + + +bool K3bDataItem::hideOnJoliet() const +{ + if( !isHideable() ) + return false; + if( getParent() ) + return m_bHideOnJoliet || getParent()->hideOnJoliet(); + else + return m_bHideOnJoliet; +} + + +void K3bDataItem::setHideOnRockRidge( bool b ) +{ + // there is no use in changing the value if + // it is already set by the parent + if( ( !getParent() || !getParent()->hideOnRockRidge() ) && + b != m_bHideOnRockRidge ) { + m_bHideOnRockRidge = b; + if ( m_doc ) + m_doc->setModified(); +} +} + + +void K3bDataItem::setHideOnJoliet( bool b ) +{ + // there is no use in changing the value if + // it is already set by the parent + if( ( !getParent() || !getParent()->hideOnJoliet() ) && + b != m_bHideOnJoliet ) { + m_bHideOnJoliet = b; + if ( m_doc ) + m_doc->setModified(); +} +} + + +int K3bDataItem::depth() const +{ + if( getParent() ) + return getParent()->depth() + 1; + else + return 0; +} diff --git a/libk3b/projects/datacd/k3bdataitem.h b/libk3b/projects/datacd/k3bdataitem.h new file mode 100644 index 0000000..36cdf05 --- /dev/null +++ b/libk3b/projects/datacd/k3bdataitem.h @@ -0,0 +1,225 @@ +/* + * + * $Id: k3bdataitem.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 K3BDATAITEM_H +#define K3BDATAITEM_H + + +class K3bDirItem; +class K3bDataDoc; + +#include <qstring.h> + +#include <kio/global.h> + +#include <k3bmsf.h> +#include "k3b_export.h" + + +/** + *@author Sebastian Trueg + */ +class LIBK3B_EXPORT K3bDataItem +{ + public: + K3bDataItem( K3bDataDoc* doc, K3bDataItem* parent = 0, int flags = 0 ); + + /** + * Default copy constructor. + * + * The result is an exact copy except that no parent dir it set and, thus, also no doc. + */ + K3bDataItem( const K3bDataItem& ); + + virtual ~K3bDataItem(); + + /** + * Return an exact copy of this data item. + * + * The result is an exact copy except that no parent dir it set and, thus, also no doc. + * + * Implementations should use the default constructor. + */ + virtual K3bDataItem* copy() const = 0; + + K3bDirItem* parent() { return m_parentDir; } + K3bDirItem* getParent() const { return m_parentDir; } + + /** + * Remove this item from it's parent and return a pointer to it. + */ + K3bDataItem* take(); + + K3bDataDoc* doc() const { return m_doc; } + virtual const QString& k3bName() const; + virtual void setK3bName( const QString& ); + + /** + * returns the path as defined by the k3b-hierachy, NOT starting with a slash + * (since this is used for graft-points!) + * directories have a trailing "/" + */ + virtual QString k3bPath() const; + + /** + * Returns the name of the item as used on the CD or DVD image. + * + * This is only valid after a call to @p K3bDataDoc::prepareFilenames() + */ + const QString& writtenName() const { return m_writtenName; } + + /** + * \return the pure name used in the Iso9660 tree. + * + * This is only valid after a call to @p K3bDataDoc::prepareFilenames() + */ + const QString& iso9660Name() const { return m_rawIsoName; } + + /** + * Returns the path of the item as written to the CD or DVD image. + * + * This is suited to be used for mkisofs graftpoints. + * + * This is only valid after a call to @p K3bDataDoc::prepareFilenames() + */ + virtual QString writtenPath() const; + + virtual QString iso9660Path() const; + + /** + * Used to set the written name by @p K3bDataDoc::prepareFilenames() + */ + void setWrittenName( const QString& s ) { m_writtenName = s; } + + /** + * Used to set the pure Iso9660 name by @p K3bDataDoc::prepareFilenames() + */ + void setIso9660Name( const QString& s ) { m_rawIsoName = s; } + + virtual K3bDataItem* nextSibling() const; + + /** returns the path to the file on the local filesystem */ + virtual QString localPath() const { return QString::null; } + + /** + * The size of the item + */ + KIO::filesize_t size() const; + + /** + * \return The number of blocks (2048 bytes) occupied by this item. + * This value equals to ceil(size()/2048) + */ + K3b::Msf blocks() const; + + /** + * \returne the dir of the item (or the item itself if it is a dir) + */ + virtual K3bDirItem* getDirItem() const { return getParent(); } + + virtual void reparent( K3bDirItem* ); + + // FIXME: use all these flags and make the isXXX methods + // non-virtual. Then move the parent()->addDataItem call + // to the K3bDataItem constructor + enum ItemFlags { + DIR = 0x1, + FILE = 0x2, + SPECIALFILE = 0x4, + SYMLINK = 0x8, + OLD_SESSION = 0x10, + BOOT_IMAGE = 0x11 + }; + + int flags() const; + + virtual bool isDir() const { return false; } + virtual bool isFile() const { return false; } + virtual bool isSpecialFile() const { return false; } + virtual bool isSymLink() const { return false; } + virtual bool isFromOldSession() const { return false; } + bool isBootItem() const; + + bool hideOnRockRidge() const; + bool hideOnJoliet() const; + + virtual void setHideOnRockRidge( bool b ); + virtual void setHideOnJoliet( bool b ); + + virtual long sortWeight() const { return m_sortWeight; } + virtual void setSortWeight( long w ) { m_sortWeight = w; } + + virtual int depth() const; + + virtual bool isValid() const { return true; } + + // these are all needed for special fileitems like + // imported sessions or the movix filesystem + virtual bool isRemoveable() const { return m_bRemoveable; } + virtual bool isMoveable() const { return m_bMovable; } + virtual bool isRenameable() const { return m_bRenameable; } + virtual bool isHideable() const { return m_bHideable; } + virtual bool writeToCd() const { return m_bWriteToCd; } + virtual const QString& extraInfo() const { return m_extraInfo; } + + void setRenameable( bool b ) { m_bRenameable = b; } + void setMoveable( bool b ) { m_bMovable = b; } + void setRemoveable( bool b ) { m_bRemoveable = b; } + void setHideable( bool b ) { m_bHideable = b; } + void setWriteToCd( bool b ) { m_bWriteToCd = b; } + void setExtraInfo( const QString& i ) { m_extraInfo = i; } + + protected: + virtual KIO::filesize_t itemSize( bool followSymlinks ) const = 0; + + /** + * \param followSymlinks If true symlinks will be followed and their + * size equals the size of the file they are + * pointing to. + * + * \return The number of blocks (2048 bytes) occupied by this item. + */ + virtual K3b::Msf itemBlocks( bool followSymlinks ) const; + + QString m_k3bName; + + void setFlags( int flags ); + + private: + class Private; + Private* d; + + QString m_writtenName; + QString m_rawIsoName; + + K3bDataDoc* m_doc; + K3bDirItem* m_parentDir; + + bool m_bHideOnRockRidge; + bool m_bHideOnJoliet; + bool m_bRemoveable; + bool m_bRenameable; + bool m_bMovable; + bool m_bHideable; + bool m_bWriteToCd; + QString m_extraInfo; + + long m_sortWeight; + + friend class K3bDirItem; +}; + +#endif diff --git a/libk3b/projects/datacd/k3bdatajob.cpp b/libk3b/projects/datacd/k3bdatajob.cpp new file mode 100644 index 0000000..7009a43 --- /dev/null +++ b/libk3b/projects/datacd/k3bdatajob.cpp @@ -0,0 +1,972 @@ +/* + * + * $Id: k3bdatajob.cpp 690187 2007-07-20 09:18:03Z trueg $ + * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + + +#include "k3bdatajob.h" +#include "k3bdatadoc.h" +#include "k3bisoimager.h" +#include "k3bmsinfofetcher.h" + +#include <k3bcore.h> +#include <k3bglobals.h> +#include <k3bversion.h> +#include <k3bdevice.h> +#include <k3bdevicehandler.h> +#include <k3btoc.h> +#include <k3btrack.h> +#include <k3bdevicehandler.h> +#include <k3bexternalbinmanager.h> +#include <k3bcdrecordwriter.h> +#include <k3bcdrdaowriter.h> +#include <k3bglobalsettings.h> +#include <k3bactivepipe.h> +#include <k3bfilesplitter.h> +#include <k3bverificationjob.h> + +#include <kprocess.h> +#include <kapplication.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <ktempfile.h> +#include <kio/global.h> +#include <kio/job.h> + +#include <qstring.h> +#include <qstringlist.h> +#include <qdatetime.h> +#include <qfile.h> +#include <qdatastream.h> +#include <kdebug.h> + + + +class K3bDataJob::Private +{ +public: + Private() + : usedWritingApp(K3b::CDRECORD), + verificationJob(0) { + } + + K3bDataDoc* doc; + + bool initializingImager; + bool imageFinished; + bool canceled; + + KTempFile* tocFile; + + int usedDataMode; + int usedWritingApp; + int usedWritingMode; + K3bDataDoc::MultiSessionMode usedMultiSessionMode; + + int copies; + int copiesDone; + + K3bVerificationJob* verificationJob; + + K3bFileSplitter imageFile; + K3bActivePipe pipe; +}; + + +K3bDataJob::K3bDataJob( K3bDataDoc* doc, K3bJobHandler* hdl, QObject* parent ) + : K3bBurnJob( hdl, parent ) +{ + d = new Private; + + d->doc = doc; + m_writerJob = 0; + d->tocFile = 0; + + m_isoImager = 0; + + m_msInfoFetcher = new K3bMsInfoFetcher( this, this ); + connect( m_msInfoFetcher, SIGNAL(finished(bool)), this, SLOT(slotMsInfoFetched(bool)) ); + connect( m_msInfoFetcher, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_msInfoFetcher, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + + d->imageFinished = true; +} + +K3bDataJob::~K3bDataJob() +{ + delete d->tocFile; + delete d; +} + + +K3bDoc* K3bDataJob::doc() const +{ + return d->doc; +} + + +K3bDevice::Device* K3bDataJob::writer() const +{ + if( doc()->onlyCreateImages() ) + return 0; // no writer needed -> no blocking on K3bBurnJob + else + return doc()->burner(); +} + + +void K3bDataJob::start() +{ + jobStarted(); + + d->canceled = false; + d->imageFinished = false; + d->copies = d->doc->copies(); + d->copiesDone = 0; + d->usedMultiSessionMode = d->doc->multiSessionMode(); + + prepareImager(); + + if( d->doc->dummy() ) { + d->doc->setVerifyData( false ); + d->copies = 1; + } + + emit newTask( i18n("Preparing data") ); + + // there is no harm in setting these even if we write on-the-fly + d->imageFile.setName( d->doc->tempDir() ); + d->pipe.readFromIODevice( &d->imageFile ); + + if( d->usedMultiSessionMode == K3bDataDoc::AUTO && !d->doc->onlyCreateImages() ) + determineMultiSessionMode(); + else + prepareWriting(); +} + + +void K3bDataJob::prepareWriting() +{ + if( !d->doc->onlyCreateImages() && + ( d->usedMultiSessionMode == K3bDataDoc::CONTINUE || + d->usedMultiSessionMode == K3bDataDoc::FINISH ) ) { + // no sense continuing the same session twice + // FIXME: why not? + d->copies = 1; + + m_msInfoFetcher->setDevice( d->doc->burner() ); + + if( !waitForMedium() ) { + cancel(); + return; + } + + if( K3b::isMounted( d->doc->burner() ) ) { + emit infoMessage( i18n("Unmounting disk"), INFO ); + K3b::unmount( d->doc->burner() ); + } + + m_msInfoFetcher->start(); + } + else { + m_isoImager->setMultiSessionInfo( QString::null ); + prepareData(); + + d->initializingImager = true; + m_isoImager->init(); + } +} + + +void K3bDataJob::slotMsInfoFetched(bool success) +{ + if( success ) { + // we call this here since in ms mode we might want to check + // the last track's datamode + prepareData(); + + if( d->usedWritingApp == K3b::CDRDAO ) // cdrdao seems to write a 150 blocks pregap that is not used by cdrecord + m_isoImager->setMultiSessionInfo( QString("%1,%2").arg(m_msInfoFetcher->lastSessionStart()).arg(m_msInfoFetcher->nextSessionStart()+150), d->doc->burner() ); + else + m_isoImager->setMultiSessionInfo( m_msInfoFetcher->msInfo(), d->doc->burner() ); + + d->initializingImager = true; + m_isoImager->init(); + } + else { + // the MsInfoFetcher already emitted failure info + cancelAll(); + jobFinished( false ); + } +} + + +void K3bDataJob::writeImage() +{ + d->initializingImager = false; + + emit burning(false); + + // get image file path + if( d->doc->tempDir().isEmpty() ) + d->doc->setTempDir( K3b::findUniqueFilePrefix( d->doc->isoOptions().volumeID() ) + ".iso" ); + + // TODO: check if the image file is part of the project and if so warn the user + // and append some number to make the path unique. + + emit newTask( i18n("Creating image file") ); + emit newSubTask( i18n("Track 1 of 1") ); + emit infoMessage( i18n("Creating image file in %1").arg(d->doc->tempDir()), INFO ); + + m_isoImager->writeToImageFile( d->doc->tempDir() ); + m_isoImager->start(); +} + + +bool K3bDataJob::startOnTheFlyWriting() +{ + if( prepareWriterJob() ) { + if( startWriterJob() ) { + // try a direct connection between the processes + if( m_writerJob->fd() != -1 ) + m_isoImager->writeToFd( m_writerJob->fd() ); + d->initializingImager = false; + m_isoImager->start(); + return true; + } + } + return false; +} + + +void K3bDataJob::cancel() +{ + emit infoMessage( i18n("Writing canceled."), K3bJob::ERROR ); + emit canceled(); + + if( m_writerJob && m_writerJob->active() ) { + // + // lets wait for the writer job to finish + // and let it finish the job for good. + // + cancelAll(); + } + else { + // + // Just cancel all and return + // This is bad design as we should wait for all subjobs to finish + // + cancelAll(); + jobFinished( false ); + } +} + + +void K3bDataJob::slotIsoImagerPercent( int p ) +{ + if( d->doc->onlyCreateImages() ) { + emit subPercent( p ); + emit percent( p ); + } + else if( !d->doc->onTheFly() ) { + double totalTasks = d->copies; + double tasksDone = d->copiesDone; // =0 when creating an image + if( d->doc->verifyData() ) { + totalTasks*=2; + tasksDone*=2; + } + if( !d->doc->onTheFly() ) { + totalTasks+=1.0; + } + + emit subPercent( p ); + emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); + } +} + + +void K3bDataJob::slotIsoImagerFinished( bool success ) +{ + if( d->initializingImager ) { + if( success ) { + if( d->doc->onTheFly() && !d->doc->onlyCreateImages() ) { + if( !startOnTheFlyWriting() ) { + cancelAll(); + jobFinished( false ); + } + } + else { + writeImage(); + } + } + else { + if( m_isoImager->hasBeenCanceled() ) + emit canceled(); + jobFinished( false ); + } + } + else { + // tell the writer that there won't be more data + if( d->doc->onTheFly() && m_writerJob ) + m_writerJob->closeFd(); + + if( !d->doc->onTheFly() || + d->doc->onlyCreateImages() ) { + + if( success ) { + emit infoMessage( i18n("Image successfully created in %1").arg(d->doc->tempDir()), K3bJob::SUCCESS ); + d->imageFinished = true; + + if( d->doc->onlyCreateImages() ) { + jobFinished( true ); + } + else { + if( prepareWriterJob() ) { + startWriterJob(); + d->pipe.writeToFd( m_writerJob->fd(), true ); + d->pipe.open(true); + } + } + } + else { + if( m_isoImager->hasBeenCanceled() ) + emit canceled(); + else + emit infoMessage( i18n("Error while creating ISO image"), ERROR ); + + cancelAll(); + jobFinished( false ); + } + } + else if( !success ) { // on-the-fly + // + // In case the imager failed let's make sure the writer does not emit an unusable + // error message. + // + if( m_writerJob && m_writerJob->active() ) + m_writerJob->setSourceUnreadable( true ); + + // there is one special case which we need to handle here: the iso imager might be canceled + // FIXME: the iso imager should not be able to cancel itself + if( m_isoImager->hasBeenCanceled() && !this->hasBeenCanceled() ) + cancel(); + } + } +} + + +bool K3bDataJob::startWriterJob() +{ + if( d->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") ); + + // if we append a new session we asked for an appendable cd already + if( d->usedMultiSessionMode == K3bDataDoc::NONE || + d->usedMultiSessionMode == K3bDataDoc::START ) { + + if( !waitForMedium() ) { + return false; + } + } + + emit burning(true); + m_writerJob->start(); + return true; +} + + +void K3bDataJob::slotWriterJobPercent( int p ) +{ + double totalTasks = d->copies; + double tasksDone = d->copiesDone; + if( d->doc->verifyData() ) { + totalTasks*=2; + tasksDone*=2; + } + if( !d->doc->onTheFly() ) { + totalTasks+=1.0; + tasksDone+=1.0; + } + + emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); +} + + +void K3bDataJob::slotWriterNextTrack( int t, int tt ) +{ + emit newSubTask( i18n("Writing Track %1 of %2").arg(t).arg(tt) ); +} + + +void K3bDataJob::slotWriterJobFinished( bool success ) +{ + d->pipe.close(); + + // + // This is a little workaround for the bad cancellation handling in this job + // see cancel() + // + if( d->canceled ) { + if( active() ) + jobFinished( false ); + } + + if( success ) { + // allright + // the writerJob should have emited the "simulation/writing successful" signal + + if( d->doc->verifyData() ) { + if( !d->verificationJob ) { + d->verificationJob = new K3bVerificationJob( this, this ); + connect( d->verificationJob, SIGNAL(infoMessage(const QString&, int)), + this, SIGNAL(infoMessage(const QString&, int)) ); + connect( d->verificationJob, SIGNAL(newTask(const QString&)), + this, SIGNAL(newSubTask(const QString&)) ); + connect( d->verificationJob, SIGNAL(newSubTask(const QString&)), + this, SIGNAL(newSubTask(const QString&)) ); + connect( d->verificationJob, SIGNAL(percent(int)), + this, SLOT(slotVerificationProgress(int)) ); + connect( d->verificationJob, SIGNAL(percent(int)), + this, SIGNAL(subPercent(int)) ); + connect( d->verificationJob, SIGNAL(finished(bool)), + this, SLOT(slotVerificationFinished(bool)) ); + connect( d->verificationJob, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + + } + d->verificationJob->clear(); + d->verificationJob->setDevice( d->doc->burner() ); + d->verificationJob->setGrownSessionSize( m_isoImager->size() ); + d->verificationJob->addTrack( 0, m_isoImager->checksum(), m_isoImager->size() ); + + emit burning(false); + + emit newTask( i18n("Verifying written data") ); + + d->verificationJob->start(); + } + else { + d->copiesDone++; + + if( d->copiesDone < d->copies ) { + K3bDevice::eject( d->doc->burner() ); + + bool failed = false; + if( d->doc->onTheFly() ) + failed = !startOnTheFlyWriting(); + else + failed = !startWriterJob(); + + if( failed ) { + cancel(); + } + else if( !d->doc->onTheFly() ) { + d->pipe.writeToFd( m_writerJob->fd(), true ); + d->pipe.open(true); + } + } + else { + cleanup(); + jobFinished(true); + } + } + } + else { + cancelAll(); + jobFinished( false ); + } +} + + +void K3bDataJob::slotVerificationProgress( int p ) +{ + double totalTasks = d->copies*2; + double tasksDone = d->copiesDone*2 + 1; // the writing of the current copy has already been finished + + if( !d->doc->onTheFly() ) { + totalTasks+=1.0; + tasksDone+=1.0; + } + + emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); +} + + +void K3bDataJob::slotVerificationFinished( bool success ) +{ + d->copiesDone++; + + // reconnect our imager which we deconnected for the verification + connectImager(); + + if( k3bcore->globalSettings()->ejectMedia() || d->copiesDone < d->copies ) + K3bDevice::eject( d->doc->burner() ); + + if( !d->canceled && d->copiesDone < d->copies ) { + bool failed = false; + if( d->doc->onTheFly() ) + failed = !startOnTheFlyWriting(); + else + failed = !startWriterJob(); + + if( failed ) + cancel(); + else if( !d->doc->onTheFly() ) { + d->pipe.writeToFd( m_writerJob->fd(), true ); + d->pipe.open(true); + } + } + else { + cleanup(); + jobFinished( success ); + } +} + + +void K3bDataJob::setWriterJob( K3bAbstractWriter* writer ) +{ + // FIXME: progressedsize for multiple copies + m_writerJob = writer; + connect( m_writerJob, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_writerJob, SIGNAL(percent(int)), this, SLOT(slotWriterJobPercent(int)) ); + connect( m_writerJob, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); + connect( m_writerJob, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) ); + connect( m_writerJob, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); + connect( m_writerJob, SIGNAL(nextTrack(int, int)), this, SLOT(slotWriterNextTrack(int, int)) ); + connect( m_writerJob, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) ); + connect( m_writerJob, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); + connect( m_writerJob, SIGNAL(writeSpeed(int, int)), this, SIGNAL(writeSpeed(int, int)) ); + connect( m_writerJob, SIGNAL(finished(bool)), this, SLOT(slotWriterJobFinished(bool)) ); + connect( m_writerJob, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( m_writerJob, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); +} + + +void K3bDataJob::setImager( K3bIsoImager* imager ) +{ + if( m_isoImager != imager ) { + delete m_isoImager; + + m_isoImager = imager; + + connectImager(); + } +} + + +void K3bDataJob::connectImager() +{ + m_isoImager->disconnect( this ); + connect( m_isoImager, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_isoImager, SIGNAL(percent(int)), this, SLOT(slotIsoImagerPercent(int)) ); + connect( m_isoImager, SIGNAL(finished(bool)), this, SLOT(slotIsoImagerFinished(bool)) ); + connect( m_isoImager, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); +} + + +void K3bDataJob::prepareImager() +{ + if( !m_isoImager ) + setImager( new K3bIsoImager( d->doc, this, this ) ); +} + + +bool K3bDataJob::prepareWriterJob() +{ + if( m_writerJob ) + return true; + + // It seems as if cdrecord is not able to append sessions in dao mode whereas cdrdao is + if( d->usedWritingApp == K3b::CDRECORD ) { + K3bCdrecordWriter* writer = new K3bCdrecordWriter( d->doc->burner(), this, this ); + + // cdrecord manpage says that "not all" writers are able to write + // multisession disks in dao mode. That means there are writers that can. + + // Does it really make sence to write DAta ms cds in DAO mode since writing the + // first session of a cd-extra in DAO mode is no problem with my writer while + // writing the second data session is only possible in TAO mode. + if( d->usedWritingMode == K3b::DAO && + d->usedMultiSessionMode != K3bDataDoc::NONE ) + emit infoMessage( i18n("Most writers do not support writing " + "multisession CDs in DAO mode."), INFO ); + + writer->setWritingMode( d->usedWritingMode ); + writer->setSimulate( d->doc->dummy() ); + writer->setBurnSpeed( d->doc->speed() ); + + // multisession + if( d->usedMultiSessionMode == K3bDataDoc::START || + d->usedMultiSessionMode == K3bDataDoc::CONTINUE ) { + writer->addArgument("-multi"); + } + + if( d->doc->onTheFly() && + ( d->usedMultiSessionMode == K3bDataDoc::CONTINUE || + d->usedMultiSessionMode == K3bDataDoc::FINISH ) ) + writer->addArgument("-waiti"); + + if( d->usedDataMode == K3b::MODE1 ) + writer->addArgument( "-data" ); + else { + if( k3bcore->externalBinManager()->binObject("cdrecord") && + k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "xamix" ) ) + writer->addArgument( "-xa" ); + else + writer->addArgument( "-xa1" ); + } + + writer->addArgument( QString("-tsize=%1s").arg(m_isoImager->size()) )->addArgument("-"); + + setWriterJob( writer ); + } + else { + // create cdrdao job + K3bCdrdaoWriter* writer = new K3bCdrdaoWriter( d->doc->burner(), this, this ); + writer->setCommand( K3bCdrdaoWriter::WRITE ); + writer->setSimulate( d->doc->dummy() ); + writer->setBurnSpeed( d->doc->speed() ); + // multisession + writer->setMulti( d->usedMultiSessionMode == K3bDataDoc::START || + d->usedMultiSessionMode == K3bDataDoc::CONTINUE ); + + // now write the tocfile + if( d->tocFile ) delete d->tocFile; + d->tocFile = new KTempFile( QString::null, "toc" ); + d->tocFile->setAutoDelete(true); + + if( QTextStream* s = d->tocFile->textStream() ) { + if( d->usedDataMode == K3b::MODE1 ) { + *s << "CD_ROM" << "\n"; + *s << "\n"; + *s << "TRACK MODE1" << "\n"; + } + else { + *s << "CD_ROM_XA" << "\n"; + *s << "\n"; + *s << "TRACK MODE2_FORM1" << "\n"; + } + + *s << "DATAFILE \"-\" " << m_isoImager->size()*2048 << "\n"; + + d->tocFile->close(); + } + else { + kdDebug() << "(K3bDataJob) could not write tocfile." << endl; + emit infoMessage( i18n("IO Error"), ERROR ); + cancelAll(); + return false; + } + + writer->setTocFile( d->tocFile->name() ); + + setWriterJob( writer ); + } + + return true; +} + + +void K3bDataJob::prepareData() +{ + // we don't need this when only creating image and it is possible + // that the burn device is null + if( d->doc->onlyCreateImages() ) + return; + + // first of all we determine the data mode + if( d->doc->dataMode() == K3b::DATA_MODE_AUTO ) { + if( !d->doc->onlyCreateImages() && + ( d->usedMultiSessionMode == K3bDataDoc::CONTINUE || + d->usedMultiSessionMode == K3bDataDoc::FINISH ) ) { + + // try to get the last track's datamode + // we already asked for an appendable cdr when fetching + // the ms info + kdDebug() << "(K3bDataJob) determining last track's datamode..." << endl; + + // FIXME: use a devicethread + K3bDevice::Toc toc = d->doc->burner()->readToc(); + if( toc.isEmpty() ) { + kdDebug() << "(K3bDataJob) could not retrieve toc." << endl; + emit infoMessage( i18n("Unable to determine the last track's datamode. Using default."), ERROR ); + d->usedDataMode = K3b::MODE2; + } + else { + if( toc[toc.count()-1].mode() == K3bDevice::Track::MODE1 ) + d->usedDataMode = K3b::MODE1; + else + d->usedDataMode = K3b::MODE2; + + kdDebug() << "(K3bDataJob) using datamode: " + << (d->usedDataMode == K3b::MODE1 ? "mode1" : "mode2") + << endl; + } + } + else if( d->usedMultiSessionMode == K3bDataDoc::NONE ) + d->usedDataMode = K3b::MODE1; + else + d->usedDataMode = K3b::MODE2; + } + else + d->usedDataMode = d->doc->dataMode(); + + + // determine the writing mode + if( d->doc->writingMode() == K3b::WRITING_MODE_AUTO ) { + // TODO: put this into the cdreocrdwriter and decide based on the size of the + // track + if( writer()->dao() && d->usedDataMode == K3b::MODE1 && + d->usedMultiSessionMode == K3bDataDoc::NONE ) + d->usedWritingMode = K3b::DAO; + else + d->usedWritingMode = K3b::TAO; + } + else + d->usedWritingMode = d->doc->writingMode(); + + + // cdrecord seems to have problems writing xa 1 disks in dao mode? At least on my system! + if( writingApp() == K3b::DEFAULT ) { + if( d->usedWritingMode == K3b::DAO ) { + if( d->usedMultiSessionMode != K3bDataDoc::NONE ) + d->usedWritingApp = K3b::CDRDAO; + else if( d->usedDataMode == K3b::MODE2 ) + d->usedWritingApp = K3b::CDRDAO; + else + d->usedWritingApp = K3b::CDRECORD; + } + else + d->usedWritingApp = K3b::CDRECORD; + } + else + d->usedWritingApp = writingApp(); +} + + +void K3bDataJob::determineMultiSessionMode() +{ + // + // THIS IS ONLY CALLED IF d->doc->multiSessionMode() == K3bDataDoc::AUTO! + // + + if( d->doc->writingMode() == K3b::WRITING_MODE_AUTO || + d->doc->writingMode() == K3b::TAO ) { + emit newSubTask( i18n("Searching for old session") ); + + // + // Wait for the medium. + // In case an old session was imported we always want to continue or finish a multisession CD/DVD. + // Otherwise we wait for everything we could handle and decide what to do in + // determineMultiSessionMode( K3bDevice::DeviceHandler* ) below. + // + + int wantedMediaState = K3bDevice::STATE_INCOMPLETE|K3bDevice::STATE_EMPTY; + if( d->doc->sessionImported() ) + wantedMediaState = K3bDevice::STATE_INCOMPLETE; + + int m = waitForMedia( d->doc->burner(), + wantedMediaState, + K3bDevice::MEDIA_WRITABLE_CD ); + + if( m < 0 ) + cancel(); + else { + // now we need to determine the media's size + connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::NG_DISKINFO, d->doc->burner() ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotDetermineMultiSessionMode(K3bDevice::DeviceHandler*)) ); + } + } + else { + // we need TAO for multisession + d->usedMultiSessionMode = K3bDataDoc::NONE; + + // carry on with the writing + prepareWriting(); + } +} + + +void K3bDataJob::slotDetermineMultiSessionMode( K3bDevice::DeviceHandler* dh ) +{ + // + // This is a little workaround for the bad cancellation handling in this job + // see cancel() + // + if( d->canceled ) { + if( active() ) { + cleanup(); + jobFinished( false ); + } + } + else { + d->usedMultiSessionMode = getMultiSessionMode( dh->diskInfo() ); + + // carry on with the writing + prepareWriting(); + } +} + + +K3bDataDoc::MultiSessionMode K3bDataJob::getMultiSessionMode( const K3bDevice::DiskInfo& info ) +{ + if( info.appendable() ) { + // + // 3 cases: + // 1. the project does not fit -> no multisession (resulting in asking for another media above) + // 2. the project does fit and fills up the CD -> finish multisession + // 3. the project does fit and does not fill up the CD -> continue multisession + // + // In case a session has been imported we do not consider NONE at all. + // + if( d->doc->size() > info.remainingSize().mode1Bytes() && !d->doc->sessionImported() ) + d->usedMultiSessionMode = K3bDataDoc::NONE; + else if( d->doc->size() >= info.remainingSize().mode1Bytes()*9/10 ) + d->usedMultiSessionMode = K3bDataDoc::FINISH; + else + d->usedMultiSessionMode = K3bDataDoc::CONTINUE; + } + + else if( info.empty() ) { + // + // We only close the CD if the project fills up the CD almost completely (90%) + // + if( d->doc->size() >= info.capacity().mode1Bytes()*9/10 || + d->doc->writingMode() == K3b::DAO ) + d->usedMultiSessionMode = K3bDataDoc::NONE; + else + d->usedMultiSessionMode = K3bDataDoc::START; + } + + else { // complete (WE SHOULD ACTUALLY NEVER GET HERE SINCE WE WAIT FOR AN EMPTY/APPENDABLE CD ABOVE!) + // + // Now we decide only based on the project size. + // let's just use a 680 MB CD as our reference + // + if( d->doc->size()/1024/1024 >= 680*9/10 || + d->doc->writingMode() == K3b::DAO ) + d->usedMultiSessionMode = K3bDataDoc::NONE; + else + d->usedMultiSessionMode = K3bDataDoc::START; + } + + return d->usedMultiSessionMode; +} + + +void K3bDataJob::cancelAll() +{ + d->canceled = true; + + m_isoImager->cancel(); + m_msInfoFetcher->cancel(); + if( m_writerJob ) + m_writerJob->cancel(); + if( d->verificationJob ) + d->verificationJob->cancel(); + + d->pipe.close(); + + cleanup(); +} + + +bool K3bDataJob::waitForMedium() +{ + emit newSubTask( i18n("Waiting for a medium") ); + if( waitForMedia( d->doc->burner(), + d->usedMultiSessionMode == K3bDataDoc::CONTINUE || + d->usedMultiSessionMode == K3bDataDoc::FINISH ? + K3bDevice::STATE_INCOMPLETE : + K3bDevice::STATE_EMPTY, + K3bDevice::MEDIA_WRITABLE_CD ) < 0 ) { + return false; + } + else + return !d->canceled; +} + + +QString K3bDataJob::jobDescription() const +{ + if( d->doc->onlyCreateImages() ) { + return i18n("Creating Data Image File"); + } + else if( d->doc->multiSessionMode() == K3bDataDoc::NONE || + d->doc->multiSessionMode() == K3bDataDoc::AUTO ) { + return i18n("Writing Data CD") + + ( d->doc->isoOptions().volumeID().isEmpty() + ? QString::null + : QString( " (%1)" ).arg(d->doc->isoOptions().volumeID()) ); + } + else { + return i18n("Writing Multisession CD") + + ( d->doc->isoOptions().volumeID().isEmpty() + ? QString::null + : QString( " (%1)" ).arg(d->doc->isoOptions().volumeID()) ); + } +} + + +QString K3bDataJob::jobDetails() const +{ + if( d->doc->copies() > 1 && + !d->doc->dummy() && + !(d->doc->multiSessionMode() == K3bDataDoc::CONTINUE || + d->doc->multiSessionMode() == K3bDataDoc::FINISH) ) + return i18n("ISO9660 Filesystem (Size: %1) - %n copy", + "ISO9660 Filesystem (Size: %1) - %n copies", + d->doc->copies() ) + .arg(KIO::convertSize( d->doc->size() )); + else + return i18n("ISO9660 Filesystem (Size: %1)") + .arg(KIO::convertSize( d->doc->size() )); +} + + +K3bDataDoc::MultiSessionMode K3bDataJob::usedMultiSessionMode() const +{ + return d->usedMultiSessionMode; +} + + +void K3bDataJob::cleanup() +{ + if( !d->doc->onTheFly() && d->doc->removeImages() ) { + if( QFile::exists( d->doc->tempDir() ) ) { + d->imageFile.remove(); + emit infoMessage( i18n("Removed image file %1").arg(d->doc->tempDir()), K3bJob::SUCCESS ); + } + } + + if( d->tocFile ) { + delete d->tocFile; + d->tocFile = 0; + } +} + + +bool K3bDataJob::hasBeenCanceled() const +{ + return d->canceled; +} + +#include "k3bdatajob.moc" diff --git a/libk3b/projects/datacd/k3bdatajob.h b/libk3b/projects/datacd/k3bdatajob.h new file mode 100644 index 0000000..58de969 --- /dev/null +++ b/libk3b/projects/datacd/k3bdatajob.h @@ -0,0 +1,111 @@ +/* + * + * $Id: k3bdatajob.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 K3BDATAJOB_H +#define K3BDATAJOB_H + +#include <k3bjob.h> +#include <k3bdatadoc.h> + +#include <qfile.h> + +class QString; +class QDataStream; +class K3bAbstractWriter; +class K3bIsoImager; +class KTempFile; +class K3bMsInfoFetcher; + +namespace K3bDevice { + class DeviceHandler; + class DiskInfo; +} + +/** + *@author Sebastian Trueg + */ +class K3bDataJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bDataJob( K3bDataDoc*, K3bJobHandler*, QObject* parent = 0 ); + virtual ~K3bDataJob(); + + K3bDoc* doc() const; + K3bDevice::Device* writer() const; + + virtual bool hasBeenCanceled() const; + + virtual QString jobDescription() const; + virtual QString jobDetails() const; + + public slots: + void cancel(); + void start(); + + /** + * Used to specify a non-default writer. + * If this does notget called K3bDataJob determines + * the writer itself. + */ + void setWriterJob( K3bAbstractWriter* ); + void setImager( K3bIsoImager* ); + + protected slots: + void slotIsoImagerFinished( bool success ); + void slotIsoImagerPercent(int); + void slotWriterJobPercent( int p ); + void slotWriterNextTrack( int t, int tt ); + void slotWriterJobFinished( bool success ); + void slotVerificationProgress( int ); + void slotVerificationFinished( bool ); + void slotMsInfoFetched(bool); + void slotDetermineMultiSessionMode( K3bDevice::DeviceHandler* dh ); + void writeImage(); + void cancelAll(); + + /** + * Just a little helper method that makes subclassing easier. + * Basically used for DVD writing. + */ + virtual bool waitForMedium(); + + protected: + virtual void prepareData(); + virtual bool prepareWriterJob(); + virtual void prepareImager(); + virtual void determineMultiSessionMode(); + virtual K3bDataDoc::MultiSessionMode getMultiSessionMode( const K3bDevice::DiskInfo& ); + virtual void cleanup(); + + K3bDataDoc::MultiSessionMode usedMultiSessionMode() const; + + K3bAbstractWriter* m_writerJob; + K3bIsoImager* m_isoImager; + K3bMsInfoFetcher* m_msInfoFetcher; + + private: + bool startWriterJob(); + bool startOnTheFlyWriting(); + void prepareWriting(); + void connectImager(); + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/datacd/k3bdatapreparationjob.cpp b/libk3b/projects/datacd/k3bdatapreparationjob.cpp new file mode 100644 index 0000000..dd29d6d --- /dev/null +++ b/libk3b/projects/datacd/k3bdatapreparationjob.cpp @@ -0,0 +1,283 @@ +/* + * + * $Id: sourceheader 511311 2006-02-19 14:51:05Z trueg $ + * Copyright (C) 2006 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + +#include "k3bdatapreparationjob.h" +#include "k3bdatadoc.h" +#include "k3bisooptions.h" + +#include <k3bthreadjob.h> +#include <k3bthread.h> +#include <k3bdiritem.h> +#include <k3bfileitem.h> +#include <k3bglobals.h> + +#include <klocale.h> +#include <kstringhandler.h> + +#include <qfile.h> +#include <qvaluelist.h> + + +class K3bDataPreparationJob::Private : public K3bThread +{ +public: + Private( K3bDataDoc* doc ); + + void run(); + void cancel(); + + K3bDataDoc* doc; + + QValueList<K3bDataItem*> nonExistingItems; + QString listOfRenamedItems; + QValueList<K3bDataItem*> folderSymLinkItems; + + K3bThreadJob* threadJob; + + bool canceled; +}; + + +K3bDataPreparationJob::Private::Private( K3bDataDoc* _doc ) + : doc(_doc) +{ +} + + +void K3bDataPreparationJob::Private::run() +{ + emitStarted(); + + // clean up + nonExistingItems.clear(); + listOfRenamedItems.truncate(0); + folderSymLinkItems.clear(); + + // initialize filenames in the project + doc->prepareFilenames(); + + // create the message string for the renamed files + if( doc->needToCutFilenames() ) { + int maxlines = 10; + QValueList<K3bDataItem*>::const_iterator it; + for( it = doc->needToCutFilenameItems().begin(); + maxlines > 0 && it != doc->needToCutFilenameItems().end(); + ++it, --maxlines ) { + K3bDataItem* item = *it; + listOfRenamedItems += i18n("<em>%1</em> renamed to <em>%2</em>") + .arg( KStringHandler::csqueeze( item->k3bName(), 30 ) ) + .arg( KStringHandler::csqueeze( item->writtenName(), 30 ) ); + listOfRenamedItems += "<br>"; + } + if( it != doc->needToCutFilenameItems().end() ) + listOfRenamedItems += "..."; + } + + // + // Check for missing files and folder symlinks + // + K3bDataItem* item = doc->root(); + while( (item = item->nextSibling()) ) { + + if( item->isSymLink() ) { + if( doc->isoOptions().followSymbolicLinks() ) { + QFileInfo f( K3b::resolveLink( item->localPath() ) ); + if( !f.exists() ) { + nonExistingItems.append( item ); + } + else if( f.isDir() ) { + folderSymLinkItems.append( item ); + } + } + } + else if( item->isFile() && !QFile::exists( item->localPath() ) ) { + nonExistingItems.append( item ); + } + + if( canceled ) { + emitCanceled(); + emitFinished(false); + return; + } + } + + + emitFinished( true ); +} + + +void K3bDataPreparationJob::Private::cancel() +{ + canceled = true; +} + + + + +static QString createItemsString( const QValueList<K3bDataItem*>& items, unsigned int max ) +{ + QString s; + unsigned int cnt = 0; + for( QValueList<K3bDataItem*>::const_iterator it = items.begin(); + it != items.end(); ++it ) { + + s += KStringHandler::csqueeze( (*it)->localPath(), 60 ); + + ++cnt; + if( cnt >= max || it == items.end() ) + break; + + s += "<br>"; + } + + if( items.count() > max ) + s += "..."; + + return s; +} + + +K3bDataPreparationJob::K3bDataPreparationJob( K3bDataDoc* doc, K3bJobHandler* hdl, QObject* parent ) + : K3bJob( hdl, parent ) +{ + d = new Private( doc ); + d->threadJob = new K3bThreadJob( d, this, this ); + connectSubJob( d->threadJob, SLOT(slotWorkDone(bool)), K3bJob::DEFAULT_SIGNAL_CONNECTION ); +} + + +K3bDataPreparationJob::~K3bDataPreparationJob() +{ + delete d; +} + + +void K3bDataPreparationJob::start() +{ + if( !active() ) { + d->canceled = false; + jobStarted(); + d->threadJob->start(); + } +} + + +void K3bDataPreparationJob::slotWorkDone( bool success ) +{ + if( success ) { + if( !d->listOfRenamedItems.isEmpty() ) { + if( !questionYesNo( "<p>" + i18n("Some filenames need to be shortened due to the %1 char restriction " + "of the Joliet extensions. If the Joliet extensions are disabled filenames " + "do not have to be shortened but long filenames will not be available on " + "Windows systems.") + .arg( d->doc->isoOptions().jolietLong() ? 103 : 64 ) + + "<p>" + d->listOfRenamedItems, + i18n("Warning"), + i18n("Shorten Filenames"), + i18n("Disable Joliet extensions") ) ) { + // No -> disable joliet + // for now we enable RockRidge to be sure we did not lie above (keep long filenames) + K3bIsoOptions op = d->doc->isoOptions(); + op.setCreateJoliet( false ); + op.setCreateRockRidge( true ); + d->doc->setIsoOptions( op ); + d->doc->prepareFilenames(); + } + } + + // + // The joliet extension encodes the volume desc in UCS-2, i.e. uses 16 bit for each char. + // Thus, the max length here is 16. + // + if( d->doc->isoOptions().createJoliet() && + d->doc->isoOptions().volumeID().length() > 16 ) { + if( !questionYesNo( "<p>" + i18n("The Joliet extensions (which are needed for long filenames on Windows systems) " + "restrict the length of the volume descriptior (the name of the filesystem) " + "to %1 characters. The selected descriptor '%2' is longer than that. Do you " + "want it to be cut or do you want to go back and change it manually?") + .arg( 16 ).arg( d->doc->isoOptions().volumeID() ), + i18n("Warning"), + i18n("Cut volume descriptor in the Joliet tree"), + i18n("Cancel and go back") ) ) { + d->canceled = true; + emit canceled(); + jobFinished( false ); + return; + } + } + + // + // Check for missing files + // + if( !d->nonExistingItems.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>" + createItemsString( d->nonExistingItems, 10 ), + i18n("Warning"), + i18n("Remove missing files and continue"), + i18n("Cancel and go back") ) ) { + for( QValueList<K3bDataItem*>::const_iterator it = d->nonExistingItems.begin(); + it != d->nonExistingItems.end(); ++it ) { + delete *it; + } + } + else { + d->canceled = true; + emit canceled(); + jobFinished(false); + return; + } + } + + // + // Warn about symlinks to folders + // + if( d->doc->isoOptions().followSymbolicLinks() && !d->folderSymLinkItems.isEmpty() ) { + if( !questionYesNo( "<p>" + i18n("K3b is not able to follow symbolic links to folders after they have been added " + "to the project. Do you want to continue " + "without writing the symbolic links to the image?") + + "<p>" + createItemsString( d->folderSymLinkItems, 10 ), + i18n("Warning"), + i18n("Discard symbolic links to folders"), + i18n("Cancel and go back") ) ) { + d->canceled = true; + emit canceled(); + jobFinished(false); + return; + } + } + + jobFinished( true ); + } + else { + if( d->canceled ) + emit canceled(); + jobFinished(false); + } +} + + +void K3bDataPreparationJob::cancel() +{ + d->cancel(); +} + + +bool K3bDataPreparationJob::hasBeenCanceled() const +{ + return d->canceled; +} + +#include "k3bdatapreparationjob.moc" diff --git a/libk3b/projects/datacd/k3bdatapreparationjob.h b/libk3b/projects/datacd/k3bdatapreparationjob.h new file mode 100644 index 0000000..1c83a42 --- /dev/null +++ b/libk3b/projects/datacd/k3bdatapreparationjob.h @@ -0,0 +1,51 @@ +/* + * + * $Id: sourceheader 511311 2006-02-19 14:51:05Z trueg $ + * Copyright (C) 2006 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + +#ifndef _K3B_DATA_PREPARATION_JOB_H_ +#define _K3B_DATA_PREPARATION_JOB_H_ + +#include <k3bjob.h> + + +class K3bDataDoc; +class K3bJobHandler; + +/** + * The K3bDataPreparationJob performs some checks on the data in a data project + * It is used by th K3bIsoImager. + */ +class K3bDataPreparationJob : public K3bJob +{ + Q_OBJECT + + public: + K3bDataPreparationJob( K3bDataDoc* doc, K3bJobHandler* hdl, QObject* parent ); + ~K3bDataPreparationJob(); + + bool hasBeenCanceled() const; + + public slots: + void start(); + void cancel(); + + private slots: + void slotWorkDone( bool success ); + + private: + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/datacd/k3bdiritem.cpp b/libk3b/projects/datacd/k3bdiritem.cpp new file mode 100644 index 0000000..3ea3236 --- /dev/null +++ b/libk3b/projects/datacd/k3bdiritem.cpp @@ -0,0 +1,406 @@ +/* + * + * $Id: k3bdiritem.cpp 652578 2007-04-11 14:21:04Z 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 "k3bdiritem.h" +#include "k3bdatadoc.h" +#include "k3bsessionimportitem.h" +#include "k3bfileitem.h" + +#include <qstring.h> +#include <qptrlist.h> + +#include <kdebug.h> + + +K3bDirItem::K3bDirItem(const QString& name, K3bDataDoc* doc, K3bDirItem* parentDir) + : K3bDataItem( doc, parentDir ), + m_size(0), + m_followSymlinksSize(0), + m_blocks(0), + m_followSymlinksBlocks(0), + m_files(0), + m_dirs(0) +{ + m_k3bName = name; + + // add automagically like a qlistviewitem + if( parent() ) + parent()->addDataItem( this ); +} + + +K3bDirItem::K3bDirItem( const K3bDirItem& item ) + : K3bDataItem( item ), + m_size(0), + m_followSymlinksSize(0), + m_blocks(0), + m_followSymlinksBlocks(0), + m_files(0), + m_dirs(0), + m_localPath( item.m_localPath ) +{ + for( QPtrListIterator<K3bDataItem> it( item.children() ); *it; ++it ) + addDataItem( (*it)->copy() ); +} + +K3bDirItem::~K3bDirItem() +{ + // delete all children + // doing this by hand is much saver than using the + // auto-delete feature since some of the items' destructors + // may change the list + K3bDataItem* i = m_children.first(); + while( i ) { + // it is important to use takeDataItem here to be sure + // the size gets updated properly + takeDataItem(i); + delete i; + i = m_children.first(); + } + + // this has to be done after deleting the children + // because the directory itself has a size of 0 in K3b + // and all it's files' sizes have already been substracted + take(); +} + + +K3bDataItem* K3bDirItem::copy() const +{ + return new K3bDirItem( *this ); +} + + +K3bDirItem* K3bDirItem::getDirItem() const +{ + return const_cast<K3bDirItem*>(this); +} + +K3bDirItem* K3bDirItem::addDataItem( K3bDataItem* item ) +{ + // check if we are a subdir of item + if( K3bDirItem* dirItem = dynamic_cast<K3bDirItem*>(item) ) { + if( dirItem->isSubItem( this ) ) { + kdDebug() << "(K3bDirItem) trying to move a dir item down in it's own tree." << endl; + return this; + } + } + + if( m_children.findRef( item ) == -1 ) { + if( item->isFile() ) { + // do we replace an old item? + QString name = item->k3bName(); + int cnt = 1; + while( K3bDataItem* oldItem = find( name ) ) { + if( !oldItem->isDir() && oldItem->isFromOldSession() ) { + // in this case we remove this item from it's parent and save it in the new one + // to be able to recover it + oldItem->take(); + static_cast<K3bSessionImportItem*>(oldItem)->setReplaceItem( static_cast<K3bFileItem*>(item) ); + static_cast<K3bFileItem*>(item)->setReplacedItemFromOldSession( oldItem ); + break; + } + else { + // + // add a counter to the filename + // + if( item->k3bName()[item->k3bName().length()-4] == '.' ) + name = item->k3bName().left( item->k3bName().length()-4 ) + QString::number(cnt++) + item->k3bName().right(4); + else + name = item->k3bName() + QString::number(cnt++); + } + } + item->setK3bName( name ); + } + + m_children.append( item->take() ); + updateSize( item, false ); + if( item->isDir() ) + updateFiles( ((K3bDirItem*)item)->numFiles(), ((K3bDirItem*)item)->numDirs()+1 ); + else + updateFiles( 1, 0 ); + + item->m_parentDir = this; + + // inform the doc + if( doc() ) + doc()->itemAddedToDir( this, item ); + } + + return this; +} + + +K3bDataItem* K3bDirItem::takeDataItem( K3bDataItem* item ) +{ + int x = m_children.findRef( item ); + if( x > -1 ) { + K3bDataItem* item = m_children.take(); + updateSize( item, true ); + if( item->isDir() ) + updateFiles( -1*((K3bDirItem*)item)->numFiles(), -1*((K3bDirItem*)item)->numDirs()-1 ); + else + updateFiles( -1, 0 ); + + item->m_parentDir = 0; + + // inform the doc + if( doc() ) + doc()->itemRemovedFromDir( this, item ); + + if( item->isFile() ) { + // restore the item imported from an old session + if( static_cast<K3bFileItem*>(item)->replaceItemFromOldSession() ) + addDataItem( static_cast<K3bFileItem*>(item)->replaceItemFromOldSession() ); + } + + return item; + } + else + return 0; +} + + +K3bDataItem* K3bDirItem::nextSibling() const +{ + if( !m_children.isEmpty() ) + return m_children.getFirst(); + else + return K3bDataItem::nextSibling(); +} + + +K3bDataItem* K3bDirItem::nextChild( K3bDataItem* prev ) const +{ + // search for prev in children + if( m_children.findRef( prev ) < 0 ) { + return 0; + } + else + return m_children.next(); +} + + +bool K3bDirItem::alreadyInDirectory( const QString& filename ) const +{ + return (find( filename ) != 0); +} + + +K3bDataItem* K3bDirItem::find( const QString& filename ) const +{ + for( QPtrListIterator<K3bDataItem> it( m_children ); it.current(); ++it ) { + if( it.current()->k3bName() == filename ) + return it.current(); + } + return 0; +} + + +K3bDataItem* K3bDirItem::findByPath( const QString& p ) +{ + if( p.isEmpty() || p == "/" ) + return this; + + QString path = p; + if( path.startsWith("/") ) + path = path.mid(1); + int pos = path.find( "/" ); + if( pos < 0 ) + return find( path ); + else { + // do it recursivly + K3bDataItem* item = find( path.left(pos) ); + if( item && item->isDir() ) + return ((K3bDirItem*)item)->findByPath( path.mid( pos+1 ) ); + else + return 0; + } +} + + +bool K3bDirItem::mkdir( const QString& dirPath ) +{ + // + // An absolut path always starts at the root item + // + if( dirPath[0] == '/' ) { + if( parent() ) + return parent()->mkdir( dirPath ); + else + return mkdir( dirPath.mid( 1 ) ); + } + + if( findByPath( dirPath ) ) + return false; + + QString restPath; + QString dirName; + int pos = dirPath.find( '/' ); + if( pos == -1 ) { + dirName = dirPath; + } + else { + dirName = dirPath.left( pos ); + restPath = dirPath.mid( pos+1 ); + } + + K3bDataItem* dir = find( dirName ); + if( !dir ) + dir = new K3bDirItem( dirName, doc(), this ); + else if( !dir->isDir() ) + return false; + + if( !restPath.isEmpty() ) + return static_cast<K3bDirItem*>(dir)->mkdir( restPath ); + + return true; +} + + +KIO::filesize_t K3bDirItem::itemSize( bool followsylinks ) const +{ + if( followsylinks ) + return m_followSymlinksSize; + else + return m_size; +} + + +K3b::Msf K3bDirItem::itemBlocks( bool followSymlinks ) const +{ + if( followSymlinks ) + return m_followSymlinksBlocks; + else + return m_blocks; +} + + +bool K3bDirItem::isSubItem( K3bDataItem* item ) const +{ + if( dynamic_cast<K3bDirItem*>(item) == this ) + return true; + + K3bDirItem* d = item->parent(); + while( d ) { + if( d == this ) { + return true; + } + d = d->parent(); + } + + return false; +} + + +long K3bDirItem::numFiles() const +{ + return m_files; +} + + +long K3bDirItem::numDirs() const +{ + return m_dirs; +} + + +bool K3bDirItem::isRemoveable() const +{ + if( !K3bDataItem::isRemoveable() ) + return false; + + for( QPtrListIterator<K3bDataItem> it( m_children ); it.current(); ++it ) { + if( !it.current()->isRemoveable() ) + return false; + } + + return true; +} + + +void K3bDirItem::updateSize( K3bDataItem* item, bool removed ) +{ + if ( !item->isFromOldSession() ) { + if( removed ) { + m_followSymlinksSize -= item->itemSize( true ); + m_size -= item->itemSize( false ); + m_followSymlinksBlocks -= item->itemBlocks( true ).lba(); + m_blocks -= item->itemBlocks( false ).lba(); + } + else { + m_followSymlinksSize += item->itemSize( true ); + m_size += item->itemSize( false ); + m_followSymlinksBlocks += item->itemBlocks( true ).lba(); + m_blocks += item->itemBlocks( false ).lba(); + } + } + + if( parent() ) + parent()->updateSize( item, removed ); +} + +void K3bDirItem::updateFiles( long files, long dirs ) +{ + m_files += files; + m_dirs += dirs; + if( parent() ) + parent()->updateFiles( files, dirs ); +} + + +bool K3bDirItem::isFromOldSession() const +{ + for( QPtrListIterator<K3bDataItem> it( m_children ); it.current(); ++it ) { + if( (*it)->isFromOldSession() ) + return true; + } + return false; +} + + +bool K3bDirItem::writeToCd() const +{ + // check if this dir contains items to write + for( QPtrListIterator<K3bDataItem> it( m_children ); it.current(); ++it ) { + if( (*it)->writeToCd() ) + return true; + } + return K3bDataItem::writeToCd(); +} + + +K3bRootItem::K3bRootItem( K3bDataDoc* doc ) + : K3bDirItem( "root", doc, 0 ) +{ +} + + +K3bRootItem::~K3bRootItem() +{ +} + + +const QString& K3bRootItem::k3bName() const +{ + return doc()->isoOptions().volumeID(); +} + + +void K3bRootItem::setK3bName( const QString& text ) +{ + doc()->setVolumeID( text ); +} diff --git a/libk3b/projects/datacd/k3bdiritem.h b/libk3b/projects/datacd/k3bdiritem.h new file mode 100644 index 0000000..a64b4fd --- /dev/null +++ b/libk3b/projects/datacd/k3bdiritem.h @@ -0,0 +1,155 @@ +/* + * + * $Id: k3bdiritem.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 K3BDIRITEM_H +#define K3BDIRITEM_H + + +#include <qstring.h> +#include <qptrlist.h> + +#include <kio/global.h> + +#include "k3bdataitem.h" +#include "k3b_export.h" +class K3bDataDoc; + +/** + *@author Sebastian Trueg + */ + +class LIBK3B_EXPORT K3bDirItem : public K3bDataItem +{ + public: + K3bDirItem( const QString& name, K3bDataDoc*, K3bDirItem* parentDir = 0 ); + + /** + * Default copy constructor. Copies the dir including all children. However, none of the + * children will have set a doc and the copy dir will not have set a parent dir. + */ + K3bDirItem( const K3bDirItem& ); + + virtual ~K3bDirItem(); + + K3bDataItem* copy() const; + + K3bDirItem* getDirItem() const; + + const QPtrList<K3bDataItem>& children() const { return m_children; } + K3bDirItem* addDataItem( K3bDataItem* item ); + K3bDataItem* takeDataItem( K3bDataItem* item ); + + K3bDataItem* nextSibling() const; + K3bDataItem* nextChild( K3bDataItem* ) const; + + bool alreadyInDirectory( const QString& fileName ) const; + K3bDataItem* find( const QString& filename ) const; + K3bDataItem* findByPath( const QString& ); + + long numFiles() const; + long numDirs() const; + + bool isEmpty() const { return ( numDirs() + numFiles() == 0 ); } + + /** + * returns true if item is a subItem of + * this dir item + * (returns also true if item == this + */ + bool isSubItem( K3bDataItem* item ) const; + + bool isDir() const { return true; } + + virtual bool isRemoveable() const; + + /** + * \return true if some child is from an old session. + */ + virtual bool isFromOldSession() const; + + /** + * Recursively creates a directory. + */ + bool mkdir( const QString& dir ); + + void setLocalPath( const QString& p ) { m_localPath = p; } + QString localPath() const { return m_localPath; } + + /** + * \reimplemented + */ + bool writeToCd() const; + + protected: + /** + * Normally one does not use this method but K3bDataItem::size() + * + * This method does not take into account the possibility to share the data + * between files with the same inode in an iso9660 filesystem. + * For that one has to use K3bFileCompilationSizeHandler. + */ + KIO::filesize_t itemSize( bool followSymlinks ) const; + + /* + * Normally one does not use this method but K3bDataItem::blocks() + */ + K3b::Msf itemBlocks( bool followSymlinks ) const; + + private: + /** + * this recursivly updates the size of the directories. + * The size of this dir and the parent dir is updated. + * These values are just used for user information. + */ + void updateSize( K3bDataItem*, bool removed = false ); + /** + * Updates the number of files and directories. These values are + * just used for user information. + */ + void updateFiles( long files, long dirs ); + + mutable QPtrList<K3bDataItem> m_children; + + // size of the items simply added + KIO::filesize_t m_size; + KIO::filesize_t m_followSymlinksSize; + + // number of blocks (2048 bytes) used by all the items + long m_blocks; + long m_followSymlinksBlocks; + + long m_files; + long m_dirs; + + // HACK: store the original path to be able to use it's permissions + // ´remove this once we have a backup project + QString m_localPath; +}; + + +class K3bRootItem : public K3bDirItem +{ + public: + K3bRootItem( K3bDataDoc* ); + ~K3bRootItem(); + + const QString& k3bName() const; + void setK3bName( const QString& ); + + bool isMoveable() const { return false; } + bool isRemoveable() const { return false; } +}; +#endif diff --git a/libk3b/projects/datacd/k3bfilecompilationsizehandler.cpp b/libk3b/projects/datacd/k3bfilecompilationsizehandler.cpp new file mode 100644 index 0000000..0ddab76 --- /dev/null +++ b/libk3b/projects/datacd/k3bfilecompilationsizehandler.cpp @@ -0,0 +1,228 @@ +/* + * + * $Id: k3bfilecompilationsizehandler.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 "k3bfilecompilationsizehandler.h" +#include "k3bfileitem.h" + +#include <kdebug.h> + +#include <qfile.h> +#include <qmap.h> +#include <qptrlist.h> + + +// TODO: remove the items from the project if the savedSize differs +// with some info-widget: "Files xxx have changed on disk. Removing them from the project." +// or we just update the sizes! + + +static long usedBlocks( const KIO::filesize_t& bytes ) +{ + if( bytes % 2048 ) + return bytes/2048 + 1; + else + return bytes/2048; +} + + +class InodeInfo +{ +public: + InodeInfo() { + number = 0; + savedSize = 0; + } + + /** + * How often has the file with + * the corresponding inode been added + */ + int number; + + /** + * The size of the first added file. This has to be saved + * to check further addings and to avoid the following situation: + * A file with inode 1 is added, then deleted. Another file is created + * at inode 1 and added to the project. Now the first file gets + * removed and then the second. If we had not saved the size we would + * have added the size of the first and removed the size of the second + * file resulting in a corrupted project size. + * This way we always use the size of the first added file and may + * warn the user if sizes differ. + */ + KIO::filesize_t savedSize; + + KIO::filesize_t completeSize() const { return savedSize*number; } + + /** + * In an iso9660 filesystem a file occupies complete blocks of 2048 bytes. + */ + K3b::Msf blocks() const { return K3b::Msf( usedBlocks(savedSize) ); } + + QPtrList<K3bDataItem> items; +}; + + +class K3bFileCompilationSizeHandler::Private +{ +public: + Private() + : size(0) { + } + + void clear() { + inodeMap.clear(); + size = 0; + blocks = 0; + } + + void addFile( K3bFileItem* item, bool followSymlinks ) { + InodeInfo& inodeInfo = inodeMap[item->localId(followSymlinks)]; + + inodeInfo.items.append( item ); + + if( inodeInfo.number == 0 ) { + inodeInfo.savedSize = item->itemSize( followSymlinks ); + + size += inodeInfo.savedSize; + blocks += inodeInfo.blocks(); + } + + inodeInfo.number++; + } + + void addSpecialItem( K3bDataItem* item ) { + // special files do not have a corresponding local file + // so we just add their k3bSize + size += item->size(); + blocks += usedBlocks(item->size()); + specialItems.append( item ); + } + + void removeFile( K3bFileItem* item, bool followSymlinks ) { + InodeInfo& inodeInfo = inodeMap[item->localId(followSymlinks)]; + + if( inodeInfo.items.findRef( item ) == -1 ) { + kdError() << "(K3bFileCompilationSizeHandler) " + << item->localPath() + << " has been removed without being added!" << endl; + } + else { + if( item->itemSize(followSymlinks) != inodeInfo.savedSize ) { + kdError() << "(K3bFileCompilationSizeHandler) savedSize differs!" << endl; + } + + inodeInfo.items.removeRef( item ); + inodeInfo.number--; + if( inodeInfo.number == 0 ) { + size -= inodeInfo.savedSize; + blocks -= inodeInfo.blocks(); + } + } + } + + void removeSpecialItem( K3bDataItem* item ) { + // special files do not have a corresponding local file + // so we just substract their k3bSize + if( specialItems.findRef( item ) == -1 ) { + kdError() << "(K3bFileCompilationSizeHandler) Special item " + << item->k3bName() + << " has been removed without being added!" << endl; + } + else { + specialItems.removeRef( item ); + size -= item->size(); + blocks -= usedBlocks(item->size()); + } + } + + + /** + * This maps from inodes to the number of occurrences of the inode. + */ + QMap<K3bFileItem::Id, InodeInfo> inodeMap; + + KIO::filesize_t size; + K3b::Msf blocks; + + QPtrList<K3bDataItem> specialItems; +}; + + + +K3bFileCompilationSizeHandler::K3bFileCompilationSizeHandler() +{ + d_symlinks = new Private; + d_noSymlinks = new Private; +} + +K3bFileCompilationSizeHandler::~K3bFileCompilationSizeHandler() +{ + delete d_symlinks; + delete d_noSymlinks; +} + + +const KIO::filesize_t& K3bFileCompilationSizeHandler::size( bool followSymlinks ) const +{ + if( followSymlinks ) + return d_noSymlinks->size; + else + return d_symlinks->size; +} + + +const K3b::Msf& K3bFileCompilationSizeHandler::blocks( bool followSymlinks ) const +{ + if( followSymlinks ) + return d_noSymlinks->blocks; + else + return d_symlinks->blocks; +} + + +void K3bFileCompilationSizeHandler::addFile( K3bDataItem* item ) +{ + if( item->isSpecialFile() ) { + d_symlinks->addSpecialItem( item ); + d_noSymlinks->addSpecialItem( item ); + } + else if( item->isFile() ) { + K3bFileItem* fileItem = static_cast<K3bFileItem*>( item ); + d_symlinks->addFile( fileItem, false ); + d_noSymlinks->addFile( fileItem, true ); + } +} + + +void K3bFileCompilationSizeHandler::removeFile( K3bDataItem* item ) +{ + if( item->isSpecialFile() ) { + d_symlinks->removeSpecialItem( item ); + d_noSymlinks->removeSpecialItem( item ); + } + else if( item->isFile() ) { + K3bFileItem* fileItem = static_cast<K3bFileItem*>( item ); + d_symlinks->removeFile( fileItem, false ); + d_noSymlinks->removeFile( fileItem, true ); + } +} + + +void K3bFileCompilationSizeHandler::clear() +{ + d_symlinks->clear(); + d_noSymlinks->clear(); +} diff --git a/libk3b/projects/datacd/k3bfilecompilationsizehandler.h b/libk3b/projects/datacd/k3bfilecompilationsizehandler.h new file mode 100644 index 0000000..c996657 --- /dev/null +++ b/libk3b/projects/datacd/k3bfilecompilationsizehandler.h @@ -0,0 +1,73 @@ +/* + * + * $Id: k3bfilecompilationsizehandler.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_FILECOMPILATION_SIZE_HANDLER_H_ +#define _K3B_FILECOMPILATION_SIZE_HANDLER_H_ + + +#include <kio/global.h> +#include <k3bmsf.h> + +class K3bDataItem; + + +/** + * This class maintains a map of indoes and the number + * of files in the doc that belong to that inode. + * This way a more accurate size calculation is possible + * + * It has to be noted that the sizes of the directories + * are only locally true. That means that in some cases + * the root directory of the project may show a much + * higher size than calculated by this class. + */ +class K3bFileCompilationSizeHandler +{ + public: + K3bFileCompilationSizeHandler(); + ~K3bFileCompilationSizeHandler(); + + /** + * This does NOT equal blocks() * 2048. + * This is the sum of the actual file sizes. + */ + const KIO::filesize_t& size( bool followSymlinks = false ) const; + + /** + * Number of blocks the files will occupy. + */ + const K3b::Msf& blocks( bool followSymlinks = false ) const; + + /** + * This will increase the counter for the inode of + * the file in url and update the totel size. + */ + void addFile( K3bDataItem* ); + + /** + * This will decrease the counter for the inode of + * the file in url and update the totel size. + */ + void removeFile( K3bDataItem* ); + + void clear(); + + private: + class Private; + Private* d_symlinks; + Private* d_noSymlinks; +}; + +#endif diff --git a/libk3b/projects/datacd/k3bfileitem.cpp b/libk3b/projects/datacd/k3bfileitem.cpp new file mode 100644 index 0000000..d9e288f --- /dev/null +++ b/libk3b/projects/datacd/k3bfileitem.cpp @@ -0,0 +1,300 @@ +/* + * + * $Id: k3bfileitem.cpp 619556 2007-01-03 17:38:12Z trueg $ + * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + +#include <config.h> +#include <k3bglobals.h> + +#include "k3bfileitem.h" +#include "k3bdatadoc.h" +#include "k3bdiritem.h" +#include "k3bisooptions.h" +#include <k3bglobals.h> + +#include <qfileinfo.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qregexp.h> +#include <qfile.h> + +#include <kurl.h> +#include <kdebug.h> + +#include <errno.h> +#include <string.h> + + +bool operator==( const K3bFileItem::Id& id1, const K3bFileItem::Id& id2 ) +{ + return ( id1.device == id2.device && id1.inode == id2.inode ); +} + + +bool operator<( const K3bFileItem::Id& id1, const K3bFileItem::Id& id2 ) +{ + if( id1.device == id2.device ) + return ( id1.inode < id2.inode ); + else + return ( id1.device < id2.device ); +} + + +bool operator>( const K3bFileItem::Id& id1, const K3bFileItem::Id& id2 ) +{ + return !( id2 < id1 || id1 == id2 ); +} + + + +K3bFileItem::K3bFileItem( const QString& filePath, K3bDataDoc* doc, K3bDirItem* dir, const QString& k3bName, int flags ) + : K3bDataItem( doc, dir, flags ), + m_replacedItemFromOldSession(0), + m_localPath(filePath) +{ + if( k3bName.isEmpty() ) + m_k3bName = filePath.section( '/', -1 ); + else + m_k3bName = k3bName; + + // we determine the size here to avoid problems with removed or renamed files + // we need to use lstat here since for symlinks both KDE and QT return the size of the file pointed to + // instead the size of the link. + k3b_struct_stat statBuf; + if( k3b_lstat( QFile::encodeName(filePath), &statBuf ) ) { + m_size = K3b::filesize( filePath ); + m_id.inode = 0; + m_id.device = 0; + m_bSymLink = false; + + kdError() << "(KFileItem) lstat failed: " << strerror(errno) << endl; + + // since we have no proper inode info, disable the inode caching in the doc + if( doc ) { + K3bIsoOptions o( doc->isoOptions() ); + o.setDoNotCacheInodes( true ); + doc->setIsoOptions( o ); + } + } + else { + m_size = (KIO::filesize_t)statBuf.st_size; + + m_bSymLink = S_ISLNK(statBuf.st_mode); + + // + // integrate the device number into the inode since files on different + // devices may have the same inode number! + // + m_id.inode = statBuf.st_ino; + m_id.device = statBuf.st_dev; + } + + m_idFollowed = m_id; + m_sizeFollowed = m_size; + + if( isSymLink() ) { + k3b_struct_stat statBuf; + if( k3b_stat( QFile::encodeName(filePath), &statBuf ) == 0 ) { + m_idFollowed.inode = statBuf.st_ino; + m_idFollowed.device = statBuf.st_dev; + + m_sizeFollowed = (KIO::filesize_t)statBuf.st_size; + } + } + + // add automagically like a qlistviewitem + if( parent() ) + parent()->addDataItem( this ); +} + + +K3bFileItem::K3bFileItem( const k3b_struct_stat* stat, + const k3b_struct_stat* followedStat, + const QString& filePath, K3bDataDoc* doc, K3bDirItem* dir, const QString& k3bName ) + : K3bDataItem( doc, dir ), + m_replacedItemFromOldSession(0), + m_localPath(filePath) +{ + if( k3bName.isEmpty() ) + m_k3bName = filePath.section( '/', -1 ); + else + m_k3bName = k3bName; + + m_size = (KIO::filesize_t)stat->st_size; + m_bSymLink = S_ISLNK(stat->st_mode); + + // + // integrate the device number into the inode since files on different + // devices may have the same inode number! + // + m_id.inode = stat->st_ino; + m_id.device = stat->st_dev; + + if( isSymLink() ) { + m_idFollowed.inode = followedStat->st_ino; + m_idFollowed.device = followedStat->st_dev; + + m_sizeFollowed = (KIO::filesize_t)followedStat->st_size; + } + else { + m_idFollowed = m_id; + m_sizeFollowed = m_size; + } + + if( parent() ) + parent()->addDataItem( this ); +} + + +K3bFileItem::K3bFileItem( const K3bFileItem& item ) + : K3bDataItem( item ), + m_replacedItemFromOldSession(0), + m_size( item.m_size ), + m_sizeFollowed( item.m_sizeFollowed ), + m_id( item.m_id ), + m_idFollowed( item.m_idFollowed ), + m_localPath( item.m_localPath ), + m_bSymLink( item.m_bSymLink ) +{ +} + + +K3bFileItem::~K3bFileItem() +{ + // remove this from parentdir + take(); +} + + +K3bDataItem* K3bFileItem::copy() const +{ + return new K3bFileItem( *this ); +} + + +KIO::filesize_t K3bFileItem::itemSize( bool followSymlinks ) const +{ + if( followSymlinks ) + return m_sizeFollowed; + else + return m_size; +} + + +K3bFileItem::Id K3bFileItem::localId() const +{ + return localId( doc() ? doc()->isoOptions().followSymbolicLinks() || !doc()->isoOptions().createRockRidge() : false ); +} + + +K3bFileItem::Id K3bFileItem::localId( bool followSymlinks ) const +{ + if( followSymlinks ) + return m_idFollowed; + else + return m_id; +} + + +bool K3bFileItem::exists() const +{ + return true; +} + +QString K3bFileItem::absIsoPath() +{ + // return m_dir->absIsoPath() + m_isoName; + return QString::null; +} + + +QString K3bFileItem::localPath() const +{ + return m_localPath; +} + +K3bDirItem* K3bFileItem::getDirItem() const +{ + return getParent(); +} + + +bool K3bFileItem::isSymLink() const +{ + return m_bSymLink; +} + + +QString K3bFileItem::linkDest() const +{ + return QFileInfo( localPath() ).readLink(); +} + + +bool K3bFileItem::isValid() const +{ + if( isSymLink() ) { + + // this link is not valid if we cannot follow it if we want to + if( doc()->isoOptions().followSymbolicLinks() ) { + return QFile::exists( K3b::resolveLink( localPath() ) ); + } + + QString dest = linkDest(); + + if( dest[0] == '/' ) + return false; // absolut links can never be part of the compilation! + + // parse the link + K3bDirItem* dir = getParent(); + + QStringList tokens = QStringList::split( QRegExp("/+"), dest ); // two slashes or more do the same as one does! + + unsigned int i = 0; + while( i < tokens.size() ) { + if( tokens[i] == "." ) { + // ignore it + } + else if( tokens[i] == ".." ) { + // change the directory + dir = dir->parent(); + if( dir == 0 ) + return false; + } + else { + // search for the item in dir + K3bDataItem* d = dir->find( tokens[i] ); + if( d == 0 ) + return false; + + if( d->isDir() ) { + // change directory + dir = (K3bDirItem*)d; + } + else { + if( i+1 != tokens.size() ) + return false; // if di is a file we need to be at the last token + else + return (dest[dest.length()-1] != '/'); // if the link destination ends with a slash + // it can only point to a directory! + } + } + + i++; + } + + return true; + } + else + return true; +} diff --git a/libk3b/projects/datacd/k3bfileitem.h b/libk3b/projects/datacd/k3bfileitem.h new file mode 100644 index 0000000..f23644f --- /dev/null +++ b/libk3b/projects/datacd/k3bfileitem.h @@ -0,0 +1,124 @@ +/* + * + * $Id: k3bfileitem.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 K3BFILEITEM_H +#define K3BFILEITEM_H + + +#include "k3bdataitem.h" +#include <k3bglobals.h> + +#include <kio/global.h> +#include <qstring.h> + +#include "k3b_export.h" + +class K3bDataDoc; +class K3bDirItem; + + +/** + *@author Sebastian Trueg + */ +class LIBK3B_EXPORT K3bFileItem : public K3bDataItem +{ +public: + /** + * Creates a new K3bFileItem + */ + K3bFileItem( const QString& fileName, K3bDataDoc* doc, K3bDirItem* dir, const QString& k3bName = 0, int flags = 0 ); + + /** + * Constructor for optimized file item creation which does no additional stat. + * + * Used by K3b to speedup file item creation. + */ + K3bFileItem( const k3b_struct_stat* stat, + const k3b_struct_stat* followedStat, + const QString& fileName, K3bDataDoc* doc, K3bDirItem* dir, const QString& k3bName = 0 ); + + /** + * Default copy constructor + * Creates a copy of the fileitem. The copy, however, is not an exact duplicate of this item. + * The copy does not have a parent dir set and any old session items are set to 0. + */ + K3bFileItem( const K3bFileItem& ); + + virtual ~K3bFileItem(); + + virtual K3bDataItem* copy() const; + + bool exists() const; + + QString absIsoPath(); + + /** reimplemented from K3bDataItem */ + QString localPath() const; + + /** + * Identification of the files on the local device. + */ + struct Id { + dev_t device; + ino_t inode; + }; + + /** + * This is not the normal inode number but it also contains + * the device number. + */ + Id localId() const; + + /** + * The id of the file the symlink is pointing to + */ + Id localId( bool followSymlinks ) const; + + K3bDirItem* getDirItem() const; + + bool isSymLink() const; + QString linkDest() const; + bool isFile() const { return true; } + + /** returns true if the item is not a link or + * if the link's destination is part of the compilation */ + bool isValid() const; + + K3bDataItem* replaceItemFromOldSession() const { return m_replacedItemFromOldSession; } + void setReplacedItemFromOldSession( K3bDataItem* item ) { m_replacedItemFromOldSession = item; } + + /** + * Normally one does not use this method but K3bDataItem::size() + */ + KIO::filesize_t itemSize( bool followSymlinks ) const; + + private: + K3bDataItem* m_replacedItemFromOldSession; + + KIO::filesize_t m_size; + KIO::filesize_t m_sizeFollowed; + Id m_id; + Id m_idFollowed; + + QString m_localPath; + bool m_bSymLink; +}; + +bool operator==( const K3bFileItem::Id&, const K3bFileItem::Id& ); +bool operator<( const K3bFileItem::Id&, const K3bFileItem::Id& ); +bool operator>( const K3bFileItem::Id&, const K3bFileItem::Id& ); + +#endif diff --git a/libk3b/projects/datacd/k3bisoimager.cpp b/libk3b/projects/datacd/k3bisoimager.cpp new file mode 100644 index 0000000..f44d3ab --- /dev/null +++ b/libk3b/projects/datacd/k3bisoimager.cpp @@ -0,0 +1,1187 @@ +/* + * + * $Id: k3bisoimager.cpp 655085 2007-04-17 17:48:36Z trueg $ + * Copyright (C) 2003-2007 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + +#include <config.h> +#include <k3bglobals.h> + +#include "k3bisoimager.h" +#include "k3bdiritem.h" +#include "k3bbootitem.h" +#include "k3bdatadoc.h" +#include "k3bdatapreparationjob.h" +#include <k3bexternalbinmanager.h> +#include <k3bdevice.h> +#include <k3bprocess.h> +#include <k3bcore.h> +#include <k3bversion.h> +#include <k3bglobals.h> +#include <k3bchecksumpipe.h> +#include <k3bfilesplitter.h> + +#include <kdebug.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <ktempfile.h> +#include <kio/netaccess.h> +#include <kio/global.h> +#include <kio/job.h> +#include <kstringhandler.h> + +#include <qfile.h> +#include <qregexp.h> +#include <qdir.h> +#include <qapplication.h> +#include <qvaluestack.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <utime.h> + + +int K3bIsoImager::s_imagerSessionCounter = 0; + + +class K3bIsoImager::Private +{ +public: + Private() + : pipe(0) { + } + + ~Private() { + delete pipe; + } + + QString imagePath; + K3bFileSplitter imageFile; + const K3bExternalBin* mkisofsBin; + + enum LinkHandling { + KEEP_ALL, + FOLLOW, + DISCARD_ALL, + DISCARD_BROKEN + }; + + int usedLinkHandling; + + bool knownError; + + K3bActivePipe* pipe; + K3bDataPreparationJob* dataPreparationJob; +}; + + +K3bIsoImager::K3bIsoImager( K3bDataDoc* doc, K3bJobHandler* hdl, QObject* parent, const char* name ) + : K3bJob( hdl, parent, name ), + m_pathSpecFile(0), + m_rrHideFile(0), + m_jolietHideFile(0), + m_sortWeightFile(0), + m_process( 0 ), + m_processExited(false), + m_doc( doc ), + m_noDeepDirectoryRelocation( false ), + m_importSession( false ), + m_device(0), + m_mkisofsPrintSizeResult( 0 ), + m_fdToWriteTo(-1) +{ + d = new Private(); + d->dataPreparationJob = new K3bDataPreparationJob( doc, this, this ); + connectSubJob( d->dataPreparationJob, + SLOT(slotDataPreparationDone(bool)), + DEFAULT_SIGNAL_CONNECTION ); +} + + +K3bIsoImager::~K3bIsoImager() +{ + cleanup(); + delete d; +} + + +bool K3bIsoImager::active() const +{ + return K3bJob::active(); +} + + +void K3bIsoImager::writeToFd( int fd ) +{ + m_fdToWriteTo = fd; +} + + +void K3bIsoImager::writeToImageFile( const QString& path ) +{ + d->imagePath = path; + m_fdToWriteTo = -1; +} + + +void K3bIsoImager::slotReceivedStderr( const QString& line ) +{ + parseMkisofsOutput( line ); + emit debuggingOutput( "mkisofs", line ); +} + + +void K3bIsoImager::handleMkisofsProgress( int p ) +{ + emit percent( p ); +} + + +void K3bIsoImager::handleMkisofsInfoMessage( const QString& line, int type ) +{ + emit infoMessage( line, type ); + if( type == ERROR ) + d->knownError = true; +} + + +void K3bIsoImager::slotProcessExited( KProcess* p ) +{ + kdDebug() << k_funcinfo << endl; + + m_processExited = true; + + d->pipe->close(); + + emit debuggingOutput( "K3bIsoImager", + QString("Pipe throughput: %1 bytes read, %2 bytes written.") + .arg(d->pipe->bytesRead()).arg(d->pipe->bytesWritten()) ); + + if( d->imageFile.isOpen() ) { + d->imageFile.close(); + + if( m_canceled || p->exitStatus() != 0 ) { + d->imageFile.remove(); + emit infoMessage( i18n("Removed incomplete image file %1.").arg(d->imageFile.name()), WARNING ); + } + } + + if( m_canceled ) { + emit canceled(); + jobFinished(false); + } + else { + if( p->normalExit() ) { + if( p->exitStatus() == 0 ) { + jobFinished( !mkisofsReadError() ); + } + else { + switch( p->exitStatus() ) { + case 104: + // connection reset by peer + // This only happens if cdrecord does not finish successfully + // so we may leave the error handling to it meaning we handle this + // as a known error + break; + + case 2: + // mkisofs seems to have a bug that prevents to use filenames + // that contain one or more backslashes + // mkisofs 1.14 has the bug, 1.15a40 not + // TODO: find out the version that fixed the bug + if( m_containsFilesWithMultibleBackslashes && + !k3bcore->externalBinManager()->binObject( "mkisofs" )->hasFeature( "backslashed_filenames" ) ) { + emit infoMessage( i18n("Due to a bug in mkisofs <= 1.15a40, K3b is unable to handle " + "filenames that contain more than one backslash:"), ERROR ); + + break; + } + // otherwise just fall through + + default: + if( !d->knownError && !mkisofsReadError() ) { + emit infoMessage( i18n("%1 returned an unknown error (code %2).").arg("mkisofs").arg(p->exitStatus()), + K3bJob::ERROR ); + emit infoMessage( i18n("Please send me an email with the last output."), K3bJob::ERROR ); + } + } + + jobFinished( false ); + } + } + else { + emit infoMessage( i18n("%1 did not exit cleanly.").arg("mkisofs"), ERROR ); + jobFinished( false ); + } + } + + cleanup(); +} + + +void K3bIsoImager::cleanup() +{ + // remove all temp files + delete m_pathSpecFile; + delete m_rrHideFile; + delete m_jolietHideFile; + delete m_sortWeightFile; + + // remove boot-images-temp files + for( QStringList::iterator it = m_tempFiles.begin(); + it != m_tempFiles.end(); ++it ) + QFile::remove( *it ); + m_tempFiles.clear(); + + m_pathSpecFile = m_jolietHideFile = m_rrHideFile = m_sortWeightFile = 0; + + delete m_process; + m_process = 0; + + clearDummyDirs(); +} + + +void K3bIsoImager::init() +{ + jobStarted(); + + cleanup(); + + d->dataPreparationJob->start(); +} + + +void K3bIsoImager::slotDataPreparationDone( bool success ) +{ + if( success ) { + // + // We always calculate the image size. It does not take long and at least the mixed job needs it + // anyway + // + startSizeCalculation(); + } + else { + if( d->dataPreparationJob->hasBeenCanceled() ) { + m_canceled = true; + emit canceled(); + } + jobFinished( false ); + } +} + + +void K3bIsoImager::calculateSize() +{ + jobStarted(); + startSizeCalculation(); +} + + +void K3bIsoImager::startSizeCalculation() +{ + d->mkisofsBin = initMkisofs(); + if( !d->mkisofsBin ) { + jobFinished( false ); + return; + } + + initVariables(); + + delete m_process; + m_process = new K3bProcess(); + m_process->setRunPrivileged(true); + m_process->setSplitStdout(true); + + emit debuggingOutput( "Used versions", "mkisofs: " + d->mkisofsBin->version ); + + *m_process << d->mkisofsBin; + + if( !prepareMkisofsFiles() || + !addMkisofsParameters(true) ) { + cleanup(); + jobFinished( false ); + return; + } + + // add empty dummy dir since one path-spec is needed + // ??? Seems it is not needed after all. At least mkisofs 1.14 and above don't need it. ??? + // *m_process << dummyDir(); + + kdDebug() << "***** mkisofs calculate size parameters:\n"; + const QValueList<QCString>& args = m_process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << endl << flush; + emit debuggingOutput("mkisofs calculate size command:", s); + + // since output changed during mkisofs version changes we grab both + // stdout and stderr + + // mkisofs version >= 1.15 (don't know about 1.14!) + // the extends on stdout (as lonely number) + // and error and warning messages on stderr + + // mkisofs >= 1.13 + // everything is written to stderr + // last line is: "Total extents scheduled to be written = XXXXX" + + // TODO: use K3bProcess::OutputCollector instead iof our own two slots. + + connect( m_process, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(slotCollectMkisofsPrintSizeStderr(KProcess*, char*, int)) ); + connect( m_process, SIGNAL(stdoutLine(const QString&)), + this, SLOT(slotCollectMkisofsPrintSizeStdout(const QString&)) ); + connect( m_process, SIGNAL(processExited(KProcess*)), + this, SLOT(slotMkisofsPrintSizeFinished()) ); + + // we also want error messages + connect( m_process, SIGNAL(stderrLine( const QString& )), + this, SLOT(slotReceivedStderr( const QString& )) ); + + m_collectedMkisofsPrintSizeStdout = QString::null; + m_collectedMkisofsPrintSizeStderr = QString::null; + m_mkisofsPrintSizeResult = 0; + + if( !m_process->start( KProcess::NotifyOnExit, KProcess::AllOutput ) ) { + emit infoMessage( i18n("Could not start %1.").arg("mkisofs"), K3bJob::ERROR ); + cleanup(); + + jobFinished( false ); + return; + } +} + + +void K3bIsoImager::slotCollectMkisofsPrintSizeStderr(KProcess*, char* data , int len) +{ + emit debuggingOutput( "mkisofs", QString::fromLocal8Bit( data, len ) ); + m_collectedMkisofsPrintSizeStderr.append( QString::fromLocal8Bit( data, len ) ); +} + + +void K3bIsoImager::slotCollectMkisofsPrintSizeStdout( const QString& line ) +{ + // newer versions of mkisofs outut additional lines of junk before the size :( + // so we only use the last line + emit debuggingOutput( "mkisofs", line ); + m_collectedMkisofsPrintSizeStdout = line; +} + + +void K3bIsoImager::slotMkisofsPrintSizeFinished() +{ + if( m_canceled ) { + emit canceled(); + jobFinished( false ); + return; + } + + bool success = true; + + // if m_collectedMkisofsPrintSizeStdout is not empty we have a recent version of + // mkisofs and parsing is very easy (s.o.) + if( !m_collectedMkisofsPrintSizeStdout.isEmpty() ) { + kdDebug() << "(K3bIsoImager) iso size: " << m_collectedMkisofsPrintSizeStdout << endl; + m_mkisofsPrintSizeResult = m_collectedMkisofsPrintSizeStdout.toInt( &success ); + } + else { + // parse the stderr output + // I hope parsing the last line is enough! + int pos = m_collectedMkisofsPrintSizeStderr.findRev( "extents scheduled to be written" ); + + if( pos == -1 ) + success = false; + else + m_mkisofsPrintSizeResult = m_collectedMkisofsPrintSizeStderr.mid( pos+33 ).toInt( &success ); + } + + emit debuggingOutput( "K3bIsoImager", + QString("mkisofs print size result: %1 (%2 bytes)") + .arg(m_mkisofsPrintSizeResult) + .arg(Q_UINT64(m_mkisofsPrintSizeResult)*2048ULL) ); + + cleanup(); + + + if( success ) { + jobFinished( true ); + } + else { + m_mkisofsPrintSizeResult = 0; + kdDebug() << "(K3bIsoImager) Parsing mkisofs -print-size failed: " << m_collectedMkisofsPrintSizeStdout << endl; + emit infoMessage( i18n("Could not determine size of resulting image file."), ERROR ); + jobFinished( false ); + } +} + + +void K3bIsoImager::initVariables() +{ + m_containsFilesWithMultibleBackslashes = false; + m_processExited = false; + m_canceled = false; + d->knownError = false; + + // determine symlink handling + // follow links superseeds discard all links which superseeds discard broken links + // without rockridge we follow the links or discard all + if( m_doc->isoOptions().followSymbolicLinks() ) + d->usedLinkHandling = Private::FOLLOW; + else if( m_doc->isoOptions().discardSymlinks() ) + d->usedLinkHandling = Private::DISCARD_ALL; + else if( m_doc->isoOptions().createRockRidge() ) { + if( m_doc->isoOptions().discardBrokenSymlinks() ) + d->usedLinkHandling = Private::DISCARD_BROKEN; + else + d->usedLinkHandling = Private::KEEP_ALL; + } + else { + d->usedLinkHandling = Private::FOLLOW; + } + + m_sessionNumber = s_imagerSessionCounter++; +} + + +void K3bIsoImager::start() +{ + jobStarted(); + + cleanup(); + + d->mkisofsBin = initMkisofs(); + if( !d->mkisofsBin ) { + jobFinished( false ); + return; + } + + initVariables(); + + m_process = new K3bProcess(); + m_process->setRunPrivileged(true); + + *m_process << d->mkisofsBin; + + // prepare the filenames as written to the image + m_doc->prepareFilenames(); + + if( !prepareMkisofsFiles() || + !addMkisofsParameters() ) { + cleanup(); + jobFinished( false ); + return; + } + + connect( m_process, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*)) ); + + connect( m_process, SIGNAL(stderrLine( const QString& )), + this, SLOT(slotReceivedStderr( const QString& )) ); + + // + // Check the image file + if( m_fdToWriteTo == -1 ) { + d->imageFile.setName( d->imagePath ); + if( !d->imageFile.open( IO_WriteOnly ) ) { + emit infoMessage( i18n("Could not open %1 for writing").arg(d->imagePath), ERROR ); + cleanup(); + jobFinished(false); + return; + } + } + + // + // Open the active pipe which does the streaming + delete d->pipe; + if( m_doc->verifyData() ) + d->pipe = new K3bChecksumPipe(); + else + d->pipe = new K3bActivePipe(); + + if( m_fdToWriteTo == -1 ) + d->pipe->writeToIODevice( &d->imageFile ); + else + d->pipe->writeToFd( m_fdToWriteTo ); + d->pipe->open(); + m_process->writeToFd( d->pipe->in() ); + + + kdDebug() << "***** mkisofs parameters:\n"; + const QValueList<QCString>& args = m_process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << endl << flush; + emit debuggingOutput("mkisofs command:", s); + + if( !m_process->start( KProcess::NotifyOnExit, KProcess::AllOutput) ) { + // something went wrong when starting the program + // it "should" be the executable + kdDebug() << "(K3bIsoImager) could not start mkisofs" << endl; + emit infoMessage( i18n("Could not start %1.").arg("mkisofs"), K3bJob::ERROR ); + jobFinished( false ); + cleanup(); + } +} + + +void K3bIsoImager::cancel() +{ + m_canceled = true; + + if( m_process && !m_processExited ) { + m_process->kill(); + } + else if( active() ) { + emit canceled(); + jobFinished(false); + } +} + + +void K3bIsoImager::setMultiSessionInfo( const QString& info, K3bDevice::Device* dev ) +{ + m_multiSessionInfo = info; + m_device = dev; +} + + +// iso9660 + RR use some latin1 variant. So we need to cut the desc fields +// counting 8bit chars. The GUI should take care of restricting the length +// and the charset +static void truncateTheHardWay( QString& s, int max ) +{ + QCString cs = s.utf8(); + cs.truncate(max); + s = QString::fromUtf8( cs ); +} + + +bool K3bIsoImager::addMkisofsParameters( bool printSize ) +{ + // add multisession info + if( !m_multiSessionInfo.isEmpty() ) { + *m_process << "-cdrecord-params" << m_multiSessionInfo; + if( m_device ) + *m_process << "-prev-session" << m_device->blockDeviceName(); + } + + // add the arguments + *m_process << "-gui"; + *m_process << "-graft-points"; + + if( printSize ) + *m_process << "-print-size" << "-quiet"; + + if( !m_doc->isoOptions().volumeID().isEmpty() ) { + QString s = m_doc->isoOptions().volumeID(); + truncateTheHardWay(s, 32); // ensure max length + *m_process << "-volid" << s; + } + else { + emit infoMessage( i18n("No volume id specified. Using default."), WARNING ); + *m_process << "-volid" << "CDROM"; + } + + QString s = m_doc->isoOptions().volumeSetId(); + truncateTheHardWay(s, 128); // ensure max length + *m_process << "-volset" << s; + + s = m_doc->isoOptions().applicationID(); + truncateTheHardWay(s, 128); // ensure max length + *m_process << "-appid" << s; + + s = m_doc->isoOptions().publisher(); + truncateTheHardWay(s, 128); // ensure max length + *m_process << "-publisher" << s; + + s = m_doc->isoOptions().preparer(); + truncateTheHardWay(s, 128); // ensure max length + *m_process << "-preparer" << s; + + s = m_doc->isoOptions().systemId(); + truncateTheHardWay(s, 32); // ensure max length + *m_process << "-sysid" << s; + + s = m_doc->isoOptions().abstractFile(); + truncateTheHardWay(s, 37); // ensure max length + if ( !s.isEmpty() ) + *m_process << "-abstract" << s; + + s = m_doc->isoOptions().copyrightFile(); + truncateTheHardWay(s, 37); // ensure max length + if ( !s.isEmpty() ) + *m_process << "-copyright" << s; + + s = m_doc->isoOptions().bibliographFile(); + truncateTheHardWay(s, 37); // ensure max length + if ( !s.isEmpty() ) + *m_process << "-biblio" << s; + + int volsetSize = m_doc->isoOptions().volumeSetSize(); + int volsetSeqNo = m_doc->isoOptions().volumeSetNumber(); + if( volsetSeqNo > volsetSize ) { + kdDebug() << "(K3bIsoImager) invalid volume set sequence number: " << volsetSeqNo + << " with volume set size: " << volsetSize << endl; + volsetSeqNo = volsetSize; + } + *m_process << "-volset-size" << QString::number(volsetSize); + *m_process << "-volset-seqno" << QString::number(volsetSeqNo); + + if( m_sortWeightFile ) { + *m_process << "-sort" << m_sortWeightFile->name(); + } + + if( m_doc->isoOptions().createRockRidge() ) { + if( m_doc->isoOptions().preserveFilePermissions() ) + *m_process << "-rock"; + else + *m_process << "-rational-rock"; + if( m_rrHideFile ) + *m_process << "-hide-list" << m_rrHideFile->name(); + } + + if( m_doc->isoOptions().createJoliet() ) { + *m_process << "-joliet"; + if( m_doc->isoOptions().jolietLong() ) + *m_process << "-joliet-long"; + if( m_jolietHideFile ) + *m_process << "-hide-joliet-list" << m_jolietHideFile->name(); + } + + if( m_doc->isoOptions().doNotCacheInodes() ) + *m_process << "-no-cache-inodes"; + + // + // Check if we have files > 2 GB and enable udf in that case. + // + bool filesGreaterThan2Gb = false; + K3bDataItem* item = m_doc->root(); + while( (item = item->nextSibling()) ) { + if( item->isFile() && item->size() > 2LL*1024LL*1024LL*1024LL ) { + filesGreaterThan2Gb = true; + break; + } + } + + if( filesGreaterThan2Gb ) { + emit infoMessage( i18n("Found files bigger than 2 GB. These files will only be fully accessible if mounted with UDF."), + WARNING ); + + // in genisoimage 1.1.3 "they" silently introduced this aweful parameter + if ( d->mkisofsBin->hasFeature( "genisoimage" ) && d->mkisofsBin->version >= K3bVersion( 1, 1, 3 ) ) { + *m_process << "-allow-limited-size"; + } + } + + bool udf = m_doc->isoOptions().createUdf(); + if( !udf && filesGreaterThan2Gb ) { + emit infoMessage( i18n("Enabling UDF extension."), INFO ); + udf = true; + } + if( udf ) + *m_process << "-udf"; + + if( m_doc->isoOptions().ISOuntranslatedFilenames() ) { + *m_process << "-untranslated-filenames"; + } + else { + if( m_doc->isoOptions().ISOallowPeriodAtBegin() ) + *m_process << "-allow-leading-dots"; + if( m_doc->isoOptions().ISOallow31charFilenames() ) + *m_process << "-full-iso9660-filenames"; + if( m_doc->isoOptions().ISOomitVersionNumbers() && !m_doc->isoOptions().ISOmaxFilenameLength() ) + *m_process << "-omit-version-number"; + if( m_doc->isoOptions().ISOrelaxedFilenames() ) + *m_process << "-relaxed-filenames"; + if( m_doc->isoOptions().ISOallowLowercase() ) + *m_process << "-allow-lowercase"; + if( m_doc->isoOptions().ISOnoIsoTranslate() ) + *m_process << "-no-iso-translate"; + if( m_doc->isoOptions().ISOallowMultiDot() ) + *m_process << "-allow-multidot"; + if( m_doc->isoOptions().ISOomitTrailingPeriod() ) + *m_process << "-omit-period"; + } + + if( m_doc->isoOptions().ISOmaxFilenameLength() ) + *m_process << "-max-iso9660-filenames"; + + if( m_noDeepDirectoryRelocation ) + *m_process << "-disable-deep-relocation"; + + // We do our own following +// if( m_doc->isoOptions().followSymbolicLinks() || !m_doc->isoOptions().createRockRidge() ) +// *m_process << "-follow-links"; + + if( m_doc->isoOptions().createTRANS_TBL() ) + *m_process << "-translation-table"; + if( m_doc->isoOptions().hideTRANS_TBL() ) + *m_process << "-hide-joliet-trans-tbl"; + + *m_process << "-iso-level" << QString::number(m_doc->isoOptions().ISOLevel()); + + if( m_doc->isoOptions().forceInputCharset() ) + *m_process << "-input-charset" << m_doc->isoOptions().inputCharset(); + + *m_process << "-path-list" << QFile::encodeName(m_pathSpecFile->name()); + + + // boot stuff + if( !m_doc->bootImages().isEmpty() ) { + bool first = true; + for( QPtrListIterator<K3bBootItem> it( m_doc->bootImages() ); + *it; ++it ) { + if( !first ) + *m_process << "-eltorito-alt-boot"; + + K3bBootItem* bootItem = *it; + + *m_process << "-eltorito-boot"; + *m_process << bootItem->writtenPath(); + + if( bootItem->imageType() == K3bBootItem::HARDDISK ) { + *m_process << "-hard-disk-boot"; + } + else if( bootItem->imageType() == K3bBootItem::NONE ) { + *m_process << "-no-emul-boot"; + if( bootItem->loadSegment() > 0 ) + *m_process << "-boot-load-seg" << QString::number(bootItem->loadSegment()); + if( bootItem->loadSize() > 0 ) + *m_process << "-boot-load-size" << QString::number(bootItem->loadSize()); + } + + if( bootItem->imageType() != K3bBootItem::NONE && bootItem->noBoot() ) + *m_process << "-no-boot"; + if( bootItem->bootInfoTable() ) + *m_process << "-boot-info-table"; + + first = false; + } + + *m_process << "-eltorito-catalog" << m_doc->bootCataloge()->writtenPath(); + } + + + // additional parameters from config + const QStringList& params = k3bcore->externalBinManager()->binObject( "mkisofs" )->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *m_process << *it; + + return true; +} + + +int K3bIsoImager::writePathSpec() +{ + delete m_pathSpecFile; + m_pathSpecFile = new KTempFile(); + m_pathSpecFile->setAutoDelete(true); + + if( QTextStream* t = m_pathSpecFile->textStream() ) { + // recursive path spec writing + int num = writePathSpecForDir( m_doc->root(), *t ); + + m_pathSpecFile->close(); + + return num; + } + else + return -1; +} + + +int K3bIsoImager::writePathSpecForDir( K3bDirItem* dirItem, QTextStream& stream ) +{ + if( !m_noDeepDirectoryRelocation && dirItem->depth() > 7 ) { + kdDebug() << "(K3bIsoImager) found directory depth > 7. Enabling no deep directory relocation." << endl; + m_noDeepDirectoryRelocation = true; + } + + // now create the graft points + int num = 0; + for( QPtrListIterator<K3bDataItem> it( dirItem->children() ); it.current(); ++it ) { + K3bDataItem* item = it.current(); + bool writeItem = item->writeToCd(); + + if( item->isSymLink() ) { + if( d->usedLinkHandling == Private::DISCARD_ALL || + ( d->usedLinkHandling == Private::DISCARD_BROKEN && + !item->isValid() ) ) + writeItem = false; + + else if( d->usedLinkHandling == Private::FOLLOW ) { + QFileInfo f( K3b::resolveLink( item->localPath() ) ); + if( !f.exists() ) { + emit infoMessage( i18n("Could not follow link %1 to non-existing file %2. Skipping...") + .arg(item->k3bName()) + .arg(f.filePath()), WARNING ); + writeItem = false; + } + else if( f.isDir() ) { + emit infoMessage( i18n("Ignoring link %1 to folder %2. K3b is unable to follow links to folders.") + .arg(item->k3bName()) + .arg(f.filePath()), WARNING ); + writeItem = false; + } + } + } + else if( item->isFile() ) { + QFileInfo f( item->localPath() ); + if( !f.exists() ) { + emit infoMessage( i18n("Could not find file %1. Skipping...").arg(item->localPath()), WARNING ); + writeItem = false; + } + else if( !f.isReadable() ) { + emit infoMessage( i18n("Could not read file %1. Skipping...").arg(item->localPath()), WARNING ); + writeItem = false; + } + } + + if( writeItem ) { + num++; + + // some versions of mkisofs seem to have a bug that prevents to use filenames + // that contain one or more backslashes + if( item->writtenPath().contains("\\") ) + m_containsFilesWithMultibleBackslashes = true; + + + if( item->isDir() ) { + stream << escapeGraftPoint( item->writtenPath() ) + << "=" + << escapeGraftPoint( dummyDir( static_cast<K3bDirItem*>(item) ) ) << "\n"; + + int x = writePathSpecForDir( dynamic_cast<K3bDirItem*>(item), stream ); + if( x >= 0 ) + num += x; + else + return -1; + } + else { + writePathSpecForFile( static_cast<K3bFileItem*>(item), stream ); + } + } + } + + return num; +} + + +void K3bIsoImager::writePathSpecForFile( K3bFileItem* item, QTextStream& stream ) +{ + stream << escapeGraftPoint( item->writtenPath() ) + << "="; + + if( m_doc->bootImages().containsRef( dynamic_cast<K3bBootItem*>(item) ) ) { // boot-image-backup-hack + + // create temp file + KTempFile temp; + QString tempPath = temp.name(); + temp.unlink(); + + if( !KIO::NetAccess::copy( KURL(item->localPath()), KURL::fromPathOrURL(tempPath) ) ) { + emit infoMessage( i18n("Failed to backup boot image file %1").arg(item->localPath()), ERROR ); + return; + } + + static_cast<K3bBootItem*>(item)->setTempPath( tempPath ); + + m_tempFiles.append(tempPath); + stream << escapeGraftPoint( tempPath ) << "\n"; + } + else if( item->isSymLink() && d->usedLinkHandling == Private::FOLLOW ) + stream << escapeGraftPoint( K3b::resolveLink( item->localPath() ) ) << "\n"; + else + stream << escapeGraftPoint( item->localPath() ) << "\n"; +} + + +bool K3bIsoImager::writeRRHideFile() +{ + delete m_rrHideFile; + m_rrHideFile = new KTempFile(); + m_rrHideFile->setAutoDelete(true); + + if( QTextStream* t = m_rrHideFile->textStream() ) { + + K3bDataItem* item = m_doc->root(); + while( item ) { + if( item->hideOnRockRidge() ) { + if( !item->isDir() ) // hiding directories does not work (all dirs point to the dummy-dir) + *t << escapeGraftPoint( item->localPath() ) << endl; + } + item = item->nextSibling(); + } + + m_rrHideFile->close(); + return true; + } + else + return false; +} + + +bool K3bIsoImager::writeJolietHideFile() +{ + delete m_jolietHideFile; + m_jolietHideFile = new KTempFile(); + m_jolietHideFile->setAutoDelete(true); + + if( QTextStream* t = m_jolietHideFile->textStream() ) { + + K3bDataItem* item = m_doc->root(); + while( item ) { + if( item->hideOnRockRidge() ) { + if( !item->isDir() ) // hiding directories does not work (all dirs point to the dummy-dir but we could introduce a second hidden dummy dir) + *t << escapeGraftPoint( item->localPath() ) << endl; + } + item = item->nextSibling(); + } + + m_jolietHideFile->close(); + return true; + } + else + return false; +} + + +bool K3bIsoImager::writeSortWeightFile() +{ + delete m_sortWeightFile; + m_sortWeightFile = new KTempFile(); + m_sortWeightFile->setAutoDelete(true); + + if( QTextStream* t = m_sortWeightFile->textStream() ) { + // + // We need to write the local path in combination with the sort weight + // mkisofs will take care of multiple entries for one local file and always + // use the highest weight + // + K3bDataItem* item = m_doc->root(); + while( (item = item->nextSibling()) ) { // we skip the root here + if( item->sortWeight() != 0 ) { + if( m_doc->bootImages().containsRef( dynamic_cast<K3bBootItem*>(item) ) ) { // boot-image-backup-hack + *t << escapeGraftPoint( static_cast<K3bBootItem*>(item)->tempPath() ) << " " << item->sortWeight() << endl; + } + else if( item->isDir() ) { + // + // Since we use dummy dirs for all directories in the filesystem and mkisofs uses the local path + // for sorting we need to create a different dummy dir for every sort weight value. + // + *t << escapeGraftPoint( dummyDir( static_cast<K3bDirItem*>(item) ) ) << " " << item->sortWeight() << endl; + } + else + *t << escapeGraftPoint( item->localPath() ) << " " << item->sortWeight() << endl; + } + } + + m_sortWeightFile->close(); + return true; + } + else + return false; +} + + +QString K3bIsoImager::escapeGraftPoint( const QString& str ) +{ + QString enc = str; + + // + // mkisofs manpage (-graft-points) is incorrect (as of mkisofs 2.01.01) + // + // Actually an equal sign needs to be escaped with one backslash only + // Single backslashes inside a filename can be used without change + // while single backslashes at the end of a filename need to be escaped + // with two backslashes. + // + // There is one more problem though: the name in the iso tree can never + // in any number of backslashes. mkisofs simply cannot handle it. So we + // need to remove these slashes somewhere or ignore those files (we do + // that in K3bDataDoc::addUrls) + // + + // + // we do not use QString::replace to have full control + // this might be slow since QString::insert is slow but we don't care + // since this is only called to prepare the iso creation which is not + // time critical. :) + // + + unsigned int pos = 0; + while( pos < enc.length() ) { + // escape every equal sign with one backslash + if( enc[pos] == '=' ) { + enc.insert( pos, "\\" ); + pos += 2; + } + else if( enc[pos] == '\\' ) { + // escape every occurrence of two backslashes with two backslashes + if( pos+1 < enc.length() && enc[pos+1] == '\\' ) { + enc.insert( pos, "\\\\" ); + pos += 4; + } + // escape the last single backslash in the filename (see above) + else if( pos == enc.length()-1 ) { + enc.insert( pos, "\\" ); + pos += 2; + } + else + ++pos; + } + else + ++pos; + } + +// enc.replace( "\\\\", "\\\\\\\\" ); +// enc.replace( "=", "\\=" ); + + return enc; +} + + +bool K3bIsoImager::prepareMkisofsFiles() +{ + // write path spec file + // ---------------------------------------------------- + int num = writePathSpec(); + if( num < 0 ) { + emit infoMessage( i18n("Could not write temporary file"), K3bJob::ERROR ); + return false; + } + else if( num == 0 ) { + emit infoMessage( i18n("No files to be written."), K3bJob::ERROR ); + return false; + } + + if( m_doc->isoOptions().createRockRidge() ) { + if( !writeRRHideFile() ) { + emit infoMessage( i18n("Could not write temporary file"), K3bJob::ERROR ); + return false; + } + } + + if( m_doc->isoOptions().createJoliet() ) { + if( !writeJolietHideFile() ) { + emit infoMessage( i18n("Could not write temporary file"), K3bJob::ERROR ); + return false ; + } + } + + if( !writeSortWeightFile() ) { + emit infoMessage( i18n("Could not write temporary file"), K3bJob::ERROR ); + return false; + } + + return true; +} + + +QString K3bIsoImager::dummyDir( K3bDirItem* dir ) +{ + // + // since we use virtual folders in order to have folders with different weight factors and different + // permissions we create different dummy dirs to be passed to mkisofs + // + + QDir _appDir( locateLocal( "appdata", "temp/" ) ); + + // + // create a unique isoimager session id + // This might become important in case we will allow multiple instances of the isoimager + // to run at the same time. + // + QString jobId = qApp->sessionId() + "_" + QString::number( m_sessionNumber ); + + if( !_appDir.cd( jobId ) ) { + _appDir.mkdir( jobId ); + _appDir.cd( jobId ); + } + + QString name( "dummydir_" ); + name += QString::number( dir->sortWeight() ); + + bool perm = false; + k3b_struct_stat statBuf; + if( !dir->localPath().isEmpty() ) { + // permissions + if( k3b_stat( QFile::encodeName(dir->localPath()), &statBuf ) == 0 ) { + name += "_"; + name += QString::number( statBuf.st_uid ); + name += "_"; + name += QString::number( statBuf.st_gid ); + name += "_"; + name += QString::number( statBuf.st_mode ); + name += "_"; + name += QString::number( statBuf.st_mtime ); + + perm = true; + } + } + + + if( !_appDir.cd( name ) ) { + + kdDebug() << "(K3bIsoImager) creating dummy dir: " << _appDir.absPath() << "/" << name << endl; + + _appDir.mkdir( name ); + _appDir.cd( name ); + + if( perm ) { + ::chmod( QFile::encodeName( _appDir.absPath() ), statBuf.st_mode ); + ::chown( QFile::encodeName( _appDir.absPath() ), statBuf.st_uid, statBuf.st_gid ); + struct utimbuf tb; + tb.actime = tb.modtime = statBuf.st_mtime; + ::utime( QFile::encodeName( _appDir.absPath() ), &tb ); + } + } + + return _appDir.absPath() + "/"; +} + + +void K3bIsoImager::clearDummyDirs() +{ + QString jobId = qApp->sessionId() + "_" + QString::number( m_sessionNumber ); + QDir appDir( locateLocal( "appdata", "temp/" ) ); + if( appDir.cd( jobId ) ) { + QStringList dummyDirEntries = appDir.entryList( "dummydir*", QDir::Dirs ); + for( QStringList::iterator it = dummyDirEntries.begin(); it != dummyDirEntries.end(); ++it ) + appDir.rmdir( *it ); + appDir.cdUp(); + appDir.rmdir( jobId ); + } +} + + +QCString K3bIsoImager::checksum() const +{ + if( K3bChecksumPipe* p = dynamic_cast<K3bChecksumPipe*>( d->pipe ) ) + return p->checksum(); + else + return QCString(); +} + + +bool K3bIsoImager::hasBeenCanceled() const +{ + return m_canceled; +} + +#include "k3bisoimager.moc" diff --git a/libk3b/projects/datacd/k3bisoimager.h b/libk3b/projects/datacd/k3bisoimager.h new file mode 100644 index 0000000..82501ba --- /dev/null +++ b/libk3b/projects/datacd/k3bisoimager.h @@ -0,0 +1,188 @@ +/* + * + * $Id: k3bisoimager.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_ISO_IMAGER_H +#define K3B_ISO_IMAGER_H + +#include <k3bjob.h> +#include "k3bmkisofshandler.h" + +#include <qptrqueue.h> +#include <qstringlist.h> + +class K3bDataDoc; +class K3bDirItem; +class K3bDataItem; +class K3bFileItem; +class QTextStream; +class K3bProcess; +class KProcess; +class K3bDevice::Device; +class KTempFile; + + +class K3bIsoImager : public K3bJob, public K3bMkisofsHandler +{ + Q_OBJECT + + public: + K3bIsoImager( K3bDataDoc*, K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + virtual ~K3bIsoImager(); + + virtual bool active() const; + + int size() const { return m_mkisofsPrintSizeResult; } + + virtual bool hasBeenCanceled() const; + + /** + * Get the checksum calculated during the creation of the image. + */ + QCString checksum() const; + + public slots: + /** + * Starts the actual image creation. Always run init() + * before starting the image creation + */ + virtual void start(); + virtual void cancel(); + + /** + * Initialize the image creator. This calculates the image size and performs + * some checks on the project. + * + * The initialization process also finishes with the finished() signal just + * like a normal job operation. Get the calculated image size via size() + */ + virtual void init(); + + /** + * Only calculates the size of the image without the additional checks in + * init() + * + * Use this if you need to recalculate the image size for example if the + * multisession info changed. + */ + virtual void calculateSize(); + + /** + * lets the isoimager write directly into fd instead of writing + * to an image file. + * Be aware that this only makes sense before starting the job. + * To disable just set @p fd to -1 + */ + void writeToFd( int fd ); + + void writeToImageFile( const QString& path ); + + /** + * If dev == 0 K3bIsoImager will ignore the data in the previous session. + * This is usable for CD-Extra. + */ + void setMultiSessionInfo( const QString&, K3bDevice::Device* dev = 0 ); + + K3bDevice::Device* device() const { return m_device; } + K3bDataDoc* doc() const { return m_doc; } + + protected: + virtual void handleMkisofsProgress( int ); + virtual void handleMkisofsInfoMessage( const QString&, int ); + + virtual bool addMkisofsParameters( bool printSize = false ); + + /** + * calls writePathSpec, writeRRHideFile, and writeJolietHideFile + */ + bool prepareMkisofsFiles(); + + /** + * The dummy dir is used to create dirs on the iso-filesystem. + * + * @return an empty dummy dir for use with K3bDirItems. + */ + QString dummyDir( K3bDirItem* ); + + void outputData(); + void initVariables(); + virtual void cleanup(); + void clearDummyDirs(); + + /** + * @returns The number of entries written or -1 on error + */ + virtual int writePathSpec(); + bool writeRRHideFile(); + bool writeJolietHideFile(); + bool writeSortWeightFile(); + + // used by writePathSpec + virtual int writePathSpecForDir( K3bDirItem* dirItem, QTextStream& stream ); + virtual void writePathSpecForFile( K3bFileItem*, QTextStream& stream ); + QString escapeGraftPoint( const QString& str ); + + KTempFile* m_pathSpecFile; + KTempFile* m_rrHideFile; + KTempFile* m_jolietHideFile; + KTempFile* m_sortWeightFile; + + K3bProcess* m_process; + + bool m_processExited; + bool m_canceled; + + protected slots: + virtual void slotReceivedStderr( const QString& ); + virtual void slotProcessExited( KProcess* ); + + private slots: + void slotCollectMkisofsPrintSizeStderr(KProcess*, char*, int); + void slotCollectMkisofsPrintSizeStdout( const QString& ); + void slotMkisofsPrintSizeFinished(); + void slotDataPreparationDone( bool success ); + + private: + void startSizeCalculation(); + + class Private; + Private* d; + + K3bDataDoc* m_doc; + + bool m_noDeepDirectoryRelocation; + + bool m_importSession; + QString m_multiSessionInfo; + K3bDevice::Device* m_device; + + // used for mkisofs -print-size parsing + QString m_collectedMkisofsPrintSizeStdout; + QString m_collectedMkisofsPrintSizeStderr; + int m_mkisofsPrintSizeResult; + + QStringList m_tempFiles; + + int m_fdToWriteTo; + + bool m_containsFilesWithMultibleBackslashes; + + // used to create a unique session id + static int s_imagerSessionCounter; + + int m_sessionNumber; +}; + + +#endif diff --git a/libk3b/projects/datacd/k3bisooptions.cpp b/libk3b/projects/datacd/k3bisooptions.cpp new file mode 100644 index 0000000..bd7314d --- /dev/null +++ b/libk3b/projects/datacd/k3bisooptions.cpp @@ -0,0 +1,216 @@ +/* + * + * $Id: k3bisooptions.cpp 639665 2007-03-05 16:29:52Z trueg $ + * Copyright (C) 2003-2007 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + +#include "k3bisooptions.h" +#include <k3bcore.h> +#include <k3bversion.h> +#include <k3bglobals.h> + +#include <kconfig.h> +#include <klocale.h> +#include <qstring.h> + + +K3bIsoOptions::K3bIsoOptions() + : m_volumeID( "K3b data project" ), + m_applicationID( QString("K3B THE CD KREATOR (C) 1998-2006 SEBASTIAN TRUEG AND THE K3B TEAM") ), + m_systemId( K3b::systemName().upper() ), + m_inputCharset( "iso8859-1" ), + m_whiteSpaceTreatmentReplaceString( "_" ) +{ + m_bForceInputCharset = false; + + m_createRockRidge = true; + m_createJoliet = true; + m_createUdf = false; + m_ISOallowLowercase = false; + m_ISOallowPeriodAtBegin = false; + m_ISOallow31charFilenames = true; + m_ISOomitVersionNumbers = false; + m_ISOomitTrailingPeriod = false; + m_ISOmaxFilenameLength = false; + m_ISOrelaxedFilenames = false; + m_ISOnoIsoTranslate = false; + m_ISOallowMultiDot = false; + m_ISOuntranslatedFilenames = false; + m_followSymbolicLinks = false; + m_createTRANS_TBL = false; + m_hideTRANS_TBL = false; + m_jolietLong = true; + + m_doNotCacheInodes = true; + + m_isoLevel = 2; + + m_discardSymlinks = false; + m_discardBrokenSymlinks = false; + + m_preserveFilePermissions = false; + + m_whiteSpaceTreatment = noChange; + + m_volumeSetSize = 1; + m_volumeSetNumber = 1; +} + + +void K3bIsoOptions::save( KConfigBase* c, bool saveVolumeDesc ) +{ + if( saveVolumeDesc ) { + c->writeEntry( "volume id", m_volumeID ); + c->writeEntry( "application id", m_applicationID ); + c->writeEntry( "preparer", m_preparer ); + c->writeEntry( "publisher", m_publisher ); + c->writeEntry( "system id", m_systemId ); + c->writeEntry( "volume set id", m_volumeSetId ); + c->writeEntry( "volume set size", m_volumeSetSize ); + c->writeEntry( "volume set number", m_volumeSetNumber ); + c->writeEntry( "abstract file", m_abstractFile ); + c->writeEntry( "copyright file", m_copyrightFile ); + c->writeEntry( "bibliograph file", m_bibliographFile ); + } + + c->writeEntry( "rock_ridge", m_createRockRidge ); + c->writeEntry( "joliet", m_createJoliet ); + c->writeEntry( "udf", m_createUdf ); + + // save iso-level + c->writeEntry( "iso_level", m_isoLevel ); + + c->writeEntry( "create TRANS_TBL", m_createTRANS_TBL ); + c->writeEntry( "hide TRANS_TBL", m_hideTRANS_TBL ); + c->writeEntry( "untranslated filenames", m_ISOuntranslatedFilenames ); + c->writeEntry( "allow 31 character filenames", m_ISOallow31charFilenames ); + c->writeEntry( "max ISO filenames", m_ISOmaxFilenameLength ); + c->writeEntry( "allow beginning period", m_ISOallowPeriodAtBegin ); + c->writeEntry( "relaxed filenames", m_ISOrelaxedFilenames ); + c->writeEntry( "omit version numbers", m_ISOomitVersionNumbers ); + c->writeEntry( "omit trailing period", m_ISOomitTrailingPeriod ); + c->writeEntry( "no iSO translation", m_ISOnoIsoTranslate ); + c->writeEntry( "allow multiple dots", m_ISOallowMultiDot ); + c->writeEntry( "allow lowercase filenames", m_ISOallowLowercase ); + // c->writeEntry( "follow symbolic links", m_followSymbolicLinks ); + + c->writeEntry( "joliet long", m_jolietLong ); + + c->writeEntry( "force input charset", m_bForceInputCharset ); + c->writeEntry( "input charset", m_inputCharset ); + + c->writeEntry( "do not cache inodes", m_doNotCacheInodes ); + + // save whitespace-treatment + switch( m_whiteSpaceTreatment ) { + case strip: + c->writeEntry( "white_space_treatment", "strip" ); + break; + case extended: + c->writeEntry( "white_space_treatment", "extended" ); + break; + case replace: + c->writeEntry( "white_space_treatment", "replace" ); + break; + default: + c->writeEntry( "white_space_treatment", "noChange" ); + } + + c->writeEntry( "whitespace replace string", m_whiteSpaceTreatmentReplaceString ); + + c->writeEntry( "discard symlinks", discardSymlinks() ); + c->writeEntry( "discard broken symlinks", discardBrokenSymlinks() ); + + c->writeEntry( "preserve file permissions", m_preserveFilePermissions ); +} + + +K3bIsoOptions K3bIsoOptions::load( KConfigBase* c, bool loadVolumeDesc ) +{ + K3bIsoOptions options; + + if( loadVolumeDesc ) { + options.setVolumeID( c->readEntry( "volume id", options.volumeID() ) ); + options.setApplicationID( c->readEntry( "application id", options.applicationID() ) ); + options.setPreparer( c->readEntry( "preparer", options.preparer() ) ); + options.setPublisher( c->readEntry( "publisher", options.publisher() ) ); + options.setSystemId( c->readEntry( "system id", options.systemId() ) ); + options.setVolumeSetId( c->readEntry( "volume set id", options.volumeSetId() ) ); + options.setVolumeSetSize( c->readNumEntry( "volume set size", options.volumeSetSize() ) ); + options.setVolumeSetNumber( c->readNumEntry( "volume set number", options.volumeSetNumber() ) ); + options.setAbstractFile( c->readEntry( "abstract file", options.abstractFile() ) ); + options.setCoprightFile( c->readEntry( "copyright file", options.copyrightFile() ) ); + options.setBibliographFile( c->readEntry( "bibliograph file", options.bibliographFile() ) ); + } + + options.setForceInputCharset( c->readBoolEntry( "force input charset", options.forceInputCharset() ) ); + if( options.forceInputCharset() ) + options.setInputCharset( c->readEntry( "input charset", options.inputCharset() ) ); + + options.setCreateRockRidge( c->readBoolEntry( "rock_ridge", options.createRockRidge() ) ); + options.setCreateJoliet( c->readBoolEntry( "joliet", options.createJoliet() ) ); + options.setCreateUdf( c->readBoolEntry( "udf", options.createUdf() ) ); + + options.setISOLevel( c->readNumEntry( "iso_level", options.ISOLevel() ) ); + + options.setCreateTRANS_TBL( c->readBoolEntry( "create TRANS_TBL", options.createTRANS_TBL() ) ); + options.setHideTRANS_TBL( c->readBoolEntry( "hide TRANS_TBL", options.hideTRANS_TBL() ) ); + + // + // We need to use the memeber variables here instead of the access methods + // which do not return the actual value of the member variables but the value + // representing the use in mkisofs (i.e. ISOomitVersionNumbers is also enabled + // if ISOmaxFilenameLength is enabled. + // + options.setISOuntranslatedFilenames( c->readBoolEntry( "untranslated filenames", options.m_ISOuntranslatedFilenames ) ); + options.setISOallow31charFilenames( c->readBoolEntry( "allow 31 character filenames", options.m_ISOallow31charFilenames ) ); + options.setISOmaxFilenameLength( c->readBoolEntry( "max ISO filenames", options.m_ISOmaxFilenameLength ) ); + options.setISOallowPeriodAtBegin( c->readBoolEntry( "allow beginning period", options.m_ISOallowPeriodAtBegin ) ); + options.setISOrelaxedFilenames( c->readBoolEntry( "relaxed filenames", options.m_ISOrelaxedFilenames ) ); + options.setISOomitVersionNumbers( c->readBoolEntry( "omit version numbers", options.m_ISOomitVersionNumbers ) ); + options.setISOnoIsoTranslate( c->readBoolEntry( "no iSO translation", options.m_ISOnoIsoTranslate ) ); + options.setISOallowMultiDot( c->readBoolEntry( "allow multiple dots", options.m_ISOallowMultiDot ) ); + options.setISOallowLowercase( c->readBoolEntry( "allow lowercase filenames", options.m_ISOallowLowercase ) ); + options.setISOomitTrailingPeriod( c->readBoolEntry( "omit trailing period", options.m_ISOomitTrailingPeriod ) ); + + // options.setFollowSymbolicLinks( c->readBoolEntry( "follow symbolic links", options.m_followSymbolicLinks ) ); + + options.setJolietLong( c->readBoolEntry( "joliet long", options.jolietLong() ) ); + + options.setDoNotCacheInodes( c->readBoolEntry( "do not cache inodes", options.doNotCacheInodes() ) ); + + QString w = c->readEntry( "white_space_treatment", "noChange" ); + if( w == "replace" ) + options.setWhiteSpaceTreatment( replace ); + else if( w == "strip" ) + options.setWhiteSpaceTreatment( strip ); + else if( w == "extended" ) + options.setWhiteSpaceTreatment( extended ); + else + options.setWhiteSpaceTreatment( noChange ); + + options.setWhiteSpaceTreatmentReplaceString( c->readEntry( "whitespace replace string", options.whiteSpaceTreatmentReplaceString() ) ); + + options.setDiscardSymlinks( c->readBoolEntry("discard symlinks", options.discardSymlinks() ) ); + options.setDiscardBrokenSymlinks( c->readBoolEntry("discard broken symlinks", options.discardBrokenSymlinks() ) ); + + options.setPreserveFilePermissions( c->readBoolEntry( "preserve file permissions", options.preserveFilePermissions() ) ); + + return options; +} + + +K3bIsoOptions K3bIsoOptions::defaults() +{ + // let the constructor create defaults + return K3bIsoOptions(); +} diff --git a/libk3b/projects/datacd/k3bisooptions.h b/libk3b/projects/datacd/k3bisooptions.h new file mode 100644 index 0000000..254c998 --- /dev/null +++ b/libk3b/projects/datacd/k3bisooptions.h @@ -0,0 +1,183 @@ +/* + * + * $Id: k3bisooptions.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_ISO_OPTIONS_H +#define K3B_ISO_OPTIONS_H + +#include <qstring.h> +#include "k3b_export.h" + +class KConfigBase; + + +class LIBK3B_EXPORT K3bIsoOptions +{ + public: + K3bIsoOptions(); + + bool forceInputCharset() const { return m_bForceInputCharset; } + const QString& inputCharset() const { return m_inputCharset; } + + void setForceInputCharset( bool b ) { m_bForceInputCharset = b; } + void setInputCharset( const QString& cs ) { m_inputCharset = cs; } + + + // -- mkisofs-options ---------------------------------------------------------------------- + bool createRockRidge() const { return m_createRockRidge; } + bool createJoliet() const { return m_createJoliet; } + bool createUdf() const { return m_createUdf; } + bool ISOallowLowercase() const { return m_ISOallowLowercase || ISOuntranslatedFilenames(); } + bool ISOallowPeriodAtBegin() const { return m_ISOallowPeriodAtBegin || ISOuntranslatedFilenames(); } + bool ISOallow31charFilenames() const { return m_ISOallow31charFilenames || ISOmaxFilenameLength() || ISOuntranslatedFilenames(); } + bool ISOomitVersionNumbers() const { return m_ISOomitVersionNumbers || ISOmaxFilenameLength(); } + bool ISOomitTrailingPeriod() const { return m_ISOomitTrailingPeriod || ISOuntranslatedFilenames(); } + bool ISOmaxFilenameLength() const { return m_ISOmaxFilenameLength || ISOuntranslatedFilenames(); } + bool ISOrelaxedFilenames() const { return m_ISOrelaxedFilenames || ISOuntranslatedFilenames(); } + bool ISOnoIsoTranslate() const { return m_ISOnoIsoTranslate; } + bool ISOallowMultiDot() const { return m_ISOallowMultiDot || ISOuntranslatedFilenames(); } + bool ISOuntranslatedFilenames() const { return m_ISOuntranslatedFilenames; } + bool followSymbolicLinks() const { return m_followSymbolicLinks; } + bool createTRANS_TBL() const { return m_createTRANS_TBL; } + bool hideTRANS_TBL() const { return m_hideTRANS_TBL; } + bool jolietLong() const { return m_jolietLong; } + + bool preserveFilePermissions() const { return m_preserveFilePermissions; } + + int ISOLevel() const { return m_isoLevel; } + const QString& systemId() const { return m_systemId; } + const QString& applicationID() const { return m_applicationID; } + const QString& volumeID() const { return m_volumeID; } + const QString& volumeSetId() const { return m_volumeSetId; } + int volumeSetSize() const { return m_volumeSetSize; } + int volumeSetNumber() const { return m_volumeSetNumber; } + const QString& publisher() const { return m_publisher; } + const QString& preparer() const { return m_preparer; } + const QString& abstractFile() const { return m_abstractFile; } + const QString& copyrightFile() const { return m_copyrightFile; } + const QString& bibliographFile() const { return m_bibliographFile; } + + void setCreateRockRidge( bool b ) { m_createRockRidge = b; } + void setCreateJoliet( bool b ) { m_createJoliet = b; } + void setCreateUdf( bool b ) { m_createUdf = b; } + void setISOallowLowercase( bool b ) { m_ISOallowLowercase = b; } + void setISOallowPeriodAtBegin( bool b ) { m_ISOallowPeriodAtBegin = b; } + void setISOallow31charFilenames( bool b ) { m_ISOallow31charFilenames = b; } + void setISOomitVersionNumbers( bool b ) { m_ISOomitVersionNumbers = b; } + void setISOomitTrailingPeriod( bool b ) { m_ISOomitTrailingPeriod = b; } + void setISOmaxFilenameLength( bool b ) { m_ISOmaxFilenameLength = b; } + void setISOrelaxedFilenames( bool b ) { m_ISOrelaxedFilenames = b; } + void setISOnoIsoTranslate( bool b ) { m_ISOnoIsoTranslate = b; } + void setISOallowMultiDot( bool b ) { m_ISOallowMultiDot = b; } + void setISOuntranslatedFilenames( bool b ) { m_ISOuntranslatedFilenames = b; } + void setFollowSymbolicLinks( bool b ) { m_followSymbolicLinks = b; } + void setCreateTRANS_TBL( bool b ) { m_createTRANS_TBL = b; } + void setHideTRANS_TBL( bool b ) { m_hideTRANS_TBL = b; } + void setJolietLong( bool b ) { m_jolietLong = b; } + + void setISOLevel( int i ) { m_isoLevel = i; } + void setSystemId( const QString& s ) { m_systemId = s; } + void setApplicationID( const QString& s ) { m_applicationID = s; } + + /** + * Set the filesystems volume id. + * + * max length for this field is 32 chars. + */ + void setVolumeID( const QString& s ) { m_volumeID = s; } + void setVolumeSetId( const QString& s ) { m_volumeSetId = s; } + void setVolumeSetSize( int size ) { m_volumeSetSize = size; } + void setVolumeSetNumber( int n ) { m_volumeSetNumber = n; } + void setPublisher( const QString& s ) { m_publisher = s; } + void setPreparer( const QString& s ) { m_preparer = s; } + void setAbstractFile( const QString& s ) { m_abstractFile = s; } + void setCoprightFile( const QString& s ) { m_copyrightFile = s; } + void setBibliographFile( const QString& s ) { m_bibliographFile = s; } + + void setPreserveFilePermissions( bool b ) { m_preserveFilePermissions = b; } + // ----------------------------------------------------------------- mkisofs-options ----------- + + enum whiteSpaceTreatments { noChange = 0, replace = 1, strip = 2, extended = 3 }; + + void setWhiteSpaceTreatment( int i ) { m_whiteSpaceTreatment = i; } + int whiteSpaceTreatment() const { return m_whiteSpaceTreatment; } + const QString& whiteSpaceTreatmentReplaceString() const { return m_whiteSpaceTreatmentReplaceString; } + void setWhiteSpaceTreatmentReplaceString( const QString& s ) { m_whiteSpaceTreatmentReplaceString = s; } + + bool discardSymlinks() const { return m_discardSymlinks; } + void setDiscardSymlinks( bool b ) { m_discardSymlinks = b; } + + bool discardBrokenSymlinks() const { return m_discardBrokenSymlinks; } + void setDiscardBrokenSymlinks( bool b ) { m_discardBrokenSymlinks = b; } + + bool doNotCacheInodes() const { return m_doNotCacheInodes; } + void setDoNotCacheInodes( bool b ) { m_doNotCacheInodes = b; } + + void save( KConfigBase* c, bool saveVolumeDesc = true ); + + static K3bIsoOptions load( KConfigBase* c, bool loadVolumeDesc = true ); + static K3bIsoOptions defaults(); + + private: + // volume descriptor + QString m_volumeID; + QString m_applicationID; + QString m_preparer; + QString m_publisher; + QString m_systemId; + QString m_volumeSetId; + QString m_abstractFile; + QString m_copyrightFile; + QString m_bibliographFile; + + int m_volumeSetSize; + int m_volumeSetNumber; + + bool m_bForceInputCharset; + QString m_inputCharset; + + // mkisofs options ------------------------------------- + bool m_createRockRidge; // -r or -R + bool m_createJoliet; // -J + bool m_createUdf; // -udf + bool m_ISOallowLowercase; // -allow-lowercase + bool m_ISOallowPeriodAtBegin; // -L + bool m_ISOallow31charFilenames; // -I + bool m_ISOomitVersionNumbers; // -N + bool m_ISOomitTrailingPeriod; // -d + bool m_ISOmaxFilenameLength; // -max-iso9660-filenames (forces -N) + bool m_ISOrelaxedFilenames; // -relaxed-filenames + bool m_ISOnoIsoTranslate; // -no-iso-translate + bool m_ISOallowMultiDot; // -allow-multidot + bool m_ISOuntranslatedFilenames; // -U (forces -d, -I, -L, -N, -relaxed-filenames, -allow-lowercase, -allow-multidot, -no-iso-translate) + bool m_followSymbolicLinks; // -f + bool m_createTRANS_TBL; // -T + bool m_hideTRANS_TBL; // -hide-joliet-trans-tbl + + bool m_preserveFilePermissions; // if true -R instead of -r is used + bool m_jolietLong; + + bool m_doNotCacheInodes; + + int m_isoLevel; + + + int m_whiteSpaceTreatment; + QString m_whiteSpaceTreatmentReplaceString; + + bool m_discardSymlinks; + bool m_discardBrokenSymlinks; +}; + +#endif diff --git a/libk3b/projects/datacd/k3bmkisofshandler.cpp b/libk3b/projects/datacd/k3bmkisofshandler.cpp new file mode 100644 index 0000000..a3579ec --- /dev/null +++ b/libk3b/projects/datacd/k3bmkisofshandler.cpp @@ -0,0 +1,150 @@ +/* + * + * $Id: k3bmkisofshandler.cpp 802340 2008-04-29 07:43:07Z 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 "k3bmkisofshandler.h" + +#include <k3bexternalbinmanager.h> +#include <k3bcore.h> +#include <k3bjob.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <cmath> + + + +class K3bMkisofsHandler::Private +{ +public: + const K3bExternalBin* mkisofsBin; + double firstProgressValue; + bool readError; +}; + + +K3bMkisofsHandler::K3bMkisofsHandler() +{ + d = new Private; + d->mkisofsBin = 0; +} + + +K3bMkisofsHandler::~K3bMkisofsHandler() +{ + delete d; +} + + +bool K3bMkisofsHandler::mkisofsReadError() const +{ + return d->readError; +} + + +const K3bExternalBin* K3bMkisofsHandler::initMkisofs() +{ + d->mkisofsBin = k3bcore->externalBinManager()->binObject( "mkisofs" ); + + if( d->mkisofsBin ) { + if( !d->mkisofsBin->copyright.isEmpty() ) + handleMkisofsInfoMessage( i18n("Using %1 %2 - Copyright (C) %3") + .arg("mkisofs").arg(d->mkisofsBin->version).arg(d->mkisofsBin->copyright), + K3bJob::INFO ); + + d->firstProgressValue = -1; + d->readError = false; + } + else { + kdDebug() << "(K3bMkisofsHandler) could not find mkisofs executable" << endl; + handleMkisofsInfoMessage( i18n("Mkisofs executable not found."), K3bJob::ERROR ); + } + + return d->mkisofsBin; +} + + +void K3bMkisofsHandler::parseMkisofsOutput( const QString& line ) +{ + if( !line.isEmpty() ) { + if( line.startsWith( d->mkisofsBin->path ) ) { + // error or warning + QString errorLine = line.mid( d->mkisofsBin->path.length() + 2 ); + if( errorLine.startsWith( "Input/output error. Cannot read from" ) ) { + handleMkisofsInfoMessage( i18n("Read error from file '%1'").arg( errorLine.mid( 38, errorLine.length()-40 ) ), + K3bJob::ERROR ); + d->readError = true; + } + else if( errorLine.startsWith( "Value too large for defined data type" ) ) { + handleMkisofsInfoMessage( i18n("Used version of mkisofs does not have large file support."), K3bJob::ERROR ); + handleMkisofsInfoMessage( i18n("Files bigger than 2 GB cannot be handled."), K3bJob::ERROR ); + d->readError = true; + } + } + else if( line.contains( "done, estimate" ) ) { + int p = parseMkisofsProgress( line ); + if( p != -1 ) + handleMkisofsProgress( p ); + } + else if( line.contains( "extents written" ) ) { + handleMkisofsProgress( 100 ); + } + else if( line.startsWith( "Incorrectly encoded string" ) ) { + handleMkisofsInfoMessage( i18n("Encountered an incorrectly encoded filename '%1'") + .arg(line.section( QRegExp("[\\(\\)]"), 1, 1 )), K3bJob::ERROR ); + handleMkisofsInfoMessage( i18n("This may be caused by a system update which changed the local character set."), K3bJob::ERROR ); + handleMkisofsInfoMessage( i18n("You may use convmv (http://j3e.de/linux/convmv/) to fix the filename encoding."), K3bJob::ERROR ); + d->readError = true; + } + else if( line.endsWith( "has not an allowable size." ) ) { + handleMkisofsInfoMessage( i18n("The boot image has an invalid size."), K3bJob::ERROR ); + d->readError = true; + } + else if( line.endsWith( "has multiple partitions." ) ) { + handleMkisofsInfoMessage( i18n("The boot image contains multiple partitions.."), K3bJob::ERROR ); + handleMkisofsInfoMessage( i18n("A hard-disk boot image has to contain a single partition."), K3bJob::ERROR ); + d->readError = true; + } + else { + kdDebug() << "(mkisofs) " << line << endl; + } + } +} + + +int K3bMkisofsHandler::parseMkisofsProgress( const QString& line ) +{ + // + // in multisession mode mkisofs' progress does not start at 0 but at (X+Y)/X + // where X is the data already on the cd and Y the data to create + // This is not very dramatic but kind or ugly. + // We just save the first emitted progress value and to some math ;) + // + + QString perStr = line; + perStr.truncate( perStr.find('%') ); + bool ok; + double p = perStr.toDouble( &ok ); + if( !ok ) { + kdDebug() << "(K3bMkisofsHandler) Parsing did not work for " << perStr << endl; + return -1; + } + else { + if( d->firstProgressValue < 0 ) + d->firstProgressValue = p; + + return( (int)::ceil( (p - d->firstProgressValue)*100.0/(100.0 - d->firstProgressValue) ) ); + } +} diff --git a/libk3b/projects/datacd/k3bmkisofshandler.h b/libk3b/projects/datacd/k3bmkisofshandler.h new file mode 100644 index 0000000..32576bc --- /dev/null +++ b/libk3b/projects/datacd/k3bmkisofshandler.h @@ -0,0 +1,74 @@ +/* + * + * $Id: k3bmkisofshandler.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_MKISOfS_HANDLER_H_ +#define _K3B_MKISOfS_HANDLER_H_ + +#include <qstring.h> + +class K3bExternalBin; + + +/** + * Derive from this to handle mkisofs. + */ +class K3bMkisofsHandler +{ + public: + K3bMkisofsHandler(); + virtual ~K3bMkisofsHandler(); + + /** + * \return true if there was a read error. + */ + bool mkisofsReadError() const; + + protected: + /** + * Initialize the MkisofsHandler. + * This method emits copyright information and an error message in case mkisofs is not installed + * through handleMkisofsInfoMessage. + * + * \return A mkisofs bin object to be used or 0 if mkisofs is not installed. + */ + const K3bExternalBin* initMkisofs(); + + void parseMkisofsOutput( const QString& line ); + + /** + * Used internally by handleMkisofsOutput. + * May be used in case handleMkisofsOutput is not sufficient. + */ + int parseMkisofsProgress( const QString& line ); + + /** + * Called by handleMkisofsOutput + */ + virtual void handleMkisofsProgress( int ) = 0; + + /** + * Called by handleMkisofsOutput + * + * Uses K3bJob::MessageType + */ + virtual void handleMkisofsInfoMessage( const QString&, int ) = 0; + + private: + class Private; + Private* d; +}; + + +#endif diff --git a/libk3b/projects/datacd/k3bmsinfofetcher.cpp b/libk3b/projects/datacd/k3bmsinfofetcher.cpp new file mode 100644 index 0000000..c30d0ff --- /dev/null +++ b/libk3b/projects/datacd/k3bmsinfofetcher.cpp @@ -0,0 +1,243 @@ +/* + * + * $Id: k3bmsinfofetcher.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 "k3bmsinfofetcher.h" + +#include <k3bexternalbinmanager.h> +#include <k3bdevicemanager.h> +#include <k3bdevicehandler.h> +#include <k3bdevice.h> +#include <k3bcore.h> +#include <k3bglobals.h> +#include <k3biso9660.h> + +#include <klocale.h> +#include <kprocess.h> +#include <kdebug.h> + +#include <qstringlist.h> + + +K3bMsInfoFetcher::K3bMsInfoFetcher( K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bJob( jh, parent, name ), + m_process(0), + m_device(0), + m_dvd(false) +{ +} + + +K3bMsInfoFetcher::~K3bMsInfoFetcher() +{ + delete m_process; +} + + +void K3bMsInfoFetcher::start() +{ + jobStarted(); + + emit infoMessage( i18n("Searching previous session"), K3bJob::INFO ); + + if( !k3bcore->externalBinManager()->foundBin( "cdrecord" ) ) { + kdDebug() << "(K3bMsInfoFetcher) could not find cdrecord executable" << endl; + emit infoMessage( i18n("Could not find %1 executable.").arg("cdrecord"), K3bJob::ERROR ); + jobFinished(false); + return; + } + + if( m_device == 0 ) { + kdDebug() << "(K3bMsInfoFetcher) internal error: No device set!" << endl; + jobFinished(false); + return; + } + + // + // first we try to determine if it is a dvd. If so we need to + // read the info on our own + // + + connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::NG_DISKINFO, m_device ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotMediaDetectionFinished(K3bDevice::DeviceHandler*)) ); +} + + +void K3bMsInfoFetcher::getMsInfo() +{ + delete m_process; + m_process = new KProcess(); + + const K3bExternalBin* bin = 0; + if( m_dvd ) { + // already handled + } + else { + bin = k3bcore->externalBinManager()->binObject( "cdrecord" ); + + if( !bin ) { + emit infoMessage( i18n("Could not find %1 executable.").arg( m_dvd ? "dvdrecord" : "cdrecord" ), ERROR ); + jobFinished(false); + return; + } + + *m_process << bin->path; + + // add the device (e.g. /dev/sg1) + *m_process << QString("dev=%1").arg( K3b::externalBinDeviceParameter(m_device, bin) ); + + *m_process << "-msinfo"; + + // additional user parameters from config + const QStringList& params = bin->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *m_process << *it; + + kdDebug() << "***** " << bin->name() << " parameters:\n"; + const QValueList<QCString>& args = m_process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << flush << endl; + emit debuggingOutput( "msinfo command:", s ); + + + // connect( m_process, SIGNAL(receivedStderr(KProcess*, char*, int)), + // this, SLOT(slotCollectOutput(KProcess*, char*, int)) ); + connect( m_process, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(slotCollectOutput(KProcess*, char*, int)) ); + connect( m_process, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited()) ); + + m_msInfo = QString::null; + m_collectedOutput = QString::null; + m_canceled = false; + + if( !m_process->start( KProcess::NotifyOnExit, KProcess::AllOutput ) ) { + emit infoMessage( i18n("Could not start %1.").arg(bin->name()), K3bJob::ERROR ); + jobFinished(false); + } + } +} + + +void K3bMsInfoFetcher::slotMediaDetectionFinished( K3bDevice::DeviceHandler* h ) +{ + if( h->success() ) { + m_dvd = h->diskInfo().isDvdMedia(); + } + else { + // for now we just default to cd and go on with the detecting + m_dvd = false; + } + + if( m_dvd ) { + if( h->diskInfo().mediaType() & (K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_RW_OVWR) ) { + // get info from iso filesystem + K3bIso9660 iso( m_device, h->toc().last().firstSector().lba() ); + if( iso.open() ) { + unsigned long long nextSession = iso.primaryDescriptor().volumeSpaceSize; + // pad to closest 32K boundary + nextSession += 15; + nextSession /= 16; + nextSession *= 16; + m_msInfo.sprintf( "16,%llu", nextSession ); + + jobFinished( true ); + } + else { + emit infoMessage( i18n("Could not open Iso9660 filesystem in %1.") + .arg( m_device->vendor() + " " + m_device->description() ), ERROR ); + jobFinished( false ); + } + } + else { + unsigned int lastSessionStart, nextWritableAdress; + if( m_device->getNextWritableAdress( lastSessionStart, nextWritableAdress ) ) { + m_msInfo.sprintf( "%u,%u", lastSessionStart+16, nextWritableAdress ); + jobFinished( true ); + } + else { + emit infoMessage( i18n("Could not determine next writable address."), ERROR ); + jobFinished( false ); + } + } + } + else // call cdrecord + getMsInfo(); +} + + +void K3bMsInfoFetcher::slotProcessExited() +{ + if( m_canceled ) + return; + + kdDebug() << "(K3bMsInfoFetcher) msinfo fetched" << endl; + + // now parse the output + QString firstLine = m_collectedOutput.left( m_collectedOutput.find("\n") ); + QStringList list = QStringList::split( ",", firstLine ); + if( list.count() == 2 ) { + bool ok1, ok2; + m_lastSessionStart = list.first().toInt( &ok1 ); + m_nextSessionStart = list[1].toInt( &ok2 ); + if( ok1 && ok2 ) + m_msInfo = firstLine.stripWhiteSpace(); + else + m_msInfo = QString::null; + } + else { + m_msInfo = QString::null; + } + + kdDebug() << "(K3bMsInfoFetcher) msinfo parsed: " << m_msInfo << endl; + + if( m_msInfo.isEmpty() ) { + emit infoMessage( i18n("Could not retrieve multisession information from disk."), K3bJob::ERROR ); + emit infoMessage( i18n("The disk is either empty or not appendable."), K3bJob::ERROR ); + jobFinished(false); + } + else { + jobFinished(true); + } +} + + +void K3bMsInfoFetcher::slotCollectOutput( KProcess*, char* output, int len ) +{ + emit debuggingOutput( "msinfo", QString::fromLocal8Bit( output, len ) ); + + m_collectedOutput += QString::fromLocal8Bit( output, len ); +} + + +void K3bMsInfoFetcher::cancel() +{ + // FIXME: this does not work if the devicehandler is running + + if( m_process ) + if( m_process->isRunning() ) { + m_canceled = true; + m_process->kill(); + emit canceled(); + jobFinished(false); + } +} + + +#include "k3bmsinfofetcher.moc" diff --git a/libk3b/projects/datacd/k3bmsinfofetcher.h b/libk3b/projects/datacd/k3bmsinfofetcher.h new file mode 100644 index 0000000..593664f --- /dev/null +++ b/libk3b/projects/datacd/k3bmsinfofetcher.h @@ -0,0 +1,64 @@ +/* + * + * $Id: k3bmsinfofetcher.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_MSINFO_FETCHER_H +#define K3B_MSINFO_FETCHER_H + +#include <k3bjob.h> + +namespace K3bDevice { + class Device; + class DeviceHandler; +} +class KProcess; + +class K3bMsInfoFetcher : public K3bJob +{ + Q_OBJECT + + public: + K3bMsInfoFetcher( K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bMsInfoFetcher(); + + const QString& msInfo() const { return m_msInfo; } + int lastSessionStart() const { return m_lastSessionStart; } + int nextSessionStart() const { return m_nextSessionStart; } + + public slots: + void start(); + void cancel(); + + void setDevice( K3bDevice::Device* dev ) { m_device = dev; } + + private slots: + void slotProcessExited(); + void slotCollectOutput( KProcess*, char* output, int len ); + void slotMediaDetectionFinished( K3bDevice::DeviceHandler* ); + void getMsInfo(); + + private: + QString m_msInfo; + int m_lastSessionStart; + int m_nextSessionStart; + QString m_collectedOutput; + + KProcess* m_process; + K3bDevice::Device* m_device; + + bool m_canceled; + bool m_dvd; +}; + +#endif diff --git a/libk3b/projects/datacd/k3bsessionimportitem.cpp b/libk3b/projects/datacd/k3bsessionimportitem.cpp new file mode 100644 index 0000000..35f7936 --- /dev/null +++ b/libk3b/projects/datacd/k3bsessionimportitem.cpp @@ -0,0 +1,59 @@ +/* + * + * $Id: k3bsessionimportitem.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 "k3bsessionimportitem.h" +#include "k3bfileitem.h" +#include "k3bdiritem.h" + +#include <k3biso9660.h> + + +K3bSessionImportItem::K3bSessionImportItem( const K3bIso9660File* isoF, K3bDataDoc* doc, K3bDirItem* dir ) + : K3bDataItem( doc, dir ), + m_replaceItem(0), + m_size( isoF->size() ) + +{ + setK3bName( isoF->name() ); + + // add automagically like a qlistviewitem + if( parent() ) + parent()->addDataItem( this ); +} + + +K3bSessionImportItem::K3bSessionImportItem( const K3bSessionImportItem& item ) + : K3bDataItem( item ), + m_replaceItem( item.m_replaceItem ), + m_size( item.m_size ) +{ +} + + +K3bSessionImportItem::~K3bSessionImportItem() +{ + if( m_replaceItem ) + m_replaceItem->setReplacedItemFromOldSession(0); + + // remove this from parentdir + if( parent() ) + parent()->takeDataItem( this ); +} + + +K3bDataItem* K3bSessionImportItem::copy() const +{ + return new K3bSessionImportItem( *this ); +} diff --git a/libk3b/projects/datacd/k3bsessionimportitem.h b/libk3b/projects/datacd/k3bsessionimportitem.h new file mode 100644 index 0000000..33f8124 --- /dev/null +++ b/libk3b/projects/datacd/k3bsessionimportitem.h @@ -0,0 +1,63 @@ +/* + * + * $Id: k3bsessionimportitem.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_SESSION_IMPORT_ITEM_H_ +#define _K3B_SESSION_IMPORT_ITEM_H_ + + +#include "k3bdataitem.h" + + +class K3bDataDoc; +class K3bFileItem; +class K3bDirItem; +class K3bIso9660File; + + +class K3bSessionImportItem : public K3bDataItem +{ + public: + K3bSessionImportItem( const K3bIso9660File*, K3bDataDoc* doc, K3bDirItem* ); + K3bSessionImportItem( const K3bSessionImportItem& ); + ~K3bSessionImportItem(); + + K3bDataItem* copy() const; + + K3bFileItem* replaceItem() const { return m_replaceItem; } + void setReplaceItem( K3bFileItem* item ) { m_replaceItem = item; } + + bool isFile() const { return false; } + bool isFromOldSession() const { return true; } + + bool isRemoveable() const { return false; } + bool isMoveable() const { return false; } + bool isRenameable() const { return false; } + bool isHideable() const { return false; } + bool writeToCd() const { return false; } + + protected: + // the size of an item from an imported session does not depend + // on the value of followSymlinks + /** + * Normally one does not use this method but K3bDataItem::size() + */ + KIO::filesize_t itemSize( bool ) const { return m_size; } + + private: + K3bFileItem* m_replaceItem; + KIO::filesize_t m_size; +}; + +#endif diff --git a/libk3b/projects/datacd/k3bspecialdataitem.h b/libk3b/projects/datacd/k3bspecialdataitem.h new file mode 100644 index 0000000..05005ed --- /dev/null +++ b/libk3b/projects/datacd/k3bspecialdataitem.h @@ -0,0 +1,76 @@ +/* + * + * $Id: k3bspecialdataitem.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 K3BSPECIALDATAITEM_H +#define K3BSPECIALDATAITEM_H + +#include "k3bdataitem.h" +#include "k3bdiritem.h" + +#include <kio/global.h> + +/** + * This can be used to create fake items like the boot catalog + * It's mainly a K3bDataItem where everything has to be set manually + */ +class K3bSpecialDataItem : public K3bDataItem +{ + public: + K3bSpecialDataItem( K3bDataDoc* doc, KIO::filesize_t size, K3bDirItem* parent = 0, const QString& k3bName = QString::null ) + : K3bDataItem( doc, parent ), + m_size( size ) + { + setK3bName( k3bName ); + + // add automagically like a qlistviewitem + if( parent ) + parent->addDataItem( this ); + } + + K3bSpecialDataItem( const K3bSpecialDataItem& item ) + : K3bDataItem( item ), + m_mimeType( item.m_mimeType ), + m_size( item.m_size ) { + } + + ~K3bSpecialDataItem() { + // remove this from parentdir + if( parent() ) + parent()->takeDataItem( this ); + } + + K3bDataItem* copy() const { + return new K3bSpecialDataItem( *this ); + } + + void setMimeType( const QString& s ) { m_mimeType = s; } + const QString& mimeType() const { return m_mimeType; } + + bool isSpecialFile() const { return true; } + + protected: + /** + * Normally one does not use this method but K3bDataItem::size() + */ + KIO::filesize_t itemSize( bool ) const { return m_size; } + + private: + QString m_mimeType; + KIO::filesize_t m_size; +}; + +#endif + diff --git a/libk3b/projects/datadvd/Makefile.am b/libk3b/projects/datadvd/Makefile.am new file mode 100644 index 0000000..99ae10c --- /dev/null +++ b/libk3b/projects/datadvd/Makefile.am @@ -0,0 +1,21 @@ +# we need the ../datacd for the uic generated header files +AM_CPPFLAGS= -I$(srcdir)/../../core \ + -I$(srcdir)/../../../libk3bdevice \ + -I$(srcdir)/../../../src \ + -I$(srcdir)/../../tools \ + -I$(srcdir)/../../jobs \ + -I$(srcdir)/../datacd \ + -I$(srcdir)/.. \ + -I../datacd \ + $(all_includes) + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libdvd.la + +libdvd_la_SOURCES = k3bdvddoc.cpp \ + k3bdvdjob.cpp \ + k3bdvdbooktypejob.cpp + +include_HEADERS = k3bdvddoc.h \ + k3bdvdjob.h diff --git a/libk3b/projects/datadvd/k3bdvdbooktypejob.cpp b/libk3b/projects/datadvd/k3bdvdbooktypejob.cpp new file mode 100644 index 0000000..f703452 --- /dev/null +++ b/libk3b/projects/datadvd/k3bdvdbooktypejob.cpp @@ -0,0 +1,350 @@ +/* + * + * $Id: k3bdvdbooktypejob.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 "k3bdvdbooktypejob.h" + +#include <k3bglobals.h> +#include <k3bprocess.h> +#include <k3bdevice.h> +#include <k3bdeviceglobals.h> +#include <k3bdevicehandler.h> +#include <k3bdiskinfo.h> +#include <k3bexternalbinmanager.h> +#include <k3bcore.h> +#include <k3bversion.h> +#include <k3bglobalsettings.h> + +#include <klocale.h> +#include <kdebug.h> + +#include <qvaluelist.h> +#include <qregexp.h> + +#include <errno.h> +#include <string.h> + + +class K3bDvdBooktypeJob::Private +{ +public: + Private() + : device(0), + process(0), + dvdBooktypeBin(0), + running(false), + forceNoEject(false) { + } + + K3bDevice::Device* device; + K3bProcess* process; + const K3bExternalBin* dvdBooktypeBin; + + bool success; + bool canceled; + bool running; + + bool forceNoEject; + + int foundMediaType; +}; + + +K3bDvdBooktypeJob::K3bDvdBooktypeJob( K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bJob( jh, parent, name ), + m_action(0) +{ + d = new Private; +} + + +K3bDvdBooktypeJob::~K3bDvdBooktypeJob() +{ + delete d->process; + delete d; +} + + +void K3bDvdBooktypeJob::setForceNoEject( bool b ) +{ + d->forceNoEject = b; +} + + +QString K3bDvdBooktypeJob::jobDescription() const +{ + return i18n("Changing DVD Booktype"); // Changing DVD±R(W) Booktype +} + + +QString K3bDvdBooktypeJob::jobDetails() const +{ + return QString::null; +} + + +void K3bDvdBooktypeJob::start() +{ + d->canceled = false; + d->running = true; + + jobStarted(); + + if( !d->device ) { + emit infoMessage( i18n("No device set"), ERROR ); + jobFinished(false); + d->running = false; + return; + } + + // + // In case we want to change the writers default we do not need to wait for a media + // + if( m_action == SET_MEDIA_DVD_ROM || + m_action == SET_MEDIA_DVD_R_W ) { + emit newSubTask( i18n("Waiting for media") ); + if( waitForMedia( d->device, + K3bDevice::STATE_COMPLETE|K3bDevice::STATE_INCOMPLETE|K3bDevice::STATE_EMPTY, + K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_PLUS_R, + i18n("Please insert an empty DVD+R or a DVD+RW medium into drive<p><b>%1 %2 (%3)</b>.") + .arg(d->device->vendor()).arg(d->device->description()).arg(d->device->devicename()) ) == -1 ) { + emit canceled(); + jobFinished(false); + d->running = false; + return; + } + + emit infoMessage( i18n("Checking media..."), INFO ); + emit newTask( i18n("Checking media") ); + + connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::NG_DISKINFO, d->device ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotDeviceHandlerFinished(K3bDevice::DeviceHandler*)) ); + } + else { + // change writer defaults + startBooktypeChange(); + } +} + + +void K3bDvdBooktypeJob::start( K3bDevice::DeviceHandler* dh ) +{ + d->canceled = false; + d->running = true; + + jobStarted(); + + slotDeviceHandlerFinished( dh ); +} + + +void K3bDvdBooktypeJob::cancel() +{ + if( d->running ) { + d->canceled = true; + if( d->process ) + d->process->kill(); + } + else { + kdDebug() << "(K3bDvdBooktypeJob) not running." << endl; + } +} + + +void K3bDvdBooktypeJob::setDevice( K3bDevice::Device* dev ) +{ + d->device = dev; +} + + +void K3bDvdBooktypeJob::slotStderrLine( const QString& line ) +{ + emit debuggingOutput( "dvd+rw-booktype", line ); + // FIXME +} + + +void K3bDvdBooktypeJob::slotProcessFinished( KProcess* p ) +{ + if( d->canceled ) { + emit canceled(); + d->success = false; + } + else if( p->normalExit() ) { + if( p->exitStatus() == 0 ) { + emit infoMessage( i18n("Booktype successfully changed"), K3bJob::SUCCESS ); + d->success = true; + } + else { + emit infoMessage( i18n("%1 returned an unknown error (code %2).").arg(d->dvdBooktypeBin->name()).arg(p->exitStatus()), + K3bJob::ERROR ); + emit infoMessage( i18n("Please send me an email with the last output."), K3bJob::ERROR ); + + d->success = false; + } + } + else { + emit infoMessage( i18n("%1 did not exit cleanly.").arg(d->dvdBooktypeBin->name()), + ERROR ); + d->success = false; + } + + // + // No need to eject the media if we changed the writer's default + // + if( m_action == SET_MEDIA_DVD_ROM || + m_action == SET_MEDIA_DVD_R_W ) { + + if( d->forceNoEject || + !k3bcore->globalSettings()->ejectMedia() ) { + d->running = false; + jobFinished(d->success); + } + else { + emit infoMessage( i18n("Ejecting DVD..."), INFO ); + connect( K3bDevice::eject( d->device ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotEjectingFinished(K3bDevice::DeviceHandler*)) ); + } + } + else { + d->running = false; + jobFinished(d->success); + } +} + + +void K3bDvdBooktypeJob::slotEjectingFinished( K3bDevice::DeviceHandler* dh ) +{ + if( !dh->success() ) + emit infoMessage( i18n("Unable to eject media."), ERROR ); + + d->running = false; + jobFinished(d->success); +} + + +void K3bDvdBooktypeJob::slotDeviceHandlerFinished( K3bDevice::DeviceHandler* dh ) +{ + if( d->canceled ) { + emit canceled(); + d->running = false; + jobFinished(false); + } + + if( dh->success() ) { + + d->foundMediaType = dh->diskInfo().mediaType(); + if( d->foundMediaType == K3bDevice::MEDIA_DVD_PLUS_R ) { + // the media needs to be empty + if( dh->diskInfo().empty() ) + startBooktypeChange(); + else { + emit infoMessage( i18n("Cannot change booktype on non-empty DVD+R media."), ERROR ); + jobFinished(false); + } + } + else if( d->foundMediaType == K3bDevice::MEDIA_DVD_PLUS_RW ) { + startBooktypeChange(); + } + else { + emit infoMessage( i18n("No DVD+R(W) media found."), ERROR ); + jobFinished(false); + } + } + else { + emit infoMessage( i18n("Unable to determine media state."), ERROR ); + d->running = false; + jobFinished(false); + } +} + + +void K3bDvdBooktypeJob::startBooktypeChange() +{ + delete d->process; + d->process = new K3bProcess(); + d->process->setRunPrivileged(true); + d->process->setSuppressEmptyLines(true); + connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStderrLine(const QString&)) ); + connect( d->process, SIGNAL(processExited(KProcess*)), this, SLOT(slotProcessFinished(KProcess*)) ); + + d->dvdBooktypeBin = k3bcore->externalBinManager()->binObject( "dvd+rw-booktype" ); + if( !d->dvdBooktypeBin ) { + emit infoMessage( i18n("Could not find %1 executable.").arg("dvd+rw-booktype"), ERROR ); + d->running = false; + jobFinished(false); + return; + } + + *d->process << d->dvdBooktypeBin; + + switch( m_action ) { + case SET_MEDIA_DVD_ROM: + *d->process << "-dvd-rom-spec" + << "-media"; + break; + case SET_MEDIA_DVD_R_W: + if( d->foundMediaType == K3bDevice::MEDIA_DVD_PLUS_RW ) + *d->process << "-dvd+rw-spec"; + else + *d->process << "-dvd+r-spec"; + *d->process << "-media"; + break; + case SET_UNIT_DVD_ROM_ON_NEW_DVD_R: + *d->process << "-dvd-rom-spec" + << "-unit+r"; + break; + case SET_UNIT_DVD_ROM_ON_NEW_DVD_RW: + *d->process << "-dvd-rom-spec" + << "-unit+rw"; + break; + case SET_UNIT_DVD_R_ON_NEW_DVD_R: + *d->process << "-dvd+r-spec" + << "-unit+r"; + break; + case SET_UNIT_DVD_RW_ON_NEW_DVD_RW: + *d->process << "-dvd+rw-spec" + << "-unit+rw"; + break; + } + + *d->process << d->device->blockDeviceName(); + + kdDebug() << "***** dvd+rw-booktype parameters:\n"; + const QValueList<QCString>& args = d->process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << endl << flush; + emit debuggingOutput( "dvd+rw-booktype command:", s ); + + + if( !d->process->start( KProcess::NotifyOnExit, KProcess::All ) ) { + // something went wrong when starting the program + // it "should" be the executable + emit infoMessage( i18n("Could not start %1.").arg(d->dvdBooktypeBin->name()), K3bJob::ERROR ); + d->running = false; + jobFinished(false); + } + else { + emit newTask( i18n("Changing Booktype") ); + } +} + +#include "k3bdvdbooktypejob.moc" diff --git a/libk3b/projects/datadvd/k3bdvdbooktypejob.h b/libk3b/projects/datadvd/k3bdvdbooktypejob.h new file mode 100644 index 0000000..b9e7e4b --- /dev/null +++ b/libk3b/projects/datadvd/k3bdvdbooktypejob.h @@ -0,0 +1,99 @@ +/* + * + * $Id: k3bdvdbooktypejob.h 619556 2007-01-03 17:38:12Z trueg $ + * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + +#ifndef _K3B_DVD_BOOKTYPE_JOB_H_ +#define _K3B_DVD_BOOKTYPE_JOB_H_ + + +#include <k3bjob.h> + + +class KProcess; +namespace K3bDevice { + class Device; + class DeviceHandler; +} + + +/** + * This job can change the compatibility bit of DVD+R(W) media + * with supported dvd writers. + */ +class K3bDvdBooktypeJob : public K3bJob +{ + Q_OBJECT + + public: + K3bDvdBooktypeJob( K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bDvdBooktypeJob(); + + QString jobDescription() const; + QString jobDetails() const; + + /** + * @list SET_MEDIA_DVD_ROM Change media identification on current media to DVD-ROM. + * @list SET_MEDIA_DVD_R_W Change media identification on current media to DVD+R or DVD+RW. + * @list SET_UNIT_DVD_ROM_ON_NEW_DVD_R Set the drive to write DVD-ROM specification on future written DVD+R discs. + * @list SET_UNIT_DVD_ROM_ON_NEW_DVD_RW Set the drive to write DVD-ROM specification on future written DVD+RW discs. + * @list SET_UNIT_DVD_R_ON_NEW_DVD_R Set the drive to write DVD+R specification on future written DVD+R discs. + * @list SET_UNIT_DVD_RW_ON_NEW_DVD_RW Set the drive to write DVD+RW specification on future written DVD+RW discs. + */ + enum Action { + SET_MEDIA_DVD_ROM, + SET_MEDIA_DVD_R_W, + SET_UNIT_DVD_ROM_ON_NEW_DVD_R, + SET_UNIT_DVD_ROM_ON_NEW_DVD_RW, + SET_UNIT_DVD_R_ON_NEW_DVD_R, + SET_UNIT_DVD_RW_ON_NEW_DVD_RW + }; + + public slots: + void start(); + + /** + * The devicehandler needs to have a valid NgDiskInfo + * Use this to prevent the job from searching a media. + */ + void start( K3bDevice::DeviceHandler* ); + + void cancel(); + + void setDevice( K3bDevice::Device* ); + + void setAction( int a ) { m_action = a; } + + /** + * If set true the job ignores the global K3b setting + * and does not eject the CD-RW after finishing + */ + void setForceNoEject( bool ); + + private slots: + void slotStderrLine( const QString& ); + void slotProcessFinished( KProcess* ); + void slotDeviceHandlerFinished( K3bDevice::DeviceHandler* ); + void slotEjectingFinished( K3bDevice::DeviceHandler* ); + + private: + void startBooktypeChange(); + + int m_action; + + class Private; + Private* d; +}; + + +#endif diff --git a/libk3b/projects/datadvd/k3bdvddoc.cpp b/libk3b/projects/datadvd/k3bdvddoc.cpp new file mode 100644 index 0000000..4ab8b9f --- /dev/null +++ b/libk3b/projects/datadvd/k3bdvddoc.cpp @@ -0,0 +1,39 @@ +/* + * + * $Id: k3bdvddoc.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 "k3bdvddoc.h" +#include "k3bdvdjob.h" + +#include <k3bisooptions.h> + +#include <kconfig.h> + + +K3bDvdDoc::K3bDvdDoc( QObject* parent ) + : K3bDataDoc( parent ) +{ +} + +K3bDvdDoc::~K3bDvdDoc() +{ +} + +K3bBurnJob* K3bDvdDoc::newBurnJob( K3bJobHandler* hdl, QObject* parent ) +{ + return new K3bDvdJob( this, hdl, parent ); +} + +//#include "k3bdvddoc.moc" diff --git a/libk3b/projects/datadvd/k3bdvddoc.h b/libk3b/projects/datadvd/k3bdvddoc.h new file mode 100644 index 0000000..03b5c3d --- /dev/null +++ b/libk3b/projects/datadvd/k3bdvddoc.h @@ -0,0 +1,37 @@ +/* + * + * $Id: k3bdvddoc.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_DVDDOC_H_ +#define _K3B_DVDDOC_H_ + +#include <k3bdatadoc.h> +#include "k3b_export.h" +class KConfig; + +class LIBK3B_EXPORT K3bDvdDoc : public K3bDataDoc +{ + public: + K3bDvdDoc( QObject* parent = 0 ); + virtual ~K3bDvdDoc(); + + virtual int type() const { return DVD; } + + virtual K3bBurnJob* newBurnJob( K3bJobHandler* hdl, QObject* parent = 0 ); + + protected: + virtual QString typeString() const { return "dvd"; } +}; + +#endif diff --git a/libk3b/projects/datadvd/k3bdvdjob.cpp b/libk3b/projects/datadvd/k3bdvdjob.cpp new file mode 100644 index 0000000..3cd1521 --- /dev/null +++ b/libk3b/projects/datadvd/k3bdvdjob.cpp @@ -0,0 +1,344 @@ +/* + * + * $Id: k3bdvdjob.cpp 690187 2007-07-20 09:18:03Z trueg $ + * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + + +#include "k3bdvdjob.h" +#include "k3bdvddoc.h" + +#include <k3bcore.h> +#include <k3bisoimager.h> +#include <k3bgrowisofswriter.h> +#include <k3bglobals.h> +#include <k3bdevice.h> +#include <k3bdevicehandler.h> +#include <k3bdiskinfo.h> +#include <k3bdeviceglobals.h> +#include <k3bglobalsettings.h> +#include <k3biso9660.h> +#include <k3bmsinfofetcher.h> + +#include <klocale.h> +#include <kapplication.h> + + +class K3bDvdJob::Private +{ +public: +}; + + +K3bDvdJob::K3bDvdJob( K3bDataDoc* doc, K3bJobHandler* hdl, QObject* parent ) + : K3bDataJob( doc, hdl, parent ), + m_doc( doc ) +{ + d = new Private(); +} + + +K3bDvdJob::~K3bDvdJob() +{ + delete d; +} + + +void K3bDvdJob::prepareData() +{ +} + + +bool K3bDvdJob::prepareWriterJob() +{ + K3bGrowisofsWriter* writer = new K3bGrowisofsWriter( m_doc->burner(), this, this ); + + // these do only make sense with DVD-R(W) + writer->setSimulate( m_doc->dummy() ); + writer->setBurnSpeed( m_doc->speed() ); + + // Andy said incremental sequential is the default mode and it seems uses have more problems with DAO anyway + // BUT: I also had a report that incremental sequential produced unreadable media! + if( m_doc->writingMode() == K3b::DAO ) +// || ( m_doc->writingMode() == K3b::WRITING_MODE_AUTO && +// usedMultiSessionMode() == K3bDataDoc::NONE ) ) + writer->setWritingMode( K3b::DAO ); + + writer->setMultiSession( usedMultiSessionMode() == K3bDataDoc::CONTINUE || + usedMultiSessionMode() == K3bDataDoc::FINISH ); + + writer->setCloseDvd( usedMultiSessionMode() == K3bDataDoc::NONE || + usedMultiSessionMode() == K3bDataDoc::FINISH ); + + writer->setImageToWrite( QString::null ); // read from stdin + writer->setTrackSize( m_isoImager->size() ); + + if( usedMultiSessionMode() != K3bDataDoc::NONE ) { + // + // growisofs wants a valid -C parameter for multisession, so we get it from the + // K3bMsInfoFetcher (see K3bDataJob::slotMsInfoFetched) + // + writer->setMultiSessionInfo( m_msInfoFetcher->msInfo() ); + } + + setWriterJob( writer ); + + return true; +} + + +void K3bDvdJob::determineMultiSessionMode() +{ + int m = requestMedia( K3bDevice::STATE_INCOMPLETE|K3bDevice::STATE_EMPTY ); + + if( m < 0 ) { + cancel(); + } + else { + connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::NG_DISKINFO, m_doc->burner() ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotDetermineMultiSessionMode(K3bDevice::DeviceHandler*)) ); + } +} + + +K3bDataDoc::MultiSessionMode K3bDvdJob::getMultiSessionMode( const K3bDevice::DiskInfo& info ) +{ + K3bDataDoc::MultiSessionMode mode = K3bDataDoc::NONE; + + if( info.mediaType() & (K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_RW_OVWR) ) { + // + // we need to handle DVD+RW and DVD-RW overwrite media differently since remainingSize() is not valid + // in both cases + // Since one never closes a DVD+RW we only differ between CONTINUE and START + // + + // try to check the filesystem size + K3bIso9660 iso( m_doc->burner() ); + if( iso.open() && info.capacity() - iso.primaryDescriptor().volumeSpaceSize >= m_doc->burningLength() ) { + mode = K3bDataDoc::CONTINUE; + } + else { + mode = K3bDataDoc::START; + } + } + else if( info.appendable() ) { + // + // 3 cases: + // 1. the project does not fit -> no multisession (resulting in asking for another media above) + // 2. the project does fit and fills up the CD (No new sessions after the 4GB boundary) -> finish multisession + // 3. the project does fit and does not fill up the CD -> continue multisession + // + if( m_doc->size() > info.remainingSize().mode1Bytes() && !m_doc->sessionImported() ) + mode = K3bDataDoc::NONE; + else if( info.size() + m_doc->burningLength() + 11400 /* used size + project size + session gap */ > 2097152 /* 4 GB */ ) + mode = K3bDataDoc::FINISH; + else + mode = K3bDataDoc::CONTINUE; + } + else { + // + // We only close the DVD if the project fills it beyond the 4GB boundary + // + if( info.size() + m_doc->burningLength() + 11400 /* used size + project size + session gap */ > 2097152 /* 4 GB */ || + m_doc->writingMode() == K3b::DAO ) + mode = K3bDataDoc::NONE; + else + mode = K3bDataDoc::START; + } + + return mode; +} + + +int K3bDvdJob::requestMedia( int state ) +{ + int mt = 0; + if( m_doc->writingMode() == K3b::WRITING_MODE_RES_OVWR ) // we treat DVD+R(W) as restricted overwrite media + mt = K3bDevice::MEDIA_DVD_RW_OVWR|K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_PLUS_R; + else + mt = K3bDevice::MEDIA_WRITABLE_DVD; + + // double layer media + // in case overburn is enabled we allow some made up max size + // before we force a DL medium + if( m_doc->size() > 4700372992LL ) { + if( !k3bcore->globalSettings()->overburn() || + m_doc->size() > 4900000000LL ) { + mt = K3bDevice::MEDIA_WRITABLE_DVD_DL; + } + } + + return waitForMedia( m_doc->burner(), + state, + mt ); +} + + +bool K3bDvdJob::waitForMedium() +{ + emit infoMessage( i18n("Waiting for media") + "...", INFO ); + + int foundMedium = requestMedia( usedMultiSessionMode() == K3bDataDoc::CONTINUE || + usedMultiSessionMode() == K3bDataDoc::FINISH ? + K3bDevice::STATE_INCOMPLETE : + K3bDevice::STATE_EMPTY ); + + if( foundMedium < 0 || hasBeenCanceled() ) { + return false; + } + + if( foundMedium == 0 ) { + emit infoMessage( i18n("Forced by user. Growisofs will be called without further tests."), INFO ); + } + + else { + // ------------------------------- + // DVD Plus + // ------------------------------- + if( foundMedium & K3bDevice::MEDIA_DVD_PLUS_ALL ) { + if( m_doc->dummy() ) { + if( !questionYesNo( i18n("K3b does not support simulation with DVD+R(W) media. " + "Do you really want to continue? The media will be written " + "for real."), + i18n("No Simulation with DVD+R(W)") ) ) { + return false; + } + + m_doc->setDummy( false ); + emit newTask( i18n("Writing") ); + } + + if( m_doc->writingMode() != K3b::WRITING_MODE_AUTO && m_doc->writingMode() != K3b::WRITING_MODE_RES_OVWR ) + emit infoMessage( i18n("Writing mode ignored when writing DVD+R(W) media."), INFO ); + + if( foundMedium & K3bDevice::MEDIA_DVD_PLUS_RW ) { + if( usedMultiSessionMode() == K3bDataDoc::NONE || + usedMultiSessionMode() == K3bDataDoc::START ) + emit infoMessage( i18n("Writing DVD+RW."), INFO ); + else + emit infoMessage( i18n("Growing ISO9660 filesystem on DVD+RW."), INFO ); + } + else if( foundMedium & K3bDevice::MEDIA_DVD_PLUS_R_DL ) + emit infoMessage( i18n("Writing Double Layer DVD+R."), INFO ); + else + emit infoMessage( i18n("Writing DVD+R."), INFO ); + } + + // ------------------------------- + // DVD Minus + // ------------------------------- + else { + if( m_doc->dummy() && !m_doc->burner()->dvdMinusTestwrite() ) { + if( !questionYesNo( i18n("Your writer (%1 %2) does not support simulation with DVD-R(W) media. " + "Do you really want to continue? The media will be written " + "for real.") + .arg(m_doc->burner()->vendor()) + .arg(m_doc->burner()->description()), + i18n("No Simulation with DVD-R(W)") ) ) { + return false; + } + + m_doc->setDummy( false ); + } + + // RESTRICTED OVERWRITE + // -------------------- + if( foundMedium & K3bDevice::MEDIA_DVD_RW_OVWR ) { + if( usedMultiSessionMode() == K3bDataDoc::NONE || + usedMultiSessionMode() == K3bDataDoc::START ) + emit infoMessage( i18n("Writing DVD-RW in restricted overwrite mode."), INFO ); + else + emit infoMessage( i18n("Growing ISO9660 filesystem on DVD-RW in restricted overwrite mode."), INFO ); + } + + // NORMAL + // ------ + else { + + // FIXME: DVD-R DL jump and stuff + + if( m_doc->writingMode() == K3b::DAO ) + // || ( m_doc->writingMode() == K3b::WRITING_MODE_AUTO && +// usedMultiSessionMode() == K3bDataDoc::NONE ) ) + emit infoMessage( i18n("Writing %1 in DAO mode.").arg( K3bDevice::mediaTypeString(foundMedium, true) ), INFO ); + + else { + // check if the writer supports writing sequential and thus multisession (on -1 the burner cannot handle + // features and we simply ignore it and hope for the best) + if( m_doc->burner()->featureCurrent( K3bDevice::FEATURE_INCREMENTAL_STREAMING_WRITABLE ) == 0 ) { + if( !questionYesNo( i18n("Your writer (%1 %2) does not support Incremental Streaming with %3 " + "media. Multisession will not be possible. Continue anyway?") + .arg(m_doc->burner()->vendor()) + .arg(m_doc->burner()->description()) + .arg( K3bDevice::mediaTypeString(foundMedium, true) ), + i18n("No Incremental Streaming") ) ) { + return false; + } + else { + emit infoMessage( i18n("Writing %1 in DAO mode.").arg( K3bDevice::mediaTypeString(foundMedium, true) ), INFO ); + } + } + else { + if( !(foundMedium & (K3bDevice::MEDIA_DVD_RW|K3bDevice::MEDIA_DVD_RW_OVWR|K3bDevice::MEDIA_DVD_RW_SEQ)) && + m_doc->writingMode() == K3b::WRITING_MODE_RES_OVWR ) + emit infoMessage( i18n("Restricted Overwrite is not possible with DVD-R media."), INFO ); + + emit infoMessage( i18n("Writing %1 in incremental mode.").arg( K3bDevice::mediaTypeString(foundMedium, true) ), INFO ); + } + } + } + } + } + + return true; +} + + +QString K3bDvdJob::jobDescription() const +{ + if( m_doc->onlyCreateImages() ) { + return i18n("Creating Data Image File"); + } + else if( m_doc->multiSessionMode() == K3bDataDoc::NONE || + m_doc->multiSessionMode() == K3bDataDoc::AUTO ) { + return i18n("Writing Data DVD") + + ( m_doc->isoOptions().volumeID().isEmpty() + ? QString::null + : QString( " (%1)" ).arg(m_doc->isoOptions().volumeID()) ); + } + else { + return i18n("Writing Multisession DVD") + + ( m_doc->isoOptions().volumeID().isEmpty() + ? QString::null + : QString( " (%1)" ).arg(m_doc->isoOptions().volumeID()) ); + } +} + + +QString K3bDvdJob::jobDetails() const +{ + if( m_doc->copies() > 1 && + !m_doc->dummy() && + !(m_doc->multiSessionMode() == K3bDataDoc::CONTINUE || + m_doc->multiSessionMode() == K3bDataDoc::FINISH) ) + return i18n("ISO9660 Filesystem (Size: %1) - %n copy", + "ISO9660 Filesystem (Size: %1) - %n copies", + m_doc->copies()) + .arg(KIO::convertSize( m_doc->size() )); + else + return i18n("ISO9660 Filesystem (Size: %1)") + .arg(KIO::convertSize( m_doc->size() )); +} + +#include "k3bdvdjob.moc" diff --git a/libk3b/projects/datadvd/k3bdvdjob.h b/libk3b/projects/datadvd/k3bdvdjob.h new file mode 100644 index 0000000..381bc1d --- /dev/null +++ b/libk3b/projects/datadvd/k3bdvdjob.h @@ -0,0 +1,57 @@ +/* + * + * $Id: k3bdvdjob.h 619556 2007-01-03 17:38:12Z trueg $ + * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + + +#ifndef _K3B_DVD_JOB_H_ +#define _K3B_DVD_JOB_H_ + +#include <k3bdatajob.h> + +#include <qfile.h> + +class K3bDataDoc; +class K3bGrowisofsWriter; + + +class K3bDvdJob : public K3bDataJob +{ + Q_OBJECT + + public: + /** + * To be more flexible we allow writing of any data doc + */ + K3bDvdJob( K3bDataDoc*, K3bJobHandler*, QObject* parent = 0 ); + virtual ~K3bDvdJob(); + + virtual QString jobDescription() const; + virtual QString jobDetails() const; + + protected: + void prepareData(); + virtual bool prepareWriterJob(); + void determineMultiSessionMode(); + K3bDataDoc::MultiSessionMode getMultiSessionMode( const K3bDevice::DiskInfo& ); + bool waitForMedium(); + int requestMedia( int state ); + + private: + K3bDataDoc* m_doc; + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/datadvd/k3bdvdview.cpp b/libk3b/projects/datadvd/k3bdvdview.cpp new file mode 100644 index 0000000..512ec4b --- /dev/null +++ b/libk3b/projects/datadvd/k3bdvdview.cpp @@ -0,0 +1,48 @@ +/* + * + * $Id: k3bdvdview.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 "k3bdvdview.h" +#include "k3bdvddoc.h" +#include "k3bdvdburndialog.h" +#include <k3bfillstatusdisplay.h> +#include <k3bdatafileview.h> + +#include <klocale.h> + + +K3bDvdView::K3bDvdView( K3bDvdDoc* doc, QWidget *parent, const char *name ) + : K3bDataView( doc, parent, name ) +{ + m_doc = doc; + + fillStatusDisplay()->showDvdSizes(true); + + m_dataFileView->setNoItemText( i18n("Use drag'n'drop to add files and directories to the project.\n" + "To remove or rename files use the context menu.\n" + "After that press the burn button to write the DVD.") ); +} + + +K3bDvdView::~K3bDvdView() +{ +} + + +K3bProjectBurnDialog* K3bDvdView::newBurnDialog( QWidget* parent, const char* name ) +{ + return new K3bDvdBurnDialog( m_doc, parent, name, true ); +} + +#include "k3bdvdview.moc" diff --git a/libk3b/projects/datadvd/k3bdvdview.h b/libk3b/projects/datadvd/k3bdvdview.h new file mode 100644 index 0000000..d9f30f3 --- /dev/null +++ b/libk3b/projects/datadvd/k3bdvdview.h @@ -0,0 +1,40 @@ +/* + * + * $Id: k3bdvdview.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_DVDVIEW_H_ +#define _K3B_DVDVIEW_H_ + +#include <k3bdataview.h> + +class K3bDvdDoc; + + +class K3bDvdView : public K3bDataView +{ + Q_OBJECT + + public: + K3bDvdView( K3bDvdDoc* doc, QWidget *parent = 0, const char *name = 0 ); + ~K3bDvdView(); + + protected: + virtual K3bProjectBurnDialog* newBurnDialog( QWidget* parent = 0, const char* name = 0 ); + + private: + K3bDvdDoc* m_doc; +}; + +#endif diff --git a/libk3b/projects/k3babstractwriter.cpp b/libk3b/projects/k3babstractwriter.cpp new file mode 100644 index 0000000..df22bc3 --- /dev/null +++ b/libk3b/projects/k3babstractwriter.cpp @@ -0,0 +1,96 @@ +/* + * + * $Id: k3babstractwriter.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 "k3babstractwriter.h" + +#include <k3bcore.h> +#include <k3bdevicemanager.h> +#include <k3bdevicehandler.h> +#include <k3bglobalsettings.h> + +#include <klocale.h> +#include <kglobal.h> + + + +K3bAbstractWriter::K3bAbstractWriter( K3bDevice::Device* dev, K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bJob( jh, parent, name ), + m_burnDevice(dev), + m_burnSpeed(1), + m_simulate(false), + m_sourceUnreadable(false) +{ +} + + +K3bAbstractWriter::~K3bAbstractWriter() +{ +} + + +K3bDevice::Device* K3bAbstractWriter::burnDevice() +{ + if( m_burnDevice ) + return m_burnDevice; + else + return k3bcore->deviceManager()->burningDevices().getFirst(); +} + + +void K3bAbstractWriter::cancel() +{ + if( burnDevice() ) { + // we need to unlock the writer because cdrecord locked it while writing + emit infoMessage( i18n("Unlocking drive..."), INFO ); + connect( K3bDevice::unblock( burnDevice() ), SIGNAL(finished(bool)), + this, SLOT(slotUnblockWhileCancellationFinished(bool)) ); + } + else { + emit canceled(); + jobFinished(false); + } +} + + +void K3bAbstractWriter::slotUnblockWhileCancellationFinished( bool success ) +{ + if( !success ) + emit infoMessage( i18n("Could not unlock CD drive."), K3bJob::ERROR ); // FIXME: simply "drive", not "CD drive" + + if( k3bcore->globalSettings()->ejectMedia() ) { + emit newSubTask( i18n("Ejecting CD") ); // FIXME: "media" instead of "CD" + connect( K3bDevice::eject( burnDevice() ), SIGNAL(finished(bool)), + this, SLOT(slotEjectWhileCancellationFinished(bool)) ); + } + else { + emit canceled(); + jobFinished( false ); + } +} + + +void K3bAbstractWriter::slotEjectWhileCancellationFinished( bool success ) +{ + if( !success ) { + emit infoMessage( i18n("Unable to eject media."), K3bJob::ERROR ); + } + + emit canceled(); + jobFinished( false ); +} + + +#include "k3babstractwriter.moc" diff --git a/libk3b/projects/k3babstractwriter.h b/libk3b/projects/k3babstractwriter.h new file mode 100644 index 0000000..3f91ee3 --- /dev/null +++ b/libk3b/projects/k3babstractwriter.h @@ -0,0 +1,92 @@ +/* + * + * $Id: k3babstractwriter.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_ABSTRACT_WRITER_H +#define K3B_ABSTRACT_WRITER_H + + +#include "k3bjob.h" + +#include <qdatetime.h> + +class K3bDevice::Device; +class K3bJobHandler; + + +class K3bAbstractWriter : public K3bJob +{ + Q_OBJECT + + public: + virtual ~K3bAbstractWriter(); + + K3bDevice::Device* burnDevice(); + int burnSpeed() const { return m_burnSpeed; } + bool simulate() const { return m_simulate; } + + /** + * This can be used to setup direct streaming between two processes + * for example the cdrecordwriter returnes the stdin fd which can be + * connected to the stdout fd of mkisofs in the isoimager + */ + virtual int fd() const { return -1; } + virtual bool closeFd() { return false; } + + public slots: + /** + * If the burnDevice is set this will try to unlock the drive and + * eject the disk if K3b is configured to do so. + * Will also emit canceled and finished signals. + * may be called by subclasses. + */ + void cancel(); + + void setBurnDevice( K3bDevice::Device* dev ) { m_burnDevice = dev; } + void setBurnSpeed( int s ) { m_burnSpeed = s; } + void setSimulate( bool b ) { m_simulate = b; } + + /** + * Used to inform the writer that the source (especially useful when reading from + * another cd/dvd media) could not be read. + * + * Basically it should be used to make sure no "write an email" message is thrown. + */ + void setSourceUnreadable( bool b = true ) { m_sourceUnreadable = b; } + + signals: + void buffer( int ); + void deviceBuffer( int ); + void writeSpeed( int, int ); + + protected: + K3bAbstractWriter( K3bDevice::Device* dev, K3bJobHandler* hdl, + QObject* parent = 0, const char* name = 0 ); + + bool wasSourceUnreadable() const { return m_sourceUnreadable; } + + protected slots: + void slotUnblockWhileCancellationFinished( bool success ); + void slotEjectWhileCancellationFinished( bool success ); + + private: + K3bDevice::Device* m_burnDevice; + int m_burnSpeed; + bool m_simulate; + bool m_sourceUnreadable; +}; + + +#endif diff --git a/libk3b/projects/k3bcdrdaowriter.cpp b/libk3b/projects/k3bcdrdaowriter.cpp new file mode 100644 index 0000000..c49cb4b --- /dev/null +++ b/libk3b/projects/k3bcdrdaowriter.cpp @@ -0,0 +1,1101 @@ +/* + * + * $Id: k3bcdrdaowriter.cpp 654649 2007-04-16 17:55:50Z trueg $ + * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org> + * Klaus-Dieter Krannich <kd@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + + +#include "k3bcdrdaowriter.h" + +#include <k3bcore.h> +#include <k3bexternalbinmanager.h> +#include <k3bdevicemanager.h> +#include <k3bprocess.h> +#include <k3bdevice.h> +#include <k3bdevicehandler.h> +#include <k3bthroughputestimator.h> +#include <k3bglobals.h> +#include <k3bglobalsettings.h> + +#include <qstring.h> +#include <qstringlist.h> +#include <qvaluelist.h> +#include <qregexp.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qdir.h> +#include <qurl.h> +#include <qsocket.h> +#include <qsocketdevice.h> + +#include <klocale.h> +#include <kdebug.h> +#include <kio/netaccess.h> +#include <kstandarddirs.h> +#include <ktempfile.h> + +#include <sys/types.h> +#include <sys/socket.h> + + + +#define PGSMSG_MIN PGSMSG_RCD_ANALYZING +#define PGSMSG_RCD_ANALYZING 1 +#define PGSMSG_RCD_EXTRACTING 2 +#define PGSMSG_WCD_LEADIN 3 +#define PGSMSG_WCD_DATA 4 +#define PGSMSG_WCD_LEADOUT 5 +#define PGSMSG_BLK 6 +#define PGSMSG_MAX PGSMSG_BLK + +struct ProgressMsg { + int status; // see PGSMSG_* constants + int totalTracks; // total number of tracks + int track; // actually written track + int trackProgress; // progress for current track 0..1000 + int totalProgress; // total writing progress 0..1000 + int bufferFillRate; // buffer fill rate 0..100 +}; + +#define PSGMSG_MINSIZE 24 + +struct ProgressMsg2 { + int status; // see PGSMSG_* constants + int totalTracks; // total number of tracks + int track; // actually written track + int trackProgress; // progress for current track 0..1000 + int totalProgress; // total writing progress 0..1000 + int bufferFillRate; // buffer fill rate 0..100 + int writerFillRate; // device write buffer fill rate 0..100 +}; + + +inline bool operator<( const ProgressMsg2& m1, const ProgressMsg2& m2 ) +{ + return m1.track < m2.track + || ( m1.track == m2.track + && m1.trackProgress < m2.trackProgress ) + || m1.totalProgress < m2.totalProgress; +} + + +inline bool operator==( const ProgressMsg2& m1, const ProgressMsg2& m2 ) +{ + return m1.status == m2.status + && m1.track == m2.track + && m1.totalTracks == m2.totalTracks + && m1.trackProgress == m2.trackProgress + && m1.totalProgress == m2.totalProgress + && m1.bufferFillRate == m2.bufferFillRate; +} + +inline bool operator!=( const ProgressMsg2& m1, const ProgressMsg2& m2 ) +{ + return !( m1 == m2 ); +} + + + +class K3bCdrdaoWriter::Private +{ +public: + Private() { + } + + K3bThroughputEstimator* speedEst; + + int usedSpeed; + + ProgressMsg2 oldMsg; + ProgressMsg2 newMsg; + + unsigned int progressMsgSize; +}; + + +K3bCdrdaoWriter::K3bCdrdaoWriter( K3bDevice::Device* dev, K3bJobHandler* hdl, + QObject* parent, const char* name ) + : K3bAbstractWriter( dev, hdl, parent, name ), + m_command(WRITE), + m_blankMode(MINIMAL), + m_sourceDevice(0), + m_readRaw(false), + m_multi(false), + m_force(false), + m_onTheFly(false), + m_fastToc(false), + m_readSubchan(None), + m_taoSource(false), + m_taoSourceAdjust(-1), + m_paranoiaMode(-1), + m_session(-1), + m_process(0), + m_comSock(0), + m_currentTrack(0), + m_forceNoEject(false) +{ + d = new Private(); + d->speedEst = new K3bThroughputEstimator( this ); + connect( d->speedEst, SIGNAL(throughput(int)), + this, SLOT(slotThroughput(int)) ); + + m_eject = k3bcore->globalSettings()->ejectMedia(); + + ::memset( &d->oldMsg, 0, sizeof(ProgressMsg2) ); + ::memset( &d->newMsg, 0, sizeof(ProgressMsg2) ); + + if( socketpair(AF_UNIX,SOCK_STREAM,0,m_cdrdaoComm) ) + { + kdDebug() << "(K3bCdrdaoWriter) could not open socketpair for cdrdao remote messages" << endl; + } + else + { + delete m_comSock; + m_comSock = new QSocket(); + m_comSock->setSocket(m_cdrdaoComm[1]); + m_comSock->socketDevice()->setReceiveBufferSize(49152); + // magic number from Qt documentation + m_comSock->socketDevice()->setBlocking(false); + connect( m_comSock, SIGNAL(readyRead()), + this, SLOT(parseCdrdaoMessage())); + } +} + +K3bCdrdaoWriter::~K3bCdrdaoWriter() +{ + delete d->speedEst; + delete d; + + // close the socket + if( m_comSock ) { + m_comSock->close(); + ::close( m_cdrdaoComm[0] ); + } + delete m_process; + delete m_comSock; +} + + +int K3bCdrdaoWriter::fd() const +{ + if( m_process ) + return m_process->stdinFd(); + else + return -1; +} + + +bool K3bCdrdaoWriter::active() const +{ + return (m_process ? m_process->isRunning() : false); +} + + +void K3bCdrdaoWriter::prepareArgumentList() +{ + + // binary + *m_process << m_cdrdaoBinObject; + + // command + switch ( m_command ) + { + case COPY: + *m_process << "copy"; + setWriteArguments(); + setReadArguments(); + setCopyArguments(); + break; + case WRITE: + *m_process << "write"; + setWriteArguments(); + break; + case READ: + *m_process << "read-cd"; + // source device and source driver + if ( m_sourceDevice ) + *m_process << "--device" + << K3b::externalBinDeviceParameter(m_sourceDevice, m_cdrdaoBinObject); + if ( m_sourceDevice->cdrdaoDriver() != "auto" ) + *m_process << "--driver" << m_sourceDevice->cdrdaoDriver(); + else if( defaultToGenericMMC( m_sourceDevice, false ) ) { + kdDebug() << "(K3bCdrdaoWriter) defaulting to generic-mmc driver for " << m_sourceDevice->blockDeviceName() << endl; + *m_process << "--driver" << "generic-mmc"; + } + setReadArguments(); + break; + case BLANK: + *m_process << "blank"; + setBlankArguments(); + break; + } + + setCommonArguments(); +} + +void K3bCdrdaoWriter::setWriteArguments() +{ + // device and driver + *m_process << "--device" + << K3b::externalBinDeviceParameter(burnDevice(), m_cdrdaoBinObject); + + if( burnDevice()->cdrdaoDriver() != "auto" ) + { + *m_process << "--driver"; + if( burnDevice()->cdTextCapable() == 1 ) + *m_process << QString("%1:0x00000010").arg( burnDevice()->cdrdaoDriver() ); + else + *m_process << burnDevice()->cdrdaoDriver(); + } + else if( defaultToGenericMMC( burnDevice(), true ) ) { + kdDebug() << "(K3bCdrdaoWriter) defaulting to generic-mmc driver for " << burnDevice()->blockDeviceName() << endl; + *m_process << "--driver" << "generic-mmc:0x00000010"; + } + + // burn speed + if( d->usedSpeed != 0 ) + *m_process << "--speed" << QString("%1").arg(d->usedSpeed); + + //simulate + if( simulate() ) + *m_process << "--simulate"; + + // multi + if( m_multi ) + *m_process << "--multi"; + + // force + if( m_force ) + *m_process << "--force"; + + // burnproof + if ( !k3bcore->globalSettings()->burnfree() ) { + if( m_cdrdaoBinObject->hasFeature( "disable-burnproof" ) ) + *m_process << "--buffer-under-run-protection" << "0"; + else + emit infoMessage( i18n("Cdrdao %1 does not support disabling burnfree.").arg(m_cdrdaoBinObject->version), WARNING ); + } + + if( k3bcore->globalSettings()->force() ) { + *m_process << "--force"; + emit infoMessage( i18n("'Force unsafe operations' enabled."), WARNING ); + } + + bool manualBufferSize = + k3bcore->globalSettings()->useManualBufferSize(); + if( manualBufferSize ) { + // + // one buffer in cdrdao holds 1 second of audio data = 75 frames = 75 * 2352 bytes + // + int bufSizeInMb = k3bcore->globalSettings()->bufferSize(); + *m_process << "--buffers" << QString::number( bufSizeInMb*1024*1024/(75*2352) ); + } + + bool overburn = + k3bcore->globalSettings()->overburn(); + if( overburn ) { + if( m_cdrdaoBinObject->hasFeature("overburn") ) + *m_process << "--overburn"; + else + emit infoMessage( i18n("Cdrdao %1 does not support overburning.").arg(m_cdrdaoBinObject->version), WARNING ); + } + +} + +void K3bCdrdaoWriter::setReadArguments() +{ + // readRaw + if ( m_readRaw ) + *m_process << "--read-raw"; + + // subchan + if ( m_readSubchan != None ) + { + *m_process << "--read-subchan"; + switch ( m_readSubchan ) + { + case RW: + *m_process << "rw"; + break; + case RW_RAW: + *m_process << "rw_raw"; + break; + case None: + break; + } + } + + // TAO Source + if ( m_taoSource ) + *m_process << "--tao-source"; + + // TAO Source Adjust + if ( m_taoSourceAdjust != -1 ) + *m_process << "--tao-source-adjust" + << QString("%1").arg(m_taoSourceAdjust); + + // paranoia Mode + if ( m_paranoiaMode != -1 ) + *m_process << "--paranoia-mode" + << QString("%1").arg(m_paranoiaMode); + + // session + if ( m_session != -1 ) + *m_process << "--session" + << QString("%1").arg(m_session); + + // fast TOC + if ( m_fastToc ) + *m_process << "--fast-toc"; + +} + +void K3bCdrdaoWriter::setCopyArguments() +{ + // source device and source driver + *m_process << "--source-device" << K3b::externalBinDeviceParameter(m_sourceDevice, m_cdrdaoBinObject); + if ( m_sourceDevice->cdrdaoDriver() != "auto" ) + *m_process << "--source-driver" << m_sourceDevice->cdrdaoDriver(); + else if( defaultToGenericMMC( m_sourceDevice, false ) ) { + kdDebug() << "(K3bCdrdaoWriter) defaulting to generic-mmc driver for " << m_sourceDevice->blockDeviceName() << endl; + *m_process << "--source-driver" << "generic-mmc"; + } + + // on-the-fly + if ( m_onTheFly ) + *m_process << "--on-the-fly"; +} + +void K3bCdrdaoWriter::setBlankArguments() +{ + // device and driver + *m_process << "--device" + << K3b::externalBinDeviceParameter(burnDevice(), m_cdrdaoBinObject); + + if( burnDevice()->cdrdaoDriver() != "auto" ) + { + *m_process << "--driver"; + if( burnDevice()->cdTextCapable() == 1 ) + *m_process << QString("%1:0x00000010").arg( burnDevice()->cdrdaoDriver() ); + else + *m_process << burnDevice()->cdrdaoDriver(); + } + else if( defaultToGenericMMC( burnDevice(), true ) ) { + kdDebug() << "(K3bCdrdaoWriter) defaulting to generic-mmc driver for " << burnDevice()->blockDeviceName() << endl; + *m_process << "--driver" << "generic-mmc"; + } + + // burn speed + if( d->usedSpeed != 0 ) + *m_process << "--speed" << QString("%1").arg(d->usedSpeed); + + // blank-mode + *m_process << "--blank-mode"; + switch (m_blankMode) + { + case FULL: + *m_process << "full"; + break; + case MINIMAL: + *m_process << "minimal"; + break; + } +} + +void K3bCdrdaoWriter::setCommonArguments() +{ + + // additional user parameters from config + const QStringList& params = m_cdrdaoBinObject->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *m_process << *it; + + + // display debug info + *m_process << "-n" << "-v" << "2"; + + // we have the power to do what ever we want. ;) + *m_process << "--force"; + + // eject + if( m_eject && !m_forceNoEject ) + *m_process << "--eject"; + + // remote + *m_process << "--remote" << QString("%1").arg(m_cdrdaoComm[0]); + + // data File + if ( ! m_dataFile.isEmpty() ) + *m_process << "--datafile" << m_dataFile; + + // BIN/CUE + if ( ! m_cueFileLnk.isEmpty() ) + *m_process << m_cueFileLnk; + // TOC File + else if ( ! m_tocFile.isEmpty() ) + *m_process << m_tocFile; +} + +K3bCdrdaoWriter* K3bCdrdaoWriter::addArgument( const QString& arg ) +{ + *m_process << arg; + return this; +} + + +void K3bCdrdaoWriter::start() +{ + jobStarted(); + + d->speedEst->reset(); + + delete m_process; // kdelibs want this! + m_process = new K3bProcess(); + m_process->setRunPrivileged(true); + m_process->setSplitStdout(false); + m_process->setRawStdin(true); + connect( m_process, SIGNAL(stderrLine(const QString&)), + this, SLOT(slotStdLine(const QString&)) ); + connect( m_process, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*)) ); + + m_canceled = false; + m_knownError = false; + + m_cdrdaoBinObject = k3bcore->externalBinManager()->binObject("cdrdao"); + + if( !m_cdrdaoBinObject ) { + emit infoMessage( i18n("Could not find %1 executable.").arg("cdrdao"), ERROR ); + jobFinished(false); + return; + } + + emit debuggingOutput( "Used versions", "cdrdao: " + m_cdrdaoBinObject->version ); + + if( !m_cdrdaoBinObject->copyright.isEmpty() ) + emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3").arg(m_cdrdaoBinObject->name()).arg(m_cdrdaoBinObject->version).arg(m_cdrdaoBinObject->copyright), INFO ); + + + // the message size changed in cdrdao 1.1.8) + if( m_cdrdaoBinObject->version >= K3bVersion( 1, 1, 8 ) ) + d->progressMsgSize = sizeof(ProgressMsg2); + else + d->progressMsgSize = sizeof(ProgressMsg); + + // since the --speed parameter is used several times in this code we + // determine the speed in auto once at the beginning + d->usedSpeed = burnSpeed(); + if( d->usedSpeed == 0 ) { + // try to determine the writeSpeed + // if it fails determineMaximalWriteSpeed() will return 0 and + // the choice is left to cdrdao + d->usedSpeed = burnDevice()->determineMaximalWriteSpeed(); + } + d->usedSpeed /= 175; + + switch ( m_command ) + { + case WRITE: + case COPY: + if (!m_tocFile.isEmpty()) + { + + // if tocfile is a cuesheet than create symlinks to *.cue and the binary listed inside the cuesheet. + // now works without the .bin extension too. + if ( !cueSheet() ) { + m_backupTocFile = m_tocFile + ".k3bbak"; + + // workaround, cdrdao deletes the tocfile when --remote parameter is set + if ( !KIO::NetAccess::copy(KURL(m_tocFile),KURL(m_backupTocFile), (QWidget*) 0) ) + { + kdDebug() << "(K3bCdrdaoWriter) could not backup " << m_tocFile << " to " << m_backupTocFile << endl; + emit infoMessage( i18n("Could not backup tocfile."), ERROR ); + jobFinished(false); + return; + } + } + } + break; + case BLANK: + case READ: + break; + } + prepareArgumentList(); + // set working dir to dir part of toc file (to allow rel names in toc-file) + m_process->setWorkingDirectory(QUrl(m_tocFile).dirPath()); + + kdDebug() << "***** cdrdao parameters:\n"; + const QValueList<QCString>& args = m_process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) + { + s += *it + " "; + } + kdDebug() << s << flush << endl; + emit debuggingOutput("cdrdao command:", s); + + m_currentTrack = 0; + reinitParser(); + + switch ( m_command ) + { + case READ: + emit newSubTask( i18n("Preparing read process...") ); + break; + case WRITE: + emit newSubTask( i18n("Preparing write process...") ); + break; + case COPY: + emit newSubTask( i18n("Preparing copy process...") ); + break; + case BLANK: + emit newSubTask( i18n("Preparing blanking process...") ); + break; + } + + // FIXME: check the return value + if( K3b::isMounted( burnDevice() ) ) { + emit infoMessage( i18n("Unmounting medium"), INFO ); + K3b::unmount( burnDevice() ); + } + + // block the device (including certain checks) + k3bcore->blockDevice( burnDevice() ); + + // lock the device for good in this process since it will + // be opened in the growisofs process + burnDevice()->close(); + burnDevice()->usageLock(); + + if( !m_process->start( KProcess::NotifyOnExit, KProcess::AllOutput ) ) + { + // something went wrong when starting the program + // it "should" be the executable + kdDebug() << "(K3bCdrdaoWriter) could not start cdrdao" << endl; + emit infoMessage( i18n("Could not start %1.").arg("cdrdao"), K3bJob::ERROR ); + jobFinished(false); + } + else + { + switch ( m_command ) + { + case WRITE: + if( simulate() ) + { + emit infoMessage(i18n("Starting DAO simulation at %1x speed...").arg(d->usedSpeed), + K3bJob::INFO ); + emit newTask( i18n("Simulating") ); + } + else + { + emit infoMessage( i18n("Starting DAO writing at %1x speed...").arg(d->usedSpeed), K3bJob::INFO ); + emit newTask( i18n("Writing") ); + } + break; + case READ: + emit infoMessage(i18n("Starting reading..."), K3bJob::INFO ); + emit newTask( i18n("Reading") ); + break; + case COPY: + if( simulate() ) + { + emit infoMessage(i18n("Starting simulation copy at %1x speed...").arg(d->usedSpeed), K3bJob::INFO ); + emit newTask( i18n("Simulating") ); + } + else + { + emit infoMessage( i18n("Starting copy at %1x speed...").arg(d->usedSpeed), K3bJob::INFO ); + emit newTask( i18n("Copying") ); + } + break; + case BLANK: + emit infoMessage(i18n("Starting blanking..."), K3bJob::INFO ); + emit newTask( i18n("Blanking") ); + } + } +} + + +void K3bCdrdaoWriter::cancel() +{ + m_canceled = true; + + if( m_process ) { + if( m_process->isRunning() ) { + m_process->disconnect(); + m_process->kill(); + + // we need to unlock the device because cdrdao locked it while writing + // + // FIXME: try to determine wheater we are writing or reading and choose + // the device to unblock based on that result. + // + if( m_command == READ ) { + // FIXME: this is a hack + setBurnDevice( m_sourceDevice ); + } + + // this will unblock and eject the drive and emit the finished/canceled signals + K3bAbstractWriter::cancel(); + } + } +} + + +bool K3bCdrdaoWriter::cueSheet() +{ + + // TODO: do this in the K3bCueFileParser + + if ( m_tocFile.lower().endsWith( ".cue" ) ) { + QFile f( m_tocFile ); + if ( f.open( IO_ReadOnly ) ) { + QTextStream ts( &f ); + if ( !ts.eof() ) { + QString line = ts.readLine(); + f.close(); + int pos = line.find( "FILE \"" ); + if( pos < 0 ) + return false; + + pos += 6; + int endPos = line.find( "\" BINARY", pos+1 ); + if( endPos < 0 ) + return false; + + line = line.mid( pos, endPos-pos ); + QFileInfo fi( QFileInfo( m_tocFile ).dirPath() + "/" + QFileInfo( line ).fileName() ); + QString binpath = fi.filePath(); + kdDebug() << QString("K3bCdrdaoWriter::cueSheet() BinFilePath from CueFile: %1").arg( line ) << endl; + kdDebug() << QString("K3bCdrdaoWriter::cueSheet() absolute BinFilePath: %1").arg( binpath ) << endl; + + if ( !fi.exists() ) + return false; + + KTempFile tempF; + QString tempFile = tempF.name(); + tempF.unlink(); + + if ( symlink(QFile::encodeName( binpath ), QFile::encodeName( tempFile + ".bin") ) == -1 ) + return false; + if ( symlink(QFile::encodeName( m_tocFile ), QFile::encodeName( tempFile + ".cue") ) == -1 ) + return false; + + kdDebug() << QString("K3bCdrdaoWriter::cueSheet() symlink BinFileName: %1.bin").arg( tempFile ) << endl; + kdDebug() << QString("K3bCdrdaoWriter::cueSheet() symlink CueFileName: %1.cue").arg( tempFile ) << endl; + m_binFileLnk = tempFile + ".bin"; + m_cueFileLnk = tempFile + ".cue"; + return true; + } + } + } + + return false; +} + +void K3bCdrdaoWriter::slotStdLine( const QString& line ) +{ + parseCdrdaoLine(line); +} + + +void K3bCdrdaoWriter::slotProcessExited( KProcess* p ) +{ + // release the device within this process + burnDevice()->usageUnlock(); + + // unblock the device + k3bcore->unblockDevice( burnDevice() ); + + switch ( m_command ) + { + case WRITE: + case COPY: + if ( !m_binFileLnk.isEmpty() ) { + KIO::NetAccess::del(KURL::fromPathOrURL(m_cueFileLnk), (QWidget*) 0); + KIO::NetAccess::del(KURL::fromPathOrURL(m_binFileLnk), (QWidget*) 0); + } + else if( (!QFile::exists( m_tocFile ) || K3b::filesize( KURL::fromPathOrURL(m_tocFile) ) == 0 ) && !m_onTheFly ) + { + // cdrdao removed the tocfile :( + // we need to recover it + if ( !KIO::NetAccess::copy(KURL::fromPathOrURL(m_backupTocFile), KURL::fromPathOrURL(m_tocFile), (QWidget*) 0) ) + { + kdDebug() << "(K3bCdrdaoWriter) restoring tocfile " << m_tocFile << " failed." << endl; + emit infoMessage( i18n("Due to a bug in cdrdao the toc/cue file %1 has been deleted. " + "K3b was unable to restore it from the backup %2.").arg(m_tocFile).arg(m_backupTocFile), ERROR ); + } + else if ( !KIO::NetAccess::del(KURL::fromPathOrURL(m_backupTocFile), (QWidget*) 0) ) + { + kdDebug() << "(K3bCdrdaoWriter) delete tocfile backkup " << m_backupTocFile << " failed." << endl; + } + } + break; + case BLANK: + case READ: + break; + } + + if( m_canceled ) + return; + + if( p->normalExit() ) + { + switch( p->exitStatus() ) + { + case 0: + if( simulate() ) + emit infoMessage( i18n("Simulation successfully completed"), K3bJob::SUCCESS ); + else + switch ( m_command ) + { + case READ: + emit infoMessage( i18n("Reading successfully completed"), K3bJob::SUCCESS ); + break; + case WRITE: + emit infoMessage( i18n("Writing successfully completed"), K3bJob::SUCCESS ); + break; + case COPY: + emit infoMessage( i18n("Copying successfully completed"), K3bJob::SUCCESS ); + break; + case BLANK: + emit infoMessage( i18n("Blanking successfully completed"), K3bJob::SUCCESS ); + break; + } + + if( m_command == WRITE || m_command == COPY ) { + int s = d->speedEst->average(); + emit infoMessage( i18n("Average overall write speed: %1 KB/s (%2x)").arg(s).arg(KGlobal::locale()->formatNumber((double)s/150.0), 2), INFO ); + } + + jobFinished( true ); + break; + + default: + if( !m_knownError && !wasSourceUnreadable() ) { + emit infoMessage( i18n("%1 returned an unknown error (code %2).").arg(m_cdrdaoBinObject->name()).arg(p->exitStatus()), + K3bJob::ERROR ); + emit infoMessage( i18n("Please include the debugging output in your problem report."), K3bJob::ERROR ); + } + + jobFinished( false ); + break; + } + } + else + { + emit infoMessage( i18n("%1 did not exit cleanly.").arg("cdrdao"), K3bJob::ERROR ); + jobFinished( false ); + } +} + + +void K3bCdrdaoWriter::unknownCdrdaoLine( const QString& line ) +{ + if( line.contains( "at speed" ) ) + { + // parse the speed and inform the user if cdrdao switched it down + int pos = line.find( "at speed" ); + int po2 = line.find( QRegExp("\\D"), pos + 9 ); + int speed = line.mid( pos+9, po2-pos-9 ).toInt(); + if( speed < d->usedSpeed ) + { + emit infoMessage( i18n("Medium or burner do not support writing at %1x speed").arg(d->usedSpeed), K3bJob::WARNING ); + emit infoMessage( i18n("Switching down burn speed to %1x").arg(speed), K3bJob::WARNING ); + } + } +} + + +void K3bCdrdaoWriter::reinitParser() +{ + ::memset( &d->oldMsg, 0, sizeof(ProgressMsg2) ); + ::memset( &d->newMsg, 0, sizeof(ProgressMsg2) ); + + m_currentTrack=0; +} + +void K3bCdrdaoWriter::parseCdrdaoLine( const QString& str ) +{ + emit debuggingOutput( "cdrdao", str ); + // kdDebug() << "(cdrdaoparse)" << str << endl; + // find some messages from cdrdao + // ----------------------------------------------------------------------------------------- + if( (str).startsWith( "Warning" ) || (str).startsWith( "WARNING" ) || (str).startsWith( "ERROR" ) ) + { + parseCdrdaoError( str ); + } + else if( (str).startsWith( "Wrote" ) && !str.contains("blocks") ) + { + parseCdrdaoWrote( str ); + } + else if( (str).startsWith( "Executing power" ) ) + { + emit newSubTask( i18n("Executing Power calibration") ); + } + else if( (str).startsWith( "Power calibration successful" ) ) + { + emit infoMessage( i18n("Power calibration successful"), K3bJob::INFO ); + emit newSubTask( i18n("Preparing burn process...") ); + } + else if( (str).startsWith( "Flushing cache" ) ) + { + emit newSubTask( i18n("Flushing cache") ); + } + else if( (str).startsWith( "Writing CD-TEXT lead" ) ) + { + emit newSubTask( i18n("Writing CD-Text lead-in...") ); + } + else if( (str).startsWith( "Turning BURN-Proof on" ) ) + { + emit infoMessage( i18n("Turning BURN-Proof on"), K3bJob::INFO ); + } + else if( str.startsWith( "Copying" ) ) + { + emit infoMessage( str, K3bJob::INFO ); + } + else if( str.startsWith( "Found ISRC" ) ) + { + emit infoMessage( i18n("Found ISRC code"), K3bJob::INFO ); + } + else if( str.startsWith( "Found pre-gap" ) ) + { + emit infoMessage( i18n("Found pregap: %1").arg( str.mid(str.find(":")+1) ), K3bJob::INFO ); + } + else + unknownCdrdaoLine(str); +} + +void K3bCdrdaoWriter::parseCdrdaoError( const QString& line ) +{ + int pos = -1; + + if( line.contains( "No driver found" ) || + line.contains( "use option --driver" ) ) + { + emit infoMessage( i18n("No cdrdao driver found."), K3bJob::ERROR ); + emit infoMessage( i18n("Please select one manually in the device settings."), K3bJob::ERROR ); + emit infoMessage( i18n("For most current drives this would be 'generic-mmc'."), K3bJob::ERROR ); + m_knownError = true; + } + else if( line.contains( "Cannot setup device" ) ) + { + // no nothing... + } + else if( line.contains( "not ready") ) + { + emit infoMessage( i18n("Device not ready, waiting."),K3bJob::WARNING ); + } + else if( line.contains("Drive does not accept any cue sheet") ) + { + emit infoMessage( i18n("Cue sheet not accepted."), K3bJob::ERROR ); + m_knownError = true; + } + else if( (pos = line.find( "Illegal option" )) > 0 ) { + // ERROR: Illegal option: -wurst + emit infoMessage( i18n("No valid %1 option: %2").arg(m_cdrdaoBinObject->name()).arg(line.mid(pos+16)), + ERROR ); + m_knownError = true; + } + else if( line.contains( "exceeds capacity" ) ) { + emit infoMessage( i18n("Data does not fit on disk."), ERROR ); + if( m_cdrdaoBinObject->hasFeature("overburn") ) + emit infoMessage( i18n("Enable overburning in the advanced K3b settings to burn anyway."), INFO ); + m_knownError = true; + } + // else if( !line.contains( "remote progress message" ) ) +// emit infoMessage( line, K3bJob::ERROR ); +} + +void K3bCdrdaoWriter::parseCdrdaoWrote( const QString& line ) +{ + int pos, po2; + pos = line.find( "Wrote" ); + po2 = line.find( " ", pos + 6 ); + int processed = line.mid( pos+6, po2-pos-6 ).toInt(); + + pos = line.find( "of" ); + po2 = line.find( " ", pos + 3 ); + m_size = line.mid( pos+3, po2-pos-3 ).toInt(); + + d->speedEst->dataWritten( processed*1024 ); + + emit processedSize( processed, m_size ); +} + + +void K3bCdrdaoWriter::parseCdrdaoMessage() +{ + static const char msgSync[] = { 0xff, 0x00, 0xff, 0x00 }; + unsigned int avail = m_comSock->bytesAvailable(); + unsigned int msgs = avail / ( sizeof(msgSync)+d->progressMsgSize ); + unsigned int count = 0; + + if ( msgs < 1 ) + return; + else if ( msgs > 1) { + // move the read-index forward to the beginnig of the most recent message + count = ( msgs-1 ) * ( sizeof(msgSync)+d->progressMsgSize ); + m_comSock->at(count); + kdDebug() << "(K3bCdrdaoParser) " << msgs-1 << " message(s) skipped" << endl; + } + + while( count < avail ) { + + // search for msg sync + int state = 0; + char buf; + while( state < 4 ) { + buf = m_comSock->getch(); + ++count; + if( count == avail ) { + // kdDebug() << "(K3bCdrdaoParser) remote message sync not found (" << count << ")" << endl; + return; + } + + if( buf == msgSync[state] ) + ++state; + else + state = 0; + } + + if( (avail - count) < d->progressMsgSize ) { + kdDebug() << "(K3bCdrdaoParser) could not read complete remote message." << endl; + return; + } + + // read one message (the message size changed in cdrdao 1.1.8) + ::memset( &d->newMsg, 0, d->progressMsgSize ); + int size = m_comSock->readBlock( (char*)&d->newMsg, d->progressMsgSize); + if( size == -1 ) { + kdDebug() << "(K3bCdrdaoParser) read error" << endl; + return; + } + count += size; + + // sometimes the progress takes one step back (on my system when using paranoia-level 3) + // so we just use messages that are greater than the previous or first messages + if( d->oldMsg < d->newMsg + || ( d->newMsg.track == 1 && + d->newMsg.trackProgress <= 10 )) { + + if( d->newMsg.track != m_currentTrack ) { + switch( d->newMsg.status ) { + case PGSMSG_RCD_EXTRACTING: + emit nextTrack( d->newMsg.track, d->newMsg.totalTracks ); + break; + case PGSMSG_WCD_LEADIN: + emit newSubTask( i18n("Writing leadin ") ); + break; + case PGSMSG_WCD_DATA: + emit nextTrack( d->newMsg.track, d->newMsg.totalTracks ); + break; + case PGSMSG_WCD_LEADOUT: + emit newSubTask( i18n("Writing leadout ") ); + break; + } + + m_currentTrack = d->newMsg.track; + } + + if( d->newMsg.status == PGSMSG_WCD_LEADIN || d->newMsg.status == PGSMSG_WCD_LEADOUT ) { + // cdrdao >= 1.1.8 emits progress data when writing the lead-in and lead-out :) + emit subPercent( d->newMsg.totalProgress/10 ); + } + else { + emit subPercent( d->newMsg.trackProgress/10 ); + emit percent( d->newMsg.totalProgress/10 ); + } + + emit buffer(d->newMsg.bufferFillRate); + + if( d->progressMsgSize == (unsigned int)sizeof(ProgressMsg2) ) + emit deviceBuffer( d->newMsg.writerFillRate ); + + ::memcpy( &d->oldMsg, &d->newMsg, d->progressMsgSize ); + } + } +} + + +void K3bCdrdaoWriter::slotThroughput( int t ) +{ + // FIXME: determine sector size + emit writeSpeed( t, 150 ); +} + + +QString K3bCdrdaoWriter::findDriverFile( const K3bExternalBin* bin ) +{ + if( !bin ) + return QString::null; + + // cdrdao normally in (prefix)/bin and driver table in (prefix)/share/cdrdao + QString path = bin->path; + path.truncate( path.findRev("/") ); + path.truncate( path.findRev("/") ); + path += "/share/cdrdao/drivers"; + if( QFile::exists(path) ) + return path; + else { + kdDebug() << "(K3bCdrdaoWriter) could not find cdrdao driver table." << endl; + return QString::null; + } +} + + +// returns true if the driver file could be opened and no driver could be found +// TODO: cache the drivers +bool K3bCdrdaoWriter::defaultToGenericMMC( K3bDevice::Device* dev, bool writer ) +{ + QString driverTable = findDriverFile( m_cdrdaoBinObject ); + if( !driverTable.isEmpty() ) { + QFile f( driverTable ); + if( f.open( IO_ReadOnly ) ) { + // read all drivers + QStringList drivers; + QTextStream fStr( &f ); + while( !fStr.atEnd() ) { + QString line = fStr.readLine(); + if( line.isEmpty() ) + continue; + if( line[0] == '#' ) + continue; + if( line[0] == 'R' && writer ) + continue; + if( line[0] == 'W' && !writer ) + continue; + drivers.append(line); + } + + // search for the driver + for( QStringList::const_iterator it = drivers.begin(); it != drivers.end(); ++it ) { + if( (*it).section( '|', 1, 1 ) == dev->vendor() && + (*it).section( '|', 2, 2 ) == dev->description() ) + return false; + } + + // no driver found + return true; + } + else { + kdDebug() << "(K3bCdrdaoWriter) could not open driver table " << driverTable << endl; + return false; + } + } + else + return false; +} + + +#include "k3bcdrdaowriter.moc" diff --git a/libk3b/projects/k3bcdrdaowriter.h b/libk3b/projects/k3bcdrdaowriter.h new file mode 100644 index 0000000..94a0c9f --- /dev/null +++ b/libk3b/projects/k3bcdrdaowriter.h @@ -0,0 +1,157 @@ +/* + * + * $Id: k3bcdrdaowriter.h 619556 2007-01-03 17:38:12Z trueg $ + * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org> + * Klaus-Dieter Krannich <kd@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + + +#ifndef K3B_CDRDAO_WRITER_H +#define K3B_CDRDAO_WRITER_H + + +#include "k3babstractwriter.h" + +class K3bExternalBin; +class K3bProcess; +class KProcess; +class K3bDevice::Device; +class QSocket; + + + +class K3bCdrdaoWriter : public K3bAbstractWriter +{ + Q_OBJECT + + public: + + enum Command { WRITE, COPY, READ, BLANK }; + enum BlankMode { FULL, MINIMAL }; + enum SubMode { None, RW, RW_RAW }; + + K3bCdrdaoWriter( K3bDevice::Device* dev, K3bJobHandler*, + QObject* parent = 0, const char* name = 0 ); + ~K3bCdrdaoWriter(); + + /** + * to be used in chain: addArgument(x)->addArgument(y) + */ + K3bCdrdaoWriter* addArgument( const QString& ); + K3bDevice::Device* sourceDevice() { return m_sourceDevice; }; + + int fd() const; + + bool active() const; + + private: + void reinitParser(); + void parseCdrdaoLine( const QString& line ); + void parseCdrdaoWrote( const QString& line ); + void parseCdrdaoError( const QString& line ); + + public slots: + void start(); + void cancel(); + + // options + // --------------------- + void setCommand( int c ) { m_command = c; } + void setBlankMode( int b ) { m_blankMode = b; } + void setMulti( bool b ) { m_multi = b; } + void setForce( bool b ) { m_force = b; } + void setOnTheFly( bool b ) { m_onTheFly = b; } + void setDataFile( const QString& s ) { m_dataFile = s; } + void setTocFile( const QString& s ) { m_tocFile = s; } + + void setSourceDevice( K3bDevice::Device* dev ) { m_sourceDevice = dev; } + void setFastToc( bool b ) { m_fastToc = b; } + void setReadRaw( bool b ) { m_readRaw = b; } + void setReadSubchan(SubMode m) { m_readSubchan=m; }; + void setParanoiaMode( int i ) { m_paranoiaMode = i; } + void setTaoSource(bool b) { m_taoSource=b; }; + void setTaoSourceAdjust(int a) { m_taoSourceAdjust=a; }; + void setSession(int s) { m_session=s; }; + void setEject(bool e) { m_eject=e; }; +// --------------------- + + /** + * If set true the job ignores the global K3b setting + * and does not eject the CD-RW after finishing + */ + void setForceNoEject( bool b ) { m_forceNoEject = b; } + + private slots: + void slotStdLine( const QString& line ); + void slotProcessExited(KProcess*); + void parseCdrdaoMessage(); + void slotThroughput( int t ); + + private: + void unknownCdrdaoLine( const QString& ); + void prepareArgumentList(); + void setWriteArguments(); + void setReadArguments(); + void setCopyArguments(); + void setBlankArguments(); + void setCommonArguments(); + + bool cueSheet(); + + QString findDriverFile( const K3bExternalBin* bin ); + bool defaultToGenericMMC( K3bDevice::Device* dev, bool writer ); + + // options + // --------------------- + int m_command; + int m_blankMode; + K3bDevice::Device* m_sourceDevice; + QString m_dataFile; + QString m_tocFile; + QString m_cueFileLnk; + QString m_binFileLnk; + QString m_backupTocFile; + bool m_readRaw; + bool m_multi; + bool m_force; + bool m_onTheFly; + bool m_fastToc; + SubMode m_readSubchan; + bool m_taoSource; + int m_taoSourceAdjust; + int m_paranoiaMode; + int m_session; + bool m_eject; + // --------------------- + + const K3bExternalBin* m_cdrdaoBinObject; + K3bProcess* m_process; + + int m_cdrdaoComm[2]; + QSocket *m_comSock; + + bool m_canceled; + + bool m_knownError; + +// parser + + int m_size; + int m_currentTrack; + + bool m_forceNoEject; + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/k3bcdrecordwriter.cpp b/libk3b/projects/k3bcdrecordwriter.cpp new file mode 100644 index 0000000..e87c767 --- /dev/null +++ b/libk3b/projects/k3bcdrecordwriter.cpp @@ -0,0 +1,810 @@ +/* + * + * $Id: k3bcdrecordwriter.cpp 690529 2007-07-21 10:51:47Z trueg $ + * Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + +#include <config.h> + + +#include "k3bcdrecordwriter.h" + +#include <k3bcore.h> +#include <k3bexternalbinmanager.h> +#include <k3bprocess.h> +#include <k3bdevice.h> +#include <k3bdevicemanager.h> +#include <k3bdevicehandler.h> +#include <k3bglobals.h> +#include <k3bthroughputestimator.h> +#include <k3bglobalsettings.h> +#include <k3btempfile.h> + +#include <qstring.h> +#include <qstringlist.h> +#include <qurl.h> +#include <qvaluelist.h> +#include <qregexp.h> +#include <qfile.h> + +#include <klocale.h> +#include <kdebug.h> +#include <kglobal.h> + + + +class K3bCdrecordWriter::Private +{ +public: + Private() + : cdTextFile(0) { + } + + K3bThroughputEstimator* speedEst; + bool canceled; + bool usingBurnfree; + int usedSpeed; + + struct Track { + int size; + bool audio; + }; + + QValueList<Track> tracks; + + KTempFile* cdTextFile; +}; + + +K3bCdrecordWriter::K3bCdrecordWriter( K3bDevice::Device* dev, K3bJobHandler* hdl, + QObject* parent, const char* name ) + : K3bAbstractWriter( dev, hdl, parent, name ), + m_clone(false), + m_cue(false), + m_forceNoEject(false) +{ + d = new Private(); + d->speedEst = new K3bThroughputEstimator( this ); + connect( d->speedEst, SIGNAL(throughput(int)), + this, SLOT(slotThroughput(int)) ); + + m_process = 0; + m_writingMode = K3b::TAO; +} + + +K3bCdrecordWriter::~K3bCdrecordWriter() +{ + delete d->cdTextFile; + delete d; + delete m_process; +} + + +bool K3bCdrecordWriter::active() const +{ + return ( m_process && m_process->isRunning() ); +} + + +int K3bCdrecordWriter::fd() const +{ + if( m_process ) + return m_process->stdinFd(); + else + return -1; +} + + +void K3bCdrecordWriter::setDao( bool b ) +{ + m_writingMode = ( b ? K3b::DAO : K3b::TAO ); +} + +void K3bCdrecordWriter::setCueFile( const QString& s) +{ + m_cue = true; + m_cueFile = s; + + // cuefile only works in DAO mode + setWritingMode( K3b::DAO ); +} + +void K3bCdrecordWriter::setClone( bool b ) +{ + m_clone = b; +} + + +void K3bCdrecordWriter::setWritingMode( int mode ) +{ + if( mode == K3b::DAO || + mode == K3b::TAO || + mode == K3b::RAW ) + m_writingMode = mode; + else + kdError() << "(K3bCdrecordWriter) wrong writing mode: " << mode << endl; +} + + +void K3bCdrecordWriter::prepareProcess() +{ + if( m_process ) delete m_process; // kdelibs want this! + m_process = new K3bProcess(); + m_process->setRunPrivileged(true); + // m_process->setPriority( KProcess::PrioHighest ); + m_process->setSplitStdout(true); + m_process->setSuppressEmptyLines(true); + m_process->setRawStdin(true); // we only use stdin when writing on-the-fly + connect( m_process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); + connect( m_process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); + connect( m_process, SIGNAL(processExited(KProcess*)), this, SLOT(slotProcessExited(KProcess*)) ); + + m_cdrecordBinObject = k3bcore->externalBinManager()->binObject("cdrecord"); + + if( !m_cdrecordBinObject ) + return; + + *m_process << m_cdrecordBinObject; + + // display progress + *m_process << "-v"; + + if( m_cdrecordBinObject->hasFeature( "gracetime") ) + *m_process << "gracetime=2"; // 2 is the lowest allowed value (Joerg, why do you do this to us?) + + // Again we assume the device to be set! + *m_process << QString("dev=%1").arg(K3b::externalBinDeviceParameter(burnDevice(), m_cdrecordBinObject)); + + d->usedSpeed = burnSpeed(); + if( d->usedSpeed == 0 ) { + // try to determine the writeSpeed + // if it fails determineMaximalWriteSpeed() will return 0 and + // the choice is left to cdrecord + d->usedSpeed = burnDevice()->determineMaximalWriteSpeed(); + } + d->usedSpeed /= 175; + if( d->usedSpeed != 0 ) + *m_process << QString("speed=%1").arg(d->usedSpeed); + + if( m_writingMode == K3b::DAO || m_cue ) { + if( burnDevice()->dao() ) + *m_process << "-dao"; + else { + if( m_cdrecordBinObject->hasFeature( "tao" ) ) + *m_process << "-tao"; + emit infoMessage( i18n("Writer does not support disk at once (DAO) recording"), WARNING ); + } + } + else if( m_writingMode == K3b::RAW ) { + if( burnDevice()->supportsWritingMode( K3bDevice::RAW_R96R ) ) + *m_process << "-raw96r"; + else if( burnDevice()->supportsWritingMode( K3bDevice::RAW_R16 ) ) + *m_process << "-raw16"; + else if( burnDevice()->supportsWritingMode( K3bDevice::RAW_R96P ) ) + *m_process << "-raw96p"; + else { + emit infoMessage( i18n("Writer does not support raw writing."), WARNING ); + if( m_cdrecordBinObject->hasFeature( "tao" ) ) + *m_process << "-tao"; + } + } + else if( m_cdrecordBinObject->hasFeature( "tao" ) ) + *m_process << "-tao"; + + if( simulate() ) + *m_process << "-dummy"; + + d->usingBurnfree = false; + if( k3bcore->globalSettings()->burnfree() ) { + if( burnDevice()->burnproof() ) { + + d->usingBurnfree = true; + + // with cdrecord 1.11a02 burnproof was renamed to burnfree + if( m_cdrecordBinObject->hasFeature( "burnproof" ) ) + *m_process << "driveropts=burnproof"; + else + *m_process << "driveropts=burnfree"; + } + else + emit infoMessage( i18n("Writer does not support buffer underrun free recording (Burnfree)"), WARNING ); + } + + if( k3bcore->globalSettings()->force() ) { + *m_process << "-force"; + emit infoMessage( i18n("'Force unsafe operations' enabled."), WARNING ); + } + + if( m_cue ) { + m_process->setWorkingDirectory(QUrl(m_cueFile).dirPath()); + *m_process << QString("cuefile=%1").arg( m_cueFile ); + } + + if( m_clone ) + *m_process << "-clone"; + + if( m_rawCdText.size() > 0 ) { + delete d->cdTextFile; + d->cdTextFile = new K3bTempFile( QString::null, ".dat" ); + d->cdTextFile->setAutoDelete(true); + d->cdTextFile->file()->writeBlock( m_rawCdText ); + d->cdTextFile->close(); + + *m_process << "textfile=" + d->cdTextFile->name(); + } + + if( k3bcore->globalSettings()->ejectMedia() && + !m_forceNoEject ) + *m_process << "-eject"; + + bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize(); + if( manualBufferSize ) { + *m_process << QString("fs=%1m").arg( k3bcore->globalSettings()->bufferSize() ); + } + + bool overburn = k3bcore->globalSettings()->overburn(); + if( overburn ) + if( m_cdrecordBinObject->hasFeature("overburn") ) + *m_process << "-overburn"; + else + emit infoMessage( i18n("Cdrecord %1 does not support overburning.").arg(m_cdrecordBinObject->version), WARNING ); + + // additional user parameters from config + const QStringList& params = m_cdrecordBinObject->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *m_process << *it; + + // add the user parameters + for( QStringList::const_iterator it = m_arguments.begin(); it != m_arguments.end(); ++it ) + *m_process << *it; +} + + +K3bCdrecordWriter* K3bCdrecordWriter::addArgument( const QString& arg ) +{ + m_arguments.append( arg ); + return this; +} + + +void K3bCdrecordWriter::clearArguments() +{ + m_arguments.clear(); +} + + +void K3bCdrecordWriter::start() +{ + jobStarted(); + + d->canceled = false; + d->speedEst->reset(); + + prepareProcess(); + + if( !m_cdrecordBinObject ) { + emit infoMessage( i18n("Could not find %1 executable.").arg("cdrecord"), ERROR ); + jobFinished(false); + return; + } + + emit debuggingOutput( "Used versions", "cdrecord: " + m_cdrecordBinObject->version ); + + if( !m_cdrecordBinObject->copyright.isEmpty() ) + emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3") + .arg(m_cdrecordBinObject->hasFeature( "wodim" ) ? "Wodim" : "Cdrecord" ) + .arg(m_cdrecordBinObject->version) + .arg(m_cdrecordBinObject->copyright), INFO ); + + + kdDebug() << "***** " << m_cdrecordBinObject->name() << " parameters:\n"; + const QValueList<QCString>& args = m_process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << flush << endl; + emit debuggingOutput( m_cdrecordBinObject->name() + " command:", s); + + m_currentTrack = 0; + m_cdrecordError = UNKNOWN; + m_totalTracksParsed = false; + m_alreadyWritten = 0; + d->tracks.clear(); + m_totalSize = 0; + + emit newSubTask( i18n("Preparing write process...") ); + + // FIXME: check the return value + if( K3b::isMounted( burnDevice() ) ) { + emit infoMessage( i18n("Unmounting medium"), INFO ); + K3b::unmount( burnDevice() ); + } + + // block the device (including certain checks) + k3bcore->blockDevice( burnDevice() ); + + // lock the device for good in this process since it will + // be opened in the growisofs process + burnDevice()->close(); + burnDevice()->usageLock(); + + if( !m_process->start( KProcess::NotifyOnExit, KProcess::All ) ) { + // something went wrong when starting the program + // it "should" be the executable + kdDebug() << "(K3bCdrecordWriter) could not start " << m_cdrecordBinObject->name() << endl; + emit infoMessage( i18n("Could not start %1.").arg(m_cdrecordBinObject->name()), K3bJob::ERROR ); + jobFinished(false); + } + else { + if( simulate() ) { + emit newTask( i18n("Simulating") ); + emit infoMessage( i18n("Starting %1 simulation at %2x speed...") + .arg(K3b::writingModeString(m_writingMode)) + .arg(d->usedSpeed), + K3bJob::INFO ); + } + else { + emit newTask( i18n("Writing") ); + emit infoMessage( i18n("Starting %1 writing at %2x speed...") + .arg(K3b::writingModeString(m_writingMode)) + .arg(d->usedSpeed), + K3bJob::INFO ); + } + } +} + + +void K3bCdrecordWriter::cancel() +{ + if( active() ) { + d->canceled = true; + if( m_process && m_process->isRunning() ) + m_process->kill(); + } +} + + +void K3bCdrecordWriter::slotStdLine( const QString& line ) +{ + static QRegExp s_burnfreeCounterRx( "^BURN\\-Free\\swas\\s(\\d+)\\stimes\\sused" ); + static QRegExp s_burnfreeCounterRxPredict( "^Total\\sof\\s(\\d+)\\s\\spossible\\sbuffer\\sunderruns\\spredicted" ); + + // tracknumber: cap(1) + // done: cap(2) + // complete: cap(3) + // fifo: cap(4) (it seems as if some patched cdrecord versions do not emit the fifo info but only the buf... :( + // buffer: cap(5) + static QRegExp s_progressRx( "Track\\s(\\d\\d)\\:\\s*(\\d*)\\sof\\s*(\\d*)\\sMB\\swritten\\s(?:\\(fifo\\s*(\\d*)\\%\\)\\s*)?(?:\\[buf\\s*(\\d*)\\%\\])?.*" ); + + emit debuggingOutput( m_cdrecordBinObject->name(), line ); + + // + // Progress and toc parsing + // + + if( line.startsWith( "Track " ) ) { + if( !m_totalTracksParsed ) { + // this is not the progress display but the list of tracks that will get written + // we always extract the tracknumber to get the highest at last + bool ok; + int tt = line.mid( 6, 2 ).toInt(&ok); + + if( ok ) { + struct Private::Track track; + track.audio = ( line.mid( 10, 5 ) == "audio" ); + + m_totalTracks = tt; + + int sizeStart = line.find( QRegExp("\\d"), 10 ); + int sizeEnd = line.find( "MB", sizeStart ); + track.size = line.mid( sizeStart, sizeEnd-sizeStart ).toInt(&ok); + + if( ok ) { + d->tracks.append(track); + m_totalSize += track.size; + } + else + kdDebug() << "(K3bCdrecordWriter) track number parse error: " + << line.mid( sizeStart, sizeEnd-sizeStart ) << endl; + } + else + kdDebug() << "(K3bCdrecordWriter) track number parse error: " + << line.mid( 6, 2 ) << endl; + } + + else if( s_progressRx.exactMatch( line ) ) { + // int num = s_progressRx.cap(1).toInt(); + int made = s_progressRx.cap(2).toInt(); + int size = s_progressRx.cap(3).toInt(); + int fifo = s_progressRx.cap(4).toInt(); + + emit buffer( fifo ); + m_lastFifoValue = fifo; + + if( s_progressRx.numCaptures() > 4 ) + emit deviceBuffer( s_progressRx.cap(5).toInt() ); + + // + // cdrecord's output sucks a bit. + // we get track sizes that differ from the sizes in the progress + // info since these are dependant on the writing mode. + // so we just use the track sizes and do a bit of math... + // + + if( d->tracks.count() > m_currentTrack-1 && size > 0 ) { + double convV = (double)d->tracks[m_currentTrack-1].size/(double)size; + made = (int)((double)made * convV); + size = d->tracks[m_currentTrack-1].size; + } + else { + kdError() << "(K3bCdrecordWriter) Did not parse all tracks sizes!" << endl; + } + + if( size > 0 ) { + emit processedSubSize( made, size ); + emit subPercent( 100*made/size ); + } + + if( m_totalSize > 0 ) { + emit processedSize( m_alreadyWritten+made, m_totalSize ); + emit percent( 100*(m_alreadyWritten+made)/m_totalSize ); + } + + d->speedEst->dataWritten( (m_alreadyWritten+made)*1024 ); + } + } + + // + // Cdrecord starts all error and warning messages with it's path + // With Debian's script it starts with cdrecord (or /usr/bin/cdrecord or whatever! I hate this script!) + // + + else if( line.startsWith( "cdrecord" ) || + line.startsWith( m_cdrecordBinObject->path ) || + line.startsWith( m_cdrecordBinObject->path.left(m_cdrecordBinObject->path.length()-5) ) ) { + // get rid of the path and the following colon and space + QString errStr = line.mid( line.find(':') + 2 ); + + if( errStr.startsWith( "Drive does not support SAO" ) ) { + emit infoMessage( i18n("DAO (Disk At Once) recording not supported with this writer"), K3bJob::ERROR ); + emit infoMessage( i18n("Please choose TAO (Track At Once) and try again"), K3bJob::ERROR ); + } + else if( errStr.startsWith( "Drive does not support RAW" ) ) { + emit infoMessage( i18n("RAW recording not supported with this writer"), K3bJob::ERROR ); + } + else if( errStr.startsWith("Input/output error.") ) { + emit infoMessage( i18n("Input/output error. Not necessarily serious."), WARNING ); + } + else if( errStr.startsWith("shmget failed") ) { + m_cdrecordError = SHMGET_FAILED; + } + else if( errStr.startsWith("OPC failed") ) { + m_cdrecordError = OPC_FAILED; + } + else if( errStr.startsWith( "Drive needs to reload the media" ) ) { + emit infoMessage( i18n("Reloading of medium required"), K3bJob::INFO ); + } + else if( errStr.startsWith( "The current problem looks like a buffer underrun" ) ) { + if( m_cdrecordError == UNKNOWN ) // it is almost never a buffer underrun these days. + m_cdrecordError = BUFFER_UNDERRUN; + } + else if( errStr.startsWith("WARNING: Data may not fit") ) { + bool overburn = k3bcore->globalSettings()->overburn(); + if( overburn && m_cdrecordBinObject->hasFeature("overburn") ) + emit infoMessage( i18n("Trying to write more than the official disk capacity"), K3bJob::WARNING ); + m_cdrecordError = OVERSIZE; + } + else if( errStr.startsWith("Bad Option") ) { + m_cdrecordError = BAD_OPTION; + // parse option + int pos = line.find( "Bad Option" ) + 13; + int len = line.length() - pos - 1; + emit infoMessage( i18n("No valid %1 option: %2").arg(m_cdrecordBinObject->name()).arg(line.mid(pos, len)), + ERROR ); + } + else if( errStr.startsWith("Cannot set speed/dummy") ) { + m_cdrecordError = CANNOT_SET_SPEED; + } + else if( errStr.startsWith("Cannot open new session") ) { + m_cdrecordError = CANNOT_OPEN_NEW_SESSION; + } + else if( errStr.startsWith("Cannot send CUE sheet") ) { + m_cdrecordError = CANNOT_SEND_CUE_SHEET; + } + else if( errStr.startsWith( "Trying to use ultra high speed" ) || + errStr.startsWith( "Trying to use high speed" ) || + errStr.startsWith( "Probably trying to use ultra high speed" ) || + errStr.startsWith( "You did use a high speed medium on an improper writer" ) || + errStr.startsWith( "You did use a ultra high speed medium on an improper writer" ) ) { + m_cdrecordError = HIGH_SPEED_MEDIUM; + } + else if( errStr.startsWith( "You may have used an ultra low speed medium" ) ) { + m_cdrecordError = LOW_SPEED_MEDIUM; + } + else if( errStr.startsWith( "Permission denied. Cannot open" ) || + errStr.startsWith( "Operation not permitted." ) ) { + m_cdrecordError = PERMISSION_DENIED; + } + else if( errStr.startsWith( "Can only copy session # 1") ) { + emit infoMessage( i18n("Only session 1 will be cloned."), WARNING ); + } + else if( errStr == "Cannot fixate disk." ) { + emit infoMessage( i18n("Unable to fixate the disk."), ERROR ); + if( m_cdrecordError == UNKNOWN ) + m_cdrecordError = CANNOT_FIXATE_DISK; + } + else if( errStr == "A write error occurred." ) { + m_cdrecordError = WRITE_ERROR; + } + else if( errStr.startsWith( "Try again with cdrecord blank=all." ) ) { + m_cdrecordError = BLANK_FAILED; + } + } + + // + // All other messages + // + + else if( line.contains( "at speed" ) ) { + // parse the speed and inform the user if cdrdao switched it down + int pos = line.find( "at speed" ); + int pos2 = line.find( "in", pos+9 ); + int speed = static_cast<int>( line.mid( pos+9, pos2-pos-10 ).toDouble() ); // cdrecord-dvd >= 2.01a25 uses 8.0 and stuff + if( speed != d->usedSpeed ) { + emit infoMessage( i18n("Medium or burner do not support writing at %1x speed").arg(d->usedSpeed), K3bJob::WARNING ); + if( speed > d->usedSpeed ) + emit infoMessage( i18n("Switching burn speed up to %1x").arg(speed), K3bJob::WARNING ); + else + emit infoMessage( i18n("Switching burn speed down to %1x").arg(speed), K3bJob::WARNING ); + } + } + else if( line.startsWith( "Starting new" ) ) { + m_totalTracksParsed = true; + if( m_currentTrack > 0 ) {// nothing has been written at the start of track 1 + if( d->tracks.count() > m_currentTrack-1 ) + m_alreadyWritten += d->tracks[m_currentTrack-1].size; + else + kdError() << "(K3bCdrecordWriter) Did not parse all tracks sizes!" << endl; + } + else + emit infoMessage( i18n("Starting disc write"), INFO ); + + m_currentTrack++; + + if( m_currentTrack > d->tracks.count() ) { + kdDebug() << "(K3bCdrecordWriter) need to add dummy track struct." << endl; + struct Private::Track t; + t.size = 1; + t.audio = false; + d->tracks.append(t); + } + + kdDebug() << "(K3bCdrecordWriter) writing track " << m_currentTrack << " of " << m_totalTracks << " tracks." << endl; + emit nextTrack( m_currentTrack, m_totalTracks ); + } + else if( line.startsWith( "Fixating" ) ) { + emit newSubTask( i18n("Closing Session") ); + } + else if( line.startsWith( "Writing lead-in" ) ) { + m_totalTracksParsed = true; + emit newSubTask( i18n("Writing Leadin") ); + } + else if( line.startsWith( "Writing Leadout") ) { + emit newSubTask( i18n("Writing Leadout") ); + } + else if( line.startsWith( "Writing pregap" ) ) { + emit newSubTask( i18n("Writing pregap") ); + } + else if( line.startsWith( "Performing OPC" ) ) { + emit infoMessage( i18n("Performing Optimum Power Calibration"), K3bJob::INFO ); + } + else if( line.startsWith( "Sending" ) ) { + emit infoMessage( i18n("Sending CUE sheet"), K3bJob::INFO ); + } + else if( line.startsWith( "Turning BURN-Free on" ) || line.startsWith( "BURN-Free is ON") ) { + emit infoMessage( i18n("Enabled Burnfree"), K3bJob::INFO ); + } + else if( line.startsWith( "Turning BURN-Free off" ) ) { + emit infoMessage( i18n("Disabled Burnfree"), K3bJob::WARNING ); + } + else if( line.startsWith( "Re-load disk and hit" ) ) { + // this happens on some notebooks where cdrecord is not able to close the + // tray itself, so we need to ask the user to do so + blockingInformation( i18n("Please reload the medium and press 'ok'"), + i18n("Unable to close the tray") ); + + // now send a <CR> to cdrecord + // hopefully this will do it since I have no possibility to test it! + ::write( fd(), "\n", 1 ); + } + else if( s_burnfreeCounterRx.search( line ) ) { + bool ok; + int num = s_burnfreeCounterRx.cap(1).toInt(&ok); + if( ok ) + emit infoMessage( i18n("Burnfree was used 1 time.", "Burnfree was used %n times.", num), INFO ); + } + else if( s_burnfreeCounterRxPredict.search( line ) ) { + bool ok; + int num = s_burnfreeCounterRxPredict.cap(1).toInt(&ok); + if( ok ) + emit infoMessage( i18n("Buffer was low 1 time.", "Buffer was low %n times.", num), INFO ); + } + else if( line.contains("Medium Error") ) { + m_cdrecordError = MEDIUM_ERROR; + } + else if( line.startsWith( "Error trying to open" ) && line.contains( "(Device or resource busy)" ) ) { + m_cdrecordError = DEVICE_BUSY; + } + else { + // debugging + kdDebug() << "(" << m_cdrecordBinObject->name() << ") " << line << endl; + } +} + + +void K3bCdrecordWriter::slotProcessExited( KProcess* p ) +{ + // remove temporary cdtext file + delete d->cdTextFile; + d->cdTextFile = 0; + + // release the device within this process + burnDevice()->usageUnlock(); + + // unblock the device + k3bcore->unblockDevice( burnDevice() ); + + if( d->canceled ) { + // this will unblock and eject the drive and emit the finished/canceled signals + K3bAbstractWriter::cancel(); + return; + } + + + if( p->normalExit() ) { + switch( p->exitStatus() ) { + case 0: + { + if( simulate() ) + emit infoMessage( i18n("Simulation successfully completed"), K3bJob::SUCCESS ); + else + emit infoMessage( i18n("Writing successfully completed"), K3bJob::SUCCESS ); + + int s = d->speedEst->average(); + emit infoMessage( i18n("Average overall write speed: %1 KB/s (%2x)").arg(s).arg(KGlobal::locale()->formatNumber((double)s/150.0), 2), INFO ); + + jobFinished( true ); + } + break; + + default: + kdDebug() << "(K3bCdrecordWriter) error: " << p->exitStatus() << endl; + + if( m_cdrecordError == UNKNOWN && m_lastFifoValue <= 3 ) + m_cdrecordError = BUFFER_UNDERRUN; + + switch( m_cdrecordError ) { + case OVERSIZE: + if( k3bcore->globalSettings()->overburn() && + m_cdrecordBinObject->hasFeature("overburn") ) + emit infoMessage( i18n("Data did not fit on disk."), ERROR ); + else { + emit infoMessage( i18n("Data does not fit on disk."), ERROR ); + if( m_cdrecordBinObject->hasFeature("overburn") ) + emit infoMessage( i18n("Enable overburning in the advanced K3b settings to burn anyway."), INFO ); + } + break; + case BAD_OPTION: + // error message has already been emited earlier since we needed the actual line + break; + case SHMGET_FAILED: + emit infoMessage( i18n("%1 could not reserve shared memory segment of requested size.").arg(m_cdrecordBinObject->name()), ERROR ); + emit infoMessage( i18n("Probably you chose a too large buffer size."), ERROR ); + break; + case OPC_FAILED: + emit infoMessage( i18n("OPC failed. Probably the writer does not like the medium."), ERROR ); + break; + case CANNOT_SET_SPEED: + emit infoMessage( i18n("Unable to set write speed to %1.").arg(d->usedSpeed), ERROR ); + emit infoMessage( i18n("Probably this is lower than your writer's lowest writing speed."), ERROR ); + break; + case CANNOT_SEND_CUE_SHEET: + emit infoMessage( i18n("Unable to send CUE sheet."), ERROR ); + if( m_writingMode == K3b::DAO ) + emit infoMessage( i18n("Sometimes using TAO writing mode solves this issue."), ERROR ); + break; + case CANNOT_OPEN_NEW_SESSION: + emit infoMessage( i18n("Unable to open new session."), ERROR ); + emit infoMessage( i18n("Probably a problem with the medium."), ERROR ); + break; + case CANNOT_FIXATE_DISK: + emit infoMessage( i18n("The disk might still be readable."), ERROR ); + if( m_writingMode == K3b::TAO && burnDevice()->dao() ) + emit infoMessage( i18n("Try DAO writing mode."), ERROR ); + break; + case PERMISSION_DENIED: + emit infoMessage( i18n("%1 has no permission to open the device.").arg("Cdrecord"), ERROR ); +#ifdef HAVE_K3BSETUP + emit infoMessage( i18n("You may use K3bsetup2 to solve this problem."), ERROR ); +#endif + break; + case BUFFER_UNDERRUN: + emit infoMessage( i18n("Probably a buffer underrun occurred."), ERROR ); + if( !d->usingBurnfree && burnDevice()->burnproof() ) + emit infoMessage( i18n("Please enable Burnfree or choose a lower burning speed."), ERROR ); + else + emit infoMessage( i18n("Please choose a lower burning speed."), ERROR ); + break; + case HIGH_SPEED_MEDIUM: + emit infoMessage( i18n("Found a high-speed medium not suitable for the writer being used."), ERROR ); + emit infoMessage( i18n("Use the 'force unsafe operations' option to ignore this."), ERROR ); + break; + case LOW_SPEED_MEDIUM: + emit infoMessage( i18n("Found a low-speed medium not suitable for the writer being used."), ERROR ); + emit infoMessage( i18n("Use the 'force unsafe operations' option to ignore this."), ERROR ); + break; + case MEDIUM_ERROR: + emit infoMessage( i18n("Most likely the burning failed due to low-quality media."), ERROR ); + break; + case DEVICE_BUSY: + emit infoMessage( i18n("Another application is blocking the device (most likely automounting)."), ERROR ); + break; + case WRITE_ERROR: + emit infoMessage( i18n("A write error occurred."), ERROR ); + if( m_writingMode == K3b::DAO ) + emit infoMessage( i18n("Sometimes using TAO writing mode solves this issue."), ERROR ); + break; + case BLANK_FAILED: + emit infoMessage( i18n("Some drives do not support all erase types."), ERROR ); + emit infoMessage( i18n("Try again using 'Complete' erasing."), ERROR ); + break; + case UNKNOWN: + if( p->exitStatus() == 12 && K3b::kernelVersion() >= K3bVersion( 2, 6, 8 ) && m_cdrecordBinObject->hasFeature( "suidroot" ) ) { + emit infoMessage( i18n("Since kernel version 2.6.8 cdrecord cannot use SCSI transport when running suid root anymore."), ERROR ); + emit infoMessage( i18n("You may use K3bSetup to solve this problem or remove the suid bit manually."), ERROR ); + } + else if( !wasSourceUnreadable() ) { + emit infoMessage( i18n("%1 returned an unknown error (code %2).") + .arg(m_cdrecordBinObject->name()).arg(p->exitStatus()), + K3bJob::ERROR ); + + if( p->exitStatus() >= 254 && m_writingMode == K3b::DAO ) { + emit infoMessage( i18n("Sometimes using TAO writing mode solves this issue."), ERROR ); + } + else { + emit infoMessage( i18n("If you are running an unpatched cdrecord version..."), ERROR ); + emit infoMessage( i18n("...and this error also occurs with high quality media..."), ERROR ); + emit infoMessage( i18n("...and the K3b FAQ does not help you..."), ERROR ); + emit infoMessage( i18n("...please include the debugging output in your problem report."), ERROR ); + } + } + break; + } + jobFinished( false ); + } + } + else { + emit infoMessage( i18n("%1 did not exit cleanly.").arg(m_cdrecordBinObject->name()), + ERROR ); + jobFinished( false ); + } +} + + +void K3bCdrecordWriter::slotThroughput( int t ) +{ + emit writeSpeed( t, d->tracks[m_currentTrack-1].audio ? 175 : 150 ); +} + +#include "k3bcdrecordwriter.moc" diff --git a/libk3b/projects/k3bcdrecordwriter.h b/libk3b/projects/k3bcdrecordwriter.h new file mode 100644 index 0000000..9333588 --- /dev/null +++ b/libk3b/projects/k3bcdrecordwriter.h @@ -0,0 +1,123 @@ +/* + * + * $Id: k3bcdrecordwriter.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_CDRECORD_WRITER_H +#define K3B_CDRECORD_WRITER_H + + +#include "k3babstractwriter.h" + +#include <qstringlist.h> + +class K3bExternalBin; +class K3bProcess; +class KProcess; +class K3bDevice::Device; + + +class K3bCdrecordWriter : public K3bAbstractWriter +{ + Q_OBJECT + + public: + K3bCdrecordWriter( K3bDevice::Device*, K3bJobHandler* hdl, + QObject* parent = 0, const char* name = 0 ); + ~K3bCdrecordWriter(); + + bool active() const; + + /** + * to be used in chain: addArgument(x)->addArgument(y) + */ + K3bCdrecordWriter* addArgument( const QString& ); + void clearArguments(); + + int fd() const; + + public slots: + void start(); + void cancel(); + + void setDao( bool b ); + void setWritingMode( int ); + void setCueFile( const QString& s); + void setClone( bool b ); + + void setRawCdText( const QByteArray& a ) { m_rawCdText = a; } + + /** + * If set true the job ignores the global K3b setting + * and does not eject the CD-RW after finishing + */ + void setForceNoEject( bool b ) { m_forceNoEject = b; } + + protected slots: + void slotStdLine( const QString& line ); + void slotProcessExited(KProcess*); + void slotThroughput( int t ); + + protected: + virtual void prepareProcess(); + + const K3bExternalBin* m_cdrecordBinObject; + K3bProcess* m_process; + + int m_writingMode; + bool m_totalTracksParsed; + bool m_clone; + bool m_cue; + + QString m_cueFile; + + enum CdrecordError { UNKNOWN, + OVERSIZE, + BAD_OPTION, + SHMGET_FAILED, + OPC_FAILED, + CANNOT_SET_SPEED, + CANNOT_SEND_CUE_SHEET, + CANNOT_OPEN_NEW_SESSION, + CANNOT_FIXATE_DISK, + WRITE_ERROR, + PERMISSION_DENIED, + BUFFER_UNDERRUN, + HIGH_SPEED_MEDIUM, + LOW_SPEED_MEDIUM, + MEDIUM_ERROR, + DEVICE_BUSY, + BLANK_FAILED }; + + QStringList m_arguments; + + private: + unsigned int m_currentTrack; + int m_totalTracks; + int m_totalSize; + int m_alreadyWritten; + + int m_lastFifoValue; + + int m_cdrecordError; + + bool m_forceNoEject; + + QByteArray m_rawCdText; + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/k3bcuefileparser.cpp b/libk3b/projects/k3bcuefileparser.cpp new file mode 100644 index 0000000..49ca4fc --- /dev/null +++ b/libk3b/projects/k3bcuefileparser.cpp @@ -0,0 +1,461 @@ +/* + * + * $Id: k3bcuefileparser.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 "k3bcuefileparser.h" + +#include <k3bmsf.h> +#include <k3bglobals.h> +#include <k3btrack.h> +#include <k3bcdtext.h> + +#include <qfile.h> +#include <qfileinfo.h> +#include <qtextstream.h> +#include <qregexp.h> +#include <qdir.h> + +#include <kdebug.h> + + +// avoid usage of QTextStream since K3b often +// tries to open big files (iso images) in a +// cue file parser to test it. +static QString readLine( QFile* f ) +{ + QString s; + Q_LONG r = f->readLine( s, 1024 ); + if( r >= 0 ) { + // remove the trailing newline + return s.stripWhiteSpace(); + } + else { + // end of file or error + return QString::null; + } +} + + +// TODO: add method: usableByCdrecordDirectly() +// TODO: add Toc with sector sizes + +class K3bCueFileParser::Private +{ +public: + bool inFile; + bool inTrack; + int trackType; + int trackMode; + bool rawData; + bool haveIndex1; + K3b::Msf currentDataPos; + K3b::Msf index0; + + K3bDevice::Toc toc; + int currentParsedTrack; + + K3bDevice::CdText cdText; +}; + + + +K3bCueFileParser::K3bCueFileParser( const QString& filename ) + : K3bImageFileReader() +{ + d = new Private; + openFile( filename ); +} + + +K3bCueFileParser::~K3bCueFileParser() +{ + delete d; +} + + +void K3bCueFileParser::readFile() +{ + setValid(true); + + d->inFile = d->inTrack = d->haveIndex1 = false; + d->trackMode = K3bDevice::Track::UNKNOWN; + d->toc.clear(); + d->cdText.clear(); + d->currentParsedTrack = 0; + + QFile f( filename() ); + if( f.open( IO_ReadOnly ) ) { + QString line = readLine( &f ); + while( !line.isNull() ) { + + if( !parseLine(line) ) { + setValid(false); + break; + } + + line = readLine( &f ); + } + + if( isValid() ) { + // save last parsed track for which we do not have the proper length :( + if( d->currentParsedTrack > 0 ) { + d->toc.append( K3bDevice::Track( d->currentDataPos, + d->currentDataPos, + d->trackType, + d->trackMode ) ); + } + + // debug the toc + kdDebug() << "(K3bCueFileParser) successfully parsed cue file." << endl + << "------------------------------------------------" << endl; + for( unsigned int i = 0; i < d->toc.count(); ++i ) { + K3bDevice::Track& track = d->toc[i]; + kdDebug() << "Track " << (i+1) + << " (" << ( track.type() == K3bDevice::Track::AUDIO ? "audio" : "data" ) << ") " + << track.firstSector().toString() << " - " << track.lastSector().toString() << endl; + } + + kdDebug() << "------------------------------------------------" << endl; + } + } + else { + kdDebug() << "(K3bCueFileParser) could not open file " << filename() << endl; + setValid(false); + } +} + + +bool K3bCueFileParser::parseLine( QString& line ) +{ + // use cap(1) for the filename + static QRegExp fileRx( "FILE\\s\"?([^\"]*)\"?\\s[^\"\\s]*" ); + + // use cap(1) for the flags + static QRegExp flagsRx( "FLAGS(\\s(DCP|4CH|PRE|SCMS)){1,4}" ); + + // use cap(1) for the tracknumber and cap(2) for the datatype + static QRegExp trackRx( "TRACK\\s(\\d{1,2})\\s(AUDIO|CDG|MODE1/2048|MODE1/2352|MODE2/2336|MODE2/2352|CDI/2336|CDI/2352)" ); + + // use cap(1) for the index number, cap(3) for the minutes, cap(4) for the seconds, cap(5) for the frames, + // and cap(2) for the MSF value string + static QRegExp indexRx( "INDEX\\s(\\d{1,2})\\s((\\d+):([0-5]\\d):((?:[0-6]\\d)|(?:7[0-4])))" ); + + // use cap(1) for the MCN + static QRegExp catalogRx( "CATALOG\\s(\\w{13,13})" ); + + // use cap(1) for the ISRC + static QRegExp isrcRx( "ISRC\\s(\\w{5,5}\\d{7,7})" ); + + static QString cdTextRxStr = "\"?([^\"]{0,80})\"?"; + + // use cap(1) for the string + static QRegExp titleRx( "TITLE\\s" + cdTextRxStr ); + static QRegExp performerRx( "PERFORMER\\s" + cdTextRxStr ); + static QRegExp songwriterRx( "SONGWRITER\\s" + cdTextRxStr ); + + + // simplify all white spaces except those in filenames and CD-TEXT + simplifyWhiteSpace( line ); + + // skip comments and empty lines + if( line.startsWith("REM") || line.startsWith("#") || line.isEmpty() ) + return true; + + + // + // FILE + // + if( fileRx.exactMatch( line ) ) { + + setValid( findImageFileName( fileRx.cap(1) ) ); + + if( d->inFile ) { + kdDebug() << "(K3bCueFileParser) only one FILE statement allowed." << endl; + return false; + } + d->inFile = true; + d->inTrack = false; + d->haveIndex1 = false; + return true; + } + + + // + // TRACK + // + else if( trackRx.exactMatch( line ) ) { + if( !d->inFile ) { + kdDebug() << "(K3bCueFileParser) TRACK statement before FILE." << endl; + return false; + } + + // check if we had index1 for the last track + if( d->inTrack && !d->haveIndex1 ) { + kdDebug() << "(K3bCueFileParser) TRACK without INDEX 1." << endl; + return false; + } + + // save last track + // TODO: use d->rawData in some way + if( d->currentParsedTrack > 0 ) { + d->toc.append( K3bDevice::Track( d->currentDataPos, + d->currentDataPos, + d->trackType, + d->trackMode ) ); + } + + d->currentParsedTrack++; + + d->cdText.resize( d->currentParsedTrack ); + + // parse the tracktype + if( trackRx.cap(2) == "AUDIO" ) { + d->trackType = K3bDevice::Track::AUDIO; + d->trackMode = K3bDevice::Track::UNKNOWN; + } + else { + d->trackType = K3bDevice::Track::DATA; + if( trackRx.cap(2).startsWith("MODE1") ) { + d->trackMode = K3bDevice::Track::MODE1; + d->rawData = (trackRx.cap(2) == "MODE1/2352"); + } + else if( trackRx.cap(2).startsWith("MODE2") ) { + d->trackMode = K3bDevice::Track::MODE2; + d->rawData = (trackRx.cap(2) == "MODE2/2352"); + } + else { + kdDebug() << "(K3bCueFileParser) unsupported track type: " << trackRx.cap(2) << endl; + return false; + } + } + + d->haveIndex1 = false; + d->inTrack = true; + d->index0 = 0; + + return true; + } + + + // + // FLAGS + // + else if( flagsRx.exactMatch( line ) ) { + if( !d->inTrack ) { + kdDebug() << "(K3bCueFileParser) FLAGS statement without TRACK." << endl; + return false; + } + + // TODO: save the flags + return true; + } + + + // + // INDEX + // + else if( indexRx.exactMatch( line ) ) { + if( !d->inTrack ) { + kdDebug() << "(K3bCueFileParser) INDEX statement without TRACK." << endl; + return false; + } + + unsigned int indexNumber = indexRx.cap(1).toInt(); + + K3b::Msf indexStart = K3b::Msf::fromString( indexRx.cap(2) ); + + if( indexNumber == 0 ) { + d->index0 = indexStart; + + if( d->currentParsedTrack < 2 && indexStart > 0 ) { + kdDebug() << "(K3bCueFileParser) first track is not allowed to have a pregap > 0." << endl; + return false; + } + } + else if( indexNumber == 1 ) { + d->haveIndex1 = true; + d->currentDataPos = indexStart; + if( d->currentParsedTrack > 1 ) { + d->toc[d->currentParsedTrack-2].setLastSector( indexStart-1 ); + if( d->index0 > 0 && d->index0 < indexStart ) { + d->toc[d->currentParsedTrack-2].setIndex0( d->index0 - d->toc[d->currentParsedTrack-2].firstSector() ); + } + } + } + else { + // TODO: add index > 0 + } + + return true; + } + + + // + // CATALOG + // + if( catalogRx.exactMatch( line ) ) { + // TODO: set the toc's mcn + return true; + } + + + // + // ISRC + // + if( isrcRx.exactMatch( line ) ) { + if( d->inTrack ) { + // TODO: set the track's ISRC + return true; + } + else { + kdDebug() << "(K3bCueFileParser) ISRC without TRACK." << endl; + return false; + } + } + + + // + // CD-TEXT + // TODO: create K3bDevice::TrackCdText entries + // + else if( titleRx.exactMatch( line ) ) { + if( d->inTrack ) + d->cdText[d->currentParsedTrack-1].setTitle( titleRx.cap(1) ); + else + d->cdText.setTitle( titleRx.cap(1) ); + return true; + } + + else if( performerRx.exactMatch( line ) ) { + if( d->inTrack ) + d->cdText[d->currentParsedTrack-1].setPerformer( performerRx.cap(1) ); + else + d->cdText.setPerformer( performerRx.cap(1) ); + return true; + } + + else if( songwriterRx.exactMatch( line ) ) { + if( d->inTrack ) + d->cdText[d->currentParsedTrack-1].setSongwriter( songwriterRx.cap(1) ); + else + d->cdText.setSongwriter( songwriterRx.cap(1) ); + return true; + } + + else { + kdDebug() << "(K3bCueFileParser) unknown Cue line: '" << line << "'" << endl; + return false; + } +} + + +void K3bCueFileParser::simplifyWhiteSpace( QString& s ) +{ + s = s.stripWhiteSpace(); + + unsigned int i = 0; + bool insideQuote = false; + while( i < s.length() ) { + if( !insideQuote ) { + if( s[i].isSpace() && s[i+1].isSpace() ) + s.remove( i, 1 ); + } + + if( s[i] == '"' ) + insideQuote = !insideQuote; + + ++i; + } +} + + +const K3bDevice::Toc& K3bCueFileParser::toc() const +{ + return d->toc; +} + + +const K3bDevice::CdText& K3bCueFileParser::cdText() const +{ + return d->cdText; +} + + +bool K3bCueFileParser::findImageFileName( const QString& dataFile ) +{ + // + // CDRDAO does not use this image filename but replaces the extension from the cue file + // with "bin" to get the image filename, we should take this into account + // + + m_imageFilenameInCue = true; + + // first try filename as a hole (absolut) + if( QFile::exists( dataFile ) ) { + setImageFilename( QFileInfo(dataFile).absFilePath() ); + return true; + } + + // try the filename in the cue's directory + if( QFileInfo( K3b::parentDir(filename()) + dataFile.section( '/', -1 ) ).isFile() ) { + setImageFilename( K3b::parentDir(filename()) + dataFile.section( '/', -1 ) ); + kdDebug() << "(K3bCueFileParser) found image file: " << imageFilename() << endl; + return true; + } + + // try the filename ignoring case + if( QFileInfo( K3b::parentDir(filename()) + dataFile.section( '/', -1 ).lower() ).isFile() ) { + setImageFilename( K3b::parentDir(filename()) + dataFile.section( '/', -1 ).lower() ); + kdDebug() << "(K3bCueFileParser) found image file: " << imageFilename() << endl; + return true; + } + + m_imageFilenameInCue = false; + + // try removing the ending from the cue file (image.bin.cue and image.bin) + if( QFileInfo( filename().left( filename().length()-4 ) ).isFile() ) { + setImageFilename( filename().left( filename().length()-4 ) ); + kdDebug() << "(K3bCueFileParser) found image file: " << imageFilename() << endl; + return true; + } + + // + // we did not find the image specified in the cue. + // Search for another one having the same filename as the cue but a different extension + // + + QDir parentDir( K3b::parentDir(filename()) ); + QString filenamePrefix = filename().section( '/', -1 ); + filenamePrefix.truncate( filenamePrefix.length() - 3 ); // remove cue extension + kdDebug() << "(K3bCueFileParser) checking folder " << parentDir.path() << " for files: " << filenamePrefix << "*" << endl; + + // + // we cannot use the nameFilter in QDir because of the spaces that may occur in filenames + // + QStringList possibleImageFiles = parentDir.entryList( QDir::Files ); + int cnt = 0; + for( QStringList::const_iterator it = possibleImageFiles.constBegin(); it != possibleImageFiles.constEnd(); ++it ) { + if( (*it).lower() == dataFile.section( '/', -1 ).lower() || + (*it).startsWith( filenamePrefix ) && !(*it).endsWith( "cue" ) ) { + ++cnt; + setImageFilename( K3b::parentDir(filename()) + *it ); + } + } + + // + // we only do this if there is one unique file which fits the requirements. + // Otherwise we cannot be certain to have the right file. + // + return ( cnt == 1 && QFileInfo( imageFilename() ).isFile() ); +} diff --git a/libk3b/projects/k3bcuefileparser.h b/libk3b/projects/k3bcuefileparser.h new file mode 100644 index 0000000..41a5ee6 --- /dev/null +++ b/libk3b/projects/k3bcuefileparser.h @@ -0,0 +1,57 @@ +/* + * + * $Id: k3bcuefileparser.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_CUEFILE_PARSER_H_ +#define _K3B_CUEFILE_PARSER_H_ + +#include "k3bimagefilereader.h" + +#include <k3btoc.h> +#include <k3bcdtext.h> +#include "k3b_export.h" +/** + * Parses a cue file. + * Datatracks have either mode1 or mode2 where the latter contains xa form1/2. + * The last track may not have a proper length! + */ +class LIBK3B_EXPORT K3bCueFileParser : public K3bImageFileReader +{ + public: + K3bCueFileParser( const QString& filename = QString::null ); + ~K3bCueFileParser(); + + /** + * CDRDAO does not use this image filename but replaces the extension from the cue file + * with "bin" to get the image filename. + * So in this case cdrecord won't be able to burn the cue file. That is why we need this hack. + */ + bool imageFilenameInCue() const { return m_imageFilenameInCue; } + + const K3bDevice::Toc& toc() const; + const K3bDevice::CdText& cdText() const; + + private: + void readFile(); + bool parseLine( QString& line ); + void simplifyWhiteSpace( QString& s ); + bool findImageFileName( const QString& fileEntry ); + + bool m_imageFilenameInCue; + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/k3bdoc.cpp b/libk3b/projects/k3bdoc.cpp new file mode 100644 index 0000000..ac5346d --- /dev/null +++ b/libk3b/projects/k3bdoc.cpp @@ -0,0 +1,221 @@ +/* + * + * $Id: k3bdoc.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 files for Qt +#include <qwidget.h> +#include <qstring.h> +#include <qdom.h> + +// include files for KDE +#include <klocale.h> +#include <kdebug.h> + +// application specific includes +#include "k3bdoc.h" +#include <k3bglobals.h> +#include <k3bdevice.h> +#include <k3bmsf.h> +#include <k3baudiodoc.h> +#include <k3bdatadoc.h> +#include <k3bvcddoc.h> +#include <k3bmixeddoc.h> +#include <k3bmovixdoc.h> +#include <k3bmovixdvddoc.h> +#include <k3bdvddoc.h> +#include <k3bvideodvddoc.h> +#include <k3bcore.h> +#include <k3bdevicemanager.h> + + +K3bDoc::K3bDoc( QObject* parent ) + : QObject( parent ), + m_modified(false), + m_view(0) +{ + connect( this, SIGNAL(changed()), this, SLOT(slotChanged()) ); +} + + +K3bDoc::~K3bDoc() +{ +} + + +void K3bDoc::slotChanged() +{ + setModified( true ); + emit changed( this ); +} + + +void K3bDoc::setModified( bool m ) +{ + if( m != m_modified ) { + m_modified = m; + if( m ) + emit changed(); + } +} + + +void K3bDoc::setDummy( bool b ) +{ + m_dummy = b; +} + +void K3bDoc::setSpeed( int speed ) +{ + m_speed = speed; +} + +void K3bDoc::setBurner( K3bDevice::Device* dev ) +{ + m_burner = dev; +} + + +void K3bDoc::addUrl( const KURL& url ) +{ + KURL::List urls(url); + addUrls( urls ); +} + + +void K3bDoc::setURL( const KURL& url ) +{ + doc_url = url; + + emit changed(); +} + +const KURL& K3bDoc::URL() const +{ + return doc_url; +} + + +QString K3bDoc::name() const +{ + return URL().path().section( '/', -1 ); +} + + +bool K3bDoc::newDocument() +{ + setModified( false ); + + m_copies = 1; + m_burner = 0; + m_onTheFly = true; + m_speed = 0; // Auto + m_onlyCreateImages = false; + m_removeImages = true; + m_dummy = false; + m_writingApp = K3b::DEFAULT; + m_writingMode = K3b::WRITING_MODE_AUTO; + m_saved = false; + + return true; +} + + +bool K3bDoc::saveGeneralDocumentData( QDomElement* part ) +{ + QDomDocument doc = part->ownerDocument(); + QDomElement mainElem = doc.createElement( "general" ); + + QDomElement propElem = doc.createElement( "writing_mode" ); + switch( writingMode() ) { + case K3b::DAO: + propElem.appendChild( doc.createTextNode( "dao" ) ); + break; + case K3b::TAO: + propElem.appendChild( doc.createTextNode( "tao" ) ); + break; + case K3b::RAW: + propElem.appendChild( doc.createTextNode( "raw" ) ); + break; + default: + propElem.appendChild( doc.createTextNode( "auto" ) ); + break; + } + mainElem.appendChild( propElem ); + + propElem = doc.createElement( "dummy" ); + propElem.setAttribute( "activated", dummy() ? "yes" : "no" ); + mainElem.appendChild( propElem ); + + propElem = doc.createElement( "on_the_fly" ); + propElem.setAttribute( "activated", onTheFly() ? "yes" : "no" ); + mainElem.appendChild( propElem ); + + propElem = doc.createElement( "only_create_images" ); + propElem.setAttribute( "activated", onlyCreateImages() ? "yes" : "no" ); + mainElem.appendChild( propElem ); + + propElem = doc.createElement( "remove_images" ); + propElem.setAttribute( "activated", removeImages() ? "yes" : "no" ); + mainElem.appendChild( propElem ); + + part->appendChild( mainElem ); + + return true; +} + + +bool K3bDoc::readGeneralDocumentData( const QDomElement& elem ) +{ + if( elem.nodeName() != "general" ) + return false; + + QDomNodeList nodes = elem.childNodes(); + for( uint i = 0; i < nodes.count(); i++ ) { + + QDomElement e = nodes.item(i).toElement(); + if( e.isNull() ) + return false; + + if( e.nodeName() == "writing_mode") { + QString mode = e.text(); + if( mode == "dao" ) + setWritingMode( K3b::DAO ); + else if( mode == "tao" ) + setWritingMode( K3b::TAO ); + else if( mode == "raw" ) + setWritingMode( K3b::RAW ); + else + setWritingMode( K3b::WRITING_MODE_AUTO ); + } + + if( e.nodeName() == "dummy") + setDummy( e.attributeNode( "activated" ).value() == "yes" ); + + if( e.nodeName() == "on_the_fly") + setOnTheFly( e.attributeNode( "activated" ).value() == "yes" ); + + if( e.nodeName() == "only_create_images") + setOnlyCreateImages( e.attributeNode( "activated" ).value() == "yes" ); + + if( e.nodeName() == "remove_images") + setRemoveImages( e.attributeNode( "activated" ).value() == "yes" ); + } + + + return true; +} + + +#include "k3bdoc.moc" diff --git a/libk3b/projects/k3bdoc.h b/libk3b/projects/k3bdoc.h new file mode 100644 index 0000000..f241487 --- /dev/null +++ b/libk3b/projects/k3bdoc.h @@ -0,0 +1,229 @@ +/* + * + * $Id: k3bdoc.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 K3BDOC_H +#define K3BDOC_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +// include files for QT +#include <qobject.h> +#include <qstring.h> +#include <qptrlist.h> + + +// include files for KDE +#include <kurl.h> +#include <kio/global.h> +#include "k3b_export.h" + +// forward declaration of the K3b classes +class QTimer; +class KTempFile; +class K3bBurnJob; +class QDomDocument; +class QDomElement; +class KConfig; +class KActionCollection; +class K3bJobHandler; + + +namespace K3bDevice { + class Device; +} +namespace K3b { + class Msf; +} + +/** + * K3bDoc is the base document class. + * It handles some general settings. + */ +class LIBK3B_EXPORT K3bDoc : public QObject +{ + Q_OBJECT + + public: + K3bDoc( QObject* = 0 ); + virtual ~K3bDoc(); + + enum DocType { + AUDIO = 1, + DATA, + MIXED, + VCD, + MOVIX, + MOVIX_DVD, + DVD, + VIDEODVD + }; + + virtual int type() const { return m_docType; } + + /** + * \return A name for the project which might for example be used as a suggestion for a file name + * when saving. The default implementation extracts a name from the URL. + */ + virtual QString name() const; + + /** + * \return A string representation of the document type. + */ + virtual QString typeString() const = 0; + + /** + * returns the view widget set with setView() or null if none has been set. + */ + QWidget* view() const { return m_view; } + + /** + * Just for convenience to make an easy mapping from doc to GUI possible. + */ + void setView( QWidget* v ) { m_view = v; } + + /** + * sets the modified flag for the document after a modifying action on the view connected to the document. + */ + virtual void setModified( bool m = true ); + + /** + * returns if the document is modified or not. Use this to determine + * if your document needs saving by the user on closing. + */ + virtual bool isModified() const { return m_modified; } + + /** + * Subclasses should call this when reimplementing. + * Sets some defaults. + */ + virtual bool newDocument(); + + /** + * Load a project from an xml stream. + * + * This is used to load/save k3b projects. + */ + virtual bool loadDocumentData( QDomElement* root ) = 0; + + /** + * Save a project to an xml stream. + * + * This is used to load/save k3b projects. + */ + virtual bool saveDocumentData( QDomElement* docElem ) = 0; + + /** returns the KURL of the document */ + const KURL& URL() const; + /** sets the URL of the document */ + virtual void setURL( const KURL& url ); + + int writingMode() const { return m_writingMode; } + bool dummy() const { return m_dummy; } + bool onTheFly() const { return m_onTheFly; } + bool removeImages() const { return m_removeImages; } + bool onlyCreateImages() const { return m_onlyCreateImages; } + int copies() const { return m_copies; } + int speed() const { return m_speed; } + K3bDevice::Device* burner() const { return m_burner; } + virtual KIO::filesize_t size() const = 0; + virtual K3b::Msf length() const = 0; + + // FIXME: rename this to something like imagePath + const QString& tempDir() const { return m_tempDir; } + + virtual int numOfTracks() const { return 1; } + + /** + * Create a new BurnJob to burn this project. It is not mandatory to use this + * method. You may also just create the BurnJob you need manually. It is just + * easier this way since you don't need to distinguish between the different + * project types. + */ + virtual K3bBurnJob* newBurnJob( K3bJobHandler*, QObject* parent = 0 ) = 0; + + int writingApp() const { return m_writingApp; } + void setWritingApp( int a ) { m_writingApp = a; } + + /** + * @return true if the document has successfully been saved to a file + */ + bool isSaved() const { return m_saved; } + + /** + * Used for session management. Use with care. + */ + void setSaved( bool s ) { m_saved = s; } + + signals: + void changed(); + void changed( K3bDoc* ); + + public slots: + void setDummy( bool d ); + void setWritingMode( int m ) { m_writingMode = m; } + void setOnTheFly( bool b ) { m_onTheFly = b; } + void setSpeed( int speed ); + void setBurner( K3bDevice::Device* dev ); + void setTempDir( const QString& dir ) { m_tempDir = dir; } + void setRemoveImages( bool b ) { m_removeImages = b; } + void setOnlyCreateImages( bool b ) { m_onlyCreateImages = b; } + void setCopies( int c ) { m_copies = c; } + + /** + * the default implementation just calls addUrls with + * list containing the url + */ + virtual void addUrl( const KURL& url ); + virtual void addUrls( const KURL::List& urls ) = 0; + + protected: + int m_docType; + + bool saveGeneralDocumentData( QDomElement* ); + + bool readGeneralDocumentData( const QDomElement& ); + + private slots: + void slotChanged(); + + private: + /** the modified flag of the current document */ + bool m_modified; + KURL doc_url; + + QWidget* m_view; + + QString m_tempDir; + K3bDevice::Device* m_burner; + bool m_dummy; + bool m_onTheFly; + bool m_removeImages; + bool m_onlyCreateImages; + int m_speed; + + /** see k3bglobals.h */ + int m_writingApp; + + int m_writingMode; + + int m_copies; + + bool m_saved; +}; + +#endif // K3BDOC_H diff --git a/libk3b/projects/k3bdvdrecordwriter.cpp b/libk3b/projects/k3bdvdrecordwriter.cpp new file mode 100644 index 0000000..0910d4a --- /dev/null +++ b/libk3b/projects/k3bdvdrecordwriter.cpp @@ -0,0 +1,119 @@ +/* + * + * $Id: k3bdvdrecordwriter.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 "k3bdvdrecordwriter.h" + +#include <k3bcore.h> +#include <k3bexternalbinmanager.h> +#include <k3bprocess.h> +#include <k3bdevice.h> +#include <k3bdevicemanager.h> +#include <k3bglobals.h> +#include <k3bglobalsettings.h> + +#include <klocale.h> + + +K3bDvdrecordWriter::K3bDvdrecordWriter( K3bDevice::Device* dev, QObject* parent, const char* name ) + : K3bCdrecordWriter( dev, parent, name ) +{ +} + + +K3bDvdrecordWriter::~K3bDvdrecordWriter() +{ +} + +void K3bDvdrecordWriter::prepareProcess() +{ + if( m_process ) delete m_process; // kdelibs want this! + m_process = new K3bProcess(); + m_process->setRunPrivileged(true); + m_process->setSplitStdout(true); + connect( m_process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); + connect( m_process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotStdLine(const QString&)) ); + connect( m_process, SIGNAL(processExited(KProcess*)), this, SLOT(slotProcessExited(KProcess*)) ); + connect( m_process, SIGNAL(wroteStdin(KProcess*)), this, SIGNAL(dataWritten()) ); + +// if( k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "dvd-patch" ) ) +// m_cdrecordBinObject = k3bcore->externalBinManager()->binObject("cdrecord"); +// else + m_cdrecordBinObject = k3bcore->externalBinManager()->binObject("dvdrecord"); + + if( !m_cdrecordBinObject ) + return; + + *m_process << m_cdrecordBinObject->path; + + // display progress + *m_process << "-v"; + + if( m_cdrecordBinObject->hasFeature( "delay") ) + *m_process << "-delay" << "0"; + else if( m_cdrecordBinObject->hasFeature( "gracetime") ) + *m_process << "gracetime=2"; // 2 is the lowest allowed value (Joerg, why do you do this to us?) + + // Again we assume the device to be set! + *m_process << QString("dev=%1").arg(K3b::externalBinDeviceParameter(burnDevice(), m_cdrecordBinObject)); + *m_process << QString("speed=%1").arg(burnSpeed()); + + // DVDs are only written in DAO mode (and Packet, but we do not support that since it does not + // make much sense here) + *m_process << "-dao"; + setWritingMode( K3b::DAO ); // just to make sure the CdrecordWriter emits the correct messages + + if( simulate() ) + *m_process << "-dummy"; + + if( burnproof() ) { + if( burnDevice()->burnproof() ) { + // with cdrecord 1.11a02 burnproof was renamed to burnfree + // what about dvdrecord?? + if( m_cdrecordBinObject->version < K3bVersion( "1.11a02" ) ) + *m_process << "driveropts=burnproof"; + else + *m_process << "driveropts=burnfree"; + } + else + emit infoMessage( i18n("Writer does not support buffer underrun free recording (BURNPROOF)"), INFO ); + } + + if( k3bcore->globalSettings()->ejectMedia() ) + *m_process << "-eject"; + + bool manualBufferSize = k3bcore->globalSettings()->manualBufferSize(); + if( manualBufferSize ) { + *m_process << QString("fs=%1m").arg( k3bcore->globalSettings()->writingBuffer() ); + } + + bool overburn = k3bcore->globalSettings()->overburn(); + if( overburn ) + if( m_cdrecordBinObject->hasFeature("overburn") ) + *m_process << "-overburn"; + else + emit infoMessage( i18n("Cdrecord %1 does not support overburning.").arg(m_cdrecordBinObject->version), INFO ); + + // additional user parameters from config + const QStringList& params = m_cdrecordBinObject->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *m_process << *it; + + // add the user parameters + for( QStringList::const_iterator it = m_arguments.begin(); it != m_arguments.end(); ++it ) + *m_process << *it; +} + +#include "k3bdvdrecordwriter.moc" + diff --git a/libk3b/projects/k3bdvdrecordwriter.h b/libk3b/projects/k3bdvdrecordwriter.h new file mode 100644 index 0000000..f9dcf4a --- /dev/null +++ b/libk3b/projects/k3bdvdrecordwriter.h @@ -0,0 +1,40 @@ +/* + * + * $Id: k3bdvdrecordwriter.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_DVDRECORD_WRITER_H_ +#define _K3B_DVDRECORD_WRITER_H_ + +#include "k3bcdrecordwriter.h" + + +class K3bDevice::Device; + +/** + * Basically this is just a wrapper around K3bCdrecordWriter + * which uses another K3bExternalBin and ignores the writingMode setting. + */ +class K3bDvdrecordWriter : public K3bCdrecordWriter +{ + Q_OBJECT + + public: + K3bDvdrecordWriter( K3bDevice::Device*, QObject* parent = 0, const char* name = 0 ); + ~K3bDvdrecordWriter(); + + protected: + void prepareProcess(); +}; + +#endif diff --git a/libk3b/projects/k3bgrowisofshandler.cpp b/libk3b/projects/k3bgrowisofshandler.cpp new file mode 100644 index 0000000..0b582ce --- /dev/null +++ b/libk3b/projects/k3bgrowisofshandler.cpp @@ -0,0 +1,318 @@ +/* + * + * $Id: k3bgrowisofshandler.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 "k3bgrowisofshandler.h" + +#include <k3bjob.h> +#include <k3bcore.h> +#include <k3bglobalsettings.h> +#include <k3bdevice.h> +#include <k3bdevicehandler.h> + +#include <klocale.h> +#include <kglobal.h> +#include <kdebug.h> + +#include <qtimer.h> + +#include <errno.h> +#include <string.h> + + +class K3bGrowisofsHandler::Private +{ +public: + int lastBuffer; + int lastDeviceBuffer; +}; + + +K3bGrowisofsHandler::K3bGrowisofsHandler( QObject* parent, const char* name ) + : QObject( parent, name ) +{ + d = new Private; + reset(); +} + + +K3bGrowisofsHandler::~K3bGrowisofsHandler() +{ + delete d; +} + + +void K3bGrowisofsHandler::reset( K3bDevice::Device* dev, bool dao ) +{ + m_device = dev; + m_error = ERROR_UNKNOWN; + m_dao = dao; + d->lastBuffer = 0; + d->lastDeviceBuffer = 0; +} + + +void K3bGrowisofsHandler::handleStart() +{ +// QTimer::singleShot( 2000, this, SLOT(slotCheckBufferStatus()) ); +} + + +void K3bGrowisofsHandler::handleLine( const QString& line ) +{ + int pos = 0; + + if( line.startsWith( ":-[" ) ) { + // Error + + if( line.contains( "ASC=30h" ) ) + m_error = ERROR_MEDIA; + + // :-[ PERFORM OPC failed with SK=3h/ASC=73h/ASCQ=03h + else if( line.startsWith( ":-[ PERFORM OPC failed" ) ) + emit infoMessage( i18n("OPC failed. Please try writing speed 1x."), K3bJob::ERROR ); + + // :-[ attempt -blank=full or re-run with -dvd-compat -dvd-compat to engage DAO ] + else if( !m_dao && + ( line.contains( "engage DAO" ) || line.contains( "media is not formatted or unsupported" ) ) ) + emit infoMessage( i18n("Please try again with writing mode DAO."), K3bJob::ERROR ); + + else if( line.startsWith( ":-[ Failed to change write speed" ) ) { + m_error = ERROR_SPEED_SET_FAILED; + } + } + else if( line.startsWith( ":-(" ) ) { + if( line.contains( "No space left on device" ) ) + m_error = ERROR_OVERSIZE; + + else if( line.contains( "blocks are free" ) && line.contains( "to be written" ) ) { + m_error = ERROR_OVERSIZE; + if( k3bcore->globalSettings()->overburn() ) + emit infoMessage( i18n("Trying to write more than the official disk capacity"), K3bJob::WARNING ); + } + + else if( line.startsWith( ":-( unable to anonymously mmap" ) ) { + m_error = ERROR_MEMLOCK; + } + + else if( line.startsWith( ":-( write failed" ) ) { + m_error = ERROR_WRITE_FAILED; + } + + else + emit infoMessage( line, K3bJob::ERROR ); + } + else if( line.startsWith( "PERFORM OPC" ) ) { + m_error = ERROR_OPC; + } + else if( line.contains( "flushing cache" ) ) { + // here is where we already should stop queriying the buffer fill + // since the device is only used there so far... + m_device = 0; + + emit flushingCache(); + emit newSubTask( i18n("Flushing Cache") ); + emit infoMessage( i18n("Flushing the cache may take some time."), K3bJob::INFO ); + } + + // FIXME: I think this starts with dev->blockDeviceName() so we could improve parsing with: + // if( line.startsWith( dev->blockDeviceName() ) ) { + // line = line.mid( dev->blockDeviceName().length() ); + // if( line.startsWith( "closing..... + + else if( line.contains( "closing track" ) ) { + emit newSubTask( i18n("Closing Track") ); + } + else if( line.contains( "closing disc" ) ) { + emit newSubTask( i18n("Closing Disk") ); + } + else if( line.contains( "closing session" ) ) { + emit newSubTask( i18n("Closing Session") ); + } + else if( line.contains( "updating RMA" ) ) { + emit newSubTask( i18n("Updating RMA") ); + emit infoMessage( i18n("Updating RMA") + "...", K3bJob::INFO ); + } + else if( line.contains( "closing session" ) ) { + emit newSubTask( i18n("Closing Session") ); + emit infoMessage( i18n("Closing Session") + "...", K3bJob::INFO ); + } + else if( line.contains( "writing lead-out" ) ) { + emit newSubTask( i18n("Writing Lead-out") ); + emit infoMessage( i18n("Writing the lead-out may take some time."), K3bJob::INFO ); + } + else if( line.contains( "Quick Grow" ) ) { + emit infoMessage( i18n("Removing reference to lead-out."), K3bJob::INFO ); + } + else if( line.contains( "copying volume descriptor" ) ) { + emit infoMessage( i18n("Modifying ISO9660 volume descriptor"), K3bJob::INFO ); + } + else if( line.contains( "FEATURE 21h is not on" ) ) { + if( !m_dao ) { + // FIXME: it's not only the writer. It may be the media: something like does not support it with this media + // da war was mit: wenn einmal formattiert, dann geht nur noch dao oder wenn einmal als overwrite + // formattiert, dann nur noch dao oder sowas + emit infoMessage( i18n("Writing mode Incremental Streaming not available"), K3bJob::WARNING ); + emit infoMessage( i18n("Engaging DAO"), K3bJob::WARNING ); + } + } + else if( ( pos = line.find( "Current Write Speed" ) ) > 0 ) { + // parse write speed + // /dev/sr0: "Current Write Speed" is 2.4x1385KBps + + pos += 24; + int endPos = line.find( 'x', pos+1 ); + bool ok = true; + double speed = line.mid( pos, endPos-pos ).toDouble(&ok); + if( ok ) + emit infoMessage( i18n("Writing speed: %1 KB/s (%2x)") + .arg((int)(speed*1385.0)) + .arg(KGlobal::locale()->formatNumber(speed)), K3bJob::INFO ); + else + kdDebug() << "(K3bGrowisofsHandler) parsing error: '" << line.mid( pos, endPos-pos ) << "'" << endl; + } + else if( (pos = line.find( "RBU" )) > 0 ) { + + // FIXME: use QRegExp + + // parse ring buffer fill for growisofs >= 6.0 + pos += 4; + int endPos = line.find( '%', pos+1 ); + bool ok = true; + double val = line.mid( pos, endPos-pos ).toDouble( &ok ); + if( ok ) { + int newBuffer = (int)(val+0.5); + if( newBuffer != d->lastBuffer ) { + d->lastBuffer = newBuffer; + emit buffer( newBuffer ); + } + + // device buffer for growisofs >= 7.0 + pos = line.find( "UBU", pos ); + endPos = line.find( '%', pos+5 ); + if( pos > 0 ) { + pos += 4; + val = line.mid( pos, endPos-pos ).toDouble( &ok ); + if( ok ) { + int newBuffer = (int)(val+0.5); + if( newBuffer != d->lastDeviceBuffer ) { + d->lastDeviceBuffer = newBuffer; + emit deviceBuffer( newBuffer ); + } + } + } + } + else + kdDebug() << "(K3bGrowisofsHandler) failed to parse ring buffer fill from '" << line.mid( pos, endPos-pos ) << "'" << endl; + } + + else { + kdDebug() << "(growisofs) " << line << endl; + } +} + + +void K3bGrowisofsHandler::handleExit( int exitCode ) +{ + switch( m_error ) { + case ERROR_MEDIA: + emit infoMessage( i18n("K3b detected a problem with the media."), K3bJob::ERROR ); + emit infoMessage( i18n("Please try another media brand, preferably one explicitly recommended by your writer's vendor."), K3bJob::ERROR ); + emit infoMessage( i18n("Report the problem if it persists anyway."), K3bJob::ERROR ); + break; + + case ERROR_OVERSIZE: + if( k3bcore->globalSettings()->overburn() ) + emit infoMessage( i18n("Data did not fit on disk."), K3bJob::ERROR ); + else + emit infoMessage( i18n("Data does not fit on disk."), K3bJob::ERROR ); + break; + + case ERROR_SPEED_SET_FAILED: + emit infoMessage( i18n("Unable to set writing speed."), K3bJob::ERROR ); + emit infoMessage( i18n("Please try again with the 'ignore speed' setting."), K3bJob::ERROR ); + break; + + case ERROR_OPC: + emit infoMessage( i18n("Optimum Power Calibration failed."), K3bJob::ERROR ); + emit infoMessage( i18n("Try adding '-use-the-force-luke=noopc' to the " + "growisofs user parameters in the K3b settings."), K3bJob::ERROR ); + break; + + case ERROR_MEMLOCK: + emit infoMessage( i18n("Unable to allocate software buffer."), K3bJob::ERROR ); + emit infoMessage( i18n("This error is caused by the low memorylocked resource limit."), K3bJob::ERROR ); + emit infoMessage( i18n("It can be solved by issuing the command 'ulimit -l unlimited'..."), K3bJob::ERROR ); + emit infoMessage( i18n("...or by lowering the used software buffer size in the advanced K3b settings."), K3bJob::ERROR ); + break; + + case ERROR_WRITE_FAILED: + emit infoMessage( i18n("Write error"), K3bJob::ERROR ); + break; + + default: + + // + // The growisofs error codes: + // + // 128 + errno: fatal error upon program startup + // errno : fatal error during recording + // + + if( exitCode > 128 ) { + // for now we just emit a message with the error + // in the future when I know more about what kinds of errors may occur + // we will enhance this + emit infoMessage( i18n("Fatal error at startup: %1").arg(strerror(exitCode-128)), + K3bJob::ERROR ); + } + else if( exitCode == 1 ) { + // Doku says: warning at exit + // Example: mkisofs error + // unable to reload + // So basically this is just for mkisofs failure since we do not let growisofs reload the media + emit infoMessage( i18n("Warning at exit: (1)"), K3bJob::ERROR ); + emit infoMessage( i18n("Most likely mkisofs failed in some way."), K3bJob::ERROR ); + } + else { + emit infoMessage( i18n("Fatal error during recording: %1").arg(strerror(exitCode)), + K3bJob::ERROR ); + } + } + + reset(); +} + + +void K3bGrowisofsHandler::slotCheckBufferStatus() +{ + connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::BUFFER_CAPACITY, m_device ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotCheckBufferStatusDone(K3bDevice::DeviceHandler*)) ); +} + + +void K3bGrowisofsHandler::slotCheckBufferStatusDone( K3bDevice::DeviceHandler* dh ) +{ + if( dh->success() && dh->bufferCapacity() > 0 ) { + emit deviceBuffer( 100 * (dh->bufferCapacity() - dh->availableBufferCapacity() ) / dh->bufferCapacity() ); + QTimer::singleShot( 500, this, SLOT(slotCheckBufferStatus()) ); + } + else { + kdDebug() << "(K3bGrowisofsHandler) stopping buffer check." << endl; + } +} + +#include "k3bgrowisofshandler.moc" diff --git a/libk3b/projects/k3bgrowisofshandler.h b/libk3b/projects/k3bgrowisofshandler.h new file mode 100644 index 0000000..42fcd2a --- /dev/null +++ b/libk3b/projects/k3bgrowisofshandler.h @@ -0,0 +1,87 @@ +/* + * + * $Id: k3bgrowisofshandler.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_GROWISOFS_HANDLER_H_ +#define _K3B_GROWISOFS_HANDLER_H_ + +#include <qobject.h> + +namespace K3bDevice { + class Device; + class DeviceHandler; +} + + +/** + * This class handles the output parsing for growisofs + * We put it in an extra class since we have two classes + * using growisofs: the writer and the imager. + */ +class K3bGrowisofsHandler : public QObject +{ + Q_OBJECT + + public: + K3bGrowisofsHandler( QObject* parent = 0, const char* name = 0 ); + ~K3bGrowisofsHandler(); + + enum ErrorType { + ERROR_UNKNOWN, + ERROR_MEDIA, + ERROR_OVERSIZE, + ERROR_SPEED_SET_FAILED, + ERROR_OPC, + ERROR_MEMLOCK, + ERROR_WRITE_FAILED + }; + + int error() const { return m_error; } + + public slots: + /** + * This will basically reset the error type + * @param dao was growisofs called with DAO? + */ + void reset( K3bDevice::Device* = 0, bool dao = false ); + + void handleStart(); + void handleLine( const QString& ); + void handleExit( int exitCode ); + + signals: + void infoMessage( const QString&, int ); + void newSubTask( const QString& ); + void buffer( int ); + void deviceBuffer( int ); + + /** + * We need this to know when the writing finished to update the progress + */ + void flushingCache(); + + private slots: + void slotCheckBufferStatus(); + void slotCheckBufferStatusDone( K3bDevice::DeviceHandler* ); + + private: + class Private; + Private* d; + + int m_error; + bool m_dao; + K3bDevice::Device* m_device; +}; + +#endif diff --git a/libk3b/projects/k3bgrowisofswriter.cpp b/libk3b/projects/k3bgrowisofswriter.cpp new file mode 100644 index 0000000..3144547 --- /dev/null +++ b/libk3b/projects/k3bgrowisofswriter.cpp @@ -0,0 +1,630 @@ +/* + * + * $Id: k3bgrowisofswriter.cpp 731898 2007-11-02 08:22:18Z 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 "k3bgrowisofswriter.h" + +#include <k3bcore.h> +#include <k3bdevice.h> +#include <k3bdevicehandler.h> +#include <k3bprocess.h> +#include <k3bexternalbinmanager.h> +#include <k3bversion.h> +#include <k3bdiskinfo.h> +#include <k3bglobals.h> +#include <k3bthroughputestimator.h> +#include "k3bgrowisofshandler.h" +#include <k3bpipebuffer.h> +#include <k3bglobalsettings.h> +#include <k3bdeviceglobals.h> + +#include <klocale.h> +#include <kdebug.h> +#include <kglobal.h> + +#include <qvaluelist.h> +#include <qfile.h> + +#include <unistd.h> + + +class K3bGrowisofsWriter::Private +{ +public: + Private() + : writingMode( 0 ), + closeDvd(false), + multiSession(false), + process( 0 ), + growisofsBin( 0 ), + trackSize(-1), + layerBreak(0), + usingRingBuffer(false), + ringBuffer(0), + forceNoEject( false ) { + } + + int writingMode; + bool closeDvd; + bool multiSession; + K3bProcess* process; + const K3bExternalBin* growisofsBin; + QString image; + + bool success; + bool canceled; + bool finished; + + QTime lastSpeedCalculationTime; + int lastSpeedCalculationBytes; + int lastProgress; + unsigned int lastProgressed; + double lastWritingSpeed; + + bool writingStarted; + + K3bThroughputEstimator* speedEst; + K3bGrowisofsHandler* gh; + + // used in DAO with growisofs >= 5.15 + long trackSize; + + long layerBreak; + + unsigned long long overallSizeFromOutput; + long long firstSizeFromOutput; + + QFile inputFile; + + bool usingRingBuffer; + K3bPipeBuffer* ringBuffer; + + QString multiSessionInfo; + + bool forceNoEject; +}; + + +K3bGrowisofsWriter::K3bGrowisofsWriter( K3bDevice::Device* dev, K3bJobHandler* hdl, + QObject* parent, const char* name ) + : K3bAbstractWriter( dev, hdl, parent, name ) +{ + d = new Private; + d->speedEst = new K3bThroughputEstimator( this ); + connect( d->speedEst, SIGNAL(throughput(int)), + this, SLOT(slotThroughput(int)) ); + + d->gh = new K3bGrowisofsHandler( this ); + connect( d->gh, SIGNAL(infoMessage(const QString&, int)), + this,SIGNAL(infoMessage(const QString&, int)) ); + connect( d->gh, SIGNAL(newSubTask(const QString&)), + this, SIGNAL(newSubTask(const QString&)) ); + connect( d->gh, SIGNAL(buffer(int)), + this, SIGNAL(buffer(int)) ); + connect( d->gh, SIGNAL(deviceBuffer(int)), + this, SIGNAL(deviceBuffer(int)) ); + connect( d->gh, SIGNAL(flushingCache()), + this, SLOT(slotFlushingCache()) ); +} + + +K3bGrowisofsWriter::~K3bGrowisofsWriter() +{ + delete d->process; + delete d; +} + + +bool K3bGrowisofsWriter::active() const +{ + return (d->process ? d->process->isRunning() : false); +} + + +int K3bGrowisofsWriter::fd() const +{ + if( d->process ) { + if( d->usingRingBuffer ) + return d->ringBuffer->inFd(); + else + return d->process->stdinFd(); + } + else + return -1; +} + + +bool K3bGrowisofsWriter::closeFd() +{ + return ( !::close( fd() ) ); +} + + +bool K3bGrowisofsWriter::prepareProcess() +{ + d->growisofsBin = k3bcore->externalBinManager()->binObject( "growisofs" ); + if( !d->growisofsBin ) { + emit infoMessage( i18n("Could not find %1 executable.").arg("growisofs"), ERROR ); + return false; + } + + if( d->growisofsBin->version < K3bVersion( 5, 10 ) ) { + emit infoMessage( i18n("Growisofs version %1 is too old. " + "K3b needs at least version 5.10.").arg(d->growisofsBin->version), + ERROR ); + return false; + } + + emit debuggingOutput( "Used versions", "growisofs: " + d->growisofsBin->version ); + + if( !d->growisofsBin->copyright.isEmpty() ) + emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3").arg("growisofs") + .arg(d->growisofsBin->version).arg(d->growisofsBin->copyright), INFO ); + + + // + // The growisofs bin is ready. Now we add the parameters + // + delete d->process; + d->process = new K3bProcess(); + d->process->setRunPrivileged(true); + // d->process->setPriority( KProcess::PrioHighest ); + d->process->setSplitStdout(true); + d->process->setRawStdin(true); + connect( d->process, SIGNAL(stderrLine(const QString&)), this, SLOT(slotReceivedStderr(const QString&)) ); + connect( d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotReceivedStderr(const QString&)) ); + connect( d->process, SIGNAL(processExited(KProcess*)), this, SLOT(slotProcessExited(KProcess*)) ); + + + // + // growisofs < 5.20 wants the tracksize to be a multiple of 16 (1 ECC block: 16*2048 bytes) + // we simply pad ourselves. + // + // But since the writer itself properly pads or writes a longer lead-out we don't really need + // to write zeros. We just tell growisofs to reserve a multiple of 16 blocks. + // This is only releveant in DAO mode anyway. + // + // FIXME: seems as we also need this for double layer writing. Better make it the default and + // actually write the pad bytes. The only possibility I see right now is to add a padding option + // to the pipebuffer. + int trackSizePadding = 0; + if( d->trackSize > 0 && d->growisofsBin->version < K3bVersion( 5, 20 ) ) { + if( d->trackSize % 16 ) { + trackSizePadding = (16 - d->trackSize%16); + kdDebug() << "(K3bGrowisofsWriter) need to pad " << trackSizePadding << " blocks." << endl; + } + } + + + *d->process << d->growisofsBin; + + // set this var to true to enable the ringbuffer + d->usingRingBuffer = ( d->growisofsBin->version < K3bVersion( 6, 0 ) ); + + QString s = burnDevice()->blockDeviceName() + "="; + if( d->usingRingBuffer || d->image.isEmpty() ) { + // we always read from stdin since the ringbuffer does the actual reading from the source + s += "/dev/fd/0"; + } + else + s += d->image; + + if( d->multiSession && !d->multiSessionInfo.isEmpty() ) + *d->process << "-C" << d->multiSessionInfo; + + if( d->multiSession ) + *d->process << "-M"; + else + *d->process << "-Z"; + *d->process << s; + + + if( !d->image.isEmpty() && d->usingRingBuffer ) { + d->inputFile.setName( d->image ); + d->trackSize = (K3b::filesize( d->image ) + 1024) / 2048; + if( !d->inputFile.open( IO_ReadOnly ) ) { + emit infoMessage( i18n("Could not open file %1.").arg(d->image), ERROR ); + return false; + } + } + + // now we use the force (luke ;) do not reload the dvd, K3b does that. + *d->process << "-use-the-force-luke=notray"; + + // we check for existing filesystems ourselves, so we always force the overwrite... + *d->process << "-use-the-force-luke=tty"; + + bool dvdCompat = d->closeDvd; + + // DL writing with forced layer break + if( d->layerBreak > 0 ) { + *d->process << "-use-the-force-luke=break:" + QString::number(d->layerBreak); + dvdCompat = true; + } + + // the tracksize parameter takes priority over the dao:tracksize parameter since growisofs 5.18 + else if( d->growisofsBin->version > K3bVersion( 5, 17 ) && d->trackSize > 0 ) + *d->process << "-use-the-force-luke=tracksize:" + QString::number(d->trackSize + trackSizePadding); + + if( simulate() ) + *d->process << "-use-the-force-luke=dummy"; + + if( d->writingMode == K3b::DAO ) { + dvdCompat = true; + if( d->growisofsBin->version >= K3bVersion( 5, 15 ) && d->trackSize > 0 ) + *d->process << "-use-the-force-luke=dao:" + QString::number(d->trackSize + trackSizePadding); + else + *d->process << "-use-the-force-luke=dao"; + d->gh->reset( burnDevice(), true ); + } + else + d->gh->reset( burnDevice(), false ); + + // + // Never use the -dvd-compat parameter with DVD+RW media + // because the only thing it does is creating problems. + // Normally this should be done in growisofs + // + int mediaType = burnDevice()->mediaType(); + if( dvdCompat && + mediaType != K3bDevice::MEDIA_DVD_PLUS_RW && + mediaType != K3bDevice::MEDIA_DVD_RW_OVWR ) + *d->process << "-dvd-compat"; + + // + // Some DVD writers do not allow changing the writing speed so we allow + // the user to ignore the speed setting + // + int speed = burnSpeed(); + if( speed >= 0 ) { + if( speed == 0 ) { + // try to determine the writeSpeed + // if it fails determineOptimalWriteSpeed() will return 0 and + // the choice is left to growisofs which means that the choice is + // really left to the drive since growisofs does not change the speed + // if no option is given + speed = burnDevice()->determineMaximalWriteSpeed(); + } + + // speed may be a float number. example: DVD+R(W): 2.4x + if( speed != 0 ) + *d->process << QString("-speed=%1").arg( speed%1385 > 0 + ? QString::number( (float)speed/1385.0, 'f', 1 ) + : QString::number( speed/1385 ) ); + } + + if( k3bcore->globalSettings()->overburn() ) + *d->process << "-overburn"; + + if( !d->usingRingBuffer && d->growisofsBin->version >= K3bVersion( 6, 0 ) ) { + bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize(); + int bufSize = ( manualBufferSize ? k3bcore->globalSettings()->bufferSize() : 32 ); + *d->process << QString("-use-the-force-luke=bufsize:%1m").arg(bufSize); + } + + // additional user parameters from config + const QStringList& params = d->growisofsBin->userParameters(); + for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *d->process << *it; + + emit debuggingOutput( "Burned media", K3bDevice::mediaTypeString(mediaType) ); + + return true; +} + + +void K3bGrowisofsWriter::start() +{ + jobStarted(); + + d->lastWritingSpeed = 0; + d->lastProgressed = 0; + d->lastProgress = 0; + d->firstSizeFromOutput = -1; + d->lastSpeedCalculationTime = QTime::currentTime(); + d->lastSpeedCalculationBytes = 0; + d->writingStarted = false; + d->canceled = false; + d->speedEst->reset(); + d->finished = false; + + if( !prepareProcess() ) { + jobFinished( false ); + } + else { + + kdDebug() << "***** " << d->growisofsBin->name() << " parameters:\n"; + const QValueList<QCString>& args = d->process->args(); + QString s; + for( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << flush << endl; + emit debuggingOutput( d->growisofsBin->name() + " command:", s); + + + emit newSubTask( i18n("Preparing write process...") ); + + // FIXME: check the return value + if( K3b::isMounted( burnDevice() ) ) { + emit infoMessage( i18n("Unmounting medium"), INFO ); + K3b::unmount( burnDevice() ); + } + + // block the device (including certain checks) + k3bcore->blockDevice( burnDevice() ); + + // lock the device for good in this process since it will + // be opened in the growisofs process + burnDevice()->close(); + burnDevice()->usageLock(); + + if( !d->process->start( KProcess::NotifyOnExit, KProcess::All ) ) { + // something went wrong when starting the program + // it "should" be the executable + kdDebug() << "(K3bGrowisofsWriter) could not start " << d->growisofsBin->path << endl; + emit infoMessage( i18n("Could not start %1.").arg(d->growisofsBin->name()), K3bJob::ERROR ); + jobFinished(false); + } + else { + if( simulate() ) { + emit newTask( i18n("Simulating") ); + emit infoMessage( i18n("Starting simulation..."), + K3bJob::INFO ); + } + else { + emit newTask( i18n("Writing") ); + emit infoMessage( i18n("Starting disc write..."), K3bJob::INFO ); + } + + d->gh->handleStart(); + + // create the ring buffer + if( d->usingRingBuffer ) { + if( !d->ringBuffer ) { + d->ringBuffer = new K3bPipeBuffer( this, this ); + connect( d->ringBuffer, SIGNAL(percent(int)), this, SIGNAL(buffer(int)) ); + connect( d->ringBuffer, SIGNAL(finished(bool)), this, SLOT(slotRingBufferFinished(bool)) ); + } + + d->ringBuffer->writeToFd( d->process->stdinFd() ); + bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize(); + int bufSize = ( manualBufferSize ? k3bcore->globalSettings()->bufferSize() : 20 ); + d->ringBuffer->setBufferSize( bufSize ); + + if( !d->image.isEmpty() ) + d->ringBuffer->readFromFd( d->inputFile.handle() ); + + d->ringBuffer->start(); + } + } + } +} + + +void K3bGrowisofsWriter::cancel() +{ + if( active() ) { + d->canceled = true; + closeFd(); + if( d->usingRingBuffer && d->ringBuffer ) + d->ringBuffer->cancel(); + d->process->kill(); + } +} + + +void K3bGrowisofsWriter::setWritingMode( int m ) +{ + d->writingMode = m; +} + + +void K3bGrowisofsWriter::setTrackSize( long size ) +{ + d->trackSize = size; +} + + +void K3bGrowisofsWriter::setLayerBreak( long lb ) +{ + d->layerBreak = lb; +} + + +void K3bGrowisofsWriter::setCloseDvd( bool b ) +{ + d->closeDvd = b; +} + + +void K3bGrowisofsWriter::setMultiSession( bool b ) +{ + d->multiSession = b; +} + + +void K3bGrowisofsWriter::setImageToWrite( const QString& filename ) +{ + d->image = filename; +} + + +void K3bGrowisofsWriter::slotReceivedStderr( const QString& line ) +{ + emit debuggingOutput( d->growisofsBin->name(), line ); + + if( line.contains( "remaining" ) ) { + + if( !d->writingStarted ) { + d->writingStarted = true; + emit newSubTask( i18n("Writing data") ); + } + + // parse progress + int pos = line.find( "/" ); + unsigned long long done = line.left( pos ).toULongLong(); + bool ok = true; + d->overallSizeFromOutput = line.mid( pos+1, line.find( "(", pos ) - pos - 1 ).toULongLong( &ok ); + if( d->firstSizeFromOutput == -1 ) + d->firstSizeFromOutput = done; + done -= d->firstSizeFromOutput; + d->overallSizeFromOutput -= d->firstSizeFromOutput; + if( ok ) { + int p = (int)(100 * done / d->overallSizeFromOutput); + if( p > d->lastProgress ) { + emit percent( p ); + emit subPercent( p ); + d->lastProgress = p; + } + if( (unsigned int)(done/1024/1024) > d->lastProgressed ) { + d->lastProgressed = (unsigned int)(done/1024/1024); + emit processedSize( d->lastProgressed, (int)(d->overallSizeFromOutput/1024/1024) ); + emit processedSubSize( d->lastProgressed, (int)(d->overallSizeFromOutput/1024/1024) ); + } + + // try parsing write speed (since growisofs 5.11) + pos = line.find( '@' ); + if( pos != -1 ) { + pos += 1; + double speed = line.mid( pos, line.find( 'x', pos ) - pos ).toDouble(&ok); + if( ok ) { + if( d->lastWritingSpeed != speed ) + emit writeSpeed( (int)(speed*1385.0), 1385 ); + d->lastWritingSpeed = speed; + } + else + kdDebug() << "(K3bGrowisofsWriter) speed parsing failed: '" + << line.mid( pos, line.find( 'x', pos ) - pos ) << "'" << endl; + } + else { + d->speedEst->dataWritten( done/1024 ); + } + } + else + kdDebug() << "(K3bGrowisofsWriter) progress parsing failed: '" + << line.mid( pos+1, line.find( "(", pos ) - pos - 1 ).stripWhiteSpace() << "'" << endl; + } + + // else + // to be able to parse the ring buffer fill in growisofs 6.0 we need to do this all the time + // FIXME: get rid of the K3bGrowisofsHandler once it is sure that we do not need the K3bGrowisofsImager anymore + d->gh->handleLine( line ); +} + + +void K3bGrowisofsWriter::slotProcessExited( KProcess* p ) +{ + d->inputFile.close(); + + // release the device within this process + burnDevice()->usageUnlock(); + + // unblock the device + k3bcore->unblockDevice( burnDevice() ); + + if( d->canceled ) { + if( !d->finished ) { + d->finished = true; + // this will unblock and eject the drive and emit the finished/canceled signals + K3bAbstractWriter::cancel(); + } + return; + } + + d->finished = true; + + // it seems that growisofs sometimes exits with a valid exit code while a write error occured + if( p->exitStatus() == 0 && d->gh->error() != K3bGrowisofsHandler::ERROR_WRITE_FAILED ) { + + int s = d->speedEst->average(); + if( s > 0 ) + emit infoMessage( i18n("Average overall write speed: %1 KB/s (%2x)") + .arg(s).arg(KGlobal::locale()->formatNumber((double)s/1385.0), 2), INFO ); + + if( simulate() ) + emit infoMessage( i18n("Simulation successfully completed"), K3bJob::SUCCESS ); + else + emit infoMessage( i18n("Writing successfully completed"), K3bJob::SUCCESS ); + + d->success = true; + } + else { + if( !wasSourceUnreadable() ) + d->gh->handleExit( p->exitStatus() ); + d->success = false; + } + + if( !k3bcore->globalSettings()->ejectMedia() || d->forceNoEject ) + jobFinished(d->success); + else { + emit newSubTask( i18n("Ejecting DVD") ); + connect( K3bDevice::eject( burnDevice() ), + SIGNAL(finished(K3bDevice::DeviceHandler*)), + this, + SLOT(slotEjectingFinished(K3bDevice::DeviceHandler*)) ); + } +} + + +void K3bGrowisofsWriter::slotRingBufferFinished( bool ) +{ + if( !d->finished ) { + d->finished = true; + // this will unblock and eject the drive and emit the finished/canceled signals + K3bAbstractWriter::cancel(); + } +} + + +void K3bGrowisofsWriter::slotEjectingFinished( K3bDevice::DeviceHandler* dh ) +{ + if( !dh->success() ) + emit infoMessage( i18n("Unable to eject media."), ERROR ); + + jobFinished(d->success); +} + + +void K3bGrowisofsWriter::slotThroughput( int t ) +{ + emit writeSpeed( t, 1385 ); +} + + +void K3bGrowisofsWriter::slotFlushingCache() +{ + if( !d->canceled ) { + // + // growisofs's progress output stops before 100%, so we do it manually + // + emit percent( 100 ); + emit processedSize( d->overallSizeFromOutput/1024/1024, + d->overallSizeFromOutput/1024/1024 ); + } +} + + +void K3bGrowisofsWriter::setMultiSessionInfo( const QString& info ) +{ + d->multiSessionInfo = info; +} + + +void K3bGrowisofsWriter::setForceNoEject( bool b ) +{ + d->forceNoEject = b; +} + +#include "k3bgrowisofswriter.moc" diff --git a/libk3b/projects/k3bgrowisofswriter.h b/libk3b/projects/k3bgrowisofswriter.h new file mode 100644 index 0000000..ed69923 --- /dev/null +++ b/libk3b/projects/k3bgrowisofswriter.h @@ -0,0 +1,106 @@ +/* + * + * $Id: k3bgrowisofswriter.h 679276 2007-06-23 13:25:21Z 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_GROWISOFS_WRITER_H_ +#define _K3B_GROWISOFS_WRITER_H_ + +#include "k3babstractwriter.h" + + +namespace K3bDevice { + class Device; + class DeviceHandler; +} +class KProcess; + + + +class K3bGrowisofsWriter : public K3bAbstractWriter +{ + Q_OBJECT + + public: + K3bGrowisofsWriter( K3bDevice::Device*, K3bJobHandler*, + QObject* parent = 0, const char* name = 0 ); + ~K3bGrowisofsWriter(); + + bool active() const; + + int fd() const; + bool closeFd(); + + public slots: + void start(); + void cancel(); + + void setWritingMode( int ); + + /** + * If true the growisofs parameter -M is used in favor of -Z. + */ + void setMultiSession( bool b ); + + /** + * Only used in DAO mode and only supported with growisofs >= 5.15 + * @param size size in blocks + */ + void setTrackSize( long size ); + + /** + * Use this in combination with setTrackSize when writing double layer media. + * @param lb The number of data sectors in the first layer. It needs to be less or equal + * to tracksize/2. The writer will pad the second layer with zeros if + * break < tracksize/2. + * If set to 0 this setting will be ignored. + */ + void setLayerBreak( long lb ); + + /** + * Close the DVD to enable max DVD compatibility (uses the growisofs --dvd-compat parameter) + * This will also be used in DAO mode and when the layerBreak has been set. + */ + void setCloseDvd( bool ); + + /** + * set this to QString::null or an empty string to let the writer + * read it's data from fd() + */ + void setImageToWrite( const QString& ); + + /** + * While reading the image from stdin growisofs needs + * a valid -C parameter for multisession. + */ + void setMultiSessionInfo( const QString& ); + + void setForceNoEject( bool ); + + protected: + bool prepareProcess(); + + protected slots: + void slotReceivedStderr( const QString& ); + void slotProcessExited( KProcess* ); + void slotEjectingFinished( K3bDevice::DeviceHandler* dh ); + void slotThroughput( int t ); + void slotFlushingCache(); + void slotRingBufferFinished( bool ); + + private: + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/k3bimagefilereader.cpp b/libk3b/projects/k3bimagefilereader.cpp new file mode 100644 index 0000000..70ece16 --- /dev/null +++ b/libk3b/projects/k3bimagefilereader.cpp @@ -0,0 +1,88 @@ +/* + * + * $Id: k3bimagefilereader.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 "k3bimagefilereader.h" + +#include <qfile.h> +#include <qtextstream.h> + +#include <kdebug.h> + + + +class K3bImageFileReader::Private +{ +public: + Private() + : isValid(false) { + } + + QString filename; + QString imageFilename; + bool isValid; +}; + + +K3bImageFileReader::K3bImageFileReader() +{ + d = new Private(); +} + + +K3bImageFileReader::~K3bImageFileReader() +{ + delete d; +} + + +void K3bImageFileReader::openFile( const QString& filename ) +{ + d->filename = filename; + d->imageFilename = QString::null; + setValid(false); + + if( !filename.isEmpty() ) + readFile(); +} + + +void K3bImageFileReader::setValid( bool b ) +{ + d->isValid = b; +} + + +void K3bImageFileReader::setImageFilename( const QString& filename ) +{ + d->imageFilename = filename; +} + + +bool K3bImageFileReader::isValid() const +{ + return d->isValid; +} + + +const QString& K3bImageFileReader::filename() const +{ + return d->filename; +} + + +const QString& K3bImageFileReader::imageFilename() const +{ + return d->imageFilename; +} diff --git a/libk3b/projects/k3bimagefilereader.h b/libk3b/projects/k3bimagefilereader.h new file mode 100644 index 0000000..2bf727e --- /dev/null +++ b/libk3b/projects/k3bimagefilereader.h @@ -0,0 +1,55 @@ +/* + * + * $Id: k3bimagefilereader.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_IMAGE_FILE_READER_H_ +#define _K3B_IMAGE_FILE_READER_H_ + +#include <qstring.h> +#include "k3b_export.h" + +class LIBK3B_EXPORT K3bImageFileReader +{ + public: + K3bImageFileReader(); + virtual ~K3bImageFileReader(); + + /** + * Open a file. In most cases the TOC file + */ + void openFile( const QString& filename ); + + virtual bool isValid() const; + + /** + * Return the current set filename; + */ + const QString& filename() const; + + /** + * returns the name of the corresponding image file. + */ + virtual const QString& imageFilename() const; + + protected: + virtual void readFile() = 0; + void setValid( bool ); + void setImageFilename( const QString& ); + + private: + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/k3binffilewriter.cpp b/libk3b/projects/k3binffilewriter.cpp new file mode 100644 index 0000000..9395b2a --- /dev/null +++ b/libk3b/projects/k3binffilewriter.cpp @@ -0,0 +1,186 @@ +/* + * + * $Id: k3binffilewriter.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 "k3binffilewriter.h" + +#include <k3bcdtext.h> +#include <k3btrack.h> +#include <k3bmsf.h> +#include <k3bcore.h> +#include <k3bversion.h> + +#include <qfile.h> +#include <qtextstream.h> +#include <qdatetime.h> + + + +K3bInfFileWriter::K3bInfFileWriter() + : m_index0(-1), + m_trackNumber(1), + m_trackStart(0), + m_trackLength(0), + m_preEmphasis(false), + m_copyPermitted(true), + m_bigEndian(false) +{ +} + + +bool K3bInfFileWriter::save( const QString& filename ) +{ + QFile f( filename ); + + if( !f.open( IO_WriteOnly ) ) { + kdDebug() << "(K3bInfFileWriter) could not open file " << f.name() << endl; + return false; + } + + QTextStream s( &f ); + + return save( s ); +} + + +bool K3bInfFileWriter::save( QTextStream& s ) +{ + // now write the inf data + // ---------------------- + // header + s << "# Cdrecord-Inf-File written by K3b " << k3bcore->version() + << ", " << QDateTime::currentDateTime().toString() << endl + << "#" << endl; + + s << "ISRC=\t\t" << m_isrc << endl; + s << "MCN=\t\t" << m_mcn << endl; + + // CD-Text + s << "Albumperformer=\t" << "'" << m_albumPerformer << "'" << endl; + s << "Albumtitle=\t" << "'" << m_albumTitle << "'" << endl; + + s << "Performer=\t" << "'" << m_trackPerformer << "'" << endl; + s << "Songwriter=\t" << "'" << m_trackSongwriter << "'" << endl; + s << "Composer=\t" << "'" << m_trackComposer << "'" << endl; + s << "Arranger=\t" << "'" << m_trackArranger << "'" << endl; + s << "Message=\t" << "'" << m_trackMessage << "'" << endl; + + s << "Tracktitle=\t" << "'" << m_trackTitle << "'" << endl; + + s << "Tracknumber=\t" << m_trackNumber << endl; + + // track start + s << "Trackstart=\t" << m_trackStart.lba() << endl; + + // track length + s << "# Tracklength: " << m_trackLength.toString() << endl; + s << "Tracklength=\t" << m_trackLength.totalFrames() << ", 0" << endl; + + // pre-emphasis + s << "Pre-emphasis=\t"; + if( m_preEmphasis ) + s << "yes"; + else + s << "no"; + s << endl; + + // channels (always 2) + s << "Channels=\t2" << endl; + + // copy-permitted + // TODO: not sure about this! + // there are three options: yes, no, once + // but using "once" gives the same result as with cdrdao + // and that's important. + s << "Copy_permitted=\t"; + if( m_copyPermitted ) + s << "yes"; + else + s << "once"; + s << endl; + + // endianess - wav is little -> onthefly: big, with images: little + s << "Endianess=\t"; + if( m_bigEndian ) + s << "big"; + else + s << "little"; + s << endl; + + // write indices + // the current tracks' data contains the pregap of the next track + // if the pregap has length 0 we need no index 0 + if( m_indices.isEmpty() ) + s << "Index=\t\t0" << endl; + else { + for( unsigned int i = 0; i < m_indices.count(); ++i ) + s << "Index=\t\t" << m_indices[i] << endl; + } + + s << "Index0=\t\t" << m_index0 << endl; + + return ( s.device()->status() == IO_Ok ); +} + + +void K3bInfFileWriter::setTrack( const K3bDevice::Track& track ) +{ + m_indices.clear(); + + // the first index always has to be a zero (cdrecord manpage) + m_indices.append( 0 ); + + const QValueVector<K3b::Msf>& indexList = track.indices(); + for( unsigned int i = 0; i < indexList.count(); ++i ) + m_indices.append( indexList[i].lba() ); + + if( track.index0() > 0 ) + m_index0 = track.index0().lba(); + else + m_index0 = -1; + + setPreEmphasis( track.preEmphasis() ); + setCopyPermitted( track.copyPermitted() ); + + setTrackStart( track.firstSector() ); + setTrackLength( track.length() ); + + setIsrc( track.isrc() ); + + setBigEndian( true ); +} + + +void K3bInfFileWriter::addIndex( long i ) +{ + m_indices.append( i ); +} + + +void K3bInfFileWriter::setTrackCdText( const K3bDevice::TrackCdText& cdtext ) +{ + setTrackTitle( cdtext.title() ); + setTrackPerformer( cdtext.performer() ); + setTrackSongwriter( cdtext.songwriter() ); + setTrackComposer( cdtext.composer() ); + setTrackArranger( cdtext.arranger() ); + setTrackMessage( cdtext.message() ); +} + + +void K3bInfFileWriter::setCdText( const K3bDevice::CdText& cdtext ) +{ + setAlbumTitle( cdtext.title() ); + setAlbumPerformer( cdtext.performer() ); +} diff --git a/libk3b/projects/k3binffilewriter.h b/libk3b/projects/k3binffilewriter.h new file mode 100644 index 0000000..74e23e4 --- /dev/null +++ b/libk3b/projects/k3binffilewriter.h @@ -0,0 +1,119 @@ +/* + * + * $Id: k3binffilewriter.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_INF_FILE_WRITER_H_ +#define _K3B_INF_FILE_WRITER_H_ + +#include <qvaluevector.h> +#include <qtextstream.h> + +#include <k3bmsf.h> + + +namespace K3bDevice { + class Track; + class TrackCdText; + class CdText; +} + + +class K3bInfFileWriter +{ + public: + K3bInfFileWriter(); + + bool save( QTextStream& ); + bool save( const QString& filename ); + + /** + * Use this to set: + * @li trackStart + * @li trackLength + * @li index0 + * @li all indices + * @li preemphasis + * @li copyPermitted + * @li ISRC + * + * Endianess is set to big. + * + * Tracknumber needs to be set manually. + */ + void setTrack( const K3bDevice::Track& ); + + void clearIndices() { m_indices.clear(); } + + /** + * This is relative to the track start + */ + void setIndex0( int i ) { m_index0 = i; } + void addIndex( long i ); + + void setTrackNumber( int i ) { m_trackNumber = i; } + + void setTrackStart( const K3b::Msf& i ) { m_trackStart = i; } + void setTrackLength( const K3b::Msf& i ) { m_trackLength = i; } + + void setPreEmphasis( bool b ) { m_preEmphasis = b; } + void setCopyPermitted( bool b ) { m_copyPermitted = b; } + + /** + * Cdrecord seems to ignore this anyway and always expect big endian + * data on stdin and wavs are little endian anyway. + */ + void setBigEndian( bool b ) { m_bigEndian = b; } + + void setTrackCdText( const K3bDevice::TrackCdText& ); + void setTrackTitle( const QString& s ) { m_trackTitle = s; } + void setTrackPerformer( const QString& s ) { m_trackPerformer = s; } + void setTrackSongwriter( const QString& s ) { m_trackSongwriter = s; } + void setTrackComposer( const QString& s ) { m_trackComposer = s; } + void setTrackArranger( const QString& s ) { m_trackArranger = s; } + void setTrackMessage( const QString& s ) { m_trackMessage = s; } + + void setCdText( const K3bDevice::CdText& ); + void setAlbumTitle( const QString& s ) { m_albumTitle = s; } + void setAlbumPerformer( const QString& s ) { m_albumPerformer = s; } + + void setIsrc( const QCString& s ) { m_isrc = s; } + void setMcn( const QCString& s ) { m_mcn = s; } + + private: + long m_index0; + + QValueVector<long> m_indices; + + int m_trackNumber; + K3b::Msf m_trackStart; + K3b::Msf m_trackLength; + bool m_preEmphasis; + bool m_copyPermitted; + bool m_bigEndian; + + QString m_trackTitle; + QString m_trackPerformer; + QString m_trackSongwriter; + QString m_trackComposer; + QString m_trackArranger; + QString m_trackMessage; + + QString m_albumTitle; + QString m_albumPerformer; + + QCString m_isrc; + QCString m_mcn; +}; + +#endif diff --git a/libk3b/projects/k3bpipebuffer.cpp b/libk3b/projects/k3bpipebuffer.cpp new file mode 100644 index 0000000..3b61116 --- /dev/null +++ b/libk3b/projects/k3bpipebuffer.cpp @@ -0,0 +1,281 @@ +/* + * + * $Id: k3bpipebuffer.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 "k3bpipebuffer.h" + +#include <k3bthread.h> + +#include <klocale.h> +#include <kdebug.h> + +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <unistd.h> +#include <fcntl.h> + + +// +// This one is based on the little pipebuf2 program by Peter Osterlund <petero2@telia.com> +// + + +class K3bPipeBuffer::WorkThread : public K3bThread +{ +public: + WorkThread() + : K3bThread(), + buffer(0), + bufSize(4*1024*1024), + canceled(false) { + outFd = inFd = -1; + inFdPair[0] = inFdPair[1] = -1; + } + + ~WorkThread() { + delete [] buffer; + } + + bool initFds() { + if( inFd == -1 ) { + if( ::socketpair(AF_UNIX, SOCK_STREAM, 0, inFdPair) ) { + // if( ::pipe( inFdPair ) ) { + kdDebug() << "(K3bPipeBuffer::WorkThread) unable to create socketpair" << endl; + inFdPair[0] = inFdPair[1] = -1; + return false; + } + else { + ::fcntl(inFdPair[0], F_SETFL, O_NONBLOCK); + ::fcntl(outFd, F_SETFL, O_NONBLOCK); + } + } + else { + ::fcntl(inFd, F_SETFL, O_NONBLOCK); + } + + delete [] buffer; + buffer = new char[bufSize]; + + return (buffer != 0); + } + + void run() { + emitStarted(); + + int usedInFd = -1; + if( inFd > 0 ) + usedInFd = inFd; + else + usedInFd = inFdPair[0]; + + kdDebug() << "(K3bPipeBuffer::WorkThread) reading from " << usedInFd + << " and writing to " << outFd << endl; + kdDebug() << "(K3bPipeBuffer::WorkThread) using buffer size of " << bufSize << endl; + + // start the buffering + unsigned int bufPos = 0; + unsigned int dataLen = 0; + bool eof = false; + bool error = false; + canceled = false; + int oldPercent = 0; + + static const unsigned int MAX_BUFFER_READ = 2048*3; + + while( !canceled && !error && (!eof || dataLen > 0) ) { + // + // create two fd sets + // + fd_set readFds, writeFds; + FD_ZERO(&readFds); + FD_ZERO(&writeFds); + + // + // fill the fd sets + // + if( !eof && dataLen < bufSize ) + FD_SET(usedInFd, &readFds); + if( dataLen > 0 ) + FD_SET(outFd, &writeFds); + + // + // wait for data + // + int ret = select( QMAX(usedInFd, outFd) + 1, &readFds, &writeFds, NULL, NULL); + + // + // Do the buffering + // + if( !canceled && ret > 0 ) { + + int percent = -1; + + // + // Read from the buffer and write to the output + // + if( FD_ISSET(outFd, &writeFds) ) { + unsigned int maxLen = QMIN(bufSize - bufPos, dataLen); + + ret = ::write( outFd, &buffer[bufPos], maxLen ); + + if( ret < 0 ) { + if( (errno != EINTR) && (errno != EAGAIN) ) { + kdDebug() << "(K3bPipeBuffer::WorkThread) error while writing to " << outFd << endl; + error = true; + } + } + else { + // + // we always emit before the reading from the buffer since + // it makes way more sense to show the buffer before the reading. + // + percent = (int)((double)dataLen*100.0/(double)bufSize); + + bufPos = (bufPos + ret) % bufSize; + dataLen -= ret; + } + } + + // + // Read into the buffer + // + else if( FD_ISSET(usedInFd, &readFds) ) { + unsigned int readPos = (bufPos + dataLen) % bufSize; + unsigned int maxLen = QMIN(bufSize - readPos, bufSize - dataLen); + // + // never read more than xxx bytes + // This is some tuning to prevent the reading from blocking the whole thread + // + if( maxLen > MAX_BUFFER_READ ) // some dummy value below 1 MB + maxLen = MAX_BUFFER_READ; + ret = ::read( usedInFd, &buffer[readPos], maxLen ); + if( ret < 0 ) { + if( (errno != EINTR) && (errno != EAGAIN) ) { + kdDebug() << "(K3bPipeBuffer::WorkThread) error while reading from " << usedInFd << endl; + error = true; + } + } + else if( ret == 0 ) { + kdDebug() << "(K3bPipeBuffer::WorkThread) end of input." << endl; + eof = true; + } + else { + dataLen += ret; + + percent = (int)((double)dataLen*100.0/(double)bufSize); + } + } + + // A little hack to keep the buffer display from flickering + if( percent == 99 ) + percent = 100; + + if( percent != -1 && percent != oldPercent ) { + emitPercent( percent ); + oldPercent = percent; + } + } + else if( !canceled ) { + error = true; + kdDebug() << "(K3bPipeBuffer::WorkThread) select: " << ::strerror(errno) << endl; + } + } + + if( inFd == -1 ) { + ::close( inFdPair[0] ); + ::close( inFdPair[1] ); + inFdPair[0] = inFdPair[1] = -1; + } + + // + // close the fd we are writing to (this is need to make growisofs happy + // TODO: perhaps make this configurable + // + ::close( outFd ); + + if( canceled ) + emitCanceled(); + emitFinished( !error && !canceled ); + } + + char* buffer; + size_t bufSize; + int outFd; + int inFd; + int inFdPair[2]; + bool canceled; +}; + + +K3bPipeBuffer::K3bPipeBuffer( K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bThreadJob( jh, parent, name ) +{ + m_thread = new WorkThread(); + setThread( m_thread ); +} + + +K3bPipeBuffer::~K3bPipeBuffer() +{ + delete m_thread; +} + + +void K3bPipeBuffer::start() +{ + // + // Create the socketpair in the gui thread to be sure it's available after + // this method returns. + // + if( !m_thread->initFds() ) + jobFinished(false); + else + K3bThreadJob::start(); +} + + +void K3bPipeBuffer::cancel() +{ + m_thread->canceled = true; +} + + +void K3bPipeBuffer::setBufferSize( int mb ) +{ + m_thread->bufSize = mb * 1024 * 1024; +} + + +void K3bPipeBuffer::readFromFd( int fd ) +{ + m_thread->inFd = fd; +} + + +void K3bPipeBuffer::writeToFd( int fd ) +{ + m_thread->outFd = fd; +} + + +int K3bPipeBuffer::inFd() const +{ + if( m_thread->inFd == -1 ) + return m_thread->inFdPair[1]; + else + return m_thread->inFd; +} diff --git a/libk3b/projects/k3bpipebuffer.h b/libk3b/projects/k3bpipebuffer.h new file mode 100644 index 0000000..6aae368 --- /dev/null +++ b/libk3b/projects/k3bpipebuffer.h @@ -0,0 +1,59 @@ +/* + * + * $Id: k3bpipebuffer.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_PIPE_BUFFER_H_ +#define _K3B_PIPE_BUFFER_H_ + + +#include <k3bthreadjob.h> + +/** + * the pipebuffer uses the signal percent to show it's status. + */ +class K3bPipeBuffer : public K3bThreadJob +{ + public: + K3bPipeBuffer( K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bPipeBuffer(); + + /** + * Set the buffer size in MB. The default value is 4 MB. + */ + void setBufferSize( int ); + + /** + * If this is set to -1 (which is the default) the pipebuffer + * will create a fd pair which can be obtained by inFd() after + * the buffer has been started. + */ + void readFromFd( int fd ); + void writeToFd( int fd ); + + /** + * This is only valid after the piepbuffer has been started and no fd + * has been set with readFromFd. + */ + int inFd() const; + + public slots: + void start(); + void cancel(); + + private: + class WorkThread; + WorkThread* m_thread; +}; + +#endif diff --git a/libk3b/projects/k3btocfilewriter.cpp b/libk3b/projects/k3btocfilewriter.cpp new file mode 100644 index 0000000..77662d6 --- /dev/null +++ b/libk3b/projects/k3btocfilewriter.cpp @@ -0,0 +1,356 @@ +/* + * + * $Id: k3btocfilewriter.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 "k3btocfilewriter.h" + +#include <k3btrack.h> +#include <k3bmsf.h> +#include <k3bcore.h> +#include <k3bversion.h> + +#include <qfile.h> +#include <qtextstream.h> +#include <qdatetime.h> + + +K3bTocFileWriter::K3bTocFileWriter() + : m_hideFirstTrack(false), + m_sessionToWrite(1) +{ +} + + +bool K3bTocFileWriter::save( const QString& filename ) +{ + QFile f( filename ); + + if( !f.open( IO_WriteOnly ) ) { + kdDebug() << "(K3bCueFileWriter) could not open file " << f.name() << endl; + return false; + } + + QTextStream s( &f ); + + return save( s ); +} + + +bool K3bTocFileWriter::save( QTextStream& t ) +{ + writeHeader(t); + + if( !m_cdText.isEmpty() ) + writeGlobalCdText(t); + + // + // see if we have multiple sessions + // + int sessions = 1; + for( K3bDevice::Toc::iterator it = m_toc.begin(); it != m_toc.end(); ++it ) { + if( (*it).session() > 1 ) + sessions = (*it).session(); + } + + if( m_sessionToWrite > sessions ) + m_sessionToWrite = 1; + + // + // We can only hide the first track if both the first and the second track are + // audio tracks. + // We also can only hide the first track in the first session. + // + bool hideFirstTrack = m_hideFirstTrack; + if( m_toc.count() < 2 || + m_toc[0].type() != K3bDevice::Track::AUDIO || + m_toc[1].type() != K3bDevice::Track::AUDIO || + (sessions > 1 && m_sessionToWrite != 1 ) ) + hideFirstTrack = false; + + + // the dataStart will be the offset in case we do not write the first session + K3b::Msf dataStart; + + unsigned int trackIndex = 0; + if( hideFirstTrack ) { + const K3bDevice::Track& hiddenTrack = m_toc[0]; + const K3bDevice::Track& track = m_toc[1]; + + t << "// Track number 1 (hidden) and track number 2 (as track 1)" << endl; + t << "TRACK AUDIO" << endl; + + if( track.copyPermitted() ) + t << "COPY" << endl; + else + t << "NO COPY" << endl; + + if( track.preEmphasis() ) + t << "PRE_EMPHASIS" << endl; + else + t << "NO PRE_EMPHASIS" << endl; + + if( !m_cdText.isEmpty() ) + writeTrackCdText( m_cdText[0], t ); + + // the "hidden" file will be used as pregap for the "first" track + t << "AUDIOFILE "; + writeDataSource( 0, t ); + if( readFromStdin() ) + t << hiddenTrack.firstSector().toString(); + else + t << " 0"; + t << " " << hiddenTrack.length().toString() << endl; + t << "START" << endl; // use the whole hidden file as pregap + + // now comes the "real" first track + t << "AUDIOFILE "; + writeDataSource( 1, t ); + if( readFromStdin() ) + t << track.firstSector().toString() << " "; + else + t << "0 "; + // no index 0 for the last track. Or should we allow this??? + if( m_toc.count() == 2 ) + t << track.length().toString(); + else + t << track.realAudioLength().toString(); + t << endl << endl; + + trackIndex+=2; + } + else { + // + // Seek to the first track to write. + // In case we hid the first track above it was the first track anyway. + // + while( m_toc[trackIndex].session() < m_sessionToWrite && + m_toc[trackIndex].session() > 0 ) + ++trackIndex; + + dataStart = m_toc[trackIndex].firstSector(); + } + + kdDebug() << "(K3bTocFileWriter) using offset of: " << dataStart.toString() << endl; + + while( trackIndex < m_toc.count() ) { + if( m_toc[trackIndex].session() == 0 || m_toc[trackIndex].session() == m_sessionToWrite ) + writeTrack( trackIndex, dataStart, t ); + trackIndex++; + } + + return ( t.device()->status() == IO_Ok ); +} + + +void K3bTocFileWriter::writeHeader( QTextStream& t ) +{ + // little comment + t << "// TOC-file to use with cdrdao created by K3b " << k3bcore->version() + << ", " << QDateTime::currentDateTime().toString() << endl << endl; + + t << "// " << m_toc.count() << " tracks" << endl; + if( m_toc.back().session() > 0 ) { + t << "// " << m_toc.back().session() << " sessions" << endl + << "// this is session number " << m_sessionToWrite << endl; + } + t << endl; + + // check the cd type + if( m_toc.contentType() == K3bDevice::AUDIO ) { + t << "CD_DA"; + } + else { + bool hasMode2Tracks = false; + for( K3bDevice::Toc::iterator it = m_toc.begin(); it != m_toc.end(); ++it ) { + const K3bDevice::Track& track = *it; + if( track.type() == K3bDevice::Track::DATA && + (track.mode() == K3bDevice::Track::MODE2 || + track.mode() == K3bDevice::Track::XA_FORM1 || + track.mode() == K3bDevice::Track::XA_FORM2 ) ) { + hasMode2Tracks = true; + break; + } + } + + if( hasMode2Tracks ) + t << "CD_ROM_XA"; + else + t << "CD_ROM"; + } + + t << endl << endl; +} + + +void K3bTocFileWriter::writeTrack( unsigned int index, const K3b::Msf& offset, QTextStream& t ) +{ + const K3bDevice::Track& track = m_toc[index]; + + t << "// Track number " << (index+1) << endl; + + if( track.type() == K3bDevice::Track::AUDIO ) { + t << "TRACK AUDIO" << endl; + + if( track.copyPermitted() ) + t << "COPY" << endl; + else + t << "NO COPY" << endl; + + if( track.preEmphasis() ) + t << "PRE_EMPHASIS" << endl; + else + t << "NO PRE_EMPHASIS" << endl; + + if( !m_cdText.isEmpty() ) + writeTrackCdText( m_cdText[index], t ); + + // + // cdrdao sees the pregap as part of the current track and not as part of + // the previous like it really is. + // + + if( index == 0 ) { + if( (track.firstSector()-offset) > 0 ) { + // + // the first track is the only track K3b does not generate null-pregap data for + // since cdrecord does not allow this. So We just do it here the same way and tell + // cdrdao to create the first pregap for us + // + + t << "PREGAP " + << (track.firstSector()-offset).toString() << endl; + } + } + else { + const K3bDevice::Track& lastTrack = m_toc[index-1]; + + // + // the pregap data + // + if( lastTrack.index0() > 0 ) { + t << "AUDIOFILE "; + writeDataSource( index-1, t ); + if( readFromStdin() ) + t << (lastTrack.firstSector() + lastTrack.index0() - offset).toString(); + else + t << (lastTrack.index0() - offset).toString(); + t << " " + << (lastTrack.length() - lastTrack.index0()).toString() + << endl + << "START" << endl; + } + } + + // + // The track data + // + t << "AUDIOFILE "; + writeDataSource( index, t ); + if( readFromStdin() ) + t << (track.firstSector() - offset).toString() << " "; + else + t << "0 "; + // no index 0 for the last track. Or should we allow this??? + if( index == m_toc.count()-1 ) + t << track.length().toString(); + else + t << track.realAudioLength().toString(); + t << endl; + } + else { + if( track.mode() == K3bDevice::Track::XA_FORM1 ) + t << "TRACK MODE2_FORM1" << endl; + else if( track.mode() == K3bDevice::Track::XA_FORM2 ) + t << "TRACK MODE2_FORM2" << endl; + else + t << "TRACK MODE1" << endl; + + if( !m_cdText.isEmpty() && !m_toc.contentType() != K3bDevice::DATA ) { + // + // insert fake cdtext + // cdrdao does not work without it and it seems not to do any harm. + // + t << "CD_TEXT {" << endl + << " LANGUAGE 0 {" << endl + << " TITLE " << "\"\"" << endl + << " PERFORMER " << "\"\"" << endl + << " ISRC " << "\"\"" << endl + << " ARRANGER " << "\"\"" << endl + << " SONGWRITER " << "\"\"" << endl + << " COMPOSER " << "\"\"" << endl + << " MESSAGE " << "\"\"" << endl + << " }" << endl + << "}" << endl; + } + + if( readFromStdin() ) + t << "DATAFILE \"-\" " << track.length().toString() << endl; + else + t << "DATAFILE \"" << m_filenames[index] << "\"" << endl; + t << endl; + } + + t << endl; +} + + +void K3bTocFileWriter::writeGlobalCdText( QTextStream& t ) +{ + t << "CD_TEXT {" << endl; + t << " LANGUAGE_MAP { 0: EN }" << endl; + t << " LANGUAGE 0 {" << endl; + t << " TITLE " << "\"" << m_cdText.title() << "\"" << endl; + t << " PERFORMER " << "\"" << m_cdText.performer() << "\"" << endl; + t << " DISC_ID " << "\"" << m_cdText.discId() << "\"" << endl; + t << " UPC_EAN " << "\"" << m_cdText.upcEan() << "\"" << endl; + t << endl; + t << " ARRANGER " << "\"" << m_cdText.arranger() << "\"" << endl; + t << " SONGWRITER " << "\"" << m_cdText.songwriter() << "\"" << endl; + t << " COMPOSER " << "\"" << m_cdText.composer() << "\"" << endl; + t << " MESSAGE " << "\"" << m_cdText.message() << "\"" << endl; + t << " }" << endl; + t << "}" << endl; + t << endl; +} + + +void K3bTocFileWriter::writeTrackCdText( const K3bDevice::TrackCdText& track, QTextStream& t ) +{ + t << "CD_TEXT {" << endl; + t << " LANGUAGE 0 {" << endl; + t << " TITLE " << "\"" << track.title() << "\"" << endl; + t << " PERFORMER " << "\"" << track.performer() << "\"" << endl; + t << " ISRC " << "\"" << track.isrc() << "\"" << endl; + t << " ARRANGER " << "\"" << track.arranger() << "\"" << endl; + t << " SONGWRITER " << "\"" << track.songwriter() << "\"" << endl; + t << " COMPOSER " << "\"" << track.composer() << "\"" << endl; + t << " MESSAGE " << "\"" << track.message() << "\"" << endl; + t << " }" << endl; + t << "}" << endl; +} + + +void K3bTocFileWriter::writeDataSource( unsigned int trackIndex, QTextStream& t ) +{ + if( readFromStdin() ) + t << "\"-\" "; + else + t << "\"" << m_filenames[trackIndex] << "\" "; +} + + +bool K3bTocFileWriter::readFromStdin() const +{ + return ( m_toc.count() > m_filenames.count() ); +} diff --git a/libk3b/projects/k3btocfilewriter.h b/libk3b/projects/k3btocfilewriter.h new file mode 100644 index 0000000..1c1da47 --- /dev/null +++ b/libk3b/projects/k3btocfilewriter.h @@ -0,0 +1,62 @@ +/* + * + * $Id: k3btocfilewriter.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_TOC_FILE_WRITER_H_ +#define _K3B_TOC_FILE_WRITER_H_ + +#include <qtextstream.h> +#include <qstringlist.h> + +#include <k3btoc.h> +#include <k3bcdtext.h> + +namespace K3bDevice { + class TrackCdText; +} + +class K3bTocFileWriter +{ + public: + K3bTocFileWriter(); + + bool save( QTextStream& ); + bool save( const QString& filename ); + + void setData( const K3bDevice::Toc& toc ) { m_toc = toc; } + void setCdText( const K3bDevice::CdText& text ) { m_cdText = text; } + void setFilenames( const QStringList& names ) { m_filenames = names; } + void setHideFirstTrack( bool b ) { m_hideFirstTrack = b; } + + /** + * The default is 1. + */ + void setSession( int s ) { m_sessionToWrite = s; } + + private: + void writeHeader( QTextStream& t ); + void writeGlobalCdText( QTextStream& t ); + void writeTrackCdText( const K3bDevice::TrackCdText& track, QTextStream& t ); + void writeTrack( unsigned int index, const K3b::Msf& offset, QTextStream& t ); + void writeDataSource( unsigned int trackNumber, QTextStream& t ); + bool readFromStdin() const; + + K3bDevice::Toc m_toc; + K3bDevice::CdText m_cdText; + QStringList m_filenames; + bool m_hideFirstTrack; + int m_sessionToWrite; +}; + +#endif diff --git a/libk3b/projects/mixedcd/Makefile.am b/libk3b/projects/mixedcd/Makefile.am new file mode 100644 index 0000000..fb5f44c --- /dev/null +++ b/libk3b/projects/mixedcd/Makefile.am @@ -0,0 +1,23 @@ +# we need the ../datacd and ../audiocd for the uic generated header files +AM_CPPFLAGS= -I$(srcdir)/../../core \ + -I$(srcdir)/../../plugin \ + -I$(srcdir)/../../../libk3bdevice \ + -I$(srcdir)/../../../src \ + -I$(srcdir)/../../tools \ + -I$(srcdir)/.. \ + -I$(srcdir)/../datacd \ + -I$(srcdir)/../audiocd \ + -I$(srcdir)/../../plugin \ + -I../datacd \ + -I../audiocd \ + $(all_includes) + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libmixed.la + +libmixed_la_SOURCES = k3bmixeddoc.cpp \ + k3bmixedjob.cpp + +include_HEADERS = k3bmixeddoc.h \ + k3bmixedjob.h diff --git a/libk3b/projects/mixedcd/k3bmixeddoc.cpp b/libk3b/projects/mixedcd/k3bmixeddoc.cpp new file mode 100644 index 0000000..a2c76b0 --- /dev/null +++ b/libk3b/projects/mixedcd/k3bmixeddoc.cpp @@ -0,0 +1,249 @@ +/* + * + * $Id: k3bmixeddoc.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 "k3bmixeddoc.h" +#include "k3bmixedjob.h" + +#include <k3bdatadoc.h> +#include <k3baudiodoc.h> +#include <k3bglobals.h> +#include <k3bmsf.h> + +#include <qfileinfo.h> +#include <qdom.h> + +#include <klocale.h> +#include <kconfig.h> +#include <kapplication.h> +#include <kmessagebox.h> + + + +K3bMixedDoc::K3bMixedDoc( QObject* parent ) + : K3bDoc( parent ) +{ + m_dataDoc = new K3bDataDoc( this ); + m_audioDoc = new K3bAudioDoc( this ); + + connect( m_dataDoc, SIGNAL(changed()), + this, SIGNAL(changed()) ); + connect( m_audioDoc, SIGNAL(changed()), + this, SIGNAL(changed()) ); +} + + +K3bMixedDoc::~K3bMixedDoc() +{ +} + + +bool K3bMixedDoc::newDocument() +{ + m_dataDoc->newDocument(); + m_audioDoc->newDocument(); + + return K3bDoc::newDocument(); +} + + +QString K3bMixedDoc::name() const +{ + return m_dataDoc->name(); +} + + +void K3bMixedDoc::setURL( const KURL& url ) +{ + K3bDoc::setURL( url ); + m_audioDoc->setURL( url ); + m_dataDoc->setURL( url ); +} + + +void K3bMixedDoc::setModified( bool m ) +{ + m_audioDoc->setModified( m ); + m_dataDoc->setModified( m ); +} + + +bool K3bMixedDoc::isModified() const +{ + return ( m_audioDoc->isModified() || m_dataDoc->isModified() ); +} + + +KIO::filesize_t K3bMixedDoc::size() const +{ + return m_dataDoc->size() + m_audioDoc->size(); +} + +K3b::Msf K3bMixedDoc::length() const +{ + return m_dataDoc->length() + m_audioDoc->length(); +} + + +int K3bMixedDoc::numOfTracks() const +{ + return m_audioDoc->numOfTracks() + 1; +} + + +K3bBurnJob* K3bMixedDoc::newBurnJob( K3bJobHandler* hdl, QObject* parent ) +{ + return new K3bMixedJob( this, hdl, parent ); +} + + +void K3bMixedDoc::addUrls( const KURL::List& urls ) +{ + dataDoc()->addUrls( urls ); +} + + +bool K3bMixedDoc::loadDocumentData( QDomElement* rootElem ) +{ + QDomNodeList nodes = rootElem->childNodes(); + + if( nodes.length() < 4 ) + return false; + + if( nodes.item(0).nodeName() != "general" ) + return false; + if( !readGeneralDocumentData( nodes.item(0).toElement() ) ) + return false; + + if( nodes.item(1).nodeName() != "audio" ) + return false; + QDomElement audioElem = nodes.item(1).toElement(); + if( !m_audioDoc->loadDocumentData( &audioElem ) ) + return false; + + if( nodes.item(2).nodeName() != "data" ) + return false; + QDomElement dataElem = nodes.item(2).toElement(); + if( !m_dataDoc->loadDocumentData( &dataElem ) ) + return false; + + if( nodes.item(3).nodeName() != "mixed" ) + return false; + + QDomNodeList optionList = nodes.item(3).childNodes(); + for( uint i = 0; i < optionList.count(); i++ ) { + + QDomElement e = optionList.item(i).toElement(); + if( e.isNull() ) + return false; + + if( e.nodeName() == "remove_buffer_files" ) + setRemoveImages( e.toElement().text() == "yes" ); + else if( e.nodeName() == "image_path" ) + setTempDir( e.toElement().text() ); + else if( e.nodeName() == "mixed_type" ) { + QString mt = e.toElement().text(); + if( mt == "last_track" ) + setMixedType( DATA_LAST_TRACK ); + else if( mt == "second_session" ) + setMixedType( DATA_SECOND_SESSION ); + else + setMixedType( DATA_FIRST_TRACK ); + } + } + + return true; +} + + +bool K3bMixedDoc::saveDocumentData( QDomElement* docElem ) +{ + QDomDocument doc = docElem->ownerDocument(); + saveGeneralDocumentData( docElem ); + + QDomElement audioElem = doc.createElement( "audio" ); + m_audioDoc->saveDocumentData( &audioElem ); + docElem->appendChild( audioElem ); + + QDomElement dataElem = doc.createElement( "data" ); + m_dataDoc->saveDocumentData( &dataElem ); + docElem->appendChild( dataElem ); + + QDomElement mixedElem = doc.createElement( "mixed" ); + docElem->appendChild( mixedElem ); + + QDomElement bufferFilesElem = doc.createElement( "remove_buffer_files" ); + bufferFilesElem.appendChild( doc.createTextNode( removeImages() ? "yes" : "no" ) ); + mixedElem.appendChild( bufferFilesElem ); + + QDomElement imagePathElem = doc.createElement( "image_path" ); + imagePathElem.appendChild( doc.createTextNode( tempDir() ) ); + mixedElem.appendChild( imagePathElem ); + + QDomElement mixedTypeElem = doc.createElement( "mixed_type" ); + switch( mixedType() ) { + case DATA_FIRST_TRACK: + mixedTypeElem.appendChild( doc.createTextNode( "first_track" ) ); + break; + case DATA_LAST_TRACK: + mixedTypeElem.appendChild( doc.createTextNode( "last_track" ) ); + break; + case DATA_SECOND_SESSION: + mixedTypeElem.appendChild( doc.createTextNode( "second_session" ) ); + break; + } + mixedElem.appendChild( mixedTypeElem ); + + setModified( false ); + + return true; +} + + +K3bDevice::Toc K3bMixedDoc::toToc( int dataMode, const K3b::Msf& dataTrackLength ) const +{ + // !inaccurate datatrack size! + K3bDevice::Track dataTrack( 0, dataTrackLength > 0 ? dataTrackLength-1 : m_dataDoc->length()-1, + K3bDevice::Track::DATA, dataMode ); + K3bDevice::Toc toc = audioDoc()->toToc(); + if( mixedType() == DATA_FIRST_TRACK ) { + // fix the audio tracks' sectors + for( K3bDevice::Toc::iterator it = toc.begin(); it != toc.end(); ++it ) { + (*it).setLastSector( (*it).lastSector() + dataTrack.length() ); + (*it).setFirstSector( (*it).firstSector() + dataTrack.length() ); + } + toc.insert( toc.begin(), dataTrack ); + } + else { + // fix the datatrack's sectors + dataTrack.setLastSector( dataTrack.lastSector() + toc.back().lastSector()+1 ); + dataTrack.setFirstSector( toc.back().lastSector()+1 ); + toc.append( dataTrack ); + + if( mixedType() == DATA_SECOND_SESSION ) { + // fix the session numbers + for( K3bDevice::Toc::iterator it = toc.begin(); it != toc.end(); ++it ) { + if( (*it).type() == K3bDevice::Track::DATA ) + (*it).setSession( 2 ); + else + (*it).setSession( 1 ); + } + } + } + + return toc; +} + +#include "k3bmixeddoc.moc" + diff --git a/libk3b/projects/mixedcd/k3bmixeddoc.h b/libk3b/projects/mixedcd/k3bmixeddoc.h new file mode 100644 index 0000000..7d71b39 --- /dev/null +++ b/libk3b/projects/mixedcd/k3bmixeddoc.h @@ -0,0 +1,95 @@ +/* + * + * $Id: k3bmixeddoc.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_MIXED_DOC_H +#define K3B_MIXED_DOC_H + +#include <k3bdoc.h> +#include <k3bdatadoc.h> +#include <k3baudiodoc.h> + +#include <k3btoc.h> +#include "k3b_export.h" +class QDomDocument; +class QDomElement; +class K3bBurnJob; +//class K3bView; +class QWidget; +class KConfig; + + +class LIBK3B_EXPORT K3bMixedDoc : public K3bDoc +{ + Q_OBJECT + + public: + K3bMixedDoc( QObject* parent = 0 ); + ~K3bMixedDoc(); + + QString name() const; + + bool newDocument(); + + void setModified( bool m = true ); + bool isModified() const; + + KIO::filesize_t size() const; + K3b::Msf length() const; + + int numOfTracks() const; + + K3bBurnJob* newBurnJob( K3bJobHandler*, QObject* parent = 0 ); + + K3bAudioDoc* audioDoc() const { return m_audioDoc; } + K3bDataDoc* dataDoc() const { return m_dataDoc; } + + enum MixedType { DATA_FIRST_TRACK, + DATA_LAST_TRACK, + DATA_SECOND_SESSION }; + + int mixedType() const { return m_mixedType; } + int type() const { return MIXED; } + + void setURL( const KURL& url ); + + /** + * Represent the structure of the doc as CD Table of Contents. + * Be aware that the length of the data track is just an estimate + * and needs to be corrected if not specified here. + * + * @param dataMode mode of the data track (MODE1 or XA_FORM1) + * @param dataTrackLength exact length of the dataTrack + */ + + K3bDevice::Toc toToc( int dataMode, const K3b::Msf& dataTrackLength = 0 ) const; + + public slots: + void setMixedType( MixedType t ) { m_mixedType = t; } + void addUrls( const KURL::List& urls ); + + protected: + bool loadDocumentData( QDomElement* ); + bool saveDocumentData( QDomElement* ); + QString typeString() const { return "mixed"; } + + private: + K3bDataDoc* m_dataDoc; + K3bAudioDoc* m_audioDoc; + + int m_mixedType; +}; + + +#endif diff --git a/libk3b/projects/mixedcd/k3bmixedjob.cpp b/libk3b/projects/mixedcd/k3bmixedjob.cpp new file mode 100644 index 0000000..a4be92c --- /dev/null +++ b/libk3b/projects/mixedcd/k3bmixedjob.cpp @@ -0,0 +1,1339 @@ +/* + * + * $Id: k3bmixedjob.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 "k3bmixedjob.h" +#include "k3bmixeddoc.h" + +#include <k3bdatadoc.h> +#include <k3bisoimager.h> +#include <k3bmsinfofetcher.h> +#include <k3baudioimager.h> +#include <k3baudiodoc.h> +#include <k3baudiotrack.h> +#include <k3baudionormalizejob.h> +#include <k3baudiojobtempdata.h> +#include <k3baudiomaxspeedjob.h> +#include <k3bdevicemanager.h> +#include <k3bdevice.h> +#include <k3bdevicehandler.h> +#include <k3bmsf.h> +#include <k3bglobals.h> +#include <k3bexternalbinmanager.h> +#include <k3bversion.h> +#include <k3bcore.h> +#include <k3bcdrecordwriter.h> +#include <k3bcdrdaowriter.h> +#include <k3btocfilewriter.h> +#include <k3binffilewriter.h> +#include <k3bglobalsettings.h> +#include <k3baudiofile.h> + +#include <qfile.h> +#include <qdatastream.h> +#include <qapplication.h> + +#include <kdebug.h> +#include <klocale.h> +#include <ktempfile.h> +#include <kio/netaccess.h> +#include <kio/global.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 K3bMixedJob::Private +{ +public: + Private() + : maxSpeedJob(0) { + } + + + int copies; + int copiesDone; + + K3bAudioMaxSpeedJob* maxSpeedJob; + bool maxSpeed; +}; + + +K3bMixedJob::K3bMixedJob( K3bMixedDoc* doc, K3bJobHandler* hdl, QObject* parent ) + : K3bBurnJob( hdl, parent ), + m_doc( doc ), + m_normalizeJob(0) +{ + d = new Private; + + m_isoImager = new K3bIsoImager( doc->dataDoc(), this, this ); + connect( m_isoImager, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_isoImager, SIGNAL(percent(int)), this, SLOT(slotIsoImagerPercent(int)) ); + connect( m_isoImager, SIGNAL(finished(bool)), this, SLOT(slotIsoImagerFinished(bool)) ); + connect( m_isoImager, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + + m_audioImager = new K3bAudioImager( doc->audioDoc(), 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_msInfoFetcher = new K3bMsInfoFetcher( this, this ); + connect( m_msInfoFetcher, SIGNAL(finished(bool)), this, SLOT(slotMsInfoFetched(bool)) ); + connect( m_msInfoFetcher, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) ); + + m_writer = 0; + m_tocFile = 0; + m_tempData = new K3bAudioJobTempData( m_doc->audioDoc(), this ); +} + + +K3bMixedJob::~K3bMixedJob() +{ + delete m_tocFile; + delete d; +} + + +K3bDevice::Device* K3bMixedJob::writer() const +{ + if( m_doc->onlyCreateImages() ) + return 0; + else + return m_doc->burner(); +} + + +K3bDoc* K3bMixedJob::doc() const +{ + return m_doc; +} + + +void K3bMixedJob::start() +{ + jobStarted(); + + m_canceled = false; + m_errorOccuredAndAlreadyReported = false; + d->copiesDone = 0; + d->copies = m_doc->copies(); + m_currentAction = PREPARING_DATA; + d->maxSpeed = false; + + if( m_doc->dummy() ) + d->copies = 1; + + prepareProgressInformation(); + + // + // Check if all files exist + // + QValueList<K3bAudioFile*> nonExistingFiles; + K3bAudioTrack* track = m_doc->audioDoc()->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->audioDoc()->numOfTracks() == 0 ) { + emit infoMessage( i18n("Please add files to your project first."), ERROR ); + jobFinished(false); + return; + } + + + // set some flags that are needed + m_doc->audioDoc()->setOnTheFly( m_doc->onTheFly() ); // for the toc writer + m_doc->audioDoc()->setHideFirstTrack( false ); // unsupported + m_doc->dataDoc()->setBurner( m_doc->burner() ); // so the isoImager can read ms data + + emit newTask( i18n("Preparing data") ); + + determineWritingMode(); + + // + // First we make sure the data portion is valid + // + + // we do not have msinfo yet + m_currentAction = INITIALIZING_IMAGER; + m_isoImager->setMultiSessionInfo( QString::null ); + m_isoImager->init(); +} + + +void K3bMixedJob::startFirstCopy() +{ + // + // if not onthefly create the iso image and then the wavs + // and write then + // if onthefly calculate the iso size + // + if( m_doc->onTheFly() ) { + if( m_doc->speed() == 0 ) { + emit newSubTask( i18n("Determining maximum writing speed") ); + + // + // try to determine the max possible speed + // no need to check the data track's max speed. Most current systems are able + // to handle the maxium possible + // + if( !d->maxSpeedJob ) { + // the maxspeed job gets the device from the doc: + m_doc->audioDoc()->setBurner( m_doc->burner() ); + d->maxSpeedJob = new K3bAudioMaxSpeedJob( m_doc->audioDoc(), this, this ); + connect( d->maxSpeedJob, SIGNAL(percent(int)), + this, SIGNAL(subPercent(int)) ); + connect( d->maxSpeedJob, SIGNAL(finished(bool)), + this, SLOT(slotMaxSpeedJobFinished(bool)) ); + } + d->maxSpeedJob->start(); + } + else if( m_doc->mixedType() != K3bMixedDoc::DATA_SECOND_SESSION ) { + m_currentAction = PREPARING_DATA; + m_isoImager->calculateSize(); + } + else { + // we cannot calculate the size since we don't have the msinfo yet + // so first write the audio session + writeNextCopy(); + } + } + else { + emit burning(false); + + emit infoMessage( i18n("Creating audio image files in %1").arg(m_doc->tempDir()), INFO ); + + m_tempFilePrefix = K3b::findUniqueFilePrefix( ( !m_doc->audioDoc()->title().isEmpty() + ? m_doc->audioDoc()->title() + : m_doc->dataDoc()->isoOptions().volumeID() ), + m_doc->tempDir() ); + + m_tempData->prepareTempFileNames( m_doc->tempDir() ); + QStringList filenames; + for( K3bAudioTrack* track = m_doc->audioDoc()->firstTrack(); track; track = track->next() ) + filenames += m_tempData->bufferFileName( track ); + m_audioImager->setImageFilenames( filenames ); + + if( m_doc->mixedType() != K3bMixedDoc::DATA_SECOND_SESSION ) { + createIsoImage(); + } + else { + emit newTask( i18n("Creating audio image files") ); + m_currentAction = CREATING_AUDIO_IMAGE; + m_audioImager->start(); + } + } +} + + +void K3bMixedJob::slotMaxSpeedJobFinished( bool success ) +{ + d->maxSpeed = success; + if( !success ) + emit infoMessage( i18n("Unable to determine maximum speed for some reason. Ignoring."), WARNING ); + + if( m_doc->mixedType() != K3bMixedDoc::DATA_SECOND_SESSION ) { + m_currentAction = PREPARING_DATA; + m_isoImager->calculateSize(); + } + else { + // we cannot calculate the size since we don't have the msinfo yet + // so first write the audio session + writeNextCopy(); + } +} + + +void K3bMixedJob::writeNextCopy() +{ + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + m_currentAction = WRITING_AUDIO_IMAGE; + if( !prepareWriter() || !startWriting() ) { + cleanupAfterError(); + jobFinished(false); + } + else if( m_doc->onTheFly() ) + m_audioImager->start(); + } + else { + // the prepareWriter method needs the action to be set + if( m_doc->mixedType() == K3bMixedDoc::DATA_LAST_TRACK ) + m_currentAction = WRITING_AUDIO_IMAGE; + else + m_currentAction = WRITING_ISO_IMAGE; + + if( !prepareWriter() || !startWriting() ) { + cleanupAfterError(); + jobFinished(false); + } + else if( m_doc->onTheFly() ) { + if( m_doc->mixedType() == K3bMixedDoc::DATA_LAST_TRACK ) + m_audioImager->start(); + else + m_isoImager->start(); + } + } +} + + +void K3bMixedJob::cancel() +{ + m_canceled = true; + + if( d->maxSpeedJob ) + d->maxSpeedJob->cancel(); + + if( m_writer ) + m_writer->cancel(); + m_isoImager->cancel(); + m_audioImager->cancel(); + m_msInfoFetcher->cancel(); + emit infoMessage( i18n("Writing canceled."), K3bJob::ERROR ); + removeBufferFiles(); + emit canceled(); + jobFinished(false); +} + + +void K3bMixedJob::slotMsInfoFetched( bool success ) +{ + if( m_canceled || m_errorOccuredAndAlreadyReported ) + return; + + if( success ) { + if( m_usedDataWritingApp == K3b::CDRECORD ) + m_isoImager->setMultiSessionInfo( m_msInfoFetcher->msInfo() ); + else // cdrdao seems to write a 150 blocks pregap that is not used by cdrecord + m_isoImager->setMultiSessionInfo( QString("%1,%2") + .arg(m_msInfoFetcher->lastSessionStart()) + .arg(m_msInfoFetcher->nextSessionStart()+150) ); + + if( m_doc->onTheFly() ) { + m_currentAction = PREPARING_DATA; + m_isoImager->calculateSize(); + } + else { + createIsoImage(); + } + } + else { + // the MsInfoFetcher already emitted failure info + cleanupAfterError(); + jobFinished(false); + } +} + + +void K3bMixedJob::slotIsoImagerFinished( bool success ) +{ + if( m_canceled || m_errorOccuredAndAlreadyReported ) + return; + + // + // Initializing imager before the first copy + // + if( m_currentAction == INITIALIZING_IMAGER ) { + if( success ) { + m_currentAction = PREPARING_DATA; + + // check the size + m_projectSize = m_isoImager->size() + m_doc->audioDoc()->length(); + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) + m_projectSize += 11400; // the session gap + + startFirstCopy(); + } + else { + cleanupAfterError(); + jobFinished( false ); + } + } + + // + // Recalculated iso image size + // + else if( m_currentAction == PREPARING_DATA ) { + if( success ) { + // 1. data in first track: + // start isoimager and writer + // when isoimager finishes start audiodecoder + + // 2. data in last track + // start audiodecoder and writer + // when audiodecoder finishes start isoimager + + // 3. data in second session + // start audiodecoder and writer + // start isoimager and writer + + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + m_currentAction = WRITING_ISO_IMAGE; + if( !prepareWriter() || !startWriting() ) { + cleanupAfterError(); + jobFinished(false); + } + else + m_isoImager->start(); + } + else + writeNextCopy(); + } + else { + cleanupAfterError(); + jobFinished( false ); + } + } + + // + // Image creation finished + // + else { + if( !success ) { + emit infoMessage( i18n("Error while creating ISO image."), ERROR ); + cleanupAfterError(); + + jobFinished( false ); + return; + } + + if( m_doc->onTheFly() ) { + if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK ) { + m_currentAction = WRITING_AUDIO_IMAGE; + m_audioImager->start(); + } + } + else { + emit infoMessage( i18n("ISO image successfully created."), SUCCESS ); + + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + m_currentAction = WRITING_ISO_IMAGE; + + if( !prepareWriter() || !startWriting() ) { + cleanupAfterError(); + jobFinished(false); + } + } + else { + emit newTask( i18n("Creating audio image files") ); + m_currentAction = CREATING_AUDIO_IMAGE; + m_audioImager->start(); + } + } + } +} + + +void K3bMixedJob::slotWriterFinished( bool success ) +{ + if( m_canceled || m_errorOccuredAndAlreadyReported ) + return; + + if( !success ) { + cleanupAfterError(); + jobFinished(false); + return; + } + + emit burning(false); + + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION && m_currentAction == WRITING_AUDIO_IMAGE ) { + // reload the media (as a subtask so the user does not see the "Flushing cache" or "Fixating" messages while + // doing so + emit newSubTask( i18n("Reloading the medium") ); + connect( K3bDevice::reload( m_doc->burner() ), SIGNAL(finished(bool)), + this, SLOT(slotMediaReloadedForSecondSession(bool)) ); + } + else { + d->copiesDone++; + if( d->copiesDone < d->copies ) { + K3bDevice::eject( m_doc->burner() ); + writeNextCopy(); + } + else { + if( !m_doc->onTheFly() && m_doc->removeImages() ) + removeBufferFiles(); + + if( k3bcore->globalSettings()->ejectMedia() ) + K3bDevice::eject( m_doc->burner() ); + + jobFinished(true); + } + } +} + + +void K3bMixedJob::slotMediaReloadedForSecondSession( bool success ) +{ + if( !success ) + blockingInformation( i18n("Please reload the medium and press 'ok'"), + i18n("Unable to close the tray") ); + + // start the next session + m_currentAction = WRITING_ISO_IMAGE; + if( d->copiesDone > 0 ) { + // we only create the image once. This should not be a problem??? + if( !prepareWriter() || !startWriting() ) { + cleanupAfterError(); + jobFinished(false); + } + else if( m_doc->onTheFly() ) { + m_isoImager->start(); + } + } + else if( m_doc->dummy() ) { + // do not try to get ms info in simulation mode since the cd is empty! + if( m_doc->onTheFly() ) { + m_currentAction = PREPARING_DATA; + m_isoImager->calculateSize(); + } + else + createIsoImage(); + } + else { + m_currentAction = FETCHING_MSINFO; + m_msInfoFetcher->setDevice( m_doc->burner() ); + m_msInfoFetcher->start(); + } +} + + +void K3bMixedJob::slotAudioDecoderFinished( bool success ) +{ + if( m_canceled || m_errorOccuredAndAlreadyReported ) + return; + + if( !success ) { + emit infoMessage( i18n("Error while decoding audio tracks."), ERROR ); + cleanupAfterError(); + jobFinished(false); + return; + } + + if( m_doc->onTheFly() ) { + if( m_doc->mixedType() == K3bMixedDoc::DATA_LAST_TRACK ) { + m_currentAction = WRITING_ISO_IMAGE; + m_isoImager->start(); + } + } + else { + emit infoMessage( i18n("Audio images successfully created."), SUCCESS ); + + if( m_doc->audioDoc()->normalize() ) { + normalizeFiles(); + } + else { + if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK ) + m_currentAction = WRITING_ISO_IMAGE; + else + m_currentAction = WRITING_AUDIO_IMAGE; + + if( !prepareWriter() || !startWriting() ) { + cleanupAfterError(); + jobFinished(false); + } + } + } +} + + +void K3bMixedJob::slotAudioDecoderNextTrack( int t, int tt ) +{ + if( m_doc->onlyCreateImages() || !m_doc->onTheFly() ) { + K3bAudioTrack* track = m_doc->audioDoc()->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 K3bMixedJob::prepareWriter() +{ + if( m_writer ) delete m_writer; + + if( ( m_currentAction == WRITING_ISO_IMAGE && m_usedDataWritingApp == K3b::CDRECORD ) || + ( m_currentAction == WRITING_AUDIO_IMAGE && m_usedAudioWritingApp == K3b::CDRECORD ) ) { + + if( !writeInfFiles() ) { + kdDebug() << "(K3bMixedJob) could not write inf-files." << endl; + emit infoMessage( i18n("IO Error"), ERROR ); + + return false; + } + + K3bCdrecordWriter* writer = new K3bCdrecordWriter( m_doc->burner(), this, this ); + + // only write the audio tracks in DAO mode + if( m_currentAction == WRITING_ISO_IMAGE ) + writer->setWritingMode( m_usedDataWritingMode ); + else + writer->setWritingMode( m_usedAudioWritingMode ); + + writer->setSimulate( m_doc->dummy() ); + writer->setBurnSpeed( m_doc->speed() ); + + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + if( m_currentAction == WRITING_ISO_IMAGE ) { + if( m_doc->onTheFly() ) + writer->addArgument("-waiti"); + + addDataTrack( writer ); + } + else { + writer->addArgument("-multi"); + addAudioTracks( writer ); + } + } + else { + if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK ) + addDataTrack( writer ); + addAudioTracks( writer ); + if( m_doc->mixedType() == K3bMixedDoc::DATA_LAST_TRACK ) + addDataTrack( writer ); + } + + 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->setSimulate( m_doc->dummy() ); + writer->setBurnSpeed( m_doc->speed() ); + + // multisession only for the first session + writer->setMulti( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION + && m_currentAction == WRITING_AUDIO_IMAGE ); + + writer->setTocFile( m_tocFile->name() ); + + 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; +} + + +bool K3bMixedJob::writeInfFiles() +{ + K3bInfFileWriter infFileWriter; + K3bAudioTrack* track = m_doc->audioDoc()->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; +} + + +bool K3bMixedJob::writeTocFile() +{ + // FIXME: create the tocfile in the same directory like all the other files. + + if( m_tocFile ) delete m_tocFile; + m_tocFile = new KTempFile( QString::null, "toc" ); + m_tocFile->setAutoDelete(true); + + // write the toc-file + if( QTextStream* s = m_tocFile->textStream() ) { + + K3bTocFileWriter tocFileWriter; + + // + // TOC + // + tocFileWriter.setData( m_doc->toToc( m_usedDataMode == K3b::MODE2 + ? K3bDevice::Track::XA_FORM1 + : K3bDevice::Track::MODE1, + m_doc->onTheFly() + ? m_isoImager->size() + : m_doc->dataDoc()->length() ) ); + + // + // CD-Text + // + if( m_doc->audioDoc()->cdText() ) { + K3bDevice::CdText text = m_doc->audioDoc()->cdTextData(); + // if data in first track we need to add a dummy cdtext + if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK ) + text.insert( text.begin(), K3bDevice::TrackCdText() ); + + tocFileWriter.setCdText( text ); + } + + // + // Session to write + // + tocFileWriter.setSession( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION && + m_currentAction == WRITING_ISO_IMAGE ? 2 : 1 ); + + // + // image filenames + // + if( !m_doc->onTheFly() ) { + QStringList files; + K3bAudioTrack* track = m_doc->audioDoc()->firstTrack(); + while( track ) { + files += m_tempData->bufferFileName( track ); + track = track->next(); + } + if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK ) + files.prepend( m_isoImageFilePath ); + else + files.append( m_isoImageFilePath ); + + tocFileWriter.setFilenames( files ); + } + + bool success = tocFileWriter.save( *s ); + + m_tocFile->close(); + + // backup for debugging +// KIO::NetAccess::del("/tmp/trueg/tocfile_debug_backup.toc",0L); +// KIO::NetAccess::copy( m_tocFile->name(), "/tmp/trueg/tocfile_debug_backup.toc",0L ); + + return success; + } + else + return false; +} + + +void K3bMixedJob::addAudioTracks( K3bCdrecordWriter* writer ) +{ + writer->addArgument( "-useinfo" ); + + // add raw cdtext data + if( m_doc->audioDoc()->cdText() ) { + writer->setRawCdText( m_doc->audioDoc()->cdTextData().rawPackData() ); + } + + writer->addArgument( "-audio" ); + + // we always pad because although K3b makes sure all tracks' length are multiples of 2352 + // it seems that normalize sometimes corrupts these lengths + // FIXME: see K3bAudioJob for the whole less4secs and zeroPregap handling + writer->addArgument( "-pad" ); + + // Allow tracks shorter than 4 seconds + writer->addArgument( "-shorttrack" ); + + // add all the audio tracks + K3bAudioTrack* track = m_doc->audioDoc()->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(); + } +} + +void K3bMixedJob::addDataTrack( K3bCdrecordWriter* writer ) +{ + // add data track + if( m_usedDataMode == K3b::MODE2 ) { + if( k3bcore->externalBinManager()->binObject("cdrecord") && + k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "xamix" ) ) + writer->addArgument( "-xa" ); + else + writer->addArgument( "-xa1" ); + } + else + writer->addArgument( "-data" ); + + if( m_doc->onTheFly() ) + writer->addArgument( QString("-tsize=%1s").arg(m_isoImager->size()) )->addArgument("-"); + else + writer->addArgument( m_isoImageFilePath ); +} + + +void K3bMixedJob::slotWriterNextTrack( int t, int ) +{ + K3bAudioTrack* track = 0; + + if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK ) { + if( t > 1 ) + track = m_doc->audioDoc()->getTrack(t-1); + } + else if( m_doc->mixedType() == K3bMixedDoc::DATA_LAST_TRACK ) { + if( t < m_doc->audioDoc()->numOfTracks()+1 ) + track = m_doc->audioDoc()->getTrack(t); + } + else if( m_currentAction == WRITING_AUDIO_IMAGE ) + track = m_doc->audioDoc()->getTrack(t); + else + t = m_doc->numOfTracks(); + + if( track ) + emit newSubTask( i18n("Writing track %1 of %2%3") + .arg(t) + .arg(m_doc->numOfTracks()) + .arg( track->title().isEmpty() || track->artist().isEmpty() + ? QString::null + : " (" + track->artist() + " - " + track->title() + ")" ) ); + else + emit newSubTask( i18n("Writing track %1 of %2 (%3)").arg(t).arg(m_doc->numOfTracks()).arg(i18n("ISO9660 data")) ); +} + + +void K3bMixedJob::slotWriterJobPercent( int p ) +{ + double totalTasks = d->copies; + double tasksDone = d->copiesDone; + if( m_doc->audioDoc()->normalize() ) { + totalTasks+=1.0; + tasksDone+=1.0; + } + if( !m_doc->onTheFly() ) { + totalTasks+=1.0; + } + + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + if( m_currentAction == WRITING_AUDIO_IMAGE ) { + // the audio imager has finished in all cases + // the iso imager only if this is not the first copy + if( d->copiesDone > 0 ) + tasksDone += 1.0; + else if( !m_doc->onTheFly() ) + tasksDone += m_audioDocPartOfProcess; + + p = (int)((double)p*m_audioDocPartOfProcess); + } + else { + // all images have been created + if( !m_doc->onTheFly() ) + tasksDone += 1.0; + + p = (int)(100.0*m_audioDocPartOfProcess + (double)p*(1.0-m_audioDocPartOfProcess)); + } + } + else if( !m_doc->onTheFly() ) + tasksDone += 1.0; + + emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); +} + + +void K3bMixedJob::slotAudioDecoderPercent( int p ) +{ + // the only thing finished here might be the isoimager which is part of this task + if( !m_doc->onTheFly() ) { + double totalTasks = d->copies+1; + if( m_doc->audioDoc()->normalize() ) + totalTasks+=1.0; + + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) + p = (int)((double)p*m_audioDocPartOfProcess); + else + p = (int)(100.0*(1.0-m_audioDocPartOfProcess) + (double)p*m_audioDocPartOfProcess); + + emit percent( (int)((double)p / totalTasks) ); + } +} + + +void K3bMixedJob::slotAudioDecoderSubPercent( int p ) +{ + if( !m_doc->onTheFly() ) { + emit subPercent( p ); + } +} + + +void K3bMixedJob::slotIsoImagerPercent( int p ) +{ + if( !m_doc->onTheFly() ) { + emit subPercent( p ); + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + + double totalTasks = d->copies+1.0; + double tasksDone = d->copiesDone; + if( m_doc->audioDoc()->normalize() ) { + totalTasks+=1.0; + // the normalizer finished + tasksDone+=1.0; + } + + // the writing of the audio part finished + tasksDone += m_audioDocPartOfProcess; + + // the audio decoder finished (which is part of this task in terms of progress) + p = (int)(100.0*m_audioDocPartOfProcess + (double)p*(1.0-m_audioDocPartOfProcess)); + + emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); + } + else { + double totalTasks = d->copies+1.0; + if( m_doc->audioDoc()->normalize() ) + totalTasks+=1.0; + + emit percent( (int)((double)(p*(1.0-m_audioDocPartOfProcess)) / totalTasks) ); + } + } +} + + +bool K3bMixedJob::startWriting() +{ + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + if( m_currentAction == WRITING_ISO_IMAGE) { + if( m_doc->dummy() ) + emit newTask( i18n("Simulating second session") ); + else if( d->copies > 1 ) + emit newTask( i18n("Writing second session of copy %1").arg(d->copiesDone+1) ); + else + emit newTask( i18n("Writing second session") ); + } + else { + if( m_doc->dummy() ) + emit newTask( i18n("Simulating first session") ); + else if( d->copies > 1 ) + emit newTask( i18n("Writing first session of copy %1").arg(d->copiesDone+1) ); + else + emit newTask( i18n("Writing first session") ); + } + } + else if( m_doc->dummy() ) + emit newTask( i18n("Simulating") ); + else + emit newTask( i18n("Writing Copy %1").arg(d->copiesDone+1) ); + + + // if we append the second session the cd is already in the drive + if( !(m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION + && m_currentAction == WRITING_ISO_IMAGE) ) { + + 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; + + // check if the project will fit on the CD + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + // the media is in and has been checked so this should be fast (hopefully) + K3b::Msf mediaSize = m_doc->burner()->diskInfo().capacity(); + if( mediaSize < m_projectSize ) { + if( k3bcore->globalSettings()->overburn() ) { + emit infoMessage( i18n("Trying to write more than the official disk capacity"), K3bJob::WARNING ); + } + else { + emit infoMessage( i18n("Data does not fit on disk."), ERROR ); + 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( d->maxSpeedJob->maxSpeed() ); + + emit burning(true); + m_writer->start(); + + 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_isoImager->writeToFd( m_writer->fd() ); + } + + return true; +} + + +void K3bMixedJob::createIsoImage() +{ + m_currentAction = CREATING_ISO_IMAGE; + + // prepare iso image file + m_isoImageFilePath = m_tempFilePrefix + "_datatrack.iso"; + + if( !m_doc->onTheFly() ) + emit newTask( i18n("Creating ISO image file") ); + emit newSubTask( i18n("Creating ISO image in %1").arg(m_isoImageFilePath) ); + emit infoMessage( i18n("Creating ISO image in %1").arg(m_isoImageFilePath), INFO ); + + m_isoImager->writeToImageFile( m_isoImageFilePath ); + m_isoImager->start(); +} + + +void K3bMixedJob::cleanupAfterError() +{ + m_errorOccuredAndAlreadyReported = true; + // m_audioImager->cancel(); + m_isoImager->cancel(); + if( m_writer ) + m_writer->cancel(); + + if( m_tocFile ) delete m_tocFile; + m_tocFile = 0; + + // remove the temp files + removeBufferFiles(); +} + + +void K3bMixedJob::removeBufferFiles() +{ + if ( !m_doc->onTheFly() ) { + emit infoMessage( i18n("Removing buffer files."), INFO ); + } + + if( QFile::exists( m_isoImageFilePath ) ) + if( !QFile::remove( m_isoImageFilePath ) ) + emit infoMessage( i18n("Could not delete file %1.").arg(m_isoImageFilePath), ERROR ); + + // removes buffer images and temp toc or inf files + m_tempData->cleanup(); +} + + +void K3bMixedJob::determineWritingMode() +{ + // we don't need this when only creating image and it is possible + // that the burn device is null + if( m_doc->onlyCreateImages() ) + return; + + // at first we determine the data mode + // -------------------------------------------------------------- + if( m_doc->dataDoc()->dataMode() == K3b::DATA_MODE_AUTO ) { + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) + m_usedDataMode = K3b::MODE2; + else + m_usedDataMode = K3b::MODE1; + } + else + m_usedDataMode = m_doc->dataDoc()->dataMode(); + + + // we try to use cdrecord if possible + bool cdrecordOnTheFly = false; + bool cdrecordCdText = false; + bool cdrecordUsable = false; + + if( k3bcore->externalBinManager()->binObject("cdrecord") ) { + cdrecordOnTheFly = + k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "audio-stdin" ); + cdrecordCdText = + k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "cdtext" ); + cdrecordUsable = + !( !cdrecordOnTheFly && m_doc->onTheFly() ) && + !( m_doc->audioDoc()->cdText() && !cdrecordCdText ); + } + + // Writing Application + // -------------------------------------------------------------- + // cdrecord seems to have problems writing xa 1 disks in dao mode? At least on my system! + if( writingApp() == K3b::DEFAULT ) { + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + if( m_doc->writingMode() == K3b::DAO || + ( m_doc->writingMode() == K3b::WRITING_MODE_AUTO && !cdrecordUsable ) ) { + m_usedAudioWritingApp = K3b::CDRDAO; + m_usedDataWritingApp = K3b::CDRDAO; + } + else { + m_usedAudioWritingApp = K3b::CDRECORD; + m_usedDataWritingApp = K3b::CDRECORD; + } + } + else { + if( cdrecordUsable ) { + m_usedAudioWritingApp = K3b::CDRECORD; + m_usedDataWritingApp = K3b::CDRECORD; + } + else { + m_usedAudioWritingApp = K3b::CDRDAO; + m_usedDataWritingApp = K3b::CDRDAO; + } + } + } + else { + m_usedAudioWritingApp = writingApp(); + m_usedDataWritingApp = writingApp(); + } + + // TODO: use K3bExceptions::brokenDaoAudio + + // Writing Mode (TAO/DAO/RAW) + // -------------------------------------------------------------- + if( m_doc->writingMode() == K3b::WRITING_MODE_AUTO ) { + + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + if( m_usedDataWritingApp == K3b::CDRECORD ) + m_usedDataWritingMode = K3b::TAO; + else + m_usedDataWritingMode = K3b::DAO; + + // default to Session at once for the audio part + m_usedAudioWritingMode = K3b::DAO; + } + else if( writer()->dao() ) { + m_usedDataWritingMode = K3b::DAO; + m_usedAudioWritingMode = K3b::DAO; + } + else { + m_usedDataWritingMode = K3b::TAO; + m_usedAudioWritingMode = K3b::TAO; + } + } + else { + m_usedAudioWritingMode = m_doc->writingMode(); + m_usedDataWritingMode = m_doc->writingMode(); + } + + + if( m_usedDataWritingApp == K3b::CDRECORD ) { + if( !cdrecordOnTheFly && m_doc->onTheFly() ) { + m_doc->setOnTheFly( false ); + emit infoMessage( i18n("On-the-fly writing with cdrecord < 2.01a13 not supported."), ERROR ); + } + + if( m_doc->audioDoc()->cdText() ) { + if( !cdrecordCdText ) { + m_doc->audioDoc()->writeCdText( false ); + emit infoMessage( i18n("Cdrecord %1 does not support CD-Text writing.").arg(k3bcore->externalBinManager()->binObject("cdrecord")->version), ERROR ); + } + else if( m_usedAudioWritingMode == K3b::TAO ) { + emit infoMessage( i18n("It is not possible to write CD-Text in TAO mode. Try DAO or RAW."), WARNING ); + } + } + } +} + + +void K3bMixedJob::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 + QValueVector<QString> files; + K3bAudioTrack* track = m_doc->audioDoc()->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 K3bMixedJob::slotNormalizeJobFinished( bool success ) +{ + if( m_canceled || m_errorOccuredAndAlreadyReported ) + return; + + if( success ) { + if( m_doc->mixedType() == K3bMixedDoc::DATA_FIRST_TRACK ) + m_currentAction = WRITING_ISO_IMAGE; + else + m_currentAction = WRITING_AUDIO_IMAGE; + + if( !prepareWriter() || !startWriting() ) { + cleanupAfterError(); + jobFinished(false); + } + } + else { + cleanupAfterError(); + jobFinished(false); + } +} + +void K3bMixedJob::slotNormalizeProgress( int p ) +{ + double totalTasks = d->copies+2.0; + double tasksDone = 0; + + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) { + // the audio imager finished (m_audioDocPartOfProcess*1 task) + // plus the normalize progress + tasksDone = m_audioDocPartOfProcess; + } + else { + // the iso and audio imagers already finished (one task) + // plus the normalize progress + tasksDone = 1.0; + } + + emit percent( (int)((100.0*tasksDone + (double)p) / totalTasks) ); +} + + +void K3bMixedJob::slotNormalizeSubProgress( int p ) +{ + emit subPercent( p ); +} + + +void K3bMixedJob::prepareProgressInformation() +{ + // calculate percentage of audio and data + // this is also used in on-the-fly mode + double ds = (double)m_doc->dataDoc()->length().totalFrames(); + double as = (double)m_doc->audioDoc()->length().totalFrames(); + m_audioDocPartOfProcess = as/(ds+as); +} + + +QString K3bMixedJob::jobDescription() const +{ + if( m_doc->mixedType() == K3bMixedDoc::DATA_SECOND_SESSION ) + return i18n("Writing Enhanced Audio CD") + + ( m_doc->audioDoc()->title().isEmpty() + ? QString::null + : QString( " (%1)" ).arg(m_doc->audioDoc()->title()) ); + else + return i18n("Writing Mixed Mode CD") + + ( m_doc->audioDoc()->title().isEmpty() + ? QString::null + : QString( " (%1)" ).arg(m_doc->audioDoc()->title()) ); +} + + +QString K3bMixedJob::jobDetails() const +{ + return ( i18n("%1 tracks (%2 minutes audio data, %3 ISO9660 data)") + .arg(m_doc->numOfTracks()) + .arg(m_doc->audioDoc()->length().toString()) + .arg(KIO::convertSize(m_doc->dataDoc()->size())) + + ( m_doc->copies() > 1 && !m_doc->dummy() + ? i18n(" - %n copy", " - %n copies", m_doc->copies()) + : QString::null ) ); +} + +#include "k3bmixedjob.moc" diff --git a/libk3b/projects/mixedcd/k3bmixedjob.h b/libk3b/projects/mixedcd/k3bmixedjob.h new file mode 100644 index 0000000..50a1dc7 --- /dev/null +++ b/libk3b/projects/mixedcd/k3bmixedjob.h @@ -0,0 +1,144 @@ +/* + * + * $Id: k3bmixedjob.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 K3BMIXEDJOB_H +#define K3BMIXEDJOB_H + +#include <k3bjob.h> + + +class K3bMixedDoc; +class K3bIsoImager; +class K3bAudioImager; +class QFile; +class QDataStream; +class K3bAbstractWriter; +class K3bWaveFileWriter; +class KTempFile; +class K3bCdrecordWriter; +class K3bMsInfoFetcher; +class K3bAudioNormalizeJob; +class K3bAudioJobTempData; +class K3bDevice::Device; + +/** + *@author Sebastian Trueg + */ +class K3bMixedJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bMixedJob( K3bMixedDoc*, K3bJobHandler*, QObject* parent = 0 ); + ~K3bMixedJob(); + + K3bDoc* doc() const; + K3bDevice::Device* writer() const; + + QString jobDescription() const; + QString jobDetails() const; + + public slots: + void cancel(); + void start(); + + protected slots: + // iso imager slots + void slotIsoImagerFinished( bool success ); + void slotIsoImagerPercent(int); + + // ms info fetcher slots + void slotMsInfoFetched(bool); + + // audio decoder slots + void slotAudioDecoderFinished( bool ); + void slotAudioDecoderNextTrack( int, int ); + void slotAudioDecoderPercent(int); + void slotAudioDecoderSubPercent( int ); + + // writer slots + void slotWriterFinished( bool success ); + void slotWriterNextTrack(int, int); + void slotWriterJobPercent(int); + + // normalizing slots + void slotNormalizeJobFinished( bool ); + void slotNormalizeProgress( int ); + void slotNormalizeSubProgress( int ); + + // misc slots + void slotMediaReloadedForSecondSession( bool ); + void slotMaxSpeedJobFinished( bool ); + + private: + bool prepareWriter(); + bool writeTocFile(); + bool writeInfFiles(); + bool startWriting(); + void startFirstCopy(); + void addAudioTracks( K3bCdrecordWriter* writer ); + void addDataTrack( K3bCdrecordWriter* writer ); + void cleanupAfterError(); + void removeBufferFiles(); + void createIsoImage(); + void determineWritingMode(); + void normalizeFiles(); + void prepareProgressInformation(); + void writeNextCopy(); + void determinePreliminaryDataImageSize(); + + K3bMixedDoc* m_doc; + K3bIsoImager* m_isoImager; + K3bAudioImager* m_audioImager; + K3bAudioJobTempData* m_tempData; + K3bWaveFileWriter* m_waveFileWriter; + K3bAbstractWriter* m_writer; + K3bMsInfoFetcher* m_msInfoFetcher; + K3bAudioNormalizeJob* m_normalizeJob; + + QString m_isoImageFilePath; + + KTempFile* m_tocFile; + + enum Action { INITIALIZING_IMAGER, + PREPARING_DATA, + CREATING_ISO_IMAGE, + CREATING_AUDIO_IMAGE, + WRITING_ISO_IMAGE, + WRITING_AUDIO_IMAGE, + FETCHING_MSINFO }; + + int m_currentAction; + double m_audioDocPartOfProcess; + + bool m_canceled; + bool m_errorOccuredAndAlreadyReported; + + int m_usedDataMode; + int m_usedDataWritingApp; + int m_usedAudioWritingApp; + int m_usedDataWritingMode; + int m_usedAudioWritingMode; + + QString m_tempFilePrefix; + + K3b::Msf m_projectSize; + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/movixcd/Makefile.am b/libk3b/projects/movixcd/Makefile.am new file mode 100644 index 0000000..d1b4f5d --- /dev/null +++ b/libk3b/projects/movixcd/Makefile.am @@ -0,0 +1,23 @@ +# we need the ../datacd for the uic generated header files +AM_CPPFLAGS= -I$(srcdir)/../../core \ + -I$(srcdir)/../../../libk3bdevice \ + -I$(srcdir)/../../../src \ + -I$(srcdir)/../../tools \ + -I$(srcdir)/../datacd \ + -I$(srcdir)/.. \ + -I../datacd \ + $(all_includes) + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libmovix.la + +libmovix_la_SOURCES = k3bmovixprogram.cpp \ + k3bmovixdoc.cpp \ + k3bmovixjob.cpp \ + k3bmovixfileitem.cpp \ + k3bmovixdocpreparer.cpp + +include_HEADERS = k3bmovixdoc.h \ + k3bmovixjob.h \ + k3bmovixfileitem.h diff --git a/libk3b/projects/movixcd/k3bmovixdoc.cpp b/libk3b/projects/movixcd/k3bmovixdoc.cpp new file mode 100644 index 0000000..f7b2198 --- /dev/null +++ b/libk3b/projects/movixcd/k3bmovixdoc.cpp @@ -0,0 +1,445 @@ +/* + * + * $Id: k3bmovixdoc.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 "k3bmovixdoc.h" +#include "k3bmovixjob.h" +#include "k3bmovixfileitem.h" + +#include <k3bdiritem.h> +#include <k3bfileitem.h> +#include <k3bglobals.h> + +#include <klocale.h> +#include <kdebug.h> +#include <kurl.h> +#include <kinputdialog.h> +#include <kmessagebox.h> +#include <kconfig.h> +#include <kapplication.h> + +#include <qdom.h> +#include <qfileinfo.h> + + +K3bMovixDoc::K3bMovixDoc( QObject* parent ) + : K3bDataDoc( parent ) +{ + connect( this, SIGNAL(itemRemoved(K3bDataItem*)), + this, SLOT(slotDataItemRemoved(K3bDataItem*)) ); +} + + +K3bMovixDoc::~K3bMovixDoc() +{ +} + + +K3bBurnJob* K3bMovixDoc::newBurnJob( K3bJobHandler* hdl, QObject* parent ) +{ + return new K3bMovixJob( this, hdl, parent ); +} + + +bool K3bMovixDoc::newDocument() +{ + m_loopPlaylist = 1; + m_ejectDisk = false; + m_reboot = false; + m_shutdown = false; + m_randomPlay = false; + + return K3bDataDoc::newDocument(); +} + + +void K3bMovixDoc::addUrls( const KURL::List& urls ) +{ + for( KURL::List::ConstIterator it = urls.begin(); it != urls.end(); ++it ) { + addMovixFile( *it ); + } + + emit newMovixFileItems(); +} + + +void K3bMovixDoc::addMovixFile( const KURL& _url, int pos ) +{ + KURL url = K3b::convertToLocalUrl( _url ); + + QFileInfo f( url.path() ); + if( !f.isFile() || !url.isLocalFile() ) + return; + + QString newName = f.fileName(); + if( nameAlreadyInDir( newName, root() ) ) { + kapp->config()->setGroup("Data project settings"); + bool dropDoubles = kapp->config()->readBoolEntry( "Drop doubles", false ); + if( dropDoubles ) + return; + + bool ok = true; + do { + newName = KInputDialog::getText( i18n("Enter New Filename"), + i18n("A file with that name already exists. Please enter a new name:"), + newName, &ok, 0 ); + } while( ok && nameAlreadyInDir( newName, root() ) ); + + if( !ok ) + return; + } + + K3bMovixFileItem* newK3bItem = new K3bMovixFileItem( f.absFilePath(), this, root(), newName ); + if( pos < 0 || pos > (int)m_movixFiles.count() ) + pos = m_movixFiles.count(); + + m_movixFiles.insert( pos, newK3bItem ); + + emit newMovixFileItems(); + + setModified(true); +} + + +bool K3bMovixDoc::loadDocumentData( QDomElement* rootElem ) +{ + if( !root() ) + newDocument(); + + QDomNodeList nodes = rootElem->childNodes(); + + if( nodes.item(0).nodeName() != "general" ) { + kdDebug() << "(K3bMovixDoc) could not find 'general' section." << endl; + return false; + } + if( !readGeneralDocumentData( nodes.item(0).toElement() ) ) + return false; + + + // parse options + // ----------------------------------------------------------------- + if( nodes.item(1).nodeName() != "data_options" ) { + kdDebug() << "(K3bMovixDoc) could not find 'data_options' section." << endl; + return false; + } + if( !loadDocumentDataOptions( nodes.item(1).toElement() ) ) + return false; + // ----------------------------------------------------------------- + + + + // parse header + // ----------------------------------------------------------------- + if( nodes.item(2).nodeName() != "data_header" ) { + kdDebug() << "(K3bMovixDoc) could not find 'data_header' section." << endl; + return false; + } + if( !loadDocumentDataHeader( nodes.item(2).toElement() ) ) + return false; + // ----------------------------------------------------------------- + + + + // parse movix options + // ----------------------------------------------------------------- + if( nodes.item(3).nodeName() != "movix_options" ) { + kdDebug() << "(K3bMovixDoc) could not find 'movix_options' section." << endl; + return false; + } + + // load the options + QDomNodeList optionList = nodes.item(3).childNodes(); + for( uint i = 0; i < optionList.count(); i++ ) { + + QDomElement e = optionList.item(i).toElement(); + if( e.isNull() ) + return false; + + if( e.nodeName() == "shutdown") + setShutdown( e.attributeNode( "activated" ).value() == "yes" ); + else if( e.nodeName() == "reboot") + setReboot( e.attributeNode( "activated" ).value() == "yes" ); + else if( e.nodeName() == "eject_disk") + setEjectDisk( e.attributeNode( "activated" ).value() == "yes" ); + else if( e.nodeName() == "random_play") + setRandomPlay( e.attributeNode( "activated" ).value() == "yes" ); + else if( e.nodeName() == "no_dma") + setNoDma( e.attributeNode( "activated" ).value() == "yes" ); + else if( e.nodeName() == "subtitle_fontset") + setSubtitleFontset( e.text() ); + else if( e.nodeName() == "boot_message_language") + setBootMessageLanguage( e.text() ); + else if( e.nodeName() == "audio_background") + setAudioBackground( e.text() ); + else if( e.nodeName() == "keyboard_language") + setKeyboardLayout( e.text() ); + else if( e.nodeName() == "codecs") + setCodecs( QStringList::split( ',', e.text() ) ); + else if( e.nodeName() == "default_boot_label") + setDefaultBootLabel( e.text() ); + else if( e.nodeName() == "additional_mplayer_options") + setAdditionalMPlayerOptions( e.text() ); + else if( e.nodeName() == "unwanted_mplayer_options") + setUnwantedMPlayerOptions( e.text() ); + else if( e.nodeName() == "loop_playlist") + setLoopPlaylist( e.text().toInt() ); + else + kdDebug() << "(K3bMovixDoc) unknown movix option: " << e.nodeName() << endl; + } + // ----------------------------------------------------------------- + + // parse files + // ----------------------------------------------------------------- + if( nodes.item(4).nodeName() != "movix_files" ) { + kdDebug() << "(K3bMovixDoc) could not find 'movix_files' section." << endl; + return false; + } + + // load file items + QDomNodeList fileList = nodes.item(4).childNodes(); + for( uint i = 0; i < fileList.count(); i++ ) { + + QDomElement e = fileList.item(i).toElement(); + if( e.isNull() ) + return false; + + if( e.nodeName() == "file" ) { + if( !e.hasAttribute( "name" ) ) { + kdDebug() << "(K3bMovixDoc) found file tag without name attribute." << endl; + return false; + } + + QDomElement urlElem = e.firstChild().toElement(); + if( urlElem.isNull() ) { + kdDebug() << "(K3bMovixDoc) found file tag without url child." << endl; + return false; + } + + // create the item + K3bMovixFileItem* newK3bItem = new K3bMovixFileItem( urlElem.text(), + this, + root(), + e.attributeNode("name").value() ); + m_movixFiles.append( newK3bItem ); + + // subtitle file? + QDomElement subTitleElem = e.childNodes().item(1).toElement(); + if( !subTitleElem.isNull() && subTitleElem.nodeName() == "subtitle_file" ) { + urlElem = subTitleElem.firstChild().toElement(); + if( urlElem.isNull() ) { + kdDebug() << "(K3bMovixDoc) found subtitle_file tag without url child." << endl; + return false; + } + + QString name = K3bMovixFileItem::subTitleFileName( newK3bItem->k3bName() ); + K3bFileItem* subItem = new K3bFileItem( urlElem.text(), this, root(), name ); + newK3bItem->setSubTitleItem( subItem ); + } + } + else { + kdDebug() << "(K3bMovixDoc) found " << e.nodeName() << " node where 'file' was expected." << endl; + return false; + } + } + // ----------------------------------------------------------------- + + + emit newMovixFileItems(); + + return true; +} + + +bool K3bMovixDoc::saveDocumentData( QDomElement* docElem ) +{ + QDomDocument doc = docElem->ownerDocument(); + + saveGeneralDocumentData( docElem ); + + QDomElement optionsElem = doc.createElement( "data_options" ); + saveDocumentDataOptions( optionsElem ); + + QDomElement headerElem = doc.createElement( "data_header" ); + saveDocumentDataHeader( headerElem ); + + QDomElement movixOptElem = doc.createElement( "movix_options" ); + QDomElement movixFilesElem = doc.createElement( "movix_files" ); + + + // save the movix options + QDomElement propElem = doc.createElement( "shutdown" ); + propElem.setAttribute( "activated", shutdown() ? "yes" : "no" ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "reboot" ); + propElem.setAttribute( "activated", reboot() ? "yes" : "no" ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "eject_disk" ); + propElem.setAttribute( "activated", ejectDisk() ? "yes" : "no" ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "random_play" ); + propElem.setAttribute( "activated", randomPlay() ? "yes" : "no" ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "no_dma" ); + propElem.setAttribute( "activated", noDma() ? "yes" : "no" ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "subtitle_fontset" ); + propElem.appendChild( doc.createTextNode( subtitleFontset() ) ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "boot_message_language" ); + propElem.appendChild( doc.createTextNode( bootMessageLanguage() ) ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "audio_background" ); + propElem.appendChild( doc.createTextNode( audioBackground() ) ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "keyboard_language" ); + propElem.appendChild( doc.createTextNode( keyboardLayout() ) ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "codecs" ); + propElem.appendChild( doc.createTextNode( codecs().join(",") ) ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "default_boot_label" ); + propElem.appendChild( doc.createTextNode( defaultBootLabel() ) ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "additional_mplayer_options" ); + propElem.appendChild( doc.createTextNode( additionalMPlayerOptions() ) ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "unwanted_mplayer_options" ); + propElem.appendChild( doc.createTextNode( unwantedMPlayerOptions() ) ); + movixOptElem.appendChild( propElem ); + + propElem = doc.createElement( "loop_playlist" ); + propElem.appendChild( doc.createTextNode( QString::number(loopPlaylist()) ) ); + movixOptElem.appendChild( propElem ); + + + // save the movix items + for( QPtrListIterator<K3bMovixFileItem> it( m_movixFiles ); + *it; ++it ) { + K3bMovixFileItem* item = *it; + + QDomElement topElem = doc.createElement( "file" ); + topElem.setAttribute( "name", item->k3bName() ); + QDomElement urlElem = doc.createElement( "url" ); + urlElem.appendChild( doc.createTextNode( item->localPath() ) ); + topElem.appendChild( urlElem ); + if( item->subTitleItem() ) { + QDomElement subElem = doc.createElement( "subtitle_file" ); + urlElem = doc.createElement( "url" ); + urlElem.appendChild( doc.createTextNode( item->subTitleItem()->localPath() ) ); + subElem.appendChild( urlElem ); + topElem.appendChild( subElem ); + } + + movixFilesElem.appendChild( topElem ); + } + + docElem->appendChild( optionsElem ); + docElem->appendChild( headerElem ); + docElem->appendChild( movixOptElem ); + docElem->appendChild( movixFilesElem ); + + return true; +} + + +void K3bMovixDoc::slotDataItemRemoved( K3bDataItem* item ) +{ + // check if it's a movix item + if( K3bMovixFileItem* fi = dynamic_cast<K3bMovixFileItem*>(item) ) + if( m_movixFiles.containsRef( fi ) ) { + emit movixItemRemoved( fi ); + m_movixFiles.removeRef( fi ); + setModified(true); + } +} + + +int K3bMovixDoc::indexOf( K3bMovixFileItem* item ) +{ + return m_movixFiles.findRef(item)+1; +} + + +void K3bMovixDoc::moveMovixItem( K3bMovixFileItem* item, K3bMovixFileItem* itemAfter ) +{ + if( item == itemAfter ) + return; + + // set the current item to track + m_movixFiles.findRef( item ); + // take the current item + item = m_movixFiles.take(); + + // if after == 0 findRef returnes -1 + int pos = m_movixFiles.findRef( itemAfter ); + m_movixFiles.insert( pos+1, item ); + + emit newMovixFileItems(); + + setModified(true); +} + + +void K3bMovixDoc::addSubTitleItem( K3bMovixFileItem* item, const KURL& url ) +{ + if( item->subTitleItem() ) + removeSubTitleItem( item ); + + QFileInfo f( url.path() ); + if( !f.isFile() || !url.isLocalFile() ) + return; + + // check if there already is a file named like we want to name the subTitle file + QString name = K3bMovixFileItem::subTitleFileName( item->k3bName() ); + + if( nameAlreadyInDir( name, root() ) ) { + KMessageBox::error( 0, i18n("Could not rename subtitle file. File with requested name %1 already exists.").arg(name) ); + return; + } + + K3bFileItem* subItem = new K3bFileItem( f.absFilePath(), this, root(), name ); + item->setSubTitleItem( subItem ); + + emit newMovixFileItems(); + + setModified(true); +} + + +void K3bMovixDoc::removeSubTitleItem( K3bMovixFileItem* item ) +{ + if( item->subTitleItem() ) { + emit subTitleItemRemoved( item ); + + delete item->subTitleItem(); + item->setSubTitleItem(0); + + setModified(true); + } +} + +#include "k3bmovixdoc.moc" diff --git a/libk3b/projects/movixcd/k3bmovixdoc.h b/libk3b/projects/movixcd/k3bmovixdoc.h new file mode 100644 index 0000000..53debfc --- /dev/null +++ b/libk3b/projects/movixcd/k3bmovixdoc.h @@ -0,0 +1,125 @@ +/* + * + * $Id: k3bmovixdoc.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_MOVIX_DOC_H_ +#define _K3B_MOVIX_DOC_H_ + + +#include <k3bdatadoc.h> + +#include <qptrlist.h> +#include "k3b_export.h" +//class K3bView; +class KURL; +class QDomElement; +class K3bFileItem; +class K3bMovixFileItem; +class K3bDataItem; +class KConfig; + + +class LIBK3B_EXPORT K3bMovixDoc : public K3bDataDoc +{ + Q_OBJECT + + public: + K3bMovixDoc( QObject* parent = 0 ); + virtual ~K3bMovixDoc(); + + virtual int type() const { return MOVIX; } + + virtual K3bBurnJob* newBurnJob( K3bJobHandler* hdl, QObject* parent ); + + bool newDocument(); + + const QPtrList<K3bMovixFileItem>& movixFileItems() const { return m_movixFiles; } + + int indexOf( K3bMovixFileItem* ); + + + bool shutdown() const { return m_shutdown; } + bool reboot() const { return m_reboot; } + bool ejectDisk() const { return m_ejectDisk; } + bool randomPlay() const { return m_randomPlay; } + const QString& subtitleFontset() const { return m_subtitleFontset; } + const QString& bootMessageLanguage() const { return m_bootMessageLanguage; } + const QString& audioBackground() const { return m_audioBackground; } + const QString& keyboardLayout() const { return m_keyboardLayout; } + const QStringList& codecs() const { return m_codecs; } + const QString& defaultBootLabel() const { return m_defaultBootLabel; } + const QString& additionalMPlayerOptions() const { return m_additionalMPlayerOptions; } + const QString& unwantedMPlayerOptions() const { return m_unwantedMPlayerOptions; } + int loopPlaylist() const { return m_loopPlaylist; } + bool noDma() const { return m_noDma; } + + void setShutdown( bool v ) { m_shutdown = v; } + void setReboot( bool v ) { m_reboot = v; } + void setEjectDisk( bool v ) { m_ejectDisk = v; } + void setRandomPlay( bool v ) { m_randomPlay = v; } + void setSubtitleFontset( const QString& v ) { m_subtitleFontset = v; } + void setBootMessageLanguage( const QString& v ) { m_bootMessageLanguage = v; } + void setAudioBackground( const QString& b ) { m_audioBackground = b; } + void setKeyboardLayout( const QString& l ) { m_keyboardLayout = l; } + void setCodecs( const QStringList& c ) { m_codecs = c; } + void setDefaultBootLabel( const QString& v ) { m_defaultBootLabel = v; } + void setAdditionalMPlayerOptions( const QString& v ) { m_additionalMPlayerOptions = v; } + void setUnwantedMPlayerOptions( const QString& v ) { m_unwantedMPlayerOptions = v; } + void setLoopPlaylist( int v ) { m_loopPlaylist = v; } + void setNoDma( bool b ) { m_noDma = b; } + + signals: + void newMovixFileItems(); + void movixItemRemoved( K3bMovixFileItem* ); + void subTitleItemRemoved( K3bMovixFileItem* ); + + public slots: + void addUrls( const KURL::List& urls ); + void addMovixFile( const KURL& url, int pos = -1 ); + void moveMovixItem( K3bMovixFileItem* item, K3bMovixFileItem* itemAfter ); + void addSubTitleItem( K3bMovixFileItem*, const KURL& ); + void removeSubTitleItem( K3bMovixFileItem* ); + + protected: + /** reimplemented from K3bDoc */ + bool loadDocumentData( QDomElement* root ); + /** reimplemented from K3bDoc */ + bool saveDocumentData( QDomElement* ); + + virtual QString typeString() const { return "movix"; } + + private slots: + void slotDataItemRemoved( K3bDataItem* ); + + private: + QPtrList<K3bMovixFileItem> m_movixFiles; + + bool m_shutdown; + bool m_reboot; + bool m_ejectDisk; + bool m_randomPlay; + QString m_subtitleFontset; + QString m_bootMessageLanguage; + QString m_audioBackground; + QString m_keyboardLayout; + QStringList m_codecs; + QString m_defaultBootLabel; + QString m_additionalMPlayerOptions; + QString m_unwantedMPlayerOptions; + int m_loopPlaylist; + bool m_noDma; +}; + +#endif diff --git a/libk3b/projects/movixcd/k3bmovixdocpreparer.cpp b/libk3b/projects/movixcd/k3bmovixdocpreparer.cpp new file mode 100644 index 0000000..57e18af --- /dev/null +++ b/libk3b/projects/movixcd/k3bmovixdocpreparer.cpp @@ -0,0 +1,490 @@ +/* + * + * $Id: k3bmovixdocpreparer.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 "k3bmovixdocpreparer.h" +#include "k3bmovixdoc.h" +#include "k3bmovixprogram.h" +#include "k3bmovixfileitem.h" + + +#include <k3bcore.h> +#include <k3bdiritem.h> +#include <k3bfileitem.h> +#include <k3bbootitem.h> +#include <k3bexternalbinmanager.h> +#include <k3bisoimager.h> + +#include <klocale.h> +#include <kdebug.h> +#include <ktempfile.h> +#include <kio/global.h> + +#include <qtextstream.h> +#include <qdir.h> + + +class K3bMovixDocPreparer::Private +{ +public: + Private() + : doc(0), + playlistFile(0), + isolinuxConfigFile(0), + movixRcFile(0), + isolinuxDir(0), + movixDir(0), + mplayerDir(0), + playlistFileItem(0), + structuresCreated(false) { + } + + K3bMovixDoc* doc; + const K3bMovixBin* eMovixBin; + + KTempFile* playlistFile; + KTempFile* isolinuxConfigFile; + KTempFile* movixRcFile; + + K3bDirItem* isolinuxDir; + K3bDirItem* movixDir; + K3bDirItem* mplayerDir; + K3bFileItem* playlistFileItem; + + QPtrList<K3bDataItem> newMovixItems; + + bool structuresCreated; +}; + + +K3bMovixDocPreparer::K3bMovixDocPreparer( K3bMovixDoc* doc, K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bJob( jh, parent, name ) +{ + d = new Private(); + d->doc = doc; +} + + +K3bMovixDocPreparer::~K3bMovixDocPreparer() +{ + removeMovixStructures(); + delete d; +} + + +K3bMovixDoc* K3bMovixDocPreparer::doc() const +{ + return d->doc; +} + + +void K3bMovixDocPreparer::start() +{ + kdDebug() << k_funcinfo << endl; + jobStarted(); + + bool success = true; + if( d->structuresCreated ) + removeMovixStructures(); + else + success = createMovixStructures(); + + jobFinished(success); +} + + +void K3bMovixDocPreparer::cancel() +{ + // do nothing... +} + + +bool K3bMovixDocPreparer::createMovixStructures() +{ + kdDebug() << k_funcinfo << endl; + removeMovixStructures(); + + if( doc() ) { + doc()->setMultiSessionMode( K3bDataDoc::NONE ); + doc()->prepareFilenames(); + } + + d->eMovixBin = dynamic_cast<const K3bMovixBin*>( k3bcore->externalBinManager()->binObject("eMovix") ); + if( d->eMovixBin ) { + bool success = false; + if( d->eMovixBin->version >= K3bVersion( 0, 9, 0 ) ) + success = addMovixFilesNew(); + else + success = addMovixFiles(); + + d->structuresCreated = success; + return success; + } + else { + emit infoMessage( i18n("Could not find a valid eMovix installation."), ERROR ); + return false; + } +} + + +void K3bMovixDocPreparer::removeMovixStructures() +{ + kdDebug() << k_funcinfo << endl; + // remove movix files from doc + // the dataitems do the cleanup in the doc + delete d->movixDir; + delete d->isolinuxDir; + delete d->mplayerDir; + delete d->playlistFileItem; + + d->movixDir = 0; + d->isolinuxDir = 0; + d->mplayerDir = 0; + d->playlistFileItem = 0; + + d->newMovixItems.setAutoDelete( true ); + d->newMovixItems.clear(); + + // remove all the temp files + delete d->playlistFile; + delete d->isolinuxConfigFile; + delete d->movixRcFile; + + d->playlistFile = 0; + d->isolinuxConfigFile = 0; + d->movixRcFile = 0; + + d->structuresCreated = false; +} + + +bool K3bMovixDocPreparer::writePlaylistFile() +{ + delete d->playlistFile; + d->playlistFile = new KTempFile(); + d->playlistFile->setAutoDelete(true); + + if( QTextStream* s = d->playlistFile->textStream() ) { + + const QPtrList<K3bMovixFileItem>& movixFileItems = d->doc->movixFileItems(); + + for( QPtrListIterator<K3bMovixFileItem> it( movixFileItems ); + *it; ++it ) { + *s << "/cdrom/"; + *s << it.current()->writtenName(); + *s << endl; + } + + d->playlistFile->close(); + return true; + } + else { + emit infoMessage( i18n("Could not write to temporary file %1").arg(d->playlistFile->name()), ERROR ); + return false; + } +} + + +bool K3bMovixDocPreparer::writeIsolinuxConfigFile( const QString& originalPath ) +{ + delete d->isolinuxConfigFile; + d->isolinuxConfigFile = new KTempFile(); + d->isolinuxConfigFile->setAutoDelete(true); + + if( QTextStream* s = d->isolinuxConfigFile->textStream() ) { + + // now open the default isolinux.cfg and copy everything except the first line which contains + // the default boot label + QFile f( originalPath ); + if( f.open( IO_ReadOnly ) ) { + + QTextStream isolinuxConfigOrig( &f ); + + if( d->doc->defaultBootLabel() != i18n("default") ) { + isolinuxConfigOrig.readLine(); // skip first line + *s << "default " << d->doc->defaultBootLabel() << endl; + } + + QString line = isolinuxConfigOrig.readLine(); + while( !line.isNull() ) { + *s << line << endl; + line = isolinuxConfigOrig.readLine(); + } + + d->isolinuxConfigFile->close(); + return true; + } + else + return false; + } + else { + emit infoMessage( i18n("Could not write to temporary file %1").arg(d->isolinuxConfigFile->name()), ERROR ); + return false; + } +} + + +bool K3bMovixDocPreparer::writeMovixRcFile() +{ + delete d->movixRcFile; + d->movixRcFile = new KTempFile(); + d->movixRcFile->setAutoDelete(true); + + if( QTextStream* s = d->movixRcFile->textStream() ) { + + if( !d->doc->additionalMPlayerOptions().isEmpty() ) + *s << "extra-mplayer-options=" << d->doc->additionalMPlayerOptions() << endl; + if( !d->doc->unwantedMPlayerOptions().isEmpty() ) + *s << "unwanted-mplayer-options=" << d->doc->unwantedMPlayerOptions() << endl; + *s << "loop=" << d->doc->loopPlaylist() << endl; + if( d->doc->shutdown() ) + *s << "shut=y" << endl; + if( d->doc->reboot() ) + *s << "reboot=y" << endl; + if( d->doc->ejectDisk() ) + *s << "eject=y" << endl; + if( d->doc->randomPlay() ) + *s << "random=y" << endl; + if( d->doc->noDma() ) + *s << "dma=n" << endl; + + d->movixRcFile->close(); + return true; + } + else { + emit infoMessage( i18n("Could not write to temporary file %1").arg(d->movixRcFile->name()), ERROR ); + return false; + } +} + + +bool K3bMovixDocPreparer::addMovixFiles() +{ + // first of all we create the directories + d->isolinuxDir = new K3bDirItem( "isolinux", d->doc, d->doc->root() ); + d->movixDir = new K3bDirItem( "movix", d->doc, d->doc->root() ); + K3bDirItem* kernelDir = d->doc->addEmptyDir( "kernel", d->isolinuxDir ); + + // add the linux kernel + (void)new K3bFileItem( d->eMovixBin->path + "/isolinux/kernel/vmlinuz", d->doc, kernelDir ); + + // add the boot image + K3bBootItem* bootItem = d->doc->createBootItem( d->eMovixBin->path + "/isolinux/isolinux.bin", + d->isolinuxDir ); + bootItem->setImageType( K3bBootItem::NONE ); + bootItem->setLoadSize( 4 ); + bootItem->setBootInfoTable(true); + + // some sort weights as defined in isolinux + d->isolinuxDir->setSortWeight( 100 ); + kernelDir->setSortWeight( 50 ); + bootItem->setSortWeight( 200 ); + + // rename the boot catalog file + d->doc->bootCataloge()->setK3bName( "isolinux.boot" ); + + // the following sucks! Redesign it! + + // add all the isolinux files + QStringList isolinuxFiles = d->eMovixBin->isolinuxFiles(); + isolinuxFiles.remove( "isolinux.bin" ); + isolinuxFiles.remove( "isolinux.cfg" ); + isolinuxFiles.remove( "kernel/vmlinuz" ); + for( QStringList::const_iterator it = isolinuxFiles.begin(); + it != isolinuxFiles.end(); ++it ) { + QString path = d->eMovixBin->path + "/isolinux/" + *it; + (void)new K3bFileItem( path, d->doc, d->isolinuxDir ); + } + + const QStringList& movixFiles = d->eMovixBin->movixFiles(); + for( QStringList::const_iterator it = movixFiles.begin(); + it != movixFiles.end(); ++it ) { + QString path = d->eMovixBin->path + "/movix/" + *it; + (void)new K3bFileItem( path, d->doc, d->movixDir ); + } + + // add doku files + QString path = d->eMovixBin->languageDir( d->doc->bootMessageLanguage() ); + QDir dir(path); + QStringList helpFiles = dir.entryList(QDir::Files); + for( QStringList::const_iterator it = helpFiles.begin(); + it != helpFiles.end(); ++it ) { + // some emovix installations include backup-files, no one's perfect ;) + if( !(*it).endsWith( "~" ) ) + (void)new K3bFileItem( path + "/" + *it, d->doc, d->isolinuxDir ); + } + + + // add subtitle font dir + if( !d->doc->subtitleFontset().isEmpty() && + d->doc->subtitleFontset() != i18n("none") ) { + d->mplayerDir = new K3bDirItem( "mplayer", d->doc, d->doc->root() ); + + QString fontPath = d->eMovixBin->subtitleFontDir( d->doc->subtitleFontset() ); + QFileInfo fontType( fontPath ); + if( fontType.isDir() ) { + K3bDirItem* fontDir = new K3bDirItem( "font", d->doc, d->mplayerDir ); + QDir dir( fontPath ); + QStringList fontFiles = dir.entryList( QDir::Files ); + for( QStringList::const_iterator it = fontFiles.begin(); + it != fontFiles.end(); ++it ) { + (void)new K3bFileItem( fontPath + "/" + *it, d->doc, fontDir ); + } + } + else { + // just a ttf file + // needs to be named: subfont.ttf and needs to be placed in mplayer/ + // instead of mplayer/font + (void)new K3bFileItem( fontPath, + d->doc, + d->mplayerDir, + "subfont.ttf" ); + } + } + + + // add movix-config-file and boot-config file + if( writeMovixRcFile() && + writeIsolinuxConfigFile( d->eMovixBin->path + "/isolinux/isolinux.cfg" ) && + writePlaylistFile() ) { + + (void)new K3bFileItem( d->movixRcFile->name(), d->doc, d->movixDir, "movixrc" ); + (void)new K3bFileItem( d->isolinuxConfigFile->name(), d->doc, d->isolinuxDir, "isolinux.cfg" ); + d->playlistFileItem = new K3bFileItem( d->playlistFile->name(), d->doc, d->doc->root(), "movix.list" ); + + return true; + } + else + return false; +} + + +bool K3bMovixDocPreparer::addMovixFilesNew() +{ + // 1. get a list of files from the movixbin + // 2. create file items (replace isolinux.cfg with the one created above) + // 3. add movixrc and movix.list files + // 4. set weights for isolinux files + + // FIXME: use the settings from the doc + QStringList files = d->eMovixBin->files( d->doc->keyboardLayout(), + d->doc->subtitleFontset(), + d->doc->audioBackground(), + d->doc->bootMessageLanguage(), + "all" /*d->doc->codecs()*/ ); // for now we simply don't allow selection + + for( QStringList::iterator it = files.begin(); it != files.end(); ++it ) { + QString docPath = (*it).section( ' ', 0, 0 ); + QString filePath = (*it).section( ' ', 1, 1 ); + QString fileName = filePath.section( '/', -1 ); + + if( fileName == "isolinux.cfg" ) { + // replace the local file with our modified one + if( writeIsolinuxConfigFile( filePath ) ) + createItem( d->isolinuxConfigFile->name(), docPath )->setK3bName( "isolinux.cfg" ); + else + return false; + } + else if( fileName == "isolinux.bin" ) { + // create boot item (no need to remember this since it's in a dir which will be removed + // anyway) + K3bBootItem* bootItem = d->doc->createBootItem( filePath, createDir(docPath) ); + bootItem->setImageType( K3bBootItem::NONE ); + bootItem->setLoadSize( 4 ); + bootItem->setBootInfoTable(true); + + // set the proper sort weight + bootItem->setSortWeight( 200 ); + bootItem->parent()->setSortWeight( 100 ); + } + else if( fileName != "movixrc" ) { // we create our own movixrc + K3bFileItem* item = createItem( filePath, docPath ); + + // Truetype subtitle fonts needs to be named subfont.ttf + if( fileName == d->doc->subtitleFontset() + ".ttf" ) { + item->setK3bName( "subfont.ttf" ); + } + else if( fileName == "vmlinuz" ) + item->setSortWeight( 50 ); + } + } + + // Some distributions (such as Gentoo for example) do use the win32codecs package instead of the + // eMovix supplied codecs. These codecs are not picked up by the movix-conf script + K3bDirItem* codecDir = dynamic_cast<K3bDirItem*>( d->doc->root()->findByPath( "/eMoviX/codecs" ) ); + if( !codecDir || codecDir->isEmpty() ) { + QDir localCodecDir( d->eMovixBin->movixDataDir() + "/codecs" ); + if( localCodecDir.exists() ) { + QStringList codecFiles = localCodecDir.entryList( QDir::Files ); + for( QStringList::const_iterator it = codecFiles.begin(); it != codecFiles.end(); ++it ) + createItem( localCodecDir.path() + '/' + *it, "/eMoviX/codecs" ); + } + } + + if( writePlaylistFile() && writeMovixRcFile() ) { + // add the two items that are not listed by the script + createItem( d->movixRcFile->name(), "/eMoviX/movix" )->setK3bName( "movixrc" ); + createItem( d->playlistFile->name(), "/" )->setK3bName( "movix.list" ); + return true; + } + else + return false; +} + + +K3bFileItem* K3bMovixDocPreparer::createItem( const QString& localPath, const QString& docPath ) +{ + // make sure the path in the doc exists + K3bDirItem* dir = createDir( docPath ); + + // create the file in dir + K3bFileItem* item = new K3bFileItem( localPath, d->doc, dir ); + + // remember the item to remove it becasue the dir cannot be removed + if( dir == d->doc->root() ) + d->newMovixItems.append( item ); + + return item; +} + + +K3bDirItem* K3bMovixDocPreparer::createDir( const QString& docPath ) +{ + QStringList docPathSections = QStringList::split( '/', docPath ); + K3bDirItem* dir = d->doc->root(); + for( QStringList::iterator it = docPathSections.begin(); it != docPathSections.end(); ++it ) { + K3bDataItem* next = dir->find( *it ); + if( !next ) + dir = new K3bDirItem( *it, d->doc, dir ); + else if( next->isDir() ) + dir = static_cast<K3bDirItem*>( next ); + else { + kdError() << "(K3bMovixDocPreparer) found non-dir item where a dir was needed." << endl; + return 0; + } + } + + // remember the dir to remove it + if( dir != d->doc->root() ) { + K3bDirItem* delDir = dir; + while( delDir->parent() != d->doc->root() ) + delDir = delDir->parent(); + if( d->newMovixItems.findRef( delDir ) == -1 ) + d->newMovixItems.append( delDir ); + } + + return dir; +} + +#include "k3bmovixdocpreparer.moc" diff --git a/libk3b/projects/movixcd/k3bmovixdocpreparer.h b/libk3b/projects/movixcd/k3bmovixdocpreparer.h new file mode 100644 index 0000000..3844eae --- /dev/null +++ b/libk3b/projects/movixcd/k3bmovixdocpreparer.h @@ -0,0 +1,67 @@ +/* + * + * $Id: k3bmovixdocpreparer.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_MOVIX_DOC_PREPARER_H_ +#define _K3B_MOVIX_DOC_PREPARER_H_ + +#include <k3bjob.h> + +class K3bMovixDoc; +class K3bFileItem; +class K3bDirItem; + + +/** + * This class creates the needed eMovix structures in an eMovix doc + * and removes them after creating the image. + */ +class K3bMovixDocPreparer : public K3bJob +{ + Q_OBJECT + + public: + explicit K3bMovixDocPreparer( K3bMovixDoc* doc, K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bMovixDocPreparer(); + + K3bMovixDoc* doc() const; + + bool createMovixStructures(); + void removeMovixStructures(); + + public slots: + /** + * use createMovixStructures and removeMovixStructures instead. + */ + void start(); + + /** + * Useless since this job works syncronously + */ + void cancel(); + + private: + bool writePlaylistFile(); + bool writeIsolinuxConfigFile( const QString& ); + bool writeMovixRcFile(); + bool addMovixFiles(); + bool addMovixFilesNew(); + K3bFileItem* createItem( const QString& localPath, const QString& docPath ); + K3bDirItem* createDir( const QString& docPath ); + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/movixcd/k3bmovixfileitem.cpp b/libk3b/projects/movixcd/k3bmovixfileitem.cpp new file mode 100644 index 0000000..12803a2 --- /dev/null +++ b/libk3b/projects/movixcd/k3bmovixfileitem.cpp @@ -0,0 +1,68 @@ +/* + * + * $Id: k3bmovixfileitem.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 "k3bmovixfileitem.h" +#include "k3bmovixdoc.h" + +#include <k3bdiritem.h> + + +K3bMovixFileItem::K3bMovixFileItem( const QString& fileName, + K3bMovixDoc* doc, + K3bDirItem* dir, + const QString& k3bName ) + : K3bFileItem( fileName, doc, dir, k3bName ), + m_doc(doc), + m_subTitleItem(0) +{ +} + + +K3bMovixFileItem::~K3bMovixFileItem() +{ + if( m_subTitleItem ) + m_doc->removeSubTitleItem( this ); + + // remove this from parentdir + // it is important to do it here and not + // rely on the K3bFileItem destructor becasue + // otherwise the doc is not informed early enough + if( parent() ) + parent()->takeDataItem( this ); +} + + +void K3bMovixFileItem::setK3bName( const QString& newName ) +{ + K3bFileItem::setK3bName( newName ); + + // take care of the subTitle file + if( m_subTitleItem ) { + m_subTitleItem->setK3bName( subTitleFileName(k3bName()) ); + } +} + + +QString K3bMovixFileItem::subTitleFileName( const QString& name ) +{ + // remove ending from k3bName + QString subName = name; + int pos = subName.findRev("."); + if( pos > 0 ) + subName.truncate( pos ); + subName += ".sub"; + return subName; +} diff --git a/libk3b/projects/movixcd/k3bmovixfileitem.h b/libk3b/projects/movixcd/k3bmovixfileitem.h new file mode 100644 index 0000000..343f00b --- /dev/null +++ b/libk3b/projects/movixcd/k3bmovixfileitem.h @@ -0,0 +1,52 @@ +/* + * + * $Id: k3bmovixfileitem.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_MOVIX_FILEITEM_H_ +#define _K3B_MOVIX_FILEITEM_H_ + +#include <k3bfileitem.h> + +class K3bMovixDoc; + + +class K3bMovixFileItem : public K3bFileItem +{ + public: + K3bMovixFileItem( const QString& fileName, K3bMovixDoc* doc, K3bDirItem* dir, const QString& k3bName = 0 ); + ~K3bMovixFileItem(); + + K3bFileItem* subTitleItem() const { return m_subTitleItem; } + void setSubTitleItem( K3bFileItem* i ) { m_subTitleItem = i; } + + /** + * reimplemented from K3bDataItem + * also renames the subTitleItem + */ + void setK3bName( const QString& ); + + /** + * returnes the name that the subtitle file must have in + * order to work with mplayer + */ + static QString subTitleFileName( const QString& ); + + private: + K3bMovixDoc* m_doc; + + K3bFileItem* m_subTitleItem; +}; + +#endif diff --git a/libk3b/projects/movixcd/k3bmovixjob.cpp b/libk3b/projects/movixcd/k3bmovixjob.cpp new file mode 100644 index 0000000..2579453 --- /dev/null +++ b/libk3b/projects/movixcd/k3bmovixjob.cpp @@ -0,0 +1,132 @@ +/* + * + * $Id: k3bmovixjob.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 "k3bmovixjob.h" +#include "k3bmovixdoc.h" +#include "k3bmovixfileitem.h" +#include "k3bmovixdocpreparer.h" + +#include <k3bcore.h> +#include <k3bdatajob.h> +#include <k3bdevice.h> + +#include <klocale.h> +#include <kdebug.h> + + +K3bMovixJob::K3bMovixJob( K3bMovixDoc* doc, K3bJobHandler* jh, QObject* parent ) + : K3bBurnJob( jh, parent ), + m_doc(doc) +{ + m_dataJob = new K3bDataJob( doc, this, this ); + m_movixDocPreparer = new K3bMovixDocPreparer( doc, this, this ); + + // pipe signals + connect( m_dataJob, SIGNAL(percent(int)), this, SIGNAL(percent(int)) ); + connect( m_dataJob, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) ); + connect( m_dataJob, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); + connect( m_dataJob, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); + connect( m_dataJob, SIGNAL(bufferStatus(int)), this, SIGNAL(bufferStatus(int)) ); + connect( m_dataJob, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) ); + connect( m_dataJob, SIGNAL(writeSpeed(int, int)), this, SIGNAL(writeSpeed(int, int)) ); + connect( m_dataJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); + connect( m_dataJob, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( m_dataJob, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + connect( m_dataJob, SIGNAL(infoMessage(const QString&, int)), + this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_dataJob, SIGNAL(burning(bool)), this, SIGNAL(burning(bool)) ); + + // we need to clean up here + connect( m_dataJob, SIGNAL(finished(bool)), this, SLOT(slotDataJobFinished(bool)) ); + + connect( m_movixDocPreparer, SIGNAL(infoMessage(const QString&, int)), + this, SIGNAL(infoMessage(const QString&, int)) ); +} + + +K3bMovixJob::~K3bMovixJob() +{ +} + + +K3bDevice::Device* K3bMovixJob::writer() const +{ + return m_dataJob->writer(); +} + + +K3bDoc* K3bMovixJob::doc() const +{ + return m_doc; +} + + +void K3bMovixJob::start() +{ + jobStarted(); + + m_canceled = false; + m_dataJob->setWritingApp( writingApp() ); + + if( m_movixDocPreparer->createMovixStructures() ) { + m_dataJob->start(); + } + else { + m_movixDocPreparer->removeMovixStructures(); + jobFinished(false); + } +} + + +void K3bMovixJob::cancel() +{ + m_canceled = true; + m_dataJob->cancel(); +} + + +void K3bMovixJob::slotDataJobFinished( bool success ) +{ + m_movixDocPreparer->removeMovixStructures(); + + if( m_canceled || m_dataJob->hasBeenCanceled() ) + emit canceled(); + + jobFinished( success ); +} + + +QString K3bMovixJob::jobDescription() const +{ + if( m_doc->isoOptions().volumeID().isEmpty() ) + return i18n("Writing eMovix CD"); + else + return i18n("Writing eMovix CD (%1)").arg(m_doc->isoOptions().volumeID()); +} + + +QString K3bMovixJob::jobDetails() const +{ + return ( i18n("1 file (%1) and about 8 MB eMovix data", + "%n files (%1) and about 8 MB eMovix data", + m_doc->movixFileItems().count()).arg(KIO::convertSize(m_doc->size())) + + ( m_doc->copies() > 1 + ? i18n(" - %n copy", " - %n copies", m_doc->copies()) + : QString::null ) ); +} + +#include "k3bmovixjob.moc" diff --git a/libk3b/projects/movixcd/k3bmovixjob.h b/libk3b/projects/movixcd/k3bmovixjob.h new file mode 100644 index 0000000..81dea8e --- /dev/null +++ b/libk3b/projects/movixcd/k3bmovixjob.h @@ -0,0 +1,60 @@ +/* + * + * $Id: k3bmovixjob.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_MOVIX_JOB_H_ +#define _K3B_MOVIX_JOB_H_ + +#include <k3bjob.h> + +class K3bMovixDoc; +class K3bDevice::Device; +class K3bDataJob; +class KTempFile; +class K3bMovixInstallation; +class K3bMovixDocPreparer; +class K3bDirItem; +class K3bFileItem; + +class K3bMovixJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bMovixJob( K3bMovixDoc* doc, K3bJobHandler*, QObject* parent = 0 ); + ~K3bMovixJob(); + + K3bDoc* doc() const; + K3bDevice::Device* writer() const; + + QString jobDescription() const; + QString jobDetails() const; + + public slots: + void start(); + void cancel(); + + private slots: + void slotDataJobFinished( bool ); + + private: + K3bMovixDoc* m_doc; + K3bDataJob* m_dataJob; + K3bMovixDocPreparer* m_movixDocPreparer; + + bool m_canceled; +}; + +#endif diff --git a/libk3b/projects/movixcd/k3bmovixprogram.cpp b/libk3b/projects/movixcd/k3bmovixprogram.cpp new file mode 100644 index 0000000..8720e8d --- /dev/null +++ b/libk3b/projects/movixcd/k3bmovixprogram.cpp @@ -0,0 +1,339 @@ +/* + * + * $Id: k3bmovixprogram.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 "k3bmovixprogram.h" + +#include <k3bprocess.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <qdir.h> +#include <qfile.h> +#include <qtextstream.h> + + +K3bMovixProgram::K3bMovixProgram() + : K3bExternalProgram( "eMovix" ) +{ +} + +bool K3bMovixProgram::scan( const QString& p ) +{ + if( p.isEmpty() ) + return false; + + QString path = p; + if( path[path.length()-1] != '/' ) + path.append("/"); + + // first test if we have a version info (eMovix >= 0.8.0pre3) + if( !QFile::exists( path + "movix-version" ) ) + return false; + + K3bMovixBin* bin = 0; + + // + // probe version and data dir + // + KProcess vp, dp; + vp << path + "movix-version"; + dp << path + "movix-conf"; + K3bProcessOutputCollector vout( &vp ), dout( &dp ); + if( vp.start( KProcess::Block, KProcess::AllOutput ) && dp.start( KProcess::Block, KProcess::AllOutput ) ) { + // movix-version just gives us the version number on stdout + if( !vout.output().isEmpty() && !dout.output().isEmpty() ) { + bin = new K3bMovixBin( this ); + bin->version = vout.output().stripWhiteSpace(); + bin->path = path; + bin->m_movixPath = dout.output().stripWhiteSpace(); + } + } + else { + kdDebug() << "(K3bMovixProgram) could not start " << path << "movix-version" << endl; + return false; + } + + if( bin->version >= K3bVersion( 0, 9, 0 ) ) + return scanNewEMovix( bin, path ); + else + return scanOldEMovix( bin, path ); +} + + +bool K3bMovixProgram::scanNewEMovix( K3bMovixBin* bin, const QString& path ) +{ + QStringList files = bin->files(); + for( QStringList::iterator it = files.begin(); + it != files.end(); ++it ) { + if( (*it).contains( "isolinux.cfg" ) ) { + bin->m_supportedBootLabels = determineSupportedBootLabels( QStringList::split( " ", *it )[1] ); + break; + } + } + + // here we simply check for the movix-conf program + if( QFile::exists( path + "movix-conf" ) ) { + bin->addFeature( "newfiles" ); + addBin(bin); + return true; + } + else { + delete bin; + return false; + } +} + + +bool K3bMovixProgram::scanOldEMovix( K3bMovixBin* bin, const QString& path ) +{ + // + // first check if all necessary directories are present + // + QDir dir( bin->movixDataDir() ); + QStringList subdirs = dir.entryList( QDir::Dirs ); + if( !subdirs.contains( "boot-messages" ) ) { + kdDebug() << "(K3bMovixProgram) could not find subdir 'boot-messages'" << endl; + delete bin; + return false; + } + if( !subdirs.contains( "isolinux" ) ) { + kdDebug() << "(K3bMovixProgram) could not find subdir 'isolinux'" << endl; + delete bin; + return false; + } + if( !subdirs.contains( "movix" ) ) { + kdDebug() << "(K3bMovixProgram) could not find subdir 'movix'" << endl; + delete bin; + return false; + } + if( !subdirs.contains( "mplayer-fonts" ) ) { + kdDebug() << "(K3bMovixProgram) could not find subdir 'mplayer-fonts'" << endl; + delete bin; + return false; + } + + + // + // check if we have a version of eMovix which contains the movix-files script + // + if( QFile::exists( path + "movix-files" ) ) { + bin->addFeature( "files" ); + + KProcess p; + K3bProcessOutputCollector out( &p ); + p << bin->path + "movix-files"; + if( p.start( KProcess::Block, KProcess::AllOutput ) ) { + bin->m_movixFiles = QStringList::split( "\n", out.output() ); + } + } + + // + // fallback: to be compatible with 0.8.0rc2 we just add all files in the movix directory + // + if( bin->m_movixFiles.isEmpty() ) { + QDir dir( bin->movixDataDir() + "/movix" ); + bin->m_movixFiles = dir.entryList(QDir::Files); + } + + // + // these files are fixed. That should not be a problem + // since Isolinux is quite stable as far as I know. + // + bin->m_isolinuxFiles.append( "initrd.gz" ); + bin->m_isolinuxFiles.append( "isolinux.bin" ); + bin->m_isolinuxFiles.append( "isolinux.cfg" ); + bin->m_isolinuxFiles.append( "kernel/vmlinuz" ); + bin->m_isolinuxFiles.append( "movix.lss" ); + bin->m_isolinuxFiles.append( "movix.msg" ); + + + // + // check every single necessary file :( + // + for( QStringList::const_iterator it = bin->m_isolinuxFiles.begin(); + it != bin->m_isolinuxFiles.end(); ++it ) { + if( !QFile::exists( bin->movixDataDir() + "/isolinux/" + *it ) ) { + kdDebug() << "(K3bMovixProgram) Could not find file " << *it << endl; + delete bin; + return false; + } + } + + // + // now check the boot-messages languages + // + dir.cd( "boot-messages" ); + bin->m_supportedLanguages = dir.entryList(QDir::Dirs); + bin->m_supportedLanguages.remove("."); + bin->m_supportedLanguages.remove(".."); + bin->m_supportedLanguages.remove("CVS"); // the eMovix makefile stuff seems not perfect ;) + bin->m_supportedLanguages.prepend( i18n("default") ); + dir.cdUp(); + + // + // now check the supported mplayer-fontsets + // FIXME: every font dir needs to contain the "font.desc" file! + // + dir.cd( "mplayer-fonts" ); + bin->m_supportedSubtitleFonts = dir.entryList( QDir::Dirs ); + bin->m_supportedSubtitleFonts.remove("."); + bin->m_supportedSubtitleFonts.remove(".."); + bin->m_supportedSubtitleFonts.remove("CVS"); // the eMovix makefile stuff seems not perfect ;) + // new ttf fonts in 0.8.0rc2 + bin->m_supportedSubtitleFonts += dir.entryList( "*.ttf", QDir::Files ); + bin->m_supportedSubtitleFonts.prepend( i18n("none") ); + dir.cdUp(); + + // + // now check the supported boot labels + // + dir.cd( "isolinux" ); + bin->m_supportedBootLabels = determineSupportedBootLabels( dir.filePath("isolinux.cfg") ); + + // + // This seems to be a valid eMovix installation. :) + // + + addBin(bin); + return true; +} + + +QStringList K3bMovixProgram::determineSupportedBootLabels( const QString& isoConfigFile ) const +{ + QStringList list( i18n("default") ); + + QFile f( isoConfigFile ); + if( !f.open( IO_ReadOnly ) ) { + kdDebug() << "(K3bMovixProgram) could not open file '" << f.name() << "'" << endl; + } + else { + QTextStream fs( &f ); + QString line = fs.readLine(); + while( !line.isNull() ) { + if( line.startsWith( "label" ) ) + list.append( line.mid( 5 ).stripWhiteSpace() ); + + line = fs.readLine(); + } + f.close(); + } + + return list; +} + + +QString K3bMovixBin::subtitleFontDir( const QString& font ) const +{ + if( font == i18n("none" ) ) + return ""; + else if( m_supportedSubtitleFonts.contains( font ) ) + return path + "/mplayer-fonts/" + font; + else + return ""; +} + + +QString K3bMovixBin::languageDir( const QString& lang ) const +{ + if( lang == i18n("default") ) + return languageDir( "en" ); + else if( m_supportedLanguages.contains( lang ) ) + return path + "/boot-messages/" + lang; + else + return ""; +} + + +QStringList K3bMovixBin::supportedSubtitleFonts() const +{ + if( version >= K3bVersion( 0, 9, 0 ) ) + return QStringList( i18n("default") ) += supported( "font" ); + else + return m_supportedSubtitleFonts; +} + + +QStringList K3bMovixBin::supportedLanguages() const +{ + if( version >= K3bVersion( 0, 9, 0 ) ) + return QStringList( i18n("default") ) += supported( "lang" ); + else + return m_supportedLanguages; +} + + +// only used for eMovix >= 0.9.0 +QStringList K3bMovixBin::supportedKbdLayouts() const +{ + return QStringList( i18n("default") ) += supported( "kbd" ); +} + + +// only used for eMovix >= 0.9.0 +QStringList K3bMovixBin::supportedBackgrounds() const +{ + return QStringList( i18n("default") ) += supported( "background" ); +} + + +// only used for eMovix >= 0.9.0 +QStringList K3bMovixBin::supportedCodecs() const +{ + return supported( "codecs" ); +} + + +QStringList K3bMovixBin::supported( const QString& type ) const +{ + KProcess p; + K3bProcessOutputCollector out( &p ); + p << path + "movix-conf" << "--supported=" + type; + if( p.start( KProcess::Block, KProcess::AllOutput ) ) + return QStringList::split( "\n", out.output() ); + else + return QStringList(); +} + + +QStringList K3bMovixBin::files( const QString& kbd, + const QString& font, + const QString& bg, + const QString& lang, + const QStringList& codecs ) const +{ + KProcess p; + K3bProcessOutputCollector out( &p ); + p << path + "movix-conf" << "--files"; + + + if( !kbd.isEmpty() && kbd != i18n("default") ) + p << "--kbd" << kbd; + if( !font.isEmpty() && font != i18n("default") ) + p << "--font" << font; + if( !bg.isEmpty() && bg != i18n("default") ) + p << "--background" << bg; + if( !lang.isEmpty() && lang != i18n("default") ) + p << "--lang" << lang; + if( !codecs.isEmpty() ) + p << "--codecs" << codecs.join( "," ); + + if( p.start( KProcess::Block, KProcess::AllOutput ) ) + return QStringList::split( "\n", out.output() ); + else + return QStringList(); +} diff --git a/libk3b/projects/movixcd/k3bmovixprogram.h b/libk3b/projects/movixcd/k3bmovixprogram.h new file mode 100644 index 0000000..a6a9ac0 --- /dev/null +++ b/libk3b/projects/movixcd/k3bmovixprogram.h @@ -0,0 +1,103 @@ +/* + * + * $Id: k3bmovixprogram.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_MOVIX_PROGRAM_H_ +#define _K3B_MOVIX_PROGRAM_H_ + +#include <k3bexternalbinmanager.h> +#include "k3b_export.h" + +class LIBK3B_EXPORT K3bMovixBin : public K3bExternalBin +{ + public: + K3bMovixBin( K3bExternalProgram* p ) + : K3bExternalBin( p ) { + } + + const QString& movixDataDir() const { return m_movixPath; } + + const QStringList& supportedBootLabels() const { return m_supportedBootLabels; } + QStringList supportedSubtitleFonts() const; + QStringList supportedLanguages() const; + QStringList supportedKbdLayouts() const; + QStringList supportedBackgrounds() const; + QStringList supportedCodecs() const; + + /* + * Unused for eMovix versions 0.9.0 and above + */ + const QStringList& movixFiles() const { return m_movixFiles; } + + /* + * Unused for eMovix versions 0.9.0 and above + */ + const QStringList& isolinuxFiles() const { return m_isolinuxFiles; } + + /** + * returnes empty string if font was not found + * + * Unused for eMovix versions 0.9.0 and above + */ + QString subtitleFontDir( const QString& font ) const; + + /** + * returnes empty string if lang was not found + * + * Unused for eMovix versions 0.9.0 and above + */ + QString languageDir( const QString& lang ) const; + + /** + * Interface for the movix-conf --files interface for + * versions >= 0.9.0 + */ + QStringList files( const QString& kbd = QString::null, + const QString& font = QString::null, + const QString& bg = QString::null, + const QString& lang = QString::null, + const QStringList& codecs = QStringList() ) const; + + private: + QStringList supported( const QString& ) const; + + QString m_movixPath; + QStringList m_movixFiles; + QStringList m_isolinuxFiles; + QStringList m_supportedBootLabels; + QStringList m_supportedSubtitleFonts; + QStringList m_supportedLanguages; + + friend class K3bMovixProgram; +}; + + +class LIBK3B_EXPORT K3bMovixProgram : public K3bExternalProgram +{ + public: + K3bMovixProgram(); + + bool scan( const QString& ); + + bool supportsUserParameters() const { return false; } + + private: + bool scanNewEMovix( K3bMovixBin* bin, const QString& ); + bool scanOldEMovix( K3bMovixBin* bin, const QString& ); + QStringList determineSupportedBootLabels( const QString& ) const; +}; + + + +#endif diff --git a/libk3b/projects/movixdvd/Makefile.am b/libk3b/projects/movixdvd/Makefile.am new file mode 100644 index 0000000..7af7283 --- /dev/null +++ b/libk3b/projects/movixdvd/Makefile.am @@ -0,0 +1,21 @@ +# we need the ../datacd and ../movixcd for the uic generated header files +AM_CPPFLAGS= -I$(srcdir)/../../core \ + -I$(srcdir)/../../../libk3bdevice \ + -I$(srcdir)/../../../src \ + -I$(srcdir)/../../tools \ + -I$(srcdir)/../datadvd \ + -I$(srcdir)/../movixcd \ + -I$(srcdir)/../datacd \ + -I$(srcdir)/.. \ + -I../datacd \ + -I../movixcd \ + $(all_includes) + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libmovixdvd.la + +libmovixdvd_la_SOURCES = k3bmovixdvddoc.cpp k3bmovixdvdjob.cpp + +include_HEADERS = k3bmovixdvddoc.h \ + k3bmovixdvdjob.h diff --git a/libk3b/projects/movixdvd/k3bmovixdvddoc.cpp b/libk3b/projects/movixdvd/k3bmovixdvddoc.cpp new file mode 100644 index 0000000..80b8ec2 --- /dev/null +++ b/libk3b/projects/movixdvd/k3bmovixdvddoc.cpp @@ -0,0 +1,36 @@ +/* + * + * $Id: k3bmovixdvddoc.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 "k3bmovixdvddoc.h" +#include "k3bmovixdvdjob.h" + +#include <kconfig.h> + + +K3bMovixDvdDoc::K3bMovixDvdDoc( QObject* parent ) + : K3bMovixDoc( parent ) +{ +} + +K3bMovixDvdDoc::~K3bMovixDvdDoc() +{ +} + +K3bBurnJob* K3bMovixDvdDoc::newBurnJob( K3bJobHandler* hdl, QObject* parent ) +{ + return new K3bMovixDvdJob( this, hdl, parent ); +} + +#include "k3bmovixdvddoc.moc" diff --git a/libk3b/projects/movixdvd/k3bmovixdvddoc.h b/libk3b/projects/movixdvd/k3bmovixdvddoc.h new file mode 100644 index 0000000..85943f0 --- /dev/null +++ b/libk3b/projects/movixdvd/k3bmovixdvddoc.h @@ -0,0 +1,40 @@ +/* + * + * $Id: k3bmovixdvddoc.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_MOVIX_DVD_DOC_H_ +#define _K3B_MOVIX_DVD_DOC_H_ + +#include <k3bmovixdoc.h> +#include "k3b_export.h" +class KConfig; + + +class LIBK3B_EXPORT K3bMovixDvdDoc : public K3bMovixDoc +{ + Q_OBJECT + + public: + K3bMovixDvdDoc( QObject* parent = 0 ); + ~K3bMovixDvdDoc(); + + int type() const { return MOVIX_DVD; } + + K3bBurnJob* newBurnJob( K3bJobHandler* hdl, QObject* parent ); + + protected: + QString typeString() const { return "movixdvd"; } +}; + +#endif diff --git a/libk3b/projects/movixdvd/k3bmovixdvdjob.cpp b/libk3b/projects/movixdvd/k3bmovixdvdjob.cpp new file mode 100644 index 0000000..b556997 --- /dev/null +++ b/libk3b/projects/movixdvd/k3bmovixdvdjob.cpp @@ -0,0 +1,131 @@ +/* + * + * $Id: k3bmovixdvdjob.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 "k3bmovixdvdjob.h" +#include "k3bmovixdvddoc.h" +#include "k3bmovixfileitem.h" +#include "k3bmovixdocpreparer.h" + +#include <k3bcore.h> +#include <k3bdvdjob.h> +#include <k3bdevice.h> + +#include <klocale.h> +#include <kdebug.h> + + +K3bMovixDvdJob::K3bMovixDvdJob( K3bMovixDvdDoc* doc, K3bJobHandler* jh, QObject* parent ) + : K3bBurnJob( jh, parent ), + m_doc(doc) +{ + m_dvdJob = new K3bDvdJob( doc, this, this ); + m_movixDocPreparer = new K3bMovixDocPreparer( doc, this, this ); + + // pipe signals + connect( m_dvdJob, SIGNAL(percent(int)), this, SIGNAL(percent(int)) ); + connect( m_dvdJob, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) ); + connect( m_dvdJob, SIGNAL(processedSubSize(int, int)), this, SIGNAL(processedSubSize(int, int)) ); + connect( m_dvdJob, SIGNAL(processedSize(int, int)), this, SIGNAL(processedSize(int, int)) ); + connect( m_dvdJob, SIGNAL(bufferStatus(int)), this, SIGNAL(bufferStatus(int)) ); + connect( m_dvdJob, SIGNAL(writeSpeed(int, int)), this, SIGNAL(writeSpeed(int, int)) ); + connect( m_dvdJob, SIGNAL(newTask(const QString&)), this, SIGNAL(newTask(const QString&)) ); + connect( m_dvdJob, SIGNAL(newSubTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) ); + connect( m_dvdJob, SIGNAL(debuggingOutput(const QString&, const QString&)), + this, SIGNAL(debuggingOutput(const QString&, const QString&)) ); + connect( m_dvdJob, SIGNAL(infoMessage(const QString&, int)), + this, SIGNAL(infoMessage(const QString&, int)) ); + connect( m_dvdJob, SIGNAL(burning(bool)), this, SIGNAL(burning(bool)) ); + + // we need to clean up here + connect( m_dvdJob, SIGNAL(finished(bool)), this, SLOT(slotDvdJobFinished(bool)) ); + + connect( m_movixDocPreparer, SIGNAL(infoMessage(const QString&, int)), + this, SIGNAL(infoMessage(const QString&, int)) ); +} + + +K3bMovixDvdJob::~K3bMovixDvdJob() +{ +} + + +K3bDevice::Device* K3bMovixDvdJob::writer() const +{ + return m_dvdJob->writer(); +} + + +K3bDoc* K3bMovixDvdJob::doc() const +{ + return m_doc; +} + + +void K3bMovixDvdJob::start() +{ + jobStarted(); + + m_canceled = false; + m_dvdJob->setWritingApp( writingApp() ); + + if( m_movixDocPreparer->createMovixStructures() ) { + m_dvdJob->start(); + } + else { + m_movixDocPreparer->removeMovixStructures(); + jobFinished(false); + } +} + + +void K3bMovixDvdJob::cancel() +{ + m_canceled = true; + m_dvdJob->cancel(); +} + + +void K3bMovixDvdJob::slotDvdJobFinished( bool success ) +{ + m_movixDocPreparer->removeMovixStructures(); + + if( m_canceled || m_dvdJob->hasBeenCanceled() ) + emit canceled(); + + jobFinished( success ); +} + + +QString K3bMovixDvdJob::jobDescription() const +{ + if( m_doc->isoOptions().volumeID().isEmpty() ) + return i18n("Writing eMovix DVD"); + else + return i18n("Writing eMovix DVD (%1)").arg(m_doc->isoOptions().volumeID()); +} + + +QString K3bMovixDvdJob::jobDetails() const +{ + return ( i18n("1 file (%1) and about 8 MB eMovix data", + "%n files (%1) and about 8 MB eMovix data", + m_doc->movixFileItems().count()).arg(KIO::convertSize(m_doc->size())) + + ( m_doc->copies() > 1 + ? i18n(" - %n copy", " - %n copies", m_doc->copies()) + : QString::null ) ); +} + +#include "k3bmovixdvdjob.moc" diff --git a/libk3b/projects/movixdvd/k3bmovixdvdjob.h b/libk3b/projects/movixdvd/k3bmovixdvdjob.h new file mode 100644 index 0000000..2b9ce10 --- /dev/null +++ b/libk3b/projects/movixdvd/k3bmovixdvdjob.h @@ -0,0 +1,60 @@ +/* + * + * $Id: k3bmovixdvdjob.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_MOVIX_DVD_JOB_H_ +#define _K3B_MOVIX_DVD_JOB_H_ + +#include <k3bjob.h> + +class K3bMovixDvdDoc; +class K3bDevice::Device; +class K3bDvdJob; +class KTempFile; +class K3bMovixInstallation; +class K3bMovixDocPreparer; +class K3bDirItem; +class K3bFileItem; + +class K3bMovixDvdJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bMovixDvdJob( K3bMovixDvdDoc* doc, K3bJobHandler*, QObject* parent = 0 ); + ~K3bMovixDvdJob(); + + K3bDoc* doc() const; + K3bDevice::Device* writer() const; + + QString jobDescription() const; + QString jobDetails() const; + + public slots: + void start(); + void cancel(); + + private slots: + void slotDvdJobFinished( bool ); + + private: + K3bMovixDvdDoc* m_doc; + K3bDvdJob* m_dvdJob; + K3bMovixDocPreparer* m_movixDocPreparer; + + bool m_canceled; +}; + +#endif diff --git a/libk3b/projects/videocd/Makefile.am b/libk3b/projects/videocd/Makefile.am new file mode 100644 index 0000000..1e18d02 --- /dev/null +++ b/libk3b/projects/videocd/Makefile.am @@ -0,0 +1,20 @@ +AM_CPPFLAGS= -I$(srcdir)/../../core \ + -I$(srcdir)/../../../src \ + -I$(srcdir)/../../../libk3bdevice \ + -I$(srcdir)/../../tools \ + -I$(srcdir)/.. \ + $(all_includes) + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libvcd.la + +libvcd_la_SOURCES = k3bvcddoc.cpp k3bvcdtrack.cpp k3bvcdjob.cpp k3bvcdoptions.cpp k3bvcdxmlview.cpp + +libvcd_la_LIBADD = mpeginfo/libmpeginfo.la + +SUBDIRS = cdi extra mpeginfo + +include_HEADERS = k3bvcdjob.h \ + k3bvcddoc.h \ + k3bvcdoptions.h diff --git a/libk3b/projects/videocd/cdi/Makefile.am b/libk3b/projects/videocd/cdi/Makefile.am new file mode 100644 index 0000000..e487acc --- /dev/null +++ b/libk3b/projects/videocd/cdi/Makefile.am @@ -0,0 +1,5 @@ + +cdidir = $(kde_datadir)/k3b/cdi +cdi_DATA = cdi_imag.rtf cdi_text.fnt cdi_vcd.app cdi_vcd.cfg vcd_on_cdi_41.pdf icdia.htm + +EXTRA_DIST = $(cdi_DATA) diff --git a/libk3b/projects/videocd/cdi/cdi_imag.rtf b/libk3b/projects/videocd/cdi/cdi_imag.rtf Binary files differnew file mode 100644 index 0000000..809145f --- /dev/null +++ b/libk3b/projects/videocd/cdi/cdi_imag.rtf diff --git a/libk3b/projects/videocd/cdi/cdi_text.fnt b/libk3b/projects/videocd/cdi/cdi_text.fnt Binary files differnew file mode 100644 index 0000000..0dd0e15 --- /dev/null +++ b/libk3b/projects/videocd/cdi/cdi_text.fnt diff --git a/libk3b/projects/videocd/cdi/cdi_vcd.app b/libk3b/projects/videocd/cdi/cdi_vcd.app Binary files differnew file mode 100644 index 0000000..ceb31fc --- /dev/null +++ b/libk3b/projects/videocd/cdi/cdi_vcd.app diff --git a/libk3b/projects/videocd/cdi/cdi_vcd.cfg b/libk3b/projects/videocd/cdi/cdi_vcd.cfg new file mode 100644 index 0000000..4aed0eb --- /dev/null +++ b/libk3b/projects/videocd/cdi/cdi_vcd.cfg @@ -0,0 +1,12 @@ +CONTROLS=ALL +CURCOL=YELLOW +PSDCURCOL=RED +PSDCURSHAPE=ARROW +CENTRTRACK=2 +AUTOPLAY=AUTO_ON +DUALCHAN=DUAL_ON +TIMECODE_X=64 +TIMECODE_Y=100 +LOTID_X=64 +LOTID_Y=64 +ALBUM=STANDARD
\ No newline at end of file diff --git a/libk3b/projects/videocd/cdi/icdia.htm b/libk3b/projects/videocd/cdi/icdia.htm new file mode 100644 index 0000000..cd6c47b --- /dev/null +++ b/libk3b/projects/videocd/cdi/icdia.htm @@ -0,0 +1,12 @@ +<HTML> +<HEAD> +<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> +<META HTTP-EQUIV="Refresh" CONTENT="0; URL=http://www.icdia.org/"> +<TITLE>The New International CD-i Association</TITLE> +</HEAD> +<BODY> + +<A HREF="http://www.icdia.org">The New International CD-i Association - http://www.icdia.org</A> + +</BODY> +</HTML> diff --git a/libk3b/projects/videocd/cdi/vcd_on_cdi_41.pdf b/libk3b/projects/videocd/cdi/vcd_on_cdi_41.pdf Binary files differnew file mode 100644 index 0000000..cdf4fed --- /dev/null +++ b/libk3b/projects/videocd/cdi/vcd_on_cdi_41.pdf diff --git a/libk3b/projects/videocd/extra/Makefile.am b/libk3b/projects/videocd/extra/Makefile.am new file mode 100644 index 0000000..717fa99 --- /dev/null +++ b/libk3b/projects/videocd/extra/Makefile.am @@ -0,0 +1,5 @@ + +extradir = $(kde_datadir)/k3b/extra +extra_DATA = k3bphotovcd.mpg k3bphotosvcd.mpg + +EXTRA_DIST = $(extra_DATA) diff --git a/libk3b/projects/videocd/extra/k3bphotosvcd.mpg b/libk3b/projects/videocd/extra/k3bphotosvcd.mpg Binary files differnew file mode 100644 index 0000000..50156d7 --- /dev/null +++ b/libk3b/projects/videocd/extra/k3bphotosvcd.mpg diff --git a/libk3b/projects/videocd/extra/k3bphotovcd.mpg b/libk3b/projects/videocd/extra/k3bphotovcd.mpg Binary files differnew file mode 100644 index 0000000..2ddb69e --- /dev/null +++ b/libk3b/projects/videocd/extra/k3bphotovcd.mpg diff --git a/libk3b/projects/videocd/k3bvcddoc.cpp b/libk3b/projects/videocd/k3bvcddoc.cpp new file mode 100644 index 0000000..462aea3 --- /dev/null +++ b/libk3b/projects/videocd/k3bvcddoc.cpp @@ -0,0 +1,894 @@ +/* +* +* $Id: k3bvcddoc.cpp 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2005 Christian Kvasny <chris@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. +*/ + +// QT-includes +#include <qstring.h> +#include <qstringlist.h> +#include <qfile.h> +#include <qdatastream.h> +#include <qdom.h> +#include <qdatetime.h> +#include <qtimer.h> +#include <qtextstream.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 <kstdguiitem.h> + +// K3b-includes +#include "k3bvcddoc.h" +#include "k3bvcdtrack.h" +#include "k3bvcdjob.h" +#include <k3bglobals.h> +#include <k3bmsf.h> + + +bool desperate_mode = false; +bool preserve_header = false; +bool print_progress = true; +bool aspect_correction = false; +byte forced_sequence_header = 0; + +K3bVcdDoc::K3bVcdDoc( QObject* parent ) + : K3bDoc( parent ) +{ + m_tracks = 0L; + m_vcdOptions = new K3bVcdOptions(); + + m_docType = VCD; + m_vcdType = NONE; + + m_urlAddingTimer = new QTimer( this ); + connect( m_urlAddingTimer, SIGNAL( timeout() ), this, SLOT( slotWorkUrlQueue() ) ); + + // FIXME: remove the newTracks() signal and replace it with the changed signal + connect( this, SIGNAL( newTracks() ), this, SIGNAL( changed() ) ); + connect( this, SIGNAL( trackRemoved( K3bVcdTrack* ) ), this, SIGNAL( changed() ) ); +} + +K3bVcdDoc::~K3bVcdDoc() +{ + if ( m_tracks ) { + m_tracks->setAutoDelete( true ); + delete m_tracks; + } + + delete m_vcdOptions; +} + +bool K3bVcdDoc::newDocument() +{ + if ( m_tracks ) + while ( m_tracks->first() ) + removeTrack( m_tracks->first() ); + else + m_tracks = new QPtrList<K3bVcdTrack>; + m_tracks->setAutoDelete( false ); + + return K3bDoc::newDocument(); +} + + +QString K3bVcdDoc::name() const +{ + return m_vcdOptions->volumeId(); +} + + +KIO::filesize_t K3bVcdDoc::calcTotalSize() const +{ + unsigned long long sum = 0; + if ( m_tracks ) { + for ( K3bVcdTrack * track = m_tracks->first(); track; track = m_tracks->next() ) { + sum += track->size(); + } + } + return sum; +} + +KIO::filesize_t K3bVcdDoc::size() const +{ + // mode2 -> mode1 int(( n+2047 ) / 2048) * 2352 + // mode1 -> mode2 int(( n+2351 ) / 2352) * 2048 + long tracksize = long( ( calcTotalSize() + 2351 ) / 2352 ) * 2048; + return tracksize + ISOsize(); +} + +KIO::filesize_t K3bVcdDoc::ISOsize() const +{ + // 136000b for vcd iso reseved + long long iso_size = 136000; + if ( vcdOptions() ->CdiSupport() ) { + iso_size += vcdOptions() ->CDIsize(); + } + + return iso_size; +} + +K3b::Msf K3bVcdDoc::length() const +{ + return K3b::Msf( size() / 2048 ); +} + + +bool K3bVcdDoc::isImage( const KURL& url ) +{ + QImage p; + return p.load( QFile::encodeName( url.path() ) ); +} + +void K3bVcdDoc::addUrls( const KURL::List& urls ) +{ + // make sure we add them at the end even if urls are in the queue + addTracks( urls, 99 ); +} + +void K3bVcdDoc::addTracks( const KURL::List& urls, uint position ) +{ + KURL::List::ConstIterator end( urls.end() ); + for ( KURL::List::ConstIterator it = urls.begin(); it != end; ++it ) { + urlsToAdd.enqueue( new PrivateUrlToAdd( K3b::convertToLocalUrl(*it), position++ ) ); + } + + m_urlAddingTimer->start( 0 ); +} + +void K3bVcdDoc::slotWorkUrlQueue() +{ + if ( !urlsToAdd.isEmpty() ) { + PrivateUrlToAdd * item = urlsToAdd.dequeue(); + lastAddedPosition = item->position; + + // append at the end by default + if ( lastAddedPosition > m_tracks->count() ) + lastAddedPosition = m_tracks->count(); + + if ( !item->url.isLocalFile() ) { + kdDebug() << item->url.path() << " no local file" << endl; + return ; + } + + if ( !QFile::exists( item->url.path() ) ) { + kdDebug() << "(K3bVcdDoc) file not found: " << item->url.path() << endl; + m_notFoundFiles.append( item->url.path() ); + return ; + } + + if ( K3bVcdTrack * newTrack = createTrack( item->url ) ) + addTrack( newTrack, lastAddedPosition ); + + delete item; + + emit newTracks(); + } else { + m_urlAddingTimer->stop(); + + emit newTracks(); + + // reorder pbc tracks + setPbcTracks(); + + informAboutNotFoundFiles(); + } +} + +K3bVcdTrack* K3bVcdDoc::createTrack( const KURL& url ) +{ + char filename[ 255 ]; + QString error_string = ""; + strcpy( filename, QFile::encodeName( url.path() ) ); + K3bMpegInfo* Mpeg = new K3bMpegInfo( filename ); + + if ( Mpeg ) { + int mpegVersion = Mpeg->version(); + if ( mpegVersion > 0 ) { + + if ( vcdType() == NONE && mpegVersion < 2 ) { + m_urlAddingTimer->stop(); + setVcdType( vcdTypes( mpegVersion ) ); + vcdOptions() ->setMpegVersion( mpegVersion ); + KMessageBox::information( kapp->mainWidget(), + i18n( "K3b will create a %1 image from the given MPEG " + "files, but these files must already be in %2 " + "format. K3b does not yet resample MPEG files." ) + .arg( i18n( "VCD" ) ) + .arg( i18n( "VCD" ) ), + i18n( "Information" ) ); + m_urlAddingTimer->start( 0 ); + } else if ( vcdType() == NONE ) { + m_urlAddingTimer->stop(); + vcdOptions() ->setMpegVersion( mpegVersion ); + bool force = false; + force = ( KMessageBox::questionYesNo( kapp->mainWidget(), + i18n( "K3b will create a %1 image from the given MPEG " + "files, but these files must already be in %2 " + "format. K3b does not yet resample MPEG files." ) + .arg( i18n( "SVCD" ) ) + .arg( i18n( "SVCD" ) ) + + "\n\n" + + i18n( "Note: Forcing MPEG2 as VCD is not supported by " + "some standalone DVD players." ), + i18n( "Information" ), + KStdGuiItem::ok().text(), + i18n( "Forcing VCD" ) ) == KMessageBox::No ); + if ( force ) { + setVcdType( vcdTypes( 1 ) ); + vcdOptions() ->setAutoDetect( false ); + } else + setVcdType( vcdTypes( mpegVersion ) ); + + m_urlAddingTimer->start( 0 ); + } + + + if ( numOfTracks() > 0 && vcdOptions() ->mpegVersion() != mpegVersion ) { + KMessageBox::error( kapp->mainWidget(), "(" + url.path() + ")\n" + + i18n( "You cannot mix MPEG1 and MPEG2 video files.\nPlease start a new Project for this filetype.\nResample not implemented in K3b yet." ), + i18n( "Wrong File Type for This Project" ) ); + + delete Mpeg; + return 0; + } + + K3bVcdTrack* newTrack = new K3bVcdTrack( m_tracks, url.path() ); + *( newTrack->mpeg_info ) = *( Mpeg->mpeg_info ); + + if ( newTrack->isSegment() && !vcdOptions()->PbcEnabled() ) { + KMessageBox::information( kapp->mainWidget(), + i18n( "PBC (Playback control) enabled.\n" + "Videoplayers can not reach Segments (Mpeg Still Pictures) without Playback control ." ) , + i18n( "Information" ) ); + + vcdOptions()->setPbcEnabled( true ); + } + + // set defaults; + newTrack->setPlayTime( vcdOptions() ->PbcPlayTime() ); + newTrack->setWaitTime( vcdOptions() ->PbcWaitTime() ); + newTrack->setPbcNumKeys( vcdOptions() ->PbcNumkeysEnabled() ); + delete Mpeg; + + // debugging output + newTrack->PrintInfo(); + + return newTrack; + } + } else if ( isImage( url ) ) { // image track + // woking on ... + // for future use + // photoalbum starts here + // return here the new photoalbum track + } + + if ( Mpeg ) { + error_string = Mpeg->error_string(); + delete Mpeg; + } + + // error (unsupported files) + KMessageBox::error( kapp->mainWidget(), "(" + url.path() + ")\n" + + i18n( "Only MPEG1 and MPEG2 video files are supported.\n" ) + error_string , + i18n( "Wrong File Format" ) ); + + + return 0; +} + +void K3bVcdDoc::addTrack( const KURL& url, uint position ) +{ + urlsToAdd.enqueue( new PrivateUrlToAdd( url, position ) ); + + m_urlAddingTimer->start( 0 ); +} + + +void K3bVcdDoc::addTrack( K3bVcdTrack* track, uint position ) +{ + if ( m_tracks->count() >= 98 ) { + kdDebug() << "(K3bVcdDoc) VCD Green Book only allows 98 tracks." << endl; + // TODO: show some messagebox + delete track; + return ; + } + + lastAddedPosition = position; + + if ( !m_tracks->insert( position, track ) ) { + lastAddedPosition = m_tracks->count(); + m_tracks->insert( m_tracks->count(), track ); + } + + if ( track->isSegment() ) + vcdOptions() ->increaseSegments( ); + else + vcdOptions() ->increaseSequence( ); + + emit newTracks(); + + setModified( true ); +} + + +void K3bVcdDoc::removeTrack( K3bVcdTrack* track ) +{ + if ( !track ) { + return ; + } + + // set the current item to track + if ( m_tracks->findRef( track ) >= 0 ) { + // take the current item + track = m_tracks->take(); + + // remove all pbc references to us? + if ( track->hasRevRef() ) + track->delRefToUs(); + + // remove all pbc references from us? + track->delRefFromUs(); + + // emit signal before deleting the track to avoid crashes + // when the view tries to call some of the tracks' methods + emit trackRemoved( track ); + + if ( track->isSegment() ) + vcdOptions() ->decreaseSegments( ); + else + vcdOptions() ->decreaseSequence( ); + + delete track; + + if ( numOfTracks() == 0 ) { + setVcdType( NONE ); + vcdOptions() ->setAutoDetect( true ); + } + + // reorder pbc tracks + setPbcTracks(); + } +} + +void K3bVcdDoc::moveTrack( const K3bVcdTrack* track, const K3bVcdTrack* after ) +{ + if ( track == after ) + return ; + + // set the current item to track + m_tracks->findRef( track ); + // take the current item + track = m_tracks->take(); + + // if after == 0 findRef returnes -1 + int pos = m_tracks->findRef( after ); + m_tracks->insert( pos + 1, track ); + + // reorder pbc tracks + setPbcTracks(); + + emit changed(); +} + + +QString K3bVcdDoc::typeString() const +{ + return "vcd"; +} + + +K3bBurnJob* K3bVcdDoc::newBurnJob( K3bJobHandler* hdl, QObject* parent ) +{ + return new K3bVcdJob( this, hdl, parent ); +} + +void K3bVcdDoc::informAboutNotFoundFiles() +{ + if ( !m_notFoundFiles.isEmpty() ) { + KMessageBox::informationList( view(), i18n( "Could not find the following files:" ), + m_notFoundFiles, i18n( "Not Found" ) ); + + m_notFoundFiles.clear(); + } +} + +void K3bVcdDoc::setVcdType( int type ) +{ + m_vcdType = type; + switch ( type ) { + case 0: + //vcd 1.1 + vcdOptions() ->setVcdClass( "vcd" ); + vcdOptions() ->setVcdVersion( "1.1" ); + break; + case 1: + //vcd 2.0 + vcdOptions() ->setVcdClass( "vcd" ); + vcdOptions() ->setVcdVersion( "2.0" ); + break; + case 2: + //svcd 1.0 + vcdOptions() ->setVcdClass( "svcd" ); + vcdOptions() ->setVcdVersion( "1.0" ); + break; + case 3: + //hqvcd 1.0 + vcdOptions() ->setVcdClass( "hqvcd" ); + vcdOptions() ->setVcdVersion( "1.0" ); + break; + + } +} + +void K3bVcdDoc::setPbcTracks() +{ + // reorder pbc tracks + /* + if ( !vcdOptions()->PbcEnabled() ) + return; + */ + + if ( m_tracks ) { + int count = m_tracks->count(); + kdDebug() << QString( "K3bVcdDoc::setPbcTracks() - we have %1 tracks in list." ).arg( count ) << endl; + + QPtrListIterator<K3bVcdTrack> iterTrack( *m_tracks ); + K3bVcdTrack* track; + while ( ( track = iterTrack.current() ) != 0 ) { + ++iterTrack; + for ( int i = 0; i < K3bVcdTrack::_maxPbcTracks; i++ ) { + // do not change userdefined tracks + if ( !track->isPbcUserDefined( i ) ) { + if ( track->getPbcTrack( i ) ) + track->getPbcTrack( i ) ->delFromRevRefList( track ); + + K3bVcdTrack* t = 0L; + int index = track->index(); + + // we are the last track + if ( index == count - 1 ) { + switch ( i ) { + case K3bVcdTrack::PREVIOUS: + // we are not alone :) + if ( count > 1 ) { + t = at( index - 1 ); + t->addToRevRefList( track ); + track->setPbcTrack( i, t ); + } else { + track->setPbcTrack( i ); + track->setPbcNonTrack( i, K3bVcdTrack::VIDEOEND ); + } + break; + case K3bVcdTrack::AFTERTIMEOUT: + case K3bVcdTrack::NEXT: + track->setPbcTrack( i ); + track->setPbcNonTrack( i, K3bVcdTrack::VIDEOEND ); + break; + case K3bVcdTrack::RETURN: + track->setPbcTrack( i ); + track->setPbcNonTrack( i, K3bVcdTrack::VIDEOEND ); + break; + case K3bVcdTrack::DEFAULT: + track->setPbcTrack( i ); + track->setPbcNonTrack( i, K3bVcdTrack::DISABLED ); + break; + } + } + // we are the first track + else if ( index == 0 ) { + switch ( i ) { + case K3bVcdTrack::PREVIOUS: + track->setPbcTrack( i ); + track->setPbcNonTrack( i, K3bVcdTrack::VIDEOEND ); + break; + case K3bVcdTrack::AFTERTIMEOUT: + case K3bVcdTrack::NEXT: + t = at( index + 1 ); + t->addToRevRefList( track ); + track->setPbcTrack( i, t ); + break; + case K3bVcdTrack::RETURN: + track->setPbcTrack( i ); + track->setPbcNonTrack( i, K3bVcdTrack::VIDEOEND ); + break; + case K3bVcdTrack::DEFAULT: + track->setPbcTrack( i ); + track->setPbcNonTrack( i, K3bVcdTrack::DISABLED ); + break; + } + } + // we are one of the other tracks and have PREVIOUS and NEXT Track + else { + switch ( i ) { + case K3bVcdTrack::PREVIOUS: + t = at( index - 1 ); + t->addToRevRefList( track ); + track->setPbcTrack( i, t ); + break; + case K3bVcdTrack::AFTERTIMEOUT: + case K3bVcdTrack::NEXT: + t = at( index + 1 ); + t->addToRevRefList( track ); + track->setPbcTrack( i, t ); + break; + case K3bVcdTrack::RETURN: + track->setPbcTrack( i ); + track->setPbcNonTrack( i, K3bVcdTrack::VIDEOEND ); + break; + case K3bVcdTrack::DEFAULT: + track->setPbcTrack( i ); + track->setPbcNonTrack( i, K3bVcdTrack::DISABLED ); + break; + } + } + } + } + } + } +} + + +bool K3bVcdDoc::loadDocumentData( QDomElement* root ) +{ + newDocument(); + + QDomNodeList nodes = root->childNodes(); + + if ( nodes.length() < 3 ) + return false; + + if ( nodes.item( 0 ).nodeName() != "general" ) + return false; + if ( !readGeneralDocumentData( nodes.item( 0 ).toElement() ) ) + return false; + + if ( nodes.item( 1 ).nodeName() != "vcd" ) + return false; + + if ( nodes.item( 2 ).nodeName() != "contents" ) + return false; + + + // vcd Label + QDomNodeList vcdNodes = nodes.item( 1 ).childNodes(); + + for ( uint i = 0; i < vcdNodes.count(); i++ ) { + QDomNode item = vcdNodes.item( i ); + QString name = item.nodeName(); + + kdDebug() << QString( "(K3bVcdDoc::loadDocumentData) nodeName = '%1'" ).arg( name ) << endl; + + if ( name == "volumeId" ) + vcdOptions() ->setVolumeId( item.toElement().text() ); + else if ( name == "albumId" ) + vcdOptions() ->setAlbumId( item.toElement().text() ); + else if ( name == "volumeSetId" ) + vcdOptions() ->setVolumeSetId( item.toElement().text() ); + else if ( name == "preparer" ) + vcdOptions() ->setPreparer( item.toElement().text() ); + else if ( name == "publisher" ) + vcdOptions() ->setPublisher( item.toElement().text() ); + else if ( name == "vcdType" ) + setVcdType( vcdTypes( item.toElement().text().toInt() ) ); + else if ( name == "mpegVersion" ) + vcdOptions() ->setMpegVersion( item.toElement().text().toInt() ); + else if ( name == "PreGapLeadout" ) + vcdOptions() ->setPreGapLeadout( item.toElement().text().toInt() ); + else if ( name == "PreGapTrack" ) + vcdOptions() ->setPreGapTrack( item.toElement().text().toInt() ); + else if ( name == "FrontMarginTrack" ) + vcdOptions() ->setFrontMarginTrack( item.toElement().text().toInt() ); + else if ( name == "RearMarginTrack" ) + vcdOptions() ->setRearMarginTrack( item.toElement().text().toInt() ); + else if ( name == "FrontMarginTrackSVCD" ) + vcdOptions() ->setFrontMarginTrackSVCD( item.toElement().text().toInt() ); + else if ( name == "RearMarginTrackSVCD" ) + vcdOptions() ->setRearMarginTrackSVCD( item.toElement().text().toInt() ); + else if ( name == "volumeCount" ) + vcdOptions() ->setVolumeCount( item.toElement().text().toInt() ); + else if ( name == "volumeNumber" ) + vcdOptions() ->setVolumeNumber( item.toElement().text().toInt() ); + else if ( name == "AutoDetect" ) + vcdOptions() ->setAutoDetect( item.toElement().text().toInt() ); + else if ( name == "CdiSupport" ) + vcdOptions() ->setCdiSupport( item.toElement().text().toInt() ); + else if ( name == "NonCompliantMode" ) + vcdOptions() ->setNonCompliantMode( item.toElement().text().toInt() ); + else if ( name == "Sector2336" ) + vcdOptions() ->setSector2336( item.toElement().text().toInt() ); + else if ( name == "UpdateScanOffsets" ) + vcdOptions() ->setUpdateScanOffsets( item.toElement().text().toInt() ); + else if ( name == "RelaxedAps" ) + vcdOptions() ->setRelaxedAps( item.toElement().text().toInt() ); + else if ( name == "UseGaps" ) + vcdOptions() ->setUseGaps( item.toElement().text().toInt() ); + else if ( name == "PbcEnabled" ) + vcdOptions() ->setPbcEnabled( item.toElement().text().toInt() ); + else if ( name == "SegmentFolder" ) + vcdOptions() ->setSegmentFolder( item.toElement().text().toInt() ); + else if ( name == "Restriction" ) + vcdOptions() ->setRestriction( item.toElement().text().toInt() ); + } + + // vcd Tracks + QDomNodeList trackNodes = nodes.item( 2 ).childNodes(); + + for ( uint i = 0; i < trackNodes.length(); i++ ) { + + // check if url is available + QDomElement trackElem = trackNodes.item( i ).toElement(); + QString url = trackElem.attributeNode( "url" ).value(); + if ( !QFile::exists( url ) ) + m_notFoundFiles.append( url ); + else { + KURL k; + k.setPath( url ); + if ( K3bVcdTrack * track = createTrack( k ) ) { + track ->setPlayTime( trackElem.attribute( "playtime", "1" ).toInt() ); + track ->setWaitTime( trackElem.attribute( "waittime", "2" ).toInt() ); + track ->setReactivity( trackElem.attribute( "reactivity", "0" ).toInt() ); + track -> setPbcNumKeys( ( trackElem.attribute( "numkeys", "yes" ).contains( "yes" ) ) ? true : false ); + track -> setPbcNumKeysUserdefined( ( trackElem.attribute( "userdefinednumkeys", "no" ).contains( "yes" ) ) ? true : false ); + + addTrack( track, m_tracks->count() ); + } + } + } + + emit newTracks(); + + // do not add saved pbcTrack links when one ore more files missing. + // TODO: add info message to informAboutNotFoundFiles(); + if ( m_notFoundFiles.isEmpty() ) { + int type; + int val; + bool pbctrack; + for ( uint trackId = 0; trackId < trackNodes.length(); trackId++ ) { + QDomElement trackElem = trackNodes.item( trackId ).toElement(); + QDomNodeList trackNodes = trackElem.childNodes(); + for ( uint i = 0; i < trackNodes.length(); i++ ) { + QDomElement trackElem = trackNodes.item( i ).toElement(); + QString name = trackElem.tagName(); + if ( name.contains( "pbc" ) ) { + if ( trackElem.hasAttribute ( "type" ) ) { + type = trackElem.attribute ( "type" ).toInt(); + if ( trackElem.hasAttribute ( "pbctrack" ) ) { + pbctrack = ( trackElem.attribute ( "pbctrack" ) == "yes" ); + if ( trackElem.hasAttribute ( "val" ) ) { + val = trackElem.attribute ( "val" ).toInt(); + K3bVcdTrack* track = m_tracks->at( trackId ); + K3bVcdTrack* pbcTrack = m_tracks->at( val ); + if ( pbctrack ) { + pbcTrack->addToRevRefList( track ); + track->setPbcTrack( type, pbcTrack ); + track->setUserDefined( type, true ); + } else { + track->setPbcTrack( type ); + track->setPbcNonTrack( type, val ); + track->setUserDefined( type, true ); + } + } + } + } + } else if ( name.contains( "numkeys" ) ) { + if ( trackElem.hasAttribute ( "key" ) ) { + int key = trackElem.attribute ( "key" ).toInt(); + if ( trackElem.hasAttribute ( "val" ) ) { + int val = trackElem.attribute ( "val" ).toInt() - 1; + K3bVcdTrack* track = m_tracks->at( trackId ); + if ( val >= 0 ) { + K3bVcdTrack * numkeyTrack = m_tracks->at( val ); + track->setDefinedNumKey( key, numkeyTrack ); + } else { + track->setDefinedNumKey( key, 0L ); + } + } + } + } + + } + + } + setPbcTracks(); + setModified( false ); + } + + informAboutNotFoundFiles(); + return true; +} + + + +bool K3bVcdDoc::saveDocumentData( QDomElement * docElem ) +{ + QDomDocument doc = docElem->ownerDocument(); + saveGeneralDocumentData( docElem ); + + // save Vcd Label + QDomElement vcdMain = doc.createElement( "vcd" ); + + QDomElement vcdElem = doc.createElement( "volumeId" ); + vcdElem.appendChild( doc.createTextNode( vcdOptions() ->volumeId() ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "albumId" ); + vcdElem.appendChild( doc.createTextNode( vcdOptions() ->albumId() ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "volumeSetId" ); + vcdElem.appendChild( doc.createTextNode( vcdOptions() ->volumeSetId() ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "preparer" ); + vcdElem.appendChild( doc.createTextNode( vcdOptions() ->preparer() ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "publisher" ); + vcdElem.appendChild( doc.createTextNode( vcdOptions() ->publisher() ) ); + vcdMain.appendChild( vcdElem ); + + // applicationId() + // systemId() + + vcdElem = doc.createElement( "vcdType" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdType() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "mpegVersion" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->mpegVersion() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "PreGapLeadout" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->PreGapLeadout() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "PreGapTrack" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->PreGapTrack() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "FrontMarginTrack" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->FrontMarginTrack() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "RearMarginTrack" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->RearMarginTrack() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "FrontMarginTrackSVCD" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->FrontMarginTrackSVCD() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "RearMarginTrackSVCD" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->RearMarginTrackSVCD() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "volumeCount" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->volumeCount() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "volumeNumber" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->volumeNumber() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "AutoDetect" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->AutoDetect() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "CdiSupport" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->CdiSupport() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "NonCompliantMode" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->NonCompliantMode() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "Sector2336" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->Sector2336() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "UpdateScanOffsets" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->UpdateScanOffsets() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "RelaxedAps" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->RelaxedAps() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "UseGaps" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->UseGaps() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "PbcEnabled" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->PbcEnabled() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "SegmentFolder" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->SegmentFolder() ) ) ); + vcdMain.appendChild( vcdElem ); + + vcdElem = doc.createElement( "Restriction" ); + vcdElem.appendChild( doc.createTextNode( QString::number( vcdOptions() ->Restriction() ) ) ); + vcdMain.appendChild( vcdElem ); + + docElem->appendChild( vcdMain ); + + // save the tracks + // ------------------------------------------------------------- + QDomElement contentsElem = doc.createElement( "contents" ); + + QPtrListIterator<K3bVcdTrack> iterTrack( *m_tracks ); + K3bVcdTrack* track; + + while ( ( track = iterTrack.current() ) != 0 ) { + ++iterTrack; + + QDomElement trackElem = doc.createElement( "track" ); + trackElem.setAttribute( "url", KIO::decodeFileName( track->absPath() ) ); + trackElem.setAttribute( "playtime", track->getPlayTime() ); + trackElem.setAttribute( "waittime", track->getWaitTime() ); + trackElem.setAttribute( "reactivity", track->Reactivity() ); + trackElem.setAttribute( "numkeys", ( track->PbcNumKeys() ) ? "yes" : "no" ); + trackElem.setAttribute( "userdefinednumkeys", ( track->PbcNumKeysUserdefined() ) ? "yes" : "no" ); + + for ( int i = 0; + i < K3bVcdTrack::_maxPbcTracks; + i++ ) { + if ( track->isPbcUserDefined( i ) ) { + // save pbcTracks + QDomElement pbcElem = doc.createElement( "pbc" ); + pbcElem.setAttribute( "type", i ); + if ( track->getPbcTrack( i ) ) { + pbcElem.setAttribute( "pbctrack", "yes" ); + pbcElem.setAttribute( "val", track->getPbcTrack( i ) ->index() ); + } else { + pbcElem.setAttribute( "pbctrack", "no" ); + pbcElem.setAttribute( "val", track->getNonPbcTrack( i ) ); + } + trackElem.appendChild( pbcElem ); + } + } + QMap<int, K3bVcdTrack*> numKeyMap = track->DefinedNumKey(); + QMap<int, K3bVcdTrack*>::const_iterator trackIt; + + for ( trackIt = numKeyMap.begin(); + trackIt != numKeyMap.end(); + ++trackIt ) { + QDomElement numElem = doc.createElement( "numkeys" ); + if ( trackIt.data() ) { + numElem.setAttribute( "key", trackIt.key() ); + numElem.setAttribute( "val", trackIt.data() ->index() + 1 ); + } else { + numElem.setAttribute( "key", trackIt.key() ); + numElem.setAttribute( "val", 0 ); + } + trackElem.appendChild( numElem ); + } + + contentsElem.appendChild( trackElem ); + } + // ------------------------------------------------------------- + + docElem->appendChild( contentsElem ); + + return true; +} + +#include "k3bvcddoc.moc" diff --git a/libk3b/projects/videocd/k3bvcddoc.h b/libk3b/projects/videocd/k3bvcddoc.h new file mode 100644 index 0000000..8b10837 --- /dev/null +++ b/libk3b/projects/videocd/k3bvcddoc.h @@ -0,0 +1,192 @@ +/* +* +* $Id: k3bvcddoc.h 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@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 K3BVCDDOC_H +#define K3BVCDDOC_H + +// Qt Includes +#include <qptrqueue.h> +#include <qfile.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qdatetime.h> +#include <qtextstream.h> +#include <qimage.h> + +// Kde Includes +#include <kurl.h> + +// K3b Includes +#include "k3bvcdoptions.h" +#include "mpeginfo/k3bmpeginfo.h" +#include <k3bdoc.h> +#include "k3b_export.h" +class K3bApp; +class K3bVcdTrack; +class K3bVcdJob; +//class K3bView; +class QWidget; +class QTimer; +class QDomDocument; +class QDomElement; +class KConfig; + + + +class LIBK3B_EXPORT K3bVcdDoc : public K3bDoc +{ + Q_OBJECT + + public: + K3bVcdDoc( QObject* ); + ~K3bVcdDoc(); + + int type() const { return VCD; } + + QString name() const; + + enum vcdTypes { VCD11, VCD20, SVCD10, HQVCD, NONE}; + + bool newDocument(); + int numOfTracks() const + { + return m_tracks->count(); + } + + const QString& vcdImage() const + { + return m_vcdImage; + } + void setVcdImage( const QString& s ) + { + m_vcdImage = s; + } + + K3bVcdTrack* first() + { + return m_tracks->first(); + } + K3bVcdTrack* current() const + { + return m_tracks->current(); + } + K3bVcdTrack* next() + { + return m_tracks->next(); + } + K3bVcdTrack* prev() + { + return m_tracks->prev(); + } + K3bVcdTrack* at( uint i ) + { + return m_tracks->at( i ); + } + K3bVcdTrack* take( uint i ) + { + return m_tracks->take( i ); + } + + const QPtrList<K3bVcdTrack>* tracks() const + { + return m_tracks; + } + + /** get the current size of the project */ + KIO::filesize_t size() const; + K3b::Msf length() const; + + K3bBurnJob* newBurnJob( K3bJobHandler* hdl, QObject* parent ); + K3bVcdOptions* vcdOptions() const + { + return m_vcdOptions; + } + + int vcdType() const + { + return m_vcdType; + } + void setVcdType( int type ); + void setPbcTracks(); + + public slots: + /** + * will test the file and add it to the project. + * connect to at least result() to know when + * the process is finished and check error() + * to know about the result. + **/ + void addUrls( const KURL::List& ); + void addTrack( const KURL&, uint ); + void addTracks( const KURL::List&, uint ); + /** adds a track without any testing */ + void addTrack( K3bVcdTrack* track, uint position = 0 ); + + // --- TODO: this should read: removeTrack( K3bVcdTrack* ) + void removeTrack( K3bVcdTrack* ); + void moveTrack( const K3bVcdTrack* track, const K3bVcdTrack* after ); + + protected slots: + /** processes queue "urlsToAdd" **/ + void slotWorkUrlQueue(); + + signals: + void newTracks(); + + void trackRemoved( K3bVcdTrack* ); + + protected: + /** reimplemented from K3bDoc */ + bool loadDocumentData( QDomElement* root ); + /** reimplemented from K3bDoc */ + bool saveDocumentData( QDomElement* ); + + QString typeString() const; + + private: + K3bVcdTrack* createTrack( const KURL& url ); + void informAboutNotFoundFiles(); + + QStringList m_notFoundFiles; + QString m_vcdImage; + + class PrivateUrlToAdd + { + public: + PrivateUrlToAdd( const KURL& u, int _pos ) + : url( u ), position( _pos ) + {} + KURL url; + int position; + }; + + /** Holds all the urls that have to be added to the list of tracks. **/ + QPtrQueue<PrivateUrlToAdd> urlsToAdd; + QTimer* m_urlAddingTimer; + + QPtrList<K3bVcdTrack>* m_tracks; + KIO::filesize_t calcTotalSize() const; + KIO::filesize_t ISOsize() const; + + bool isImage( const KURL& url ); + + K3bVcdTrack* m_lastAddedTrack; + K3bVcdOptions* m_vcdOptions; + + int m_vcdType; + uint lastAddedPosition; +}; + +#endif diff --git a/libk3b/projects/videocd/k3bvcdjob.cpp b/libk3b/projects/videocd/k3bvcdjob.cpp new file mode 100644 index 0000000..a1b347a --- /dev/null +++ b/libk3b/projects/videocd/k3bvcdjob.cpp @@ -0,0 +1,567 @@ +/* +* +* $Id: k3bvcdjob.cpp 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@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 <klocale.h> +#include <kconfig.h> +#include <kstandarddirs.h> +#include <kurl.h> +#include <ktempfile.h> +#include <kio/global.h> + +#include <qstring.h> +#include <qdatetime.h> +#include <qfile.h> +#include <qtimer.h> +#include <kdebug.h> +#include <qregexp.h> +#include <qdom.h> + +#include "k3bvcdjob.h" + +// K3b Includes +#include "k3bvcddoc.h" +#include "k3bvcdtrack.h" +#include "k3bvcdxmlview.h" +#include <k3bcore.h> +#include <k3bdoc.h> +#include <k3bprocess.h> +#include <k3bdevice.h> +#include <k3bexternalbinmanager.h> +#include <k3bglobals.h> +#include <k3bcdrecordwriter.h> +#include <k3bcdrdaowriter.h> + +K3bVcdJob::K3bVcdJob( K3bVcdDoc* doc, K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bBurnJob( jh, parent, name ) +{ + m_doc = doc; + m_doc->setCopies( m_doc->dummy() || m_doc->onlyCreateImages() ? 1 : m_doc->copies() ); + m_process = 0; + m_currentWrittenTrackNumber = 0; + m_bytesFinishedTracks = 0; + m_writerJob = 0; + // m_createimageonlypercent = 33.3; + m_createimageonlypercent = 100 / ( m_doc->copies() + 2 ); + m_currentcopy = 1; + m_imageFinished = false; +} + + +K3bVcdJob::~K3bVcdJob() +{ + delete m_process; + + if ( m_writerJob ) + delete m_writerJob; +} + + +K3bDoc* K3bVcdJob::doc() const +{ + return m_doc; +} + + +K3bDevice::Device* K3bVcdJob::writer() const +{ + if( doc()->onlyCreateImages() ) + return 0; + else + return doc() ->burner(); +} + +void K3bVcdJob::cancel() +{ + cancelAll(); + + emit canceled(); + jobFinished( false ); +} + + +void K3bVcdJob::cancelAll() +{ + m_canceled = true; + + if ( m_writerJob ) + m_writerJob->cancel(); + + if ( m_process->isRunning() ) { + m_process->disconnect( this ); + m_process->kill(); + } + + // remove bin-file if it is unfinished or the user selected to remove image + if ( QFile::exists( m_doc->vcdImage() ) ) { + if ( !m_doc->onTheFly() && m_doc->removeImages() || !m_imageFinished ) { + emit infoMessage( i18n( "Removing Binary file %1" ).arg( m_doc->vcdImage() ), K3bJob::SUCCESS ); + QFile::remove + ( m_doc->vcdImage() ); + m_doc->setVcdImage( "" ); + } + } + + // remove cue-file if it is unfinished or the user selected to remove image + if ( QFile::exists( m_cueFile ) ) { + if ( !m_doc->onTheFly() && m_doc->removeImages() || !m_imageFinished ) { + emit infoMessage( i18n( "Removing Cue file %1" ).arg( m_cueFile ), K3bJob::SUCCESS ); + QFile::remove + ( m_cueFile ); + m_cueFile = ""; + } + } +} + + +void K3bVcdJob::start() +{ + kdDebug() << "(K3bVcdJob) starting job" << endl; + + jobStarted(); + emit burning( false ); + m_canceled = false; + + int pos = QString( m_doc->vcdImage() ).find( ".bin", QString( m_doc->vcdImage() ).length() - 4 ); + if ( pos > 0 ) { + m_cueFile = m_doc->vcdImage().left( pos ) + ".cue"; + } else { + m_cueFile = m_doc->vcdImage() + ".cue"; + m_doc->setVcdImage( m_doc->vcdImage() + ".bin" ); + } + + if ( vcdDoc() ->onlyCreateImages() ) + m_createimageonlypercent = 50.0; + + // vcdxGen(); + xmlGen(); +} + +void K3bVcdJob::xmlGen() +{ + + KTempFile tempF; + m_xmlFile = tempF.name(); + tempF.unlink(); + + K3bVcdXmlView xmlView( m_doc ); + + if ( !xmlView.write( m_xmlFile ) ) { + kdDebug() << "(K3bVcdJob) could not write xmlfile." << endl; + emit infoMessage( i18n( "Could not write correct XML-file." ), K3bJob::ERROR ); + cancelAll(); + jobFinished( false ); + } + + // emit infoMessage( i18n( "XML-file successfully created" ), K3bJob::SUCCESS ); + emit debuggingOutput( "K3bVcdXml:", xmlView.xmlString() ); + + vcdxBuild(); + +} + +void K3bVcdJob::vcdxBuild() +{ + emit newTask( i18n( "Creating image files" ) ); + + m_stage = stageUnknown; + firstTrack = true; + delete m_process; + m_process = new K3bProcess(); + + emit infoMessage( i18n( "Creating Cue/Bin files ..." ), K3bJob::INFO ); + const K3bExternalBin* bin = k3bcore ->externalBinManager() ->binObject( "vcdxbuild" ); + if ( !bin ) { + kdDebug() << "(K3bVcdJob) could not find vcdxbuild executable" << endl; + emit infoMessage( i18n( "Could not find %1 executable." ).arg( "vcdxbuild" ), K3bJob::ERROR ); + emit infoMessage( i18n( "To create VideoCDs you must install VcdImager Version %1." ).arg( ">= 0.7.12" ), K3bJob::INFO ); + emit infoMessage( i18n( "You can find this on your distribution disks or download it from http://www.vcdimager.org" ), K3bJob::INFO ); + cancelAll(); + jobFinished( false ); + return ; + } + + if ( bin->version < K3bVersion( "0.7.12" ) ) { + kdDebug() << "(K3bVcdJob) vcdxbuild executable too old!" << endl; + emit infoMessage( i18n( "%1 executable too old: need version %2 or greater." ).arg( "Vcdxbuild" ).arg( "0.7.12" ), K3bJob::ERROR ); + emit infoMessage( i18n( "You can find this on your distribution disks or download it from http://www.vcdimager.org" ), K3bJob::INFO ); + cancelAll(); + 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 ); + + *m_process << bin; + + // additional user parameters from config + const QStringList& params = k3bcore->externalBinManager() ->program( "vcdxbuild" ) ->userParameters(); + for ( QStringList::const_iterator it = params.begin(); it != params.end(); ++it ) + *m_process << *it; + + + if ( vcdDoc() ->vcdOptions() ->Sector2336() ) { + kdDebug() << "(K3bVcdJob) Write 2336 Sectors = on" << endl; + *m_process << "--sector-2336"; + } + + *m_process << "--progress" << "--gui"; + + *m_process << QString( "--cue-file=%1" ).arg( m_cueFile ); + + *m_process << QString( "--bin-file=%1" ).arg( m_doc->vcdImage() ); + + *m_process << QString( "%1" ).arg( QFile::encodeName( m_xmlFile ) ); + + connect( m_process, SIGNAL( receivedStderr( KProcess*, char*, int ) ), + this, SLOT( slotParseVcdxBuildOutput( KProcess*, char*, int ) ) ); + connect( m_process, SIGNAL( receivedStdout( KProcess*, char*, int ) ), + this, SLOT( slotParseVcdxBuildOutput( KProcess*, char*, int ) ) ); + connect( m_process, SIGNAL( processExited( KProcess* ) ), + this, SLOT( slotVcdxBuildFinished() ) ); + + // vcdxbuild commandline parameters + kdDebug() << "***** vcdxbuild parameters:" << endl; + ; + const QValueList<QCString>& args = m_process->args(); + QString s; + for ( QValueList<QCString>::const_iterator it = args.begin(); it != args.end(); ++it ) { + s += *it + " "; + } + kdDebug() << s << flush << endl; + emit debuggingOutput( "vcdxbuild command:", s ); + + if ( !m_process->start( KProcess::NotifyOnExit, KProcess::AllOutput ) ) { + kdDebug() << "(K3bVcdJob) could not start vcdxbuild" << endl; + emit infoMessage( i18n( "Could not start %1." ).arg( "vcdxbuild" ), K3bJob::ERROR ); + cancelAll(); + jobFinished( false ); + } +} + +void K3bVcdJob::slotParseVcdxBuildOutput( KProcess*, char* output, int len ) +{ + QString buffer = QString::fromLocal8Bit( output, len ); + + // split to lines + QStringList lines = QStringList::split( "\n", buffer ); + + QDomDocument xml_doc; + QDomElement xml_root; + + // do every line + for ( QStringList::Iterator str = lines.begin(); str != lines.end(); ++str ) { + *str = ( *str ).stripWhiteSpace(); + + emit debuggingOutput( "vcdxbuild", *str ); + + xml_doc.setContent( QString( "<?xml version='1.0'?><vcdxbuild>" ) + *str + "</vcdxbuild>" ); + + xml_root = xml_doc.documentElement(); + + // There should be only one... but ... + for ( QDomNode node = xml_root.firstChild(); !node.isNull(); node = node.nextSibling() ) { + QDomElement el = node.toElement(); + if ( el.isNull() ) + continue; + + const QString tagName = el.tagName().lower(); + + if ( tagName == "progress" ) { + const QString oper = el.attribute( "operation" ).lower(); + const unsigned long long pos = el.attribute( "position" ).toLong(); + const long long size = el.attribute( "size" ).toLong(); + + if ( oper == "scan" ) { + // Scan Video Files + if ( m_stage == stageUnknown || pos < m_bytesFinished ) { + const uint index = el.attribute( "id" ).replace( QRegExp( "sequence-" ), "" ).toUInt(); + + m_currentWrittenTrack = m_doc->at( m_currentWrittenTrackNumber ); + emit newSubTask( i18n( "Scanning video file %1 of %2 (%3)" ).arg( index + 1 ).arg( doc() ->numOfTracks() ).arg( m_currentWrittenTrack->fileName() ) ); + m_bytesFinished = 0; + + if ( !firstTrack ) { + m_bytesFinishedTracks += m_doc->at( m_currentWrittenTrackNumber ) ->size(); + m_currentWrittenTrackNumber++; + } else + firstTrack = false; + } + emit subPercent( ( int ) ( 100.0 * ( double ) pos / ( double ) size ) ); + emit processedSubSize( pos / 1024 / 1024, size / 1024 / 1024 ); + + // this is the first of three processes. + double relOverallWritten = ( ( double ) m_bytesFinishedTracks + ( double ) pos ) / ( double ) doc() ->size(); + emit percent( ( int ) ( m_createimageonlypercent * relOverallWritten ) ); + + m_bytesFinished = pos; + m_stage = stageScan; + + } else if ( oper == "write" ) { + emit subPercent( ( int ) ( 100.0 * ( double ) pos / ( double ) size ) ); + emit processedSubSize( ( pos * 2048 ) / 1024 / 1024, ( size * 2048 ) / 1024 / 1024 ); + emit percent( ( int ) ( m_createimageonlypercent + ( m_createimageonlypercent * ( double ) pos / ( double ) size ) ) ); + + m_stage = stageWrite; + } else { + return ; + } + } else if ( tagName == "log" ) { + QDomText tel = el.firstChild().toText(); + const QString level = el.attribute( "level" ).lower(); + if ( tel.isText() ) { + const QString text = tel.data(); + if ( m_stage == stageWrite && level == "information" ) + kdDebug() << QString( "(K3bVcdJob) VcdxBuild information, %1" ).arg( text ) << endl; + if ( ( text ).startsWith( "writing track" ) ) + emit newSubTask( i18n( "Creating Image for track %1" ).arg( ( text ).mid( 14 ) ) ); + else { + if ( level != "error" ) { + kdDebug() << QString( "(K3bVcdJob) vcdxbuild warning, %1" ).arg( text ) << endl; + parseInformation( text ); + } else { + kdDebug() << QString( "(K3bVcdJob) vcdxbuild error, %1" ).arg( text ) << endl; + emit infoMessage( text, K3bJob::ERROR ); + } + } + } + } + } + } +} + + +void K3bVcdJob::slotVcdxBuildFinished() +{ + if ( m_process->normalExit() ) { + // TODO: check the process' exitStatus() + switch ( m_process->exitStatus() ) { + case 0: + emit infoMessage( i18n( "Cue/Bin files successfully created." ), K3bJob::SUCCESS ); + m_imageFinished = true; + break; + default: + emit infoMessage( i18n( "%1 returned an unknown error (code %2)." ).arg( "vcdxbuild" ).arg( m_process->exitStatus() ), + K3bJob::ERROR ); + emit infoMessage( i18n( "Please send me an email with the last output." ), K3bJob::ERROR ); + cancelAll(); + jobFinished( false ); + return ; + } + } else { + emit infoMessage( i18n( "%1 did not exit cleanly." ).arg( "Vcdxbuild" ), K3bJob::ERROR ); + cancelAll(); + jobFinished( false ); + return ; + } + + //remove xml-file + if ( QFile::exists( m_xmlFile ) ) + QFile::remove + ( m_xmlFile ); + + kdDebug() << QString( "(K3bVcdJob) create only image: %1" ).arg( vcdDoc() ->onlyCreateImages() ) << endl; + if ( !vcdDoc() ->onlyCreateImages() ) + startWriterjob(); + else + jobFinished( true ); +} + +void K3bVcdJob::startWriterjob() +{ + kdDebug() << QString( "(K3bVcdJob) writing copy %1 of %2" ).arg( m_currentcopy ).arg( m_doc->copies() ) << endl; + if ( prepareWriterJob() ) { + if ( waitForMedia( m_doc->burner() ) < 0 ) { + cancel(); + return ; + } + // just to be sure we did not get canceled during the async discWaiting + if ( m_canceled ) + return ; + + if ( m_doc->copies() > 1 ) + emit newTask( i18n( "Writing Copy %1 of %2" ).arg( m_currentcopy ).arg( m_doc->copies() ) ); + + emit burning( true ); + m_writerJob->start(); + } +} + +bool K3bVcdJob::prepareWriterJob() +{ + if ( m_writerJob ) + delete m_writerJob; + + const K3bExternalBin* cdrecordBin = k3bcore->externalBinManager() ->binObject( "cdrecord" ); + if ( writingApp() == K3b::DEFAULT && cdrecordBin->hasFeature( "cuefile" ) && m_doc->burner() ->dao() ) + setWritingApp( K3b::CDRECORD ); + + if ( writingApp() == K3b::CDRDAO || writingApp() == K3b::DEFAULT ) { + K3bCdrdaoWriter * writer = new K3bCdrdaoWriter( m_doc->burner(), this, this ); + // create cdrdao job + writer->setCommand( K3bCdrdaoWriter::WRITE ); + writer->setSimulate( m_doc->dummy() ); + writer->setBurnSpeed( m_doc->speed() ); + + writer->setTocFile( m_cueFile ); + + m_writerJob = writer; + + } else if ( writingApp() == K3b::CDRECORD ) { + K3bCdrecordWriter * writer = new K3bCdrecordWriter( m_doc->burner(), this, this ); + // create cdrecord job + + writer->setSimulate( m_doc->dummy() ); + writer->setBurnSpeed( m_doc->speed() ); + writer->setDao( true ); + writer->setCueFile( m_cueFile ); + + m_writerJob = writer; + + } + + connect( m_writerJob, SIGNAL( infoMessage( const QString&, int ) ), this, SIGNAL( infoMessage( const QString&, int ) ) ); + connect( m_writerJob, SIGNAL( percent( int ) ), this, SLOT( slotWriterJobPercent( int ) ) ); + connect( m_writerJob, SIGNAL( processedSize( int, int ) ), this, SLOT( slotProcessedSize( int, int ) ) ); + connect( m_writerJob, SIGNAL( subPercent( int ) ), this, SIGNAL( subPercent( int ) ) ); + connect( m_writerJob, SIGNAL( processedSubSize( int, int ) ), this, SIGNAL( processedSubSize( int, int ) ) ); + connect( m_writerJob, SIGNAL( nextTrack( int, int ) ), this, SLOT( slotWriterNextTrack( int, int ) ) ); + connect( m_writerJob, SIGNAL( buffer( int ) ), this, SIGNAL( bufferStatus( int ) ) ); + connect( m_writerJob, SIGNAL( deviceBuffer( int ) ), this, SIGNAL( deviceBuffer( int ) ) ); + connect( m_writerJob, SIGNAL( writeSpeed( int, int ) ), this, SIGNAL( writeSpeed( int, int ) ) ); + connect( m_writerJob, SIGNAL( finished( bool ) ), this, SLOT( slotWriterJobFinished( bool ) ) ); + connect( m_writerJob, SIGNAL( newTask( const QString& ) ), this, SIGNAL( newTask( const QString& ) ) ); + connect( m_writerJob, SIGNAL( newSubTask( const QString& ) ), this, SIGNAL( newSubTask( const QString& ) ) ); + connect( m_writerJob, SIGNAL( debuggingOutput( const QString&, const QString& ) ), this, SIGNAL( debuggingOutput( const QString&, const QString& ) ) ); + + return true; +} + +void K3bVcdJob::slotWriterJobPercent( int p ) +{ + emit percent( ( int ) ( ( m_createimageonlypercent * ( m_currentcopy + 1 ) ) + p / ( m_doc->copies() + 2 ) ) ); +} + +void K3bVcdJob::slotProcessedSize( int cs, int ts ) +{ + emit processedSize( cs + ( ts * ( m_currentcopy - 1 ) ) , ts * m_doc->copies() ); +} + +void K3bVcdJob::slotWriterNextTrack( int t, int tt ) +{ + emit newSubTask( i18n( "Writing Track %1 of %2" ).arg( t ).arg( tt ) ); +} + +void K3bVcdJob::slotWriterJobFinished( bool success ) +{ + if ( m_canceled ) + return ; + + if ( m_currentcopy >= m_doc->copies() ) { + // remove bin-file if it is unfinished or the user selected to remove image + if ( QFile::exists( m_doc->vcdImage() ) ) { + if ( !m_doc->onTheFly() && m_doc->removeImages() || !m_imageFinished ) { + emit infoMessage( i18n( "Removing Binary file %1" ).arg( m_doc->vcdImage() ), K3bJob::SUCCESS ); + QFile::remove + ( m_doc->vcdImage() ); + m_doc->setVcdImage( "" ); + } + } + + // remove cue-file if it is unfinished or the user selected to remove image + if ( QFile::exists( m_cueFile ) ) { + if ( !m_doc->onTheFly() && m_doc->removeImages() || !m_imageFinished ) { + emit infoMessage( i18n( "Removing Cue file %1" ).arg( m_cueFile ), K3bJob::SUCCESS ); + QFile::remove + ( m_cueFile ); + m_cueFile = ""; + } + } + } + + if ( success ) { + // allright + // the writerJob should have emited the "simulation/writing successful" signal + if ( m_currentcopy >= m_doc->copies() ) { + jobFinished( true ); + } else { + m_currentcopy++; + startWriterjob(); + } + } else { + cancelAll(); + jobFinished( false ); + } +} + +void K3bVcdJob::parseInformation( const QString &text ) +{ + // parse warning + if ( text.contains( "mpeg user scan data: one or more BCD fields out of range for" ) ) { + int index = text.find( " for" ); + + emit infoMessage( i18n( "One or more BCD fields out of range for %1" ).arg( text.mid( index + 4 ).stripWhiteSpace() ), K3bJob::WARNING ); + + } else if ( text.contains( "mpeg user scan data: from now on, scan information data errors will not be reported anymore" ) ) { + emit infoMessage( i18n( "From now on, scan information data errors will not be reported anymore" ), K3bJob::INFO ); + emit infoMessage( i18n( "Consider enabling the 'update scan offsets' option, if it is not enabled already." ), K3bJob::INFO ); + + } else if ( text.contains( "APS' pts seems out of order (actual pts" ) ) { + int index = text.find( "(actual pts" ); + int index2 = text.find( ", last seen pts" ); + int index3 = text.find( ") -- ignoring this aps" ); + + emit infoMessage( i18n( "APS' pts seems out of order (actual pts %1, last seen pts %2)" ).arg( text.mid( index + 12, index2 - index - 12 ).stripWhiteSpace() ).arg( text.mid( index2 + 14, index3 - index2 - 14 ).stripWhiteSpace() ), K3bJob::WARNING ); + emit infoMessage( i18n( "Ignoring this aps" ), K3bJob::INFO ); + + } else if ( text.contains( "bad packet at packet" ) ) { + int index = text.find( "at packet #" ); + int index2 = text.find( "(stream byte offset" ); + int index3 = text.find( ") -- remaining " ); + int index4 = text.find( "bytes of stream will be ignored" ); + + emit infoMessage( i18n( "Bad packet at packet #%1 (stream byte offset %2)" ).arg( text.mid( index + 11, index2 - index - 11 ).stripWhiteSpace() ).arg( text.mid( index2 + 19, index3 - index2 - 19 ).stripWhiteSpace() ), K3bJob::WARNING ); + emit infoMessage( i18n( "Remaining %1 bytes of stream will be ignored." ).arg( text.mid( index3 + 15, index4 - index3 - 15 ).stripWhiteSpace() ), K3bJob::WARNING ); + } +} + +QString K3bVcdJob::jobDescription() const +{ + switch ( m_doc->vcdType() ) { + case K3bVcdDoc::VCD11: + return i18n( "Writing Video CD (Version 1.1)" ); + case K3bVcdDoc::VCD20: + return i18n( "Writing Video CD (Version 2.0)" ); + case K3bVcdDoc::SVCD10: + return i18n( "Writing Super Video CD" ); + case K3bVcdDoc::HQVCD: + return i18n( "Writing High-Quality Video CD" ); + default: + return i18n( "Writing Video CD" ); + } +} + + +QString K3bVcdJob::jobDetails() const +{ + return ( i18n( "1 MPEG (%1)", + "%n MPEGs (%1)", + m_doc->tracks() ->count() ).arg( KIO::convertSize( m_doc->size() ) ) + + ( m_doc->copies() > 1 + ? i18n( " - %n copy", " - %n copies", m_doc->copies() ) + : QString::null ) ); +} + +#include "k3bvcdjob.moc" diff --git a/libk3b/projects/videocd/k3bvcdjob.h b/libk3b/projects/videocd/k3bvcdjob.h new file mode 100644 index 0000000..917c8b1 --- /dev/null +++ b/libk3b/projects/videocd/k3bvcdjob.h @@ -0,0 +1,115 @@ +/* +* +* $Id: k3bvcdjob.h 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@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 K3BVCDJOB_H +#define K3BVCDJOB_H + +#include <k3bjob.h> + +class K3bVcdDoc; +class K3bVcdTrack; +class QString; +class K3bProcess; +class KProcess; +class QDataStream; +class K3bAbstractWriter; +class K3bDevice::Device; + + +class K3bVcdJob : public K3bBurnJob +{ + Q_OBJECT + + public: + K3bVcdJob( K3bVcdDoc*, K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + ~K3bVcdJob(); + + K3bDoc* doc() const; + K3bVcdDoc* vcdDoc() const + { + return m_doc; + } + K3bDevice::Device* writer() const; + + QString jobDescription() const; + QString jobDetails() const; + + public slots: + void start(); + void cancel(); + + private slots: + void cancelAll(); + + protected slots: + void slotVcdxBuildFinished(); + void slotParseVcdxBuildOutput( KProcess*, char* output, int len ); + + void slotWriterJobPercent( int p ); + void slotProcessedSize( int cs, int ts ); + void slotWriterNextTrack( int t, int tt ); + void slotWriterJobFinished( bool success ); + + + private: + bool prepareWriterJob(); + + void xmlGen(); + void vcdxBuild(); + void parseInformation( const QString& ); + void startWriterjob(); + + int m_copies; + int m_finishedCopies; + + unsigned long m_blocksToCopy; + unsigned long m_bytesFinishedTracks; + unsigned long m_bytesFinished; + + enum { stageUnknown, stageScan, stageWrite, _stage_max }; + + K3bVcdDoc* m_doc; + K3bDevice::Device* m_writer; + K3bDevice::Device* m_reader; + K3bVcdTrack* m_currentWrittenTrack; + + int m_speed; + int m_stage; + int m_currentcopy; + int m_currentWrittenTrackNumber; + + double m_createimageonlypercent; + + bool firstTrack; + bool m_burnProof; + bool m_keepImage; + bool m_onlyCreateImage; + bool m_onTheFly; + bool m_dummy; + bool m_fastToc; + bool m_readRaw; + bool m_imageFinished; + bool m_canceled; + + QString m_tempPath; + QString m_cueFile; + QString m_xmlFile; + QString m_collectedOutput; + + K3bAbstractWriter* m_writerJob; + K3bProcess* m_process; +}; + +#endif diff --git a/libk3b/projects/videocd/k3bvcdoptions.cpp b/libk3b/projects/videocd/k3bvcdoptions.cpp new file mode 100644 index 0000000..6009a4a --- /dev/null +++ b/libk3b/projects/videocd/k3bvcdoptions.cpp @@ -0,0 +1,146 @@ +/* +* +* $Id: k3bvcdoptions.cpp 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@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. +*/ + +// Kde Includes +#include <kapplication.h> +#include <kconfig.h> +#include <k3bcore.h> +#include <klocale.h> +#include <kstandarddirs.h> + +// Qt Includes +#include <qstring.h> +#include <qfile.h> + +// K3b Includes +#include "k3bvcdoptions.h" +#include <k3bversion.h> + +K3bVcdOptions::K3bVcdOptions() + : m_restriction( 0 ), + m_segment( 0 ), + m_sequence( 0 ), + m_pbcenabled( PbcEnabled() ), + m_pbcnumkeysenabled( PbcNumkeysEnabled() ), + m_volumeID( "VIDEOCD" ), + m_albumID( "" ), + m_volumeSetId( "" ), + m_publisher( QString( "K3b - Version %1" ).arg( k3bcore->version() ) ), + m_applicationId( "CDI/CDI_VCD.APP;1" ), + m_systemId( "CD-RTOS CD-BRIDGE" ), + m_vcdclass( "vcd" ), + m_vcdversion( "2.0" ), + m_pregapleadout( 150 ), + m_pregaptrack( 150 ), + m_frontmargintrack( 30 ), + m_rearmargintrack( 45 ), + m_frontmargintrackSVCD( 0 ), + m_rearmargintrackSVCD( 0 ), + m_mpegversion( 1 ), + m_volumeCount( 1 ), + m_volumeNumber( 1 ), + m_autodetect( true ), + m_cdisupport( false ), + m_brokensvcdmode( false ), + m_VCD30interpretation( false ), + m_sector2336( false ), + m_updatescanoffsets( false ), + m_relaxedaps( false ), + m_segmentfolder( true ), + m_usegaps( false ) +{} + +bool K3bVcdOptions::checkCdiFiles() +{ + m_cdisize = 0; + if ( !QFile::exists( locate( "data", "k3b/cdi/cdi_imag.rtf" ) ) ) + return false; + if ( !QFile::exists( locate( "data", "k3b/cdi/cdi_text.fnt" ) ) ) + return false; + if ( !QFile::exists( locate( "data", "k3b/cdi/cdi_vcd.app" ) ) ) + return false; + if ( !QFile::exists( locate( "data", "k3b/cdi/cdi_vcd.cfg" ) ) ) + return false; + + m_cdisize += QFile( locate( "data", "k3b/cdi/cdi_imag.rtf" ) ).size(); + m_cdisize += QFile( locate( "data", "k3b/cdi/cdi_text.fnt" ) ).size(); + m_cdisize += QFile( locate( "data", "k3b/cdi/cdi_vcd.app" ) ).size(); + m_cdisize += QFile( locate( "data", "k3b/cdi/cdi_vcd.cfg" ) ).size(); + + return true; +} + +void K3bVcdOptions::save( KConfigBase* c ) +{ + c->writeEntry( "volume_id", m_volumeID ); + c->writeEntry( "album_id", m_albumID ); + c->writeEntry( "volume_set_id", m_volumeSetId ); + c->writeEntry( "preparer", m_preparer ); + c->writeEntry( "publisher", m_publisher ); + c->writeEntry( "volume_count", m_volumeCount ); + c->writeEntry( "volume_number", m_volumeNumber ); + c->writeEntry( "autodetect", m_autodetect ); + c->writeEntry( "cdi_support", m_cdisupport ); + c->writeEntry( "broken_svcd_mode", m_brokensvcdmode ); + c->writeEntry( "VCD30interpretation", m_VCD30interpretation ); + c->writeEntry( "2336_sectors", m_sector2336 ); + c->writeEntry( "UpdateScanOffsets", m_updatescanoffsets ); + c->writeEntry( "RelaxedAps", m_relaxedaps ); + c->writeEntry( "PbcEnabled", m_pbcenabled ); + c->writeEntry( "SegmentFolder", m_segmentfolder ); + c->writeEntry( "Restriction", m_restriction ); + c->writeEntry( "PreGapLeadout", m_pregapleadout ); + c->writeEntry( "PreGapTrack", m_pregaptrack ); + c->writeEntry( "FrontMarginTrack", m_frontmargintrack ); + c->writeEntry( "RearMarginTrack", m_rearmargintrack ); + c->writeEntry( "UseGaps", m_usegaps ); +} + + +K3bVcdOptions K3bVcdOptions::load( KConfigBase* c ) +{ + K3bVcdOptions options; + + options.setVolumeId( c->readEntry( "volume_id", options.volumeId() ) ); + options.setAlbumId( c->readEntry( "album_id", options.albumId() ) ); + options.setVolumeSetId( c->readEntry( "volume_set_id", options.volumeSetId() ) ); + options.setPreparer( c->readEntry( "preparer", options.preparer() ) ); + options.setPublisher( c->readEntry( "publisher", options.publisher() ) ); + options.setVolumeCount( c->readNumEntry( "volume_count", options.volumeCount() ) ); + options.setVolumeNumber( c->readNumEntry( "volume_number", options.volumeNumber() ) ); + options.setAutoDetect( c->readBoolEntry( "autodetect", options.AutoDetect() ) ); + options.setCdiSupport( c->readBoolEntry( "cdi_support", options.CdiSupport() ) ); + options.setNonCompliantMode( c->readBoolEntry( "broken_svcd_mode", options.NonCompliantMode() ) ); + options.setVCD30interpretation( c->readBoolEntry( "VCD30interpretation", options.VCD30interpretation() ) ); + options.setSector2336( c->readBoolEntry( "2336_sectors", options.Sector2336() ) ); + options.setUpdateScanOffsets( c->readBoolEntry( "UpdateScanOffsets", options.UpdateScanOffsets() ) ); + options.setRelaxedAps( c->readBoolEntry( "RelaxedAps", options.RelaxedAps() ) ); + options.setPbcEnabled( c->readBoolEntry( "PbcEnabled", options.PbcEnabled() ) ); + options.setSegmentFolder( c->readBoolEntry( "SegmentFolder", options.SegmentFolder() ) ); + options.setRestriction( c->readNumEntry( "Restriction", options.Restriction() ) ); + options.setPreGapLeadout( c->readNumEntry( "PreGapLeadout", options.PreGapLeadout() ) ); + options.setPreGapTrack( c->readNumEntry( "PreGapTrack", options.PreGapTrack() ) ); + options.setFrontMarginTrack( c->readNumEntry( "FrontMarginTrack", options.FrontMarginTrack() ) ); + options.setRearMarginTrack( c->readNumEntry( "RearMarginTrack", options.RearMarginTrack() ) ); + options.setUseGaps( c->readBoolEntry( "UseGaps", options.UseGaps() ) ); + + return options; +} + +K3bVcdOptions K3bVcdOptions::defaults() +{ + // let the constructor create defaults + return K3bVcdOptions(); +} diff --git a/libk3b/projects/videocd/k3bvcdoptions.h b/libk3b/projects/videocd/k3bvcdoptions.h new file mode 100644 index 0000000..aa5fed2 --- /dev/null +++ b/libk3b/projects/videocd/k3bvcdoptions.h @@ -0,0 +1,377 @@ +/* +* +* $Id: k3bvcdoptions.h 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@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_VCD_OPTIONS_H +#define K3B_VCD_OPTIONS_H + +#include <qstring.h> +#include "k3b_export.h" + +class KConfigBase; + +class LIBK3B_EXPORT K3bVcdOptions +{ + public: + K3bVcdOptions(); + const QString& volumeId() const + { + return m_volumeID; + } + const QString& albumId() const + { + return m_albumID; + } + const QString& volumeSetId() const + { + return m_volumeSetId; + } + const QString& preparer() const + { + return m_preparer; + } + const QString& publisher() const + { + return m_publisher; + } + + const QString& applicationId() const + { + return m_applicationId; + } + const QString& systemId() const + { + return m_systemId; + } + + const QString& vcdClass() const + { + return m_vcdclass; + } + const QString& vcdVersion() const + { + return m_vcdversion; + } + + const int PreGapLeadout() + { + return m_pregapleadout; + } + const int PreGapTrack() + { + return m_pregaptrack; + } + const int FrontMarginTrack() + { + return m_frontmargintrack; + } + const int RearMarginTrack() + { + return m_rearmargintrack; + } + const int FrontMarginTrackSVCD() + { + return m_frontmargintrackSVCD; + } + const int RearMarginTrackSVCD() + { + return m_rearmargintrackSVCD; + } + + const int mpegVersion() const + { + return m_mpegversion; + } + + const int volumeCount() const + { + return m_volumeCount; + } + const int volumeNumber() const + { + return m_volumeNumber; + } + + const bool AutoDetect() const + { + return m_autodetect; + } + const bool CdiSupport() const + { + return m_cdisupport; + } + const bool NonCompliantMode() const + { + return m_brokensvcdmode; + } + const bool VCD30interpretation() const + { + return m_VCD30interpretation; + } + const bool Sector2336() const + { + return m_sector2336; + } + const bool UpdateScanOffsets() const + { + return m_updatescanoffsets; + } + const bool RelaxedAps() const + { + return m_relaxedaps; + } + const bool UseGaps() const + { + return m_usegaps; + } + const unsigned long long CDIsize() const + { + return m_cdisize; + } + + void setAlbumId( const QString& s ) + { + m_albumID = s; + } + void setVolumeId( const QString& s ) + { + m_volumeID = s; + } + void setVolumeSetId( const QString& s ) + { + m_volumeSetId = s; + } + void setPreparer( const QString& s ) + { + m_preparer = s; + } + void setPublisher( const QString& s ) + { + m_publisher = s; + } + + void setVcdClass( const QString& s ) + { + m_vcdclass = s; + } + void setVcdVersion( const QString& s ) + { + m_vcdversion = s; + } + + void setPreGapLeadout( const int i ) + { + m_pregapleadout = i; + } + void setPreGapTrack( const int i ) + { + m_pregaptrack = i; + } + void setFrontMarginTrack( const int i ) + { + m_frontmargintrack = i; + } + void setRearMarginTrack( const int i ) + { + m_rearmargintrack = i; + } + void setFrontMarginTrackSVCD( const int i ) + { + m_frontmargintrackSVCD = i; + } + void setRearMarginTrackSVCD( const int i ) + { + m_rearmargintrackSVCD = i; + } + + void setMpegVersion( const int v ) + { + m_mpegversion = v; + } + void setVolumeCount( const int c ) + { + m_volumeCount = c; + } + void setVolumeNumber( const int n ) + { + m_volumeNumber = n; + } + + void setAutoDetect( const bool& b ) + { + m_autodetect = b; + } + void setCdiSupport( const bool& b ) + { + m_cdisupport = b; + } + void setNonCompliantMode( const bool& b ) + { + m_brokensvcdmode = b; + } + void setVCD30interpretation( const bool& b ) + { + m_VCD30interpretation = b; + } + void setSector2336( const bool& b ) + { + m_sector2336 = b; + } + void setUpdateScanOffsets( const bool& b ) + { + m_updatescanoffsets = b; + } + void setRelaxedAps( const bool& b ) + { + m_relaxedaps = b; + } + void setUseGaps( const bool& b ) + { + m_usegaps = b; + } + + bool checkCdiFiles(); + void save( KConfigBase* c ); + + static K3bVcdOptions load( KConfigBase* c ); + static K3bVcdOptions defaults(); + + void setPbcEnabled( const bool& b ) + { + m_pbcenabled = b; + } + bool PbcEnabled() const + { + return m_pbcenabled; + }; + void setPbcNumkeysEnabled( const bool& b ) + { + m_pbcnumkeysenabled = b; + } + bool PbcNumkeysEnabled() const + { + return m_pbcnumkeysenabled; + }; + + void setPbcPlayTime( const int i ) + { + m_def_pbcplaytime = i; + } + int PbcPlayTime( ) + { + return m_def_pbcplaytime; + } + + void setPbcWaitTime( const int i ) + { + m_def_pbcwaittime = i; + } + int PbcWaitTime( ) + { + return m_def_pbcwaittime; + } + + void setSegmentFolder( const bool& b ) + { + m_segmentfolder = b; + } + bool SegmentFolder() const + { + return m_segmentfolder; + }; + + void setRestriction( const int i ) + { + m_restriction = i; + } + int Restriction() const + { + return m_restriction; + }; + void increaseSegments( ) + { + m_segment += 1; + } + void decreaseSegments( ) + { + m_segment -= 1; + } + bool haveSegments() const + { + return m_segment > 0; + }; + void increaseSequence( ) + { + m_sequence += 1; + } + void decreaseSequence( ) + { + m_sequence -= 1; + } + + bool haveSequence() const + { + return m_sequence > 0; + }; + + private: + int m_restriction; + int m_segment; + int m_sequence; + + // pbc + bool m_pbcenabled; + bool m_pbcnumkeysenabled; + + // volume descriptor + QString m_volumeID; + QString m_albumID; + QString m_volumeSetId; + + QString m_preparer; + QString m_publisher; + + QString m_applicationId; + QString m_systemId; + + QString m_vcdclass; + QString m_vcdversion; + + int m_pregapleadout; + int m_pregaptrack; + int m_frontmargintrack; + int m_rearmargintrack; + int m_frontmargintrackSVCD; + int m_rearmargintrackSVCD; + + int m_mpegversion; + int m_volumeCount; + int m_volumeNumber; + + bool m_autodetect; + bool m_cdisupport; + bool m_brokensvcdmode; + bool m_VCD30interpretation; + bool m_sector2336; + bool m_updatescanoffsets; + bool m_relaxedaps; + bool m_segmentfolder; + bool m_usegaps; + + int m_def_pbcplaytime; + int m_def_pbcwaittime; + unsigned long long m_cdisize; +}; + +#endif diff --git a/libk3b/projects/videocd/k3bvcdtrack.cpp b/libk3b/projects/videocd/k3bvcdtrack.cpp new file mode 100644 index 0000000..7f0043f --- /dev/null +++ b/libk3b/projects/videocd/k3bvcdtrack.cpp @@ -0,0 +1,456 @@ +/* +* +* $Id: k3bvcdtrack.cpp 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@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 <kapplication.h> +#include <kconfig.h> + +#include <qstring.h> +#include <qfileinfo.h> + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <kdebug.h> +#include <klocale.h> + +// K3b Includes +#include "k3bvcdtrack.h" +#include <k3bglobals.h> + +K3bVcdTrack::K3bVcdTrack( QPtrList<K3bVcdTrack>* parent, const QString& filename ) + : m_pbcnumkeys( true ), + m_pbcnumkeysuserdefined( false ), + m_file( filename ) +{ + m_parent = parent; + m_title = QFileInfo( m_file ).baseName( true ); + + m_revreflist = new QPtrList<K3bVcdTrack>; + + for ( int i = 0; i < K3bVcdTrack::_maxPbcTracks; i++ ) { + m_pbctrackmap.insert( i, 0L ); + m_pbcnontrackmap.insert( i, K3bVcdTrack::DISABLED ); + m_pbcusrdefmap.insert( i, false ); + } + + m_reactivity = false; + + m_definedkeysmap.clear(); + + mpeg_info = new Mpeginfo(); +} + + +K3bVcdTrack::~K3bVcdTrack() +{} + + +KIO::filesize_t K3bVcdTrack::size() const +{ + return m_file.size(); +} + +int K3bVcdTrack::index() const +{ + int i = m_parent->find( this ); + if ( i < 0 ) + kdDebug() << "(K3bVcdTrack) I'm not part of my parent!" << endl; + return i; +} + +void K3bVcdTrack::addToRevRefList( K3bVcdTrack* revreftrack ) +{ + kdDebug() << "K3bVcdTrack::addToRevRefList: track = " << revreftrack << endl; + + m_revreflist->append( revreftrack ); + + kdDebug() << "K3bVcdTrack::hasRevRef count = " << m_revreflist->count() << " empty = " << m_revreflist->isEmpty() << endl; +} + +void K3bVcdTrack::delFromRevRefList( K3bVcdTrack* revreftrack ) +{ + if ( !m_revreflist->isEmpty() ) { + m_revreflist->remove + ( revreftrack ); + } +} + +bool K3bVcdTrack::hasRevRef() +{ + return !m_revreflist->isEmpty() ; +} + +void K3bVcdTrack::delRefToUs() +{ + for ( K3bVcdTrack * track = m_revreflist->first(); track; track = m_revreflist->next() ) { + for ( int i = 0; i < K3bVcdTrack::_maxPbcTracks; i++ ) { + kdDebug() << "K3bVcdTrack::delRefToUs count = " << m_revreflist->count() << " empty = " << m_revreflist->isEmpty() << " track = " << track << " this = " << this << endl; + if ( this == track->getPbcTrack( i ) ) { + track->setPbcTrack( i ); + track->setUserDefined( i, false ); + track->delFromRevRefList( this ); + } + } + } +} + +void K3bVcdTrack::delRefFromUs() +{ + for ( int i = 0; i < K3bVcdTrack::_maxPbcTracks; i++ ) { + if ( this->getPbcTrack( i ) ) { + this->getPbcTrack( i ) ->delFromRevRefList( this ); + } + } +} + +void K3bVcdTrack::setPbcTrack( int which, K3bVcdTrack* pbctrack ) +{ + kdDebug() << "K3bVcdTrack::setPbcTrack " << which << ", " << pbctrack << endl; + m_pbctrackmap.replace( which, pbctrack ); +} + +void K3bVcdTrack::setPbcNonTrack( int which, int type ) +{ + kdDebug() << "K3bVcdTrack::setNonPbcTrack " << which << ", " << type << endl; + m_pbcnontrackmap.replace( which, type ); +} + +void K3bVcdTrack::setUserDefined( int which, bool ud ) +{ + m_pbcusrdefmap.replace( which, ud ); +} + +K3bVcdTrack* K3bVcdTrack::getPbcTrack( const int& which ) +{ + if ( m_pbctrackmap.find( which ) == m_pbctrackmap.end() ) + return 0; + else + return m_pbctrackmap[ which ]; +} + +int K3bVcdTrack::getNonPbcTrack( const int& which ) +{ + if ( m_pbcnontrackmap.find( which ) == m_pbcnontrackmap.end() ) + return 0; + else + return m_pbcnontrackmap[ which ]; +} + +bool K3bVcdTrack::isPbcUserDefined( int which ) +{ + return m_pbcusrdefmap[ which ]; +} + +const QString K3bVcdTrack::resolution() +{ + if ( mpeg_info->has_video ) { + for ( int i = 0; i < 2; i++ ) { + if ( mpeg_info->video[ i ].seen ) { + return QString( "%1 x %2" ).arg( mpeg_info->video[ i ].hsize ).arg( mpeg_info->video[ i ].vsize ); + } + } + } + + return i18n( "n/a" ); +} + +const QString K3bVcdTrack::highresolution() +{ + if ( mpeg_info->has_video ) { + if ( mpeg_info->video[ 2 ].seen ) { + return QString( "%1 x %2" ).arg( mpeg_info->video[ 2 ].hsize ).arg( mpeg_info->video[ 2 ].vsize ); + } + } + return i18n( "n/a" ); +} + +const QString K3bVcdTrack::video_frate() +{ + if ( mpeg_info->has_video ) { + for ( int i = 0; i < 2; i++ ) { + if ( mpeg_info->video[ i ].seen ) { + return QString::number( mpeg_info->video[ i ].frate ); + } + } + } + + return i18n( "n/a" ); +} + +const QString K3bVcdTrack::video_bitrate() +{ + if ( mpeg_info->has_video ) { + for ( int i = 0; i < 2; i++ ) { + if ( mpeg_info->video[ i ].seen ) { + return i18n( "%1 bit/s" ).arg( mpeg_info->video[ i ].bitrate ) ; + } + } + } + + return i18n( "n/a" ); +} + + + +const QString K3bVcdTrack::video_format() +{ + if ( mpeg_info->has_video ) { + for ( int i = 0; i < 2; i++ ) { + if ( mpeg_info->video[ i ].seen ) { + switch ( mpeg_info->video[ i ].video_format ) { + case 0 : + return i18n( "Component" ); + break; + case 1 : + return "PAL"; + break; + case 2 : + return "NTSC"; + break; + case 3 : + return "SECAM"; + break; + case 4 : + return "MAC"; + break; + case 5 : + default: + return i18n( "Unspecified" ); + kdDebug() << "K3bVcdTrack::video_format() :" << mpeg_info->video[ i ].video_format << endl; + break; + } + } + } + } + return i18n( "n/a" ); +} + +const QString K3bVcdTrack::video_chroma() +{ + if ( mpeg_info->has_video ) { + // MPEG1 only supports 4:2:0 Format + if ( version() == K3bMpegInfo::MPEG_VERS_MPEG1 ) + return QString( "4:2:0" ); + + for ( int i = 0; i < 2; i++ ) { + if ( mpeg_info->video[ i ].seen ) { + switch ( mpeg_info->video[ i ].chroma_format ) { + case 1 : + return QString( "4:2:0" ); + break; + case 2 : + return QString( "4:2:2" ); + break; + case 3 : + return QString( "4:4:4" ); + break; + + } + } + } + } + + return i18n( "n/a" ); +} + +const QString K3bVcdTrack::audio_layer() +{ + if ( mpeg_info->has_audio ) { + for ( int i = 0; i < 2; i++ ) { + if ( mpeg_info->audio[ i ].seen ) { + return QString::number( mpeg_info->audio[ i ].layer ); + } + } + } + + return i18n( "n/a" ); +} + +const QString K3bVcdTrack::audio_bitrate() +{ + if ( mpeg_info->has_audio ) { + for ( int i = 0; i < 2; i++ ) { + if ( mpeg_info->audio[ i ].seen ) { + return i18n( "%1 bit/s" ).arg( mpeg_info->audio[ i ].bitrate ) ; + } + } + } + + return i18n( "n/a" ); +} + +const QString K3bVcdTrack::audio_sampfreq() +{ + if ( mpeg_info->has_audio ) { + for ( int i = 0; i < 2; i++ ) { + if ( mpeg_info->audio[ i ].seen ) { + return i18n( "%1 Hz" ).arg( mpeg_info->audio[ i ].sampfreq ) ; + } + } + } + + return i18n( "n/a" ); +} + +const QString K3bVcdTrack::audio_mode( ) +{ + if ( mpeg_info->has_audio ) { + for ( int i = 2; i >= 0; i-- ) + if ( mpeg_info->audio[ i ].seen ) + return QString( audio_type2str( mpeg_info->audio[ i ].version, mpeg_info->audio[ i ].mode, i ) ); + + } + + return i18n( "n/a" ); +} + +const QString K3bVcdTrack::audio_copyright( ) +{ + if ( mpeg_info->has_audio ) { + for ( int i = 2; i >= 0; i-- ) + if ( mpeg_info->audio[ i ].seen ) + if ( mpeg_info->audio[ i ].copyright ) + return QString( "(c) " ) + ( mpeg_info->audio[ i ].original ? i18n( "original" ) : i18n( "duplicate" ) ); + else + return ( mpeg_info->audio[ i ].original ? i18n( "original" ) : i18n( "duplicate" ) ); + } + + return i18n( "n/a" ); +} + +const QString K3bVcdTrack::mpegTypeS( bool audio ) +{ + if ( mpeg_info->has_video && !audio ) { + for ( int i = 0; i < 3; i++ ) + if ( mpeg_info->video[ i ].seen ) { + if ( i == 0 ) { + return QString( "MPEG%1 " ).arg( mpeg_info->version ) + i18n( "Motion Picture" ); + } else { + return QString( "MPEG%1 " ).arg( mpeg_info->version ) + i18n( "Still Picture" ); + } + } + } + if ( mpeg_info->has_audio && audio ) { + for ( int i = 0; i < 3; i++ ) + if ( mpeg_info->audio[ i ].seen ) { + return QString( "MPEG%1 " ).arg( mpeg_info->audio[ i ].version ) + i18n( "Layer %1" ).arg( mpeg_info->audio[ i ].layer ); + } + } + + return i18n( "n/a" ); +} + +const int K3bVcdTrack::mpegType( ) +{ + if ( mpeg_info->has_video ) { + for ( int i = 0; i < 3; i++ ) + if ( mpeg_info->video[ i ].seen ) { + if ( i == 0 ) { + return 0; // MPEG_MOTION; + } else { + return 1; // MPEG_STILL; + } + } + } + if ( mpeg_info->has_audio ) { + for ( int i = 0; i < 3; i++ ) + if ( mpeg_info->audio[ i ].seen ) + return 2; // MPEG_AUDIO; + } + + return -1; // MPEG_UNKNOWN; +} + +const QString K3bVcdTrack::audio_type2str( unsigned int version, unsigned int audio_mode, unsigned int audio_type ) +{ + kdDebug() << "K3bVcdTrack::audio_type2str() version:" << version << " audio_mode:" << audio_mode << " audio_type:" << audio_type << endl; + + QString audio_types[ 3 ][ 5 ] = { + { + i18n( "unknown" ), + i18n( "invalid" ), + QString::null, + QString::null, + QString::null + }, + { + i18n( "stereo" ), + i18n( "joint stereo" ), + i18n( "dual channel" ), + i18n( "single channel" ) + }, + { + QString::null, + i18n( "dual channel" ), + i18n( "surround sound" ), + QString::null, + QString::null + } + }; + switch ( version ) { + case K3bMpegInfo::MPEG_VERS_MPEG1: + return audio_types[ 1 ][ audio_mode ]; + break; + + case K3bMpegInfo::MPEG_VERS_MPEG2: + if ( audio_type > 0 ) { + return audio_types[ 2 ][ audio_type ]; + } + return audio_types[ 1 ][ audio_mode ]; + break; + } + + return i18n( "n/a" ); +} + +// convert a time in second to HH:mm:ss notation +QString K3bVcdTrack::SecsToHMS( double duration ) +{ + byte hours = ( byte ) ( duration / 3600 ); + byte mins = ( byte ) ( ( duration / 60 ) - ( hours * 60 ) ); + float secs = duration - 60 * mins - 3600 * hours; + if ( hours != 0 ) { + return QString( "%1:" ).arg( hours ).rightJustify( 3, ' ' ) + QString( "%1:" ).arg( mins ).rightJustify( 3, '0' ) + QString::number( secs, 'f', 2 ); + } + if ( mins != 0 ) { + return QString( "%1:" ).arg( mins ).rightJustify( 3, '0' ) + QString::number( secs, 'f', 2 ); + } + return QString::number( secs, 'f', 2 ); +} + +void K3bVcdTrack::PrintInfo() +{ + + kdDebug() << "K3bVcdTrack::PrintInfo() ....................." << endl; + kdDebug() << " version : MPEG" << version() << endl; + kdDebug() << " duration : " << duration() << endl; + kdDebug() << " muxrate : " << muxrate() << endl; + kdDebug() << " video ......................................" << endl; + kdDebug() << " type : " << mpegTypeS() << endl; + kdDebug() << " resolution : " << resolution() << endl; + kdDebug() << " high resolution: " << highresolution() << endl; + kdDebug() << " frate : " << video_frate() << endl; + kdDebug() << " bitrate : " << video_bitrate() << endl; + kdDebug() << " format : " << video_format( ) << endl; + kdDebug() << " chroma : " << video_chroma( ) << endl; + kdDebug() << " audio ......................................" << endl; + kdDebug() << " type : " << mpegTypeS( true ) << endl; + kdDebug() << " mode : " << audio_mode() << endl; + kdDebug() << " layer : " << audio_layer() << endl; + kdDebug() << " bitrate : " << audio_bitrate() << endl; + kdDebug() << " sampfreq : " << audio_sampfreq() << endl; + +} diff --git a/libk3b/projects/videocd/k3bvcdtrack.h b/libk3b/projects/videocd/k3bvcdtrack.h new file mode 100644 index 0000000..0d9a3cf --- /dev/null +++ b/libk3b/projects/videocd/k3bvcdtrack.h @@ -0,0 +1,198 @@ +/* +* +* $Id: k3bvcdtrack.h 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@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 K3BVCDTRACK_H +#define K3BVCDTRACK_H + +// Qt Includes +#include <qstring.h> +#include <qfileinfo.h> +#include <qfile.h> +#include <qptrlist.h> + +// Kde Includes +#include <kio/global.h> + +// K3b Includes +#include "mpeginfo/k3bmpeginfo.h" +#include "k3b_export.h" +class LIBK3B_EXPORT K3bVcdTrack +{ + public: + K3bVcdTrack( QPtrList<K3bVcdTrack>* parent, const QString& filename ); + ~K3bVcdTrack(); + + QString fileName() const + { + return QFileInfo( m_file ).fileName(); + } + QString absPath() const + { + return QFileInfo( m_file ).absFilePath(); + } + KIO::filesize_t size() const; + int index() const; + + const QString& title() const + { + return m_title; + } + void setTitle( const QString& t ) + { + m_title = t; + } + bool isSegment() + { + return mpegType() == 1; + }; + + + + // PBC + enum PbcTracks { PREVIOUS, NEXT, RETURN, DEFAULT, AFTERTIMEOUT, _maxPbcTracks }; + enum PbcTypes { DISABLED, VIDEOEND }; + + void addToRevRefList( K3bVcdTrack* revreftrack ); + void delFromRevRefList( K3bVcdTrack* revreftrack ); + bool hasRevRef(); + void delRefToUs(); + void delRefFromUs(); + + void setPbcTrack( int, K3bVcdTrack* pbctrack = 0L ); + void setPbcNonTrack( int, int ); + void setUserDefined( int, bool ); + void setPlayTime( int t ) + { + m_pbcplaytime = t; + } + void setWaitTime( int t ) + { + m_pbcwaittime = t; + } + void setReactivity( bool b ) + { + m_reactivity = b; + } + void setPbcNumKeys( const bool& b ) + { + m_pbcnumkeys = b; + } + bool PbcNumKeys() const + { + return m_pbcnumkeys; + }; + void setPbcNumKeysUserdefined( const bool& b ) + { + m_pbcnumkeysuserdefined = b; + }; + bool PbcNumKeysUserdefined() const + { + return m_pbcnumkeysuserdefined; + }; + + K3bVcdTrack* getPbcTrack( const int& ); + int getNonPbcTrack( const int& ); + bool isPbcUserDefined( int ); + int getPlayTime() + { + return m_pbcplaytime; + } + int getWaitTime() + { + return m_pbcwaittime; + } + bool Reactivity() + { + return m_reactivity; + } + + // Numeric keys + void setDefinedNumKey( int key, K3bVcdTrack* track ) + { + m_definedkeysmap.insert( key, track ); + } + void delDefinedNumKey( int key ) + { + m_definedkeysmap.remove( key ); + } + void delDefinedNumKey() + { + m_definedkeysmap.clear(); + } + QMap<int, K3bVcdTrack*> DefinedNumKey() + { + return m_definedkeysmap; + } + + // Mpeg Infos + const QString resolution(); + const QString highresolution(); + const QString video_frate(); + const QString video_bitrate(); + const QString audio_layer(); + const QString audio_bitrate(); + const QString audio_sampfreq(); + + const QString duration() + { + return SecsToHMS( mpeg_info->playing_time ); + }; + const int version() + { + return mpeg_info->version; + }; + const unsigned long muxrate() + { + return mpeg_info->muxrate; + }; + const QString video_format( ); + const QString video_chroma( ); + const QString audio_mode( ); + const QString audio_copyright( ); + const QString mpegTypeS( bool audio = false ); + const int mpegType(); + + void PrintInfo(); + + Mpeginfo* mpeg_info; + + protected: + + const QString audio_type2str( unsigned int , unsigned int, unsigned int ); + QString SecsToHMS( double ); + + QPtrList<K3bVcdTrack>* m_parent; + + // PBC + QPtrList<K3bVcdTrack>* m_revreflist; // List of Tracks which points to us + QMap<int, K3bVcdTrack*> m_pbctrackmap; // Pbc Tracks (Previous, Next, ...) + QMap<int, int> m_pbcnontrackmap; // Pbc NON Track types (Previous, Next, ...) + QMap<int, bool> m_pbcusrdefmap; // Pbc is userdefined or defaults (Previous, Next, ...) + QMap<int, K3bVcdTrack*> m_definedkeysmap; + + bool m_pbcnumkeys; + bool m_pbcnumkeysuserdefined; + + int m_pbcplaytime; + int m_pbcwaittime; + /********************************************************************************/ + + bool m_reactivity; + int m_filetype; + QFile m_file; + QString m_title; +}; + +#endif diff --git a/libk3b/projects/videocd/k3bvcdxmlview.cpp b/libk3b/projects/videocd/k3bvcdxmlview.cpp new file mode 100644 index 0000000..0b6f250 --- /dev/null +++ b/libk3b/projects/videocd/k3bvcdxmlview.cpp @@ -0,0 +1,440 @@ +/* +* +* $Id: k3bvcdxmlview.cpp 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@k3b.org> +* THX to Manfred Odenstein <odix@chello.at> +* +* 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 <qfile.h> + +#include <kstandarddirs.h> +#include <kdebug.h> + + +#include "k3bvcdxmlview.h" +#include "k3bvcdtrack.h" +#include <k3bcore.h> +#include <k3bversion.h> + +K3bVcdXmlView::K3bVcdXmlView( K3bVcdDoc* pDoc ) +{ + + m_doc = pDoc; + +} + +K3bVcdXmlView::~K3bVcdXmlView() +{} + +bool K3bVcdXmlView::write( const QString& fname ) +{ + + QDomDocument xmlDoc( "videocd PUBLIC \"-//GNU//DTD VideoCD//EN\" \"http://www.gnu.org/software/vcdimager/videocd.dtd\"" ); + // xmlDoc.appendChild( xmlDoc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"iso-8859-1\"" ) ); + xmlDoc.appendChild( xmlDoc.createProcessingInstruction( "xml", "version=\"1.0\"" ) ); + + // create root element + QDomElement root = xmlDoc.createElement( "videocd" ); + root.setAttribute( "xmlns", "http://www.gnu.org/software/vcdimager/1.0/" ); + root.setAttribute( "class", m_doc->vcdOptions() ->vcdClass() ); + root.setAttribute( "version", m_doc->vcdOptions() ->vcdVersion() ); + xmlDoc.appendChild( root ); + + // create option elements + + // Broken SVCD mode - NonCompliantMode + if ( m_doc->vcdOptions() ->NonCompliantMode() ) { + QDomElement elemOption; + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "svcd vcd30 mpegav" ); + elemOption.setAttribute( "value", "true" ); + + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "svcd vcd30 entrysvd" ); + elemOption.setAttribute( "value", "true" ); + } + + // VCD3.0 track interpretation + if ( m_doc->vcdOptions() ->VCD30interpretation() ) { + QDomElement elemOption; + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "svcd vcd30 tracksvd" ); + elemOption.setAttribute( "value", "true" ); + } + + // Relaxed aps + if ( m_doc->vcdOptions() ->RelaxedAps() ) { + QDomElement elemOption; + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "relaxed aps" ); + elemOption.setAttribute( "value", "true" ); + } + + // Update scan offsets + if ( m_doc->vcdOptions() ->UpdateScanOffsets() ) { + QDomElement elemOption; + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "update scan offsets" ); + elemOption.setAttribute( "value", "true" ); + + } + + // Gaps & Margins + if ( m_doc->vcdOptions() ->UseGaps() ) { + QDomElement elemOption; + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "leadout pregap" ); + elemOption.setAttribute( "value", m_doc->vcdOptions() ->PreGapLeadout() ); + + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "track pregap" ); + elemOption.setAttribute( "value", m_doc->vcdOptions() ->PreGapTrack() ); + + if ( m_doc->vcdOptions() ->vcdClass() == "vcd" ) { + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "track front margin" ); + elemOption.setAttribute( "value", m_doc->vcdOptions() ->FrontMarginTrack() ); + + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "track rear margin" ); + elemOption.setAttribute( "value", m_doc->vcdOptions() ->RearMarginTrack() ); + } else { + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "track front margin" ); + elemOption.setAttribute( "value", m_doc->vcdOptions() ->FrontMarginTrackSVCD() ); + + elemOption = addSubElement( xmlDoc, root, "option" ); + elemOption.setAttribute( "name", "track rear margin" ); + elemOption.setAttribute( "value", m_doc->vcdOptions() ->RearMarginTrackSVCD() ); + } + + } + + // create info element + QDomElement elemInfo = addSubElement( xmlDoc, root, "info" ); + addSubElement( xmlDoc, elemInfo, "album-id", m_doc->vcdOptions() ->albumId().upper() ); + addSubElement( xmlDoc, elemInfo, "volume-count", m_doc->vcdOptions() ->volumeCount() ); + addSubElement( xmlDoc, elemInfo, "volume-number", m_doc->vcdOptions() ->volumeNumber() ); + addSubElement( xmlDoc, elemInfo, "restriction", m_doc->vcdOptions() ->Restriction() ); + + // create pvd element + QDomElement elemPvd = addSubElement( xmlDoc, root, "pvd" ); + addSubElement( xmlDoc, elemPvd, "volume-id", m_doc->vcdOptions() ->volumeId().upper() ); + addSubElement( xmlDoc, elemPvd, "system-id", m_doc->vcdOptions() ->systemId() ); + addSubElement( xmlDoc, elemPvd, "application-id", m_doc->vcdOptions() ->applicationId() ); + addSubElement( xmlDoc, elemPvd, "preparer-id", QString( "K3b - Version %1" ).arg( k3bcore->version() ).upper() ); + addSubElement( xmlDoc, elemPvd, "publisher-id", m_doc->vcdOptions() ->publisher().upper() ); + + + // create filesystem element + QDomElement elemFileSystem = addSubElement( xmlDoc, root, "filesystem" ); + + // SEGMENT folder, some standalone DVD-Player need this + if ( !m_doc->vcdOptions() ->haveSegments() && m_doc->vcdOptions() ->SegmentFolder() ) + addFolderElement( xmlDoc, elemFileSystem, "SEGMENT" ); + + // create cdi element + if ( m_doc->vcdOptions() ->CdiSupport() ) { + QDomElement elemFolder = addFolderElement( xmlDoc, elemFileSystem, "CDI" ); + + addFileElement( xmlDoc, elemFolder, locate( "data", "k3b/cdi/cdi_imag.rtf" ), "CDI_IMAG.RTF", true ); + addFileElement( xmlDoc, elemFolder, locate( "data", "k3b/cdi/cdi_text.fnt" ), "CDI_TEXT.FNT" ); + addFileElement( xmlDoc, elemFolder, locate( "data", "k3b/cdi/cdi_vcd.app" ), "CDI_VCD.APP" ); + + QString usercdicfg = locateLocal( "appdata", "cdi/cdi_vcd.cfg" ); + if ( QFile::exists( usercdicfg ) ) + addFileElement( xmlDoc, elemFolder, usercdicfg, "CDI_VCD.CFG" ); + else + addFileElement( xmlDoc, elemFolder, locate( "data", "k3b/cdi/cdi_vcd.cfg" ), "CDI_VCD.CFG" ); + } + + // sequence-items element & segment-items element + QDomElement elemsequenceItems; + QDomElement elemsegmentItems; + + // sequence-item element & segment-item element + QDomElement elemsequenceItem; + QDomElement elemsegmentItem; + + // if we have segments, elemsegmentItems must be before any sequence in xml file order + if ( m_doc->vcdOptions()->haveSegments() ) + elemsegmentItems = addSubElement( xmlDoc, root, "segment-items" ); + + // sequence must always available ... + elemsequenceItems = addSubElement( xmlDoc, root, "sequence-items" ); + // if we have no sequence (photo (s)vcd) we must add a dummy sequence they inform the user to turn on pbc on there videoplayer + if ( !m_doc->vcdOptions()->haveSequence() ) { + QString filename; + if ( m_doc->vcdOptions()->mpegVersion() == 1 ) + filename = locate( "data", "k3b/extra/k3bphotovcd.mpg" ); + else + filename = locate( "data", "k3b/extra/k3bphotosvcd.mpg" ); + + elemsequenceItem = addSubElement( xmlDoc, elemsequenceItems, "sequence-item" ); + elemsequenceItem.setAttribute( "src", QString( "%1" ).arg( QFile::encodeName( filename ) ) ); + elemsequenceItem.setAttribute( "id", "sequence-000" ); + // default entry + QDomElement elemdefaultEntry; + elemdefaultEntry = addSubElement( xmlDoc, elemsequenceItem, "default-entry" ); + elemdefaultEntry.setAttribute( "id", "entry-000" ); + } + + + // pbc + QDomElement elemPbc; + + // Add Tracks to XML + QPtrListIterator<K3bVcdTrack> it( *m_doc->tracks() ); + for ( ; it.current(); ++it ) { + if ( !it.current() ->isSegment() ) { + QString seqId = QString::number( it.current() ->index() ).rightJustify( 3, '0' ); + + elemsequenceItem = addSubElement( xmlDoc, elemsequenceItems, "sequence-item" ); + elemsequenceItem.setAttribute( "src", QString( "%1" ).arg( QFile::encodeName( it.current() ->absPath() ) ) ); + elemsequenceItem.setAttribute( "id", QString( "sequence-%1" ).arg( seqId ) ); + + // default entry + QDomElement elemdefaultEntry; + elemdefaultEntry = addSubElement( xmlDoc, elemsequenceItem, "default-entry" ); + elemdefaultEntry.setAttribute( "id", QString( "entry-%1" ).arg( seqId ) ); + + } else { + // sequence-items element needs at least one segment to fit the XML + elemsegmentItem = addSubElement( xmlDoc, elemsegmentItems, "segment-item" ); + elemsegmentItem.setAttribute( "src", QString( "%1" ).arg( QFile::encodeName( it.current() ->absPath() ) ) ); + elemsegmentItem.setAttribute( "id", QString( "segment-%1" ).arg( QString::number( it.current() ->index() ).rightJustify( 3, '0' ) ) ); + + } + } + for ( it.toFirst(); it.current(); ++it ) { + + if ( m_doc->vcdOptions() ->PbcEnabled() ) { + if ( elemPbc.isNull() ) + elemPbc = addSubElement( xmlDoc, root, "pbc" ); + + doPbc( xmlDoc, elemPbc, it.current() ); + } + } + + if ( ! elemPbc.isNull() ) { + QDomElement elemEndlist = addSubElement( xmlDoc, elemPbc, "endlist" ); + elemEndlist.setAttribute( "id", "end" ); + elemEndlist.setAttribute( "rejected", "true" ); + } + + m_xmlstring = xmlDoc.toString(); + kdDebug() << QString( "(K3bVcdXmlView) Write Data to %1:" ).arg( fname ) << endl; + + QFile xmlFile( fname ); + if ( xmlFile.open( IO_WriteOnly ) ) { + QTextStream ts( & xmlFile ); + ts << m_xmlstring; + xmlFile.close(); + return true; + } + + return false; +} + +void K3bVcdXmlView::addComment( QDomDocument& doc, QDomElement& parent, const QString& text ) +{ + QDomComment comment = doc.createComment( text ); + parent.appendChild( comment ); +} + +QDomElement K3bVcdXmlView::addSubElement( QDomDocument& doc, QDomElement& parent, const QString& name, const QString& value ) +{ + QDomElement element = doc.createElement( name ); + parent.appendChild( element ); + if ( !value.isNull() ) { + QDomText t = doc.createTextNode( value ); + element.appendChild( t ); + } + return element; +} + +QDomElement K3bVcdXmlView::addSubElement( QDomDocument& doc, QDomElement& parent, const QString& name, const int& value ) +{ + QDomElement element = doc.createElement( name ); + parent.appendChild( element ); + if ( value >= -1 ) { + QDomText t = doc.createTextNode( QString( "%1" ).arg( value ) ); + element.appendChild( t ); + } + return element; +} + +QDomElement K3bVcdXmlView::addFolderElement( QDomDocument& doc, QDomElement& parent, const QString& name ) +{ + QDomElement elemFolder = addSubElement( doc, parent, "folder" ); + addSubElement( doc, elemFolder, "name", name ); + + return elemFolder; +} + +void K3bVcdXmlView::addFileElement( QDomDocument& doc, QDomElement& parent, const QString& src, const QString& name, bool mixed ) +{ + QDomElement elemFile = addSubElement( doc, parent, "file" ); + elemFile.setAttribute( "src", QString( "%1" ).arg( src ) ); + if ( mixed ) + elemFile.setAttribute( "format", "mixed" ); + + addSubElement( doc, elemFile, "name", name ); +} + +void K3bVcdXmlView::doPbc( QDomDocument& doc, QDomElement& parent, K3bVcdTrack* track ) +{ + QString ref = ( track->isSegment() ) ? "segment" : "sequence"; + + QDomElement elemSelection = addSubElement( doc, parent, "selection" ); + elemSelection.setAttribute( "id", QString( "select-%1-%2" ).arg( ref ).arg( QString::number( track->index() ).rightJustify( 3, '0' ) ) ); + + setNumkeyBSN( doc, elemSelection, track ); + + for ( int i = 0; i < K3bVcdTrack::_maxPbcTracks; i++ ) { + QDomElement elemPbcSelectionPNRDT; + + if ( track->getPbcTrack( i ) ) { + int index = track->getPbcTrack( i ) ->index(); + QString ref = ( track->getPbcTrack( i ) ->isSegment() ) ? "segment" : "sequence"; + + switch ( i ) { + case K3bVcdTrack::PREVIOUS: + elemPbcSelectionPNRDT = addSubElement( doc, elemSelection, "prev" ); + elemPbcSelectionPNRDT.setAttribute( "ref", QString( "select-%1-%2" ).arg( ref ).arg( QString::number( index ).rightJustify( 3, '0' ) ) ); + break; + case K3bVcdTrack::NEXT: + elemPbcSelectionPNRDT = addSubElement( doc, elemSelection, "next" ); + elemPbcSelectionPNRDT.setAttribute( "ref", QString( "select-%1-%2" ).arg( ref ).arg( QString::number( index ).rightJustify( 3, '0' ) ) ); + break; + case K3bVcdTrack::RETURN: + elemPbcSelectionPNRDT = addSubElement( doc, elemSelection, "return" ); + elemPbcSelectionPNRDT.setAttribute( "ref", QString( "select-%1-%2" ).arg( ref ).arg( QString::number( index ).rightJustify( 3, '0' ) ) ); + break; + case K3bVcdTrack::DEFAULT: + elemPbcSelectionPNRDT = addSubElement( doc, elemSelection, "default" ); + elemPbcSelectionPNRDT.setAttribute( "ref", QString( "select-%1-%2" ).arg( ref ).arg( QString::number( index ).rightJustify( 3, '0' ) ) ); + break; + case K3bVcdTrack::AFTERTIMEOUT: + if ( track->getWaitTime() >= 0 ) { + elemPbcSelectionPNRDT = addSubElement( doc, elemSelection, "timeout" ); + elemPbcSelectionPNRDT.setAttribute( "ref", QString( "select-%1-%2" ).arg( ref ).arg( QString::number( index ).rightJustify( 3, '0' ) ) ); + } + break; + } + } else { + // jump to <endlist> otherwise do noop while disabled + if ( track->getNonPbcTrack( i ) == K3bVcdTrack::VIDEOEND ) { + switch ( i ) { + case K3bVcdTrack::PREVIOUS: + elemPbcSelectionPNRDT = addSubElement( doc, elemSelection, "prev" ); + elemPbcSelectionPNRDT.setAttribute( "ref", "end" ); + break; + case K3bVcdTrack::NEXT: + elemPbcSelectionPNRDT = addSubElement( doc, elemSelection, "next" ); + elemPbcSelectionPNRDT.setAttribute( "ref", "end" ); + break; + case K3bVcdTrack::RETURN: + elemPbcSelectionPNRDT = addSubElement( doc, elemSelection, "return" ); + elemPbcSelectionPNRDT.setAttribute( "ref", "end" ); + break; + case K3bVcdTrack::DEFAULT: + elemPbcSelectionPNRDT = addSubElement( doc, elemSelection, "default" ); + elemPbcSelectionPNRDT.setAttribute( "ref", "end" ); + break; + case K3bVcdTrack::AFTERTIMEOUT: + if ( track->getWaitTime() >= 0 ) { + elemPbcSelectionPNRDT = addSubElement( doc, elemSelection, "timeout" ); + elemPbcSelectionPNRDT.setAttribute( "ref", "end" ); + } + break; + } + } + } + } + + addSubElement( doc, elemSelection, "wait", track->getWaitTime() ); + QDomElement loop = addSubElement( doc, elemSelection, "loop", track->getPlayTime() ); + if ( track->Reactivity() ) + loop.setAttribute( "jump-timing", "delayed" ); + else + loop.setAttribute( "jump-timing", "immediate" ); + + addSubElement( doc, elemSelection, "play-item" ).setAttribute( "ref", QString( "%1-%2" ).arg( ref ).arg( QString::number( track->index() ).rightJustify( 3, '0' ) ) ); + + setNumkeySEL( doc, elemSelection, track ); +} + +void K3bVcdXmlView::setNumkeyBSN( QDomDocument& doc, QDomElement& parent, K3bVcdTrack* track ) +{ + if ( track->PbcNumKeys() ) { + if ( track->PbcNumKeysUserdefined() ) { + QMap<int, K3bVcdTrack*> numKeyMap = track->DefinedNumKey(); + QMap<int, K3bVcdTrack*>::const_iterator trackIt; + + m_startkey = 0; + trackIt = numKeyMap.begin(); + if ( trackIt != numKeyMap.end() ) + m_startkey = trackIt.key(); + + if ( m_startkey > 0 ) + addSubElement( doc, parent, "bsn", m_startkey ); + else // user has no numKeys defined for this track + track->setPbcNumKeys( false ); + + } else { + // default start with key #1 + addSubElement( doc, parent, "bsn", 1 ); + } + } +} + +void K3bVcdXmlView::setNumkeySEL( QDomDocument& doc, QDomElement& parent, K3bVcdTrack* track ) +{ + if ( track->PbcNumKeys() ) { + QDomElement elemPbcSelectionNumKeySEL; + QString ref = ( track->isSegment() ) ? "segment" : "sequence"; + int none = m_startkey; + if ( track->PbcNumKeysUserdefined() ) { + QMap<int, K3bVcdTrack*> numKeyMap = track->DefinedNumKey(); + QMap<int, K3bVcdTrack*>::const_iterator trackIt; + + for ( trackIt = numKeyMap.begin(); trackIt != numKeyMap.end(); ++trackIt ) { + + kdDebug() << QString( "trackIt key: %1 none: %2" ).arg( trackIt.key() ).arg( none ) << endl; + while ( none < trackIt.key() ) { + elemPbcSelectionNumKeySEL = addSubElement( doc, parent, "select" ); + elemPbcSelectionNumKeySEL.setAttribute( "ref", QString( "select-%1-%2" ).arg( ref ).arg( QString::number( track->index() ).rightJustify( 3, '0' ) ) ); + addComment( doc, parent, QString( "key %1 -> %2 (normal none)" ).arg( none ).arg( QFile::encodeName( track->absPath() ) ) ); + none++; + } + + if ( trackIt.data() ) { + QString ref = ( trackIt.data() ->isSegment() ) ? "segment" : "sequence"; + elemPbcSelectionNumKeySEL = addSubElement( doc, parent, "select" ); + elemPbcSelectionNumKeySEL.setAttribute( "ref", QString( "select-%1-%2" ).arg( ref ).arg( QString::number( trackIt.data() ->index() ).rightJustify( 3, '0' ) ) ); + addComment( doc, parent, QString( "key %1 -> %2" ).arg( trackIt.key() ).arg( QFile::encodeName( trackIt.data() ->absPath() ) ) ); + } else { + elemPbcSelectionNumKeySEL = addSubElement( doc, parent, "select" ); + elemPbcSelectionNumKeySEL.setAttribute( "ref", "end" ); + addComment( doc, parent, QString( "key %1 -> end" ).arg( trackIt.key() ) ); + } + none++; + } + } else { + // default reference to itSelf + elemPbcSelectionNumKeySEL = addSubElement( doc, parent, "select" ); + elemPbcSelectionNumKeySEL.setAttribute( "ref", QString( "select-%1-%2" ).arg( ref ).arg( QString::number( track->index() ).rightJustify( 3, '0' ) ) ); + } + } +} + diff --git a/libk3b/projects/videocd/k3bvcdxmlview.h b/libk3b/projects/videocd/k3bvcdxmlview.h new file mode 100644 index 0000000..d99549b --- /dev/null +++ b/libk3b/projects/videocd/k3bvcdxmlview.h @@ -0,0 +1,59 @@ +/* +* +* $Id: k3bvcdxmlview.h 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@k3b.org> +* THX to Manfred Odenstein <odix@chello.at> +* +* 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_VCD_XMLVIEW_H +#define K3B_VCD_XMLVIEW_H + +#include <qstring.h> +#include <qdom.h> +#include <ktempfile.h> + +#include <k3bvcddoc.h> + +class K3bVcdOptions; +class K3bVcdTrack; + + +class K3bVcdXmlView +{ + + public: + K3bVcdXmlView( K3bVcdDoc* ); + ~K3bVcdXmlView(); + + bool write( const QString& ); + QString xmlString() + { + return m_xmlstring; + } + + private: + QString m_xmlstring; + + void addComment( QDomDocument& doc, QDomElement& parent, const QString& text ); + QDomElement addSubElement( QDomDocument&, QDomElement&, const QString& name, const QString& value = QString::null ); + QDomElement addSubElement( QDomDocument&, QDomElement&, const QString& name, const int& value ); + + QDomElement addFolderElement( QDomDocument&, QDomElement&, const QString& name ); + void addFileElement( QDomDocument&, QDomElement&, const QString& src, const QString& name, bool mixed = false ); + void doPbc( QDomDocument&, QDomElement&, K3bVcdTrack* ); + void setNumkeyBSN( QDomDocument& , QDomElement&, K3bVcdTrack* ); + void setNumkeySEL( QDomDocument& , QDomElement&, K3bVcdTrack* ); + K3bVcdDoc* m_doc; + int m_startkey; +}; + +#endif diff --git a/libk3b/projects/videocd/mpeginfo/Makefile.am b/libk3b/projects/videocd/mpeginfo/Makefile.am new file mode 100644 index 0000000..af1b06b --- /dev/null +++ b/libk3b/projects/videocd/mpeginfo/Makefile.am @@ -0,0 +1,5 @@ +INCLUDES = $(all_includes) +noinst_LTLIBRARIES = libmpeginfo.la + +libmpeginfo_la_SOURCES = k3bmpeginfo.cpp + diff --git a/libk3b/projects/videocd/mpeginfo/k3bmpeginfo.cpp b/libk3b/projects/videocd/mpeginfo/k3bmpeginfo.cpp new file mode 100644 index 0000000..583a0aa --- /dev/null +++ b/libk3b/projects/videocd/mpeginfo/k3bmpeginfo.cpp @@ -0,0 +1,844 @@ +/* +* +* $Id: k3bmpeginfo.cpp 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@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. +*/ + +// kde includes +#include <klocale.h> + +// k3b includes +#include "k3bmpeginfo.h" + +static const double frame_rates[ 16 ] = + { + 0.0, 24000.0 / 1001, 24.0, 25.0, + 30000.0 / 1001, 30.0, 50.0, 60000.0 / 1001, + 60.0, 0.0, + }; + +K3bMpegInfo::K3bMpegInfo( const char* filename ) + : m_mpegfile( 0 ), + m_filename( filename ), + m_done( false ), + m_buffstart( 0 ), + m_buffend( 0 ), + m_buffer( 0 ), + m_initial_TS( 0.0 ) +{ + + mpeg_info = new Mpeginfo(); + + m_mpegfile = fopen( filename, "rb" ); + + if ( m_mpegfile == 0 ) { + kdDebug() << QString( "Unable to open %1" ).arg( m_filename ) << endl; + return ; + } + + if ( fseeko( m_mpegfile, 0, SEEK_END ) ) { + kdDebug() << QString( "Unable to seek in file %1" ).arg( m_filename ) << endl; + return ; + } + + llong lof = ftello( m_mpegfile ); + + if ( lof == -1 ) { + kdDebug() << QString( "Seeking to end of input file %1 failed." ).arg( m_filename ) << endl; + //give up.. + return ; + } else + m_filesize = lof; + + // nothing to do on an empty file + if ( !m_filesize ) { + kdDebug() << QString( "File %1 is empty." ).arg( m_filename ) << endl; + m_error_string = i18n( "File %1 is empty." ).arg( m_filename ); + return ; + } + + m_buffer = new byte[ BUFFERSIZE ]; + + MpegParsePacket ( ); + +} + +K3bMpegInfo::~K3bMpegInfo() +{ + if ( m_buffer ) { + delete[] m_buffer; + } + if ( m_mpegfile ) { + fclose( m_mpegfile ); + } + + delete mpeg_info; +} +bool K3bMpegInfo::MpegParsePacket () +{ + + /* verify the packet begins with a pack header */ + if ( !EnsureMPEG( 0, MPEG_PACK_HEADER_CODE ) ) { + llong code = GetNBytes( 0, 4 ); + + kdDebug() << QString( "(K3bMpegInfo::mpeg_parse_packet ()) pack header code 0x%1 expected, but 0x%2 found" ).arg( 0x00000100 + MPEG_PACK_HEADER_CODE, 0, 16 ).arg( code, 0, 16 ) << endl; + + if ( code == 0x00000100 + MPEG_SEQUENCE_CODE ) { + kdDebug() << "...this looks like a elementary video stream but a multiplexed program stream was required." << endl; + m_error_string = i18n( "This looks like a elementary video stream but a multiplexed program stream was required." ); + } + + if ( ( 0xfff00000 & code ) == 0xfff00000 ) { + kdDebug() << "...this looks like a elementary audio stream but a multiplexed program stream was required." << endl; + m_error_string = i18n( "This looks like a elementary audio stream but a multiplexed program stream was required." ); + } + + if ( code == 0x52494646 ) { + kdDebug() << "...this looks like a RIFF header but a plain multiplexed program stream was required." << endl; + m_error_string = i18n( "This looks like a RIFF header but a plain multiplexed program stream was required." ); + } + + return false; + } + + + /* take a look at the pack header */ + int offset = 0; + while ( GetByte( offset ) == 0x00 ) + offset ++; + //here we're on the first non null byte let's get back to leave two zeros (packet start code) + offset -= 2; + + if ( offset != 0 ) { + // we actually skipped some zeroes + kdDebug() << QString( "Skipped %1 zeroes at start of file" ).arg( offset ) << endl; + } + + // here while schleife + while ( offset != -1 ) { + offset = MpegParsePacket( offset ); + } + + /* + int pkt = 0; + offset = FindNextMarker( 0, MPEG_PACK_HEADER_CODE ); + + while ( offset != -1 ) { + pkt++; + offset = FindNextMarker( offset+1, MPEG_PACK_HEADER_CODE ); + } + + kdDebug() << "Pkt found: " << pkt << endl; + */ + + //seek the file duration by fetching the last PACK + //and reading its timestamp + llong last_pack = bdFindNextMarker( m_filesize - 13, MPEG_PACK_HEADER_CODE ); + // -12 because a PACK is at least 12 bytes + double duration; + last_pack += 4; + int bits = GetByte( last_pack ) >> 4; + + if ( bits == 0x2 ) /* %0010 ISO11172-1 */ + { + duration = ReadTS( last_pack ); + } else if ( bits >> 2 == 0x1 ) /* %01xx ISO13818-1 */ + { + duration = ReadTSMpeg2( last_pack ); + } else { + kdDebug() << QString( "no timestamp found" ) << endl; + duration = ReadTS( last_pack ); + } + + mpeg_info->playing_time = duration - m_initial_TS; + + + if ( !mpeg_info->has_video ) + for ( int i = 0; i < 2; i++ ) + if ( mpeg_info->video[ i ].seen ) + mpeg_info->has_video = true; + + if ( !mpeg_info->has_audio ) + for ( int i = 0; i < 2; i++ ) + if ( mpeg_info->audio[ i ].seen ) + mpeg_info->has_audio = true; + + return true; +} + +llong K3bMpegInfo::MpegParsePacket ( llong offset ) +{ + byte mark = 0; + uint size = 0; + + /* continue until start code seen */ + offset = FindNextMarker( offset, &mark ); + + if ( offset < 0 ) + return offset; + + switch ( mark ) { + int bits; + + case MPEG_PACK_HEADER_CODE: + // kdDebug() << QString( "MPEG_PACK_HEADER_CODE @ %1" ).arg( offset ) << endl; + + offset += 4; + + if ( mpeg_info->version != MPEG_VERS_INVALID ) + break; + + bits = GetByte( offset ) >> 4; + + if ( bits == 0x2 ) /* %0010 ISO11172-1 */ + { + mpeg_info->version = MPEG_VERS_MPEG1; + + unsigned long muxrate = 0; + + muxrate = ( GetByte( offset + 5 ) & 0x7F ) << 15; + muxrate |= ( GetByte( offset + 6 ) << 7 ); + muxrate |= ( GetByte( offset + 7 ) >> 1 ); + + mpeg_info->muxrate = muxrate * 50 * 8; + + if ( m_initial_TS == 0.0 ) + { + m_initial_TS = ReadTS( offset ); + kdDebug() << QString( "Initial TS = %1" ).arg( m_initial_TS ) << endl; + } + + } else if ( bits >> 2 == 0x1 ) /* %01xx ISO13818-1 */ + { + mpeg_info->version = MPEG_VERS_MPEG2; + + unsigned long muxrate = 0; + muxrate = GetByte( offset + 6 ) << 14; + muxrate |= GetByte( offset + 7 ) << 6; + muxrate |= GetByte( offset + 8 ) >> 2; + + mpeg_info->muxrate = muxrate * 50 * 8; + + if ( m_initial_TS == 0.0 ) + { + m_initial_TS = ReadTSMpeg2( offset ); + kdDebug() << QString( "Initial TS = %1" ).arg( m_initial_TS ) << endl; + } + + } else { + kdDebug() << QString( "packet not recognized as either version 1 or 2 (%1)" ).arg( bits ) << endl; + mpeg_info->version = MPEG_VERS_INVALID; + return -1; + } + break; + + case MPEG_SYSTEM_HEADER_CODE: + case MPEG_PAD_CODE: + case MPEG_PRIVATE_1_CODE: + case MPEG_VIDEO_E0_CODE: + case MPEG_VIDEO_E1_CODE: + case MPEG_VIDEO_E2_CODE: + case MPEG_AUDIO_C0_CODE: + case MPEG_AUDIO_C1_CODE: + case MPEG_AUDIO_C2_CODE: + + offset += 4; + size = GetSize( offset ); + offset += 2; + // kdDebug() << QString( "offset = %1, size = %2" ).arg( offset ).arg( size ) << endl; + + switch ( mark ) { + case MPEG_SYSTEM_HEADER_CODE: + // kdDebug() << QString( "Systemheader: %1" ).arg( m_code, 0, 16 ) << endl; + break; + + case MPEG_VIDEO_E0_CODE: + case MPEG_VIDEO_E1_CODE: + case MPEG_VIDEO_E2_CODE: + ParseVideo( offset, mark ); + // _analyze_video_pes (code & 0xff, buf + pos, size, !parse_pes, ctx); + if ( mpeg_info->has_video && mpeg_info->has_audio ) { + return -1; + } else if ( mark == MPEG_VIDEO_E0_CODE || mpeg_info->version == MPEG_VERS_MPEG2 && mark == MPEG_VIDEO_E1_CODE || mpeg_info->version == MPEG_VERS_MPEG1 && mark == MPEG_VIDEO_E2_CODE ) { + mpeg_info->has_video = true; + offset = FindNextAudio( offset ); + } + break; + case MPEG_AUDIO_C0_CODE: + case MPEG_AUDIO_C1_CODE: + case MPEG_AUDIO_C2_CODE: + offset = SkipPacketHeader( offset - 6 ); + ParseAudio( offset, mark ); + // audio packet doesn't begin with 0xFFF + if ( !mpeg_info->audio[ GetAudioIdx( mark ) ].seen ) { + int a_idx = GetAudioIdx( mark ); + while ( ( offset < m_filesize - 10 ) && !mpeg_info->audio[ a_idx ].seen ) { + if ( ( GetByte( offset ) == 0xFF ) && ( GetByte( offset + 1 ) & 0xF0 ) == 0xF0 ) + ParseAudio( offset, mark ); + offset++; + } + } + + mpeg_info->has_audio = true; + if ( mpeg_info->has_video ) + return -1; + + offset = FindNextVideo( offset ); + break; + + case MPEG_PRIVATE_1_CODE: + kdDebug() << QString( "PrivateCode: %1" ).arg( mark, 0, 16 ) << endl; + break; + } + break; + + case MPEG_PROGRAM_END_CODE: + kdDebug() << QString( "ProgramEndCode: %1" ).arg( mark, 0, 16 ) << endl; + offset += 4; + break; + + case MPEG_PICTURE_CODE: + kdDebug() << QString( "PictureCode: %1" ).arg( mark, 0, 16 ) << endl; + offset += 3; + break; + + default: + offset += 4; + break; + } + + return offset; +} + +byte K3bMpegInfo::GetByte( llong offset ) +{ + unsigned long nread; + if ( ( offset >= m_buffend ) || ( offset < m_buffstart ) ) { + + if ( fseeko( m_mpegfile, offset, SEEK_SET ) ) { + kdDebug() << QString( "could not get seek to offset (%1) in file %2 (size:%3)" ).arg( offset ).arg( m_filename ).arg( m_filesize ) << endl; + return 0x11; + } + nread = fread( m_buffer, 1, BUFFERSIZE, m_mpegfile ); + m_buffstart = offset; + m_buffend = offset + nread; + if ( ( offset >= m_buffend ) || ( offset < m_buffstart ) ) { + // weird + kdDebug() << QString( "could not get offset %1 in file %2 [%3]" ).arg( offset ).arg( m_filename ).arg( m_filesize ) << endl; + return 0x11; + } + } + return m_buffer[ offset - m_buffstart ]; +} + +// same as above but improved for backward search +byte K3bMpegInfo::bdGetByte( llong offset ) +{ + unsigned long nread; + if ( ( offset >= m_buffend ) || ( offset < m_buffstart ) ) { + llong start = offset - BUFFERSIZE + 1 ; + start = start >= 0 ? start : 0; + + fseeko( m_mpegfile, start, SEEK_SET ); + + nread = fread( m_buffer, 1, BUFFERSIZE, m_mpegfile ); + m_buffstart = start; + m_buffend = start + nread; + if ( ( offset >= m_buffend ) || ( offset < m_buffstart ) ) { + // weird + kdDebug() << QString( "could not get offset %1 in file %2 [%3]" ).arg( offset ).arg( m_filename ).arg( m_filesize ) << endl; + + return 0x11; + } + } + return m_buffer[ offset - m_buffstart ]; +} + + +llong K3bMpegInfo::GetNBytes( llong offset, int n ) +{ + llong nbytes = 0; + n--; + for ( int i = 0; i < n; i++ ) + ( ( char* ) & nbytes ) [ n - i ] = GetByte( offset + i ); + + return nbytes; + +} + +// get a two byte size +unsigned short int K3bMpegInfo::GetSize( llong offset ) +{ + return GetByte( offset ) * 256 + GetByte( offset + 1 ); + // return GetNBytes( offset, 2 ); + +} + +bool K3bMpegInfo::EnsureMPEG( llong offset, byte mark ) +{ + if ( ( GetByte( offset ) == 0x00 ) && + ( GetByte( offset + 1 ) == 0x00 ) && + ( GetByte( offset + 2 ) == 0x01 ) && + ( GetByte( offset + 3 ) == mark ) ) + return true; + else + return false; +} + + +// find next 0x 00 00 01 xx sequence, returns offset or -1 on err +llong K3bMpegInfo::FindNextMarker( llong from ) +{ + llong offset; + for ( offset = from; offset < ( m_filesize - 4 ); offset++ ) { + if ( + ( GetByte( offset + 0 ) == 0x00 ) && + ( GetByte( offset + 1 ) == 0x00 ) && + ( GetByte( offset + 2 ) == 0x01 ) ) { + return offset; + } + } + return -1; +} + +// find next 0x 00 00 01 xx sequence, returns offset or -1 on err and +// change mark to xx +llong K3bMpegInfo::FindNextMarker( llong from, byte* mark ) +{ + llong offset = FindNextMarker( from ); + if ( offset >= 0 ) { + *mark = GetByte( offset + 3 ); + return offset; + } else { + return -1; + } +} + +// find next 0X00 00 01 mark +llong K3bMpegInfo::FindNextMarker( llong from, byte mark ) +{ + llong offset = from; + while ( offset >= 0 ) { + offset = FindNextMarker( offset ); + if ( offset < 0 ) { + return -1; + } + if ( EnsureMPEG( offset, mark ) ) { + return offset; + } else + offset++; + } + + //shouldn't be here + return -1; +} + +llong K3bMpegInfo::bdFindNextMarker( llong from, byte mark ) +{ + llong offset; + for ( offset = from; offset >= 0; offset-- ) { + if ( + ( bdGetByte( offset ) == 0x00 ) && + ( bdGetByte( offset + 1 ) == 0x00 ) && + ( bdGetByte( offset + 2 ) == 0x01 ) && + ( bdGetByte( offset + 3 ) == mark ) ) { + return offset; + } + } + return -1; +} + +llong K3bMpegInfo::bdFindNextMarker( llong from, byte* mark ) +{ + llong offset; + for ( offset = from; offset >= 0; offset-- ) { + if ( ( bdGetByte( offset ) == 0x00 ) && + ( bdGetByte( offset + 1 ) == 0x00 ) && + ( bdGetByte( offset + 2 ) == 0x01 ) ) { + *mark = bdGetByte( offset + 3 ); + return offset; + } + } + return -1; + +} + +llong K3bMpegInfo::FindNextVideo( llong from ) +{ + llong offset = from; + while ( offset >= 0 ) { + offset = FindNextMarker( offset ); + if ( offset < 0 ) { + return -1; + } + if ( EnsureMPEG( offset, MPEG_VIDEO_E0_CODE ) || EnsureMPEG( offset, MPEG_VIDEO_E1_CODE ) || EnsureMPEG( offset, MPEG_VIDEO_E2_CODE ) ) { + return offset; + } else + offset++; + } + + //shouldn't be here + return -1; +} + +llong K3bMpegInfo::FindNextAudio( llong from ) +{ + llong offset = from; + while ( offset >= 0 ) { + offset = FindNextMarker( offset ); + if ( offset < 0 ) { + return -1; + } + if ( EnsureMPEG( offset, MPEG_AUDIO_C0_CODE ) || EnsureMPEG( offset, MPEG_AUDIO_C1_CODE ) || EnsureMPEG( offset, MPEG_AUDIO_C2_CODE ) ) { + return offset; + } else + offset++; + } + + return -1; +} + + +int K3bMpegInfo::GetVideoIdx ( byte marker ) +{ + switch ( marker ) { + case MPEG_VIDEO_E0_CODE: + return 0; + break; + + case MPEG_VIDEO_E1_CODE: + return 1; + break; + + case MPEG_VIDEO_E2_CODE: + return 2; + break; + + default: + kdDebug() << "VideoCode not reached" << endl; + break; + } + + return -1; +} + +int K3bMpegInfo::GetAudioIdx ( byte marker ) +{ + switch ( marker ) { + case MPEG_AUDIO_C0_CODE: + return 0; + break; + + case MPEG_AUDIO_C1_CODE: + return 1; + break; + + case MPEG_AUDIO_C2_CODE: + return 2; + break; + + default: + kdDebug() << "VideoCode not reached" << endl; + break; + } + + return -1; +} + +llong K3bMpegInfo::SkipPacketHeader( llong offset ) +{ + byte tmp_byte; + if ( mpeg_info->version == MPEG_VERS_MPEG1 ) { + // skip startcode and packet size + offset += 6; + //remove stuffing bytes + tmp_byte = GetByte( offset ); + while ( tmp_byte & 0x80 ) + tmp_byte = GetByte( ++offset ); + + if ( ( tmp_byte & 0xC0 ) == 0x40 ) // next two bits are 01 + offset += 2; + + tmp_byte = GetByte( offset ); + if ( ( tmp_byte & 0xF0 ) == 0x20 ) + offset += 5; + else if ( ( tmp_byte & 0xF0 ) == 0x30 ) + offset += 10; + else + offset++; + + return offset; + } else if ( mpeg_info->version == MPEG_VERS_MPEG2 ) { + return ( offset + 9 + GetByte( offset + 8 ) ); + } else + return ( offset + 10 ); +} + +void K3bMpegInfo::ParseAudio ( llong offset, byte marker ) +{ + unsigned brate, srate; + bool mpeg2_5 = false; + + const int a_idx = GetAudioIdx( marker ); + + if ( mpeg_info->audio[ a_idx ].seen ) /* we have it already */ + return ; + + if ( ( GetByte( offset ) != 0xFF ) || ( ( GetByte( offset + 1 ) & 0xF0 ) != 0xF0 ) ) { + // doesn't start with 12 bits set + if ( ( GetByte( offset ) != 0xFF ) || ( ( GetByte( offset + 1 ) & 0xE0 ) != 0xE0 ) ) { + // doesn't start with 11 bits set + return ; + } else { + // start with 11 bits set + mpeg2_5 = true; + } + } + + // Find mpeg version 1.0 or 2.0 + if ( GetByte( offset + 1 ) & 0x08 ) { + if ( !mpeg2_5 ) + mpeg_info->audio[ a_idx ].version = 1; + else + return ; // invalid 01 encountered + } else { + if ( !mpeg2_5 ) + mpeg_info->audio[ a_idx ].version = 2; + else + mpeg_info->audio[ a_idx ].version = 3; //for mpeg 2.5 + } + + // Find Layer + mpeg_info->audio[ a_idx ].layer = ( GetByte( offset + 1 ) & 0x06 ) >> 1; + switch ( mpeg_info->audio[ a_idx ].layer ) { + case 0: + mpeg_info->audio[ a_idx ].layer = 0; + break; + case 1: + mpeg_info->audio[ a_idx ].layer = 3; + break; + case 2: + mpeg_info->audio[ a_idx ].layer = 2; + break; + case 3: + mpeg_info->audio[ a_idx ].layer = 1; + break; + } + + // Protection Bit + mpeg_info->audio[ a_idx ].protect = GetByte( offset + 1 ) & 0x01; + if ( mpeg_info->audio[ a_idx ].protect ) + mpeg_info->audio[ a_idx ].protect = 0; + else + mpeg_info->audio[ a_idx ].protect = 1; + + const unsigned bit_rates[ 4 ][ 16 ] = { + { + 0, + }, + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0}, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0}, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0} + }; + + + /* const unsigned bit_rates [ 3 ][ 3 ][ 16 ] = { + { + {0, }, + {0, }, + {0, }, + }, + { + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0}, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0}, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0} + }, + { + {0, 32, 48, 56, 64, 80 , 96 , 112, 128, 144, 160, 176, 192, 224, 256, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64 , 80 , 96 , 112, 128, 144, 160, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64 , 80 , 96 , 112, 128, 144, 160, 0} + } + }; + */ + + const unsigned sampling_rates[ 4 ][ 4 ] = { + { + 0, + }, + {44100, 48000, 32000, 0}, //mpeg 1 + {22050, 24000, 16000, 0}, //mpeg 2 + {11025, 12000, 8000, 0} //mpeg 2.5 + }; + + + // Bitrate index and sampling index to pass through the array + brate = GetByte( offset + 2 ) >> 4; + srate = ( GetByte( offset + 2 ) & 0x0f ) >> 2; + + mpeg_info->audio[ a_idx ].bitrate = 1024 * bit_rates[ mpeg_info->audio[ a_idx ].layer ][ brate ]; + mpeg_info->audio[ a_idx ].byterate = ( float ) ( mpeg_info->audio[ a_idx ].bitrate / 8.0 ); + mpeg_info->audio[ a_idx ].sampfreq = sampling_rates[ mpeg_info->audio[ a_idx ].version ][ srate ]; + + // Audio mode + mpeg_info->audio[ a_idx ].mode = 1 + ( GetByte( offset + 3 ) >> 6 ) ; + + // Copyright bit + if ( GetByte( offset + 3 ) & 0x08 ) + mpeg_info->audio[ a_idx ].copyright = true; + else + mpeg_info->audio[ a_idx ].copyright = false; + + // Original/Copy bit + if ( GetByte( offset + 3 ) & 0x04 ) + mpeg_info->audio[ a_idx ].original = true; + else + mpeg_info->audio[ a_idx ].original = false; + + + mpeg_info->audio[ a_idx ].seen = true; +} + +void K3bMpegInfo::ParseVideo ( llong offset, byte marker ) +{ + unsigned long aratio, frate, brate; + + const int v_idx = GetVideoIdx( marker ); + + const double aspect_ratios[ 16 ] = { + 0.0000, 1.0000, 0.6735, 0.7031, + 0.7615, 0.8055, 0.8437, 0.8935, + 0.9375, 0.9815, 1.0255, 1.0695, + 1.1250, 1.1575, 1.2015, 0.0000 + }; + + if ( mpeg_info->video[ v_idx ].seen ) /* we have it already */ + return ; + + offset = FindNextMarker( offset + 1, MPEG_SEQUENCE_CODE ); + + if ( !offset ) + return ; + + offset += 4; + + mpeg_info->video[ v_idx ].hsize = GetSize( offset ) >> 4; + mpeg_info->video[ v_idx ].vsize = GetSize( offset + 1 ) & 0x0FFF; + + // Get picture rate + offset += 3; // after picture sizes + + aratio = ( GetByte( offset ) & 0x0F ) >> 4; + mpeg_info->video[ v_idx ].aratio = aspect_ratios[ aratio ]; + + // offset += 3; // after picture sizes + frate = GetByte( offset ) & 0x0F; + mpeg_info->video[ v_idx ].frate = frame_rates[ frate ]; + + offset += 1; // after picture rate + + // 18 following bytes are the bitrate /400 + + //read first 16 bytes + brate = GetSize( offset ); + // scale + brate <<= 2; + byte lasttwo = GetByte( offset + 2 ); + lasttwo >>= 6; + brate |= lasttwo; + + mpeg_info->video[ v_idx ].bitrate = 400 * brate; + + byte mark; + while ( true ) { + offset = FindNextMarker( offset, &mark ); + if ( mark == MPEG_GOP_CODE ) + break; + switch ( GetByte( offset + 3 ) ) { + case MPEG_EXT_CODE : + // Extension + offset += 4; + switch ( GetByte( offset ) >> 4 ) { + case 1: + //SequenceExt + if ( GetByte( offset + 1 ) & 0x08 ) + mpeg_info->video[ v_idx ].progressive = true; + mpeg_info->video[ v_idx ].chroma_format = ( GetByte( offset + 1 ) & 0x06 ) >> 1; + break; + case 2: + // SequenceDisplayExt + mpeg_info->video[ v_idx ].video_format = ( GetByte( offset ) & 0x0E ) >> 1; + break; + } + + break; + case MPEG_USER_CODE : + // UserData + break; + } + offset++; + } + + mpeg_info->video[ v_idx ].seen = true; +} + +double K3bMpegInfo::ReadTS( llong offset ) +{ + byte highbit; + unsigned long low4Bytes; + double TS; + + highbit = ( GetByte( offset ) >> 3 ) & 0x01; + + low4Bytes = ( ( GetByte( offset ) >> 1 ) & 0x03 ) << 30; + low4Bytes |= GetByte( offset + 1 ) << 22; + low4Bytes |= ( GetByte( offset + 2 ) >> 1 ) << 15; + low4Bytes |= GetByte( offset + 3 ) << 7; + low4Bytes |= GetByte( offset + 4 ) >> 1; + + + TS = ( double ) ( highbit * FLOAT_0x10000 * FLOAT_0x10000 ); + TS += ( double ) ( low4Bytes ); + TS /= ( double ) ( STD_SYSTEM_CLOCK_FREQ ); + + return TS; +} + +double K3bMpegInfo::ReadTSMpeg2( llong offset ) +{ + byte highbit; + unsigned long low4Bytes; + unsigned long sys_clock_ref; + double TS; + + highbit = ( GetByte( offset ) & 0x20 ) >> 5; + + low4Bytes = ( ( GetByte( offset ) & 0x18 ) >> 3 ) << 30; + low4Bytes |= ( GetByte( offset ) & 0x03 ) << 28; + low4Bytes |= GetByte( offset + 1 ) << 20; + low4Bytes |= ( GetByte( offset + 2 ) & 0xF8 ) << 12; + low4Bytes |= ( GetByte( offset + 2 ) & 0x03 ) << 13; + low4Bytes |= GetByte( offset + 3 ) << 5; + low4Bytes |= ( GetByte( offset + 4 ) ) >> 3; + + sys_clock_ref = ( GetByte( offset + 4 ) & 0x3 ) << 7; + sys_clock_ref |= ( GetByte( offset + 5 ) >> 1 ); + + TS = ( double ) ( highbit * FLOAT_0x10000 * FLOAT_0x10000 ); + TS += ( double ) ( low4Bytes ); + if ( sys_clock_ref == 0 ) + TS /= ( double ) ( STD_SYSTEM_CLOCK_FREQ ); + else { + TS /= ( double ) ( 27000000 / sys_clock_ref ); + } + + return TS; +} diff --git a/libk3b/projects/videocd/mpeginfo/k3bmpeginfo.h b/libk3b/projects/videocd/mpeginfo/k3bmpeginfo.h new file mode 100644 index 0000000..3436214 --- /dev/null +++ b/libk3b/projects/videocd/mpeginfo/k3bmpeginfo.h @@ -0,0 +1,178 @@ +/* +* +* $Id: k3bmpeginfo.h 619556 2007-01-03 17:38:12Z trueg $ +* Copyright (C) 2003-2004 Christian Kvasny <chris@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 K3BMPEGINFO +#define K3BMPEGINFO + +#include <stdio.h> + +// #define BUFFERSIZE 16384 +#define BUFFERSIZE 65536 + +#define MPEG_START_CODE_PATTERN ((ulong) 0x00000100) +#define MPEG_START_CODE_MASK ((ulong) 0xffffff00) + +#define MPEG_PICTURE_CODE ((ulong) 0x00000100) +/* [...slice codes... 0x1a7] */ + +#define MPEG_USER_CODE ((uchar) 0xb2) +#define MPEG_SEQUENCE_CODE ((uchar) 0xb3) +#define MPEG_EXT_CODE ((uchar) 0xb5) +#define MPEG_SEQ_END_CODE ((uchar) 0xb7) +#define MPEG_GOP_CODE ((uchar) 0xb8) +#define MPEG_PROGRAM_END_CODE ((uchar) 0xb9) +#define MPEG_PACK_HEADER_CODE ((uchar) 0xba) +#define MPEG_SYSTEM_HEADER_CODE ((uchar) 0xbb) +#define MPEG_PRIVATE_1_CODE ((uchar) 0xbd) +#define MPEG_PAD_CODE ((uchar) 0xbe) + +#define MPEG_AUDIO_C0_CODE ((uchar) 0xc0) /* default */ +#define MPEG_AUDIO_C1_CODE ((uchar) 0xc1) /* 2nd audio stream id (dual channel) */ +#define MPEG_AUDIO_C2_CODE ((uchar) 0xc2) /* 3rd audio stream id (surround sound) */ + +#define MPEG_VIDEO_E0_CODE ((uchar) 0xe0) /* motion */ +#define MPEG_VIDEO_E1_CODE ((uchar) 0xe1) /* lowres still */ +#define MPEG_VIDEO_E2_CODE ((uchar) 0xe2) /* hires still */ + +#define FLOAT_0x10000 (double)((unsigned long)1 << 16) +#define STD_SYSTEM_CLOCK_FREQ (unsigned long)90000 + +typedef unsigned char byte; +typedef long long llong; + +#include <kdebug.h> + +class video_info +{ + public: + bool seen; + unsigned long hsize; + unsigned long vsize; + double aratio; + double frate; + unsigned long bitrate; + unsigned long vbvsize; + bool progressive; + unsigned char video_format; + unsigned char chroma_format; + bool constrained_flag; +}; + +class audio_info +{ + public: + bool seen; + unsigned int version; + unsigned int layer; + unsigned int protect; + unsigned long bitrate; + float byterate; + unsigned long sampfreq; + int mode; + bool copyright; + bool original; +}; + +class Mpeginfo +{ + + public: + Mpeginfo() + : version( 0 ), + muxrate( 0 ), + playing_time( 0 ), + has_video ( false ), + has_audio ( false ) + { + for ( int i = 0; i < 3; i++ ) { + video[ i ].seen = false; + audio[ i ].seen = false; + } + }; + + ~Mpeginfo() + {} + ; + + unsigned int version; + unsigned long muxrate; + double playing_time; + bool has_video; + bool has_audio; + video_info video[ 3 ]; + audio_info audio[ 3 ]; +}; + +class K3bMpegInfo +{ + public: + K3bMpegInfo( const char* filename ); + ~K3bMpegInfo(); + enum mpeg_version { MPEG_VERS_INVALID = 0, MPEG_VERS_MPEG1 = 1, MPEG_VERS_MPEG2 = 2 }; + enum mode { MPEG_STEREO = 1, MPEG_JOINT_STEREO, MPEG_DUAL_CHANNEL, MPEG_SINGLE_CHANNEL }; + + const int version() + { + return mpeg_info->version; + }; + const QString error_string() + { + return m_error_string; + }; + Mpeginfo* mpeg_info; + + + private: + // General ToolBox + byte GetByte( llong offset ); + byte bdGetByte( llong offset ); + llong GetNBytes( llong, int ); + unsigned short int GetSize( llong offset ); + llong FindNextMarker( llong ); + llong FindNextMarker( llong, byte* ); + llong FindNextMarker( llong, byte ); + llong bdFindNextMarker( llong, byte ); + llong bdFindNextMarker( llong, byte* ); + llong FindNextVideo( llong ); + llong FindNextAudio( llong ); + + int GetVideoIdx ( byte ); + int GetAudioIdx ( byte ); + bool EnsureMPEG( llong, byte ); + void ParseVideo ( llong, byte ); + void ParseAudio ( llong, byte ); + bool MpegParsePacket (); + llong MpegParsePacket ( llong ); + llong SkipPacketHeader( llong ); + + double ReadTS( llong offset ); + double ReadTSMpeg2( llong offset ); + + FILE* m_mpegfile; + + const char* m_filename; + llong m_filesize; + + bool m_done; + + llong m_buffstart; + llong m_buffend; + byte* m_buffer; + double m_initial_TS; + QString m_error_string; + +}; + +#endif //K3bMpegInfo diff --git a/libk3b/projects/videodvd/Makefile.am b/libk3b/projects/videodvd/Makefile.am new file mode 100644 index 0000000..1b3e92b --- /dev/null +++ b/libk3b/projects/videodvd/Makefile.am @@ -0,0 +1,20 @@ +# we need the ../datacd for the uic generated header files +AM_CPPFLAGS= -I$(srcdir)/../../core \ + -I$(srcdir)/../../../libk3bdevice \ + -I$(srcdir)/../../../src \ + -I$(srcdir)/../../tools \ + -I$(srcdir)/../datadvd \ + -I$(srcdir)/../datacd \ + -I$(srcdir)/.. \ + -I../datacd \ + $(all_includes) + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libvideodvd.la + +libvideodvd_la_SOURCES = k3bvideodvddoc.cpp \ + k3bvideodvdjob.cpp \ + k3bvideodvdimager.cpp + +include_HEADERS = k3bvideodvddoc.h k3bvideodvdjob.h diff --git a/libk3b/projects/videodvd/k3bvideodvddoc.cpp b/libk3b/projects/videodvd/k3bvideodvddoc.cpp new file mode 100644 index 0000000..2f02ac6 --- /dev/null +++ b/libk3b/projects/videodvd/k3bvideodvddoc.cpp @@ -0,0 +1,71 @@ +/* + * + * $Id: k3bvideodvddoc.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 "k3bvideodvddoc.h" +#include "k3bvideodvdjob.h" + +#include <k3bdiritem.h> + +#include <k3bisooptions.h> + +#include <kconfig.h> + + +K3bVideoDvdDoc::K3bVideoDvdDoc( QObject* parent ) + : K3bDvdDoc( parent ) +{ +} + + +K3bVideoDvdDoc::~K3bVideoDvdDoc() +{ +} + + +bool K3bVideoDvdDoc::newDocument() +{ + if( K3bDataDoc::newDocument() ) { + + // K3bDataDoc::newDocument already deleted m_videoTsDir (again: bad design!) + m_videoTsDir = new K3bDirItem( "VIDEO_TS", this, root() ); + m_videoTsDir->setRemoveable(false); + m_videoTsDir->setRenameable(false); + m_videoTsDir->setMoveable(false); + m_videoTsDir->setHideable(false); + + K3bDirItem* audioTsDir = new K3bDirItem( "AUDIO_TS", this, root() ); + audioTsDir->setRemoveable(false); + audioTsDir->setRenameable(false); + audioTsDir->setMoveable(false); + audioTsDir->setHideable(false); + + setMultiSessionMode( NONE ); + + setModified( false ); + + return true; + } + else + return false; +} + + +K3bBurnJob* K3bVideoDvdDoc::newBurnJob( K3bJobHandler* hdl, QObject* parent ) +{ + return new K3bVideoDvdJob( this, hdl, parent ); +} + +//#include "k3bdvddoc.moc" diff --git a/libk3b/projects/videodvd/k3bvideodvddoc.h b/libk3b/projects/videodvd/k3bvideodvddoc.h new file mode 100644 index 0000000..034ae4d --- /dev/null +++ b/libk3b/projects/videodvd/k3bvideodvddoc.h @@ -0,0 +1,46 @@ +/* + * + * $Id: k3bvideodvddoc.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_VIDEODVD_DOC_H_ +#define _K3B_VIDEODVD_DOC_H_ + +#include <k3bdvddoc.h> +#include "k3b_export.h" +class KConfig; + +class LIBK3B_EXPORT K3bVideoDvdDoc : public K3bDvdDoc +{ + public: + K3bVideoDvdDoc( QObject* parent = 0 ); + virtual ~K3bVideoDvdDoc(); + + virtual int type() const { return VIDEODVD; } + + virtual K3bBurnJob* newBurnJob( K3bJobHandler* hdl, QObject* parent ); + + virtual bool newDocument(); + + K3bDirItem* videoTsDir() const { return m_videoTsDir; } + + // TODO: implement load- and saveDocumentData since we do not need all those options + + protected: + virtual QString typeString() const { return "video_dvd"; } + + private: + K3bDirItem* m_videoTsDir; +}; + +#endif diff --git a/libk3b/projects/videodvd/k3bvideodvdimager.cpp b/libk3b/projects/videodvd/k3bvideodvdimager.cpp new file mode 100644 index 0000000..7362aa0 --- /dev/null +++ b/libk3b/projects/videodvd/k3bvideodvdimager.cpp @@ -0,0 +1,221 @@ +/* + * + * $Id: k3bvideodvdimager.cpp 633751 2007-02-15 08:22:49Z trueg $ + * Copyright (C) 2004-2007 Sebastian Trueg <trueg@k3b.org> + * + * This file is part of the K3b project. + * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * See the file "COPYING" for the exact licensing terms. + */ + +#include "k3bvideodvdimager.h" +#include "k3bvideodvddoc.h" +#include <k3bdiritem.h> +#include <k3bfileitem.h> +#include <k3bprocess.h> +#include <k3bglobals.h> + +#include <ktempfile.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kdebug.h> +#include <klocale.h> + +#include <qtextstream.h> +#include <qdir.h> +#include <qfile.h> +#include <qptrlist.h> + + + +class K3bVideoDvdImager::Private +{ +public: + K3bVideoDvdDoc* doc; + + QString tempPath; +}; + + +K3bVideoDvdImager::K3bVideoDvdImager( K3bVideoDvdDoc* doc, K3bJobHandler* jh, QObject* parent, const char* name ) + : K3bIsoImager( doc, jh, parent, name ) +{ + d = new Private; + d->doc = doc; +} + + +K3bVideoDvdImager::~K3bVideoDvdImager() +{ + delete d; +} + + +void K3bVideoDvdImager::start() +{ + fixVideoDVDSettings(); + K3bIsoImager::start(); +} + + +void K3bVideoDvdImager::init() +{ + fixVideoDVDSettings(); + K3bIsoImager::init(); +} + + +void K3bVideoDvdImager::fixVideoDVDSettings() +{ + // Video DVD defaults, we cannot set these in K3bVideoDvdDoc since they + // will be overwritten in the burn dialog unless we create some K3bVideoDVDIsoOptions + // class with different defaults. But since the whole Video DVD project is a hack we + // go the easy road. + K3bIsoOptions o = d->doc->isoOptions(); + o.setISOLevel(1); + o.setISOallow31charFilenames(false); + o.setCreateJoliet(false); + o.setJolietLong(false); + o.setCreateRockRidge(false); + o.setCreateUdf(true); + d->doc->setIsoOptions( o ); +} + + +void K3bVideoDvdImager::calculateSize() +{ + fixVideoDVDSettings(); + K3bIsoImager::calculateSize(); +} + + +int K3bVideoDvdImager::writePathSpec() +{ + // + // Create a temp dir and link all contents of the VIDEO_TS dir to make mkisofs + // able to handle the VideoDVD stuff. + // + // mkisofs is not able to create VideoDVDs from graft-points. + // + // We do this here since K3bIsoImager::start calls cleanup which deletes the temp files + // + QDir dir( KGlobal::dirs()->resourceDirs( "tmp" ).first() ); + d->tempPath = K3b::findUniqueFilePrefix( "k3bVideoDvd", dir.path() ); + kdDebug() << "(K3bVideoDvdImager) creating temp dir: " << d->tempPath << endl; + if( !dir.mkdir( d->tempPath, true ) ) { + emit infoMessage( i18n("Unable to create temporary directory '%1'.").arg(d->tempPath), ERROR ); + return -1; + } + + dir.cd( d->tempPath ); + if( !dir.mkdir( "VIDEO_TS" ) ) { + emit infoMessage( i18n("Unable to create temporary directory '%1'.").arg(d->tempPath + "/VIDEO_TS"), ERROR ); + return -1; + } + + for( QPtrListIterator<K3bDataItem> it( d->doc->videoTsDir()->children() ); *it; ++it ) { + if( (*it)->isDir() ) { + emit infoMessage( i18n("Found invalid entry in the VIDEO_TS folder (%1).").arg((*it)->k3bName()), ERROR ); + return -1; + } + + // convert to upper case names + if( ::symlink( QFile::encodeName( (*it)->localPath() ), + QFile::encodeName( d->tempPath + "/VIDEO_TS/" + (*it)->k3bName().upper() ) ) == -1 ) { + emit infoMessage( i18n("Unable to link temporary file in folder %1.").arg( d->tempPath ), ERROR ); + return -1; + } + } + + + return K3bIsoImager::writePathSpec(); +} + + +int K3bVideoDvdImager::writePathSpecForDir( K3bDirItem* dirItem, QTextStream& stream ) +{ + // + // We handle the VIDEO_TS dir differently since otherwise mkisofs is not able to + // open the VideoDVD structures (see addMkisofsParameters) + // + if( dirItem == d->doc->videoTsDir() ) { + return 0; + } + + int num = 0; + for( QPtrListIterator<K3bDataItem> it( dirItem->children() ); it.current(); ++it ) { + K3bDataItem* item = it.current(); + num++; + + if( item->isDir() ) { + // we cannot add the video_ts dir twice + if( item != d->doc->videoTsDir() ) { + stream << escapeGraftPoint( item->writtenPath() ) + << "=" + << escapeGraftPoint( dummyDir( static_cast<K3bDirItem*>(item) ) ) << "\n"; + } + + int x = writePathSpecForDir( dynamic_cast<K3bDirItem*>(item), stream ); + if( x >= 0 ) + num += x; + else + return -1; + } + else { + writePathSpecForFile( static_cast<K3bFileItem*>(item), stream ); + } + } + + return num; +} + + +bool K3bVideoDvdImager::addMkisofsParameters( bool printSize ) +{ + // Here is another bad design: we assume that K3bIsoImager::start does not add additional + // parameters to the process. :( + if( K3bIsoImager::addMkisofsParameters( printSize ) ) { + *m_process << "-dvd-video"; + *m_process << "-f"; // follow symlinks + *m_process << d->tempPath; + return true; + } + else + return false; +} + + +void K3bVideoDvdImager::cleanup() +{ + if( QFile::exists( d->tempPath ) ) { + QDir dir( d->tempPath ); + dir.cd( "VIDEO_TS" ); + for( QPtrListIterator<K3bDataItem> it( d->doc->videoTsDir()->children() ); *it; ++it ) + dir.remove( (*it)->k3bName().upper() ); + dir.cdUp(); + dir.rmdir( "VIDEO_TS" ); + dir.cdUp(); + dir.rmdir( d->tempPath ); + } + d->tempPath = QString::null; + + K3bIsoImager::cleanup(); +} + + +void K3bVideoDvdImager::slotReceivedStderr( const QString& line ) +{ + if( line.contains( "Unable to make a DVD-Video image" ) ) { + emit infoMessage( i18n("The project does not contain all necessary VideoDVD files."), WARNING ); + emit infoMessage( i18n("The resulting DVD will most likely not be playable on a Hifi DVD player."), WARNING ); + } + else + K3bIsoImager::slotReceivedStderr( line ); +} + +#include "k3bvideodvdimager.moc" diff --git a/libk3b/projects/videodvd/k3bvideodvdimager.h b/libk3b/projects/videodvd/k3bvideodvdimager.h new file mode 100644 index 0000000..0062d55 --- /dev/null +++ b/libk3b/projects/videodvd/k3bvideodvdimager.h @@ -0,0 +1,61 @@ +/* + * + * $Id: k3bvideodvdimager.h 633751 2007-02-15 08:22:49Z 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_VIDEODVD_IMAGER_H_ +#define _K3B_VIDEODVD_IMAGER_H_ + + +#include <k3bisoimager.h> + +class K3bVideoDvdDoc; + + +/** + * Create VideoDVD images with mkisofs. The difference + * to the IsoImager is the -dvd-video option and the fact + * that all VIDEO_TS files need to be in one local folder since + * otherwise mkisofs is not able to find the dvd structures. + */ +class K3bVideoDvdImager : public K3bIsoImager +{ + Q_OBJECT + + public: + K3bVideoDvdImager( K3bVideoDvdDoc* doc, K3bJobHandler*, QObject* parent = 0, const char* name = 0 ); + virtual ~K3bVideoDvdImager(); + + public slots: + virtual void start(); + virtual void init(); + virtual void calculateSize(); + + protected: + bool addMkisofsParameters( bool printSize = false ); + int writePathSpec(); + void cleanup(); + int writePathSpecForDir( K3bDirItem* dirItem, QTextStream& stream ); + + protected slots: + virtual void slotReceivedStderr( const QString& ); + + private: + void fixVideoDVDSettings(); + + class Private; + Private* d; +}; + +#endif diff --git a/libk3b/projects/videodvd/k3bvideodvdjob.cpp b/libk3b/projects/videodvd/k3bvideodvdjob.cpp new file mode 100644 index 0000000..f4e6129 --- /dev/null +++ b/libk3b/projects/videodvd/k3bvideodvdjob.cpp @@ -0,0 +1,101 @@ +/* + * + * $Id: k3bvideodvdjob.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 "k3bvideodvdjob.h" +#include "k3bvideodvddoc.h" +#include "k3bvideodvdimager.h" + +#include <k3bcore.h> +#include <k3bisoimager.h> +#include <k3bgrowisofswriter.h> +#include <k3bglobals.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kapplication.h> +#include <kconfig.h> + + + +K3bVideoDvdJob::K3bVideoDvdJob( K3bVideoDvdDoc* doc, K3bJobHandler* jh, QObject* parent ) + : K3bDvdJob( doc, jh, parent ), + m_doc(doc) +{ +} + + +K3bVideoDvdJob::~K3bVideoDvdJob() +{ +} + + +void K3bVideoDvdJob::prepareImager() +{ + setImager( new K3bVideoDvdImager( m_doc, this ) ); +} + + +bool K3bVideoDvdJob::prepareWriterJob() +{ + K3bGrowisofsWriter* writer = new K3bGrowisofsWriter( m_doc->burner(), this, this ); + + // these do only make sense with DVD-R(W) + writer->setSimulate( m_doc->dummy() ); + writer->setBurnSpeed( m_doc->speed() ); + + // DAO seems to be the better default for Video DVD... !? + if( m_doc->writingMode() == K3b::DAO || m_doc->writingMode() == K3b::WRITING_MODE_AUTO ) + writer->setWritingMode( K3b::DAO ); + + writer->setMultiSession( false ); + writer->setCloseDvd( true ); + + if( m_doc->onTheFly() ) { + writer->setImageToWrite( QString::null ); // read from stdin + writer->setTrackSize( m_isoImager->size() ); + } + else + writer->setImageToWrite( m_doc->tempDir() ); + + setWriterJob( writer ); + + return true; +} + + +QString K3bVideoDvdJob::jobDescription() const +{ + if( m_doc->onlyCreateImages() ) { + return i18n("Creating Video DVD Image File"); + } + else { + return i18n("Writing Video DVD") + + ( m_doc->isoOptions().volumeID().isEmpty() + ? QString::null + : QString( " (%1)" ).arg(m_doc->isoOptions().volumeID()) ); + } +} + + +QString K3bVideoDvdJob::jobDetails() const +{ + return ( i18n("ISO9660/Udf Filesystem (Size: %1)").arg(KIO::convertSize( doc()->size() )) + + ( m_doc->copies() > 1 + ? i18n(" - %n copy", " - %n copies", m_doc->copies()) + : QString::null ) ); +} + +#include "k3bvideodvdjob.moc" diff --git a/libk3b/projects/videodvd/k3bvideodvdjob.h b/libk3b/projects/videodvd/k3bvideodvdjob.h new file mode 100644 index 0000000..3f03cea --- /dev/null +++ b/libk3b/projects/videodvd/k3bvideodvdjob.h @@ -0,0 +1,46 @@ +/* + * + * $Id: k3bvideodvdjob.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_VIDEO_DVD_JOB_H_ +#define _K3B_VIDEO_DVD_JOB_H_ + +#include <k3bdvdjob.h> + + +class K3bVideoDvdDoc; + +/** + * This class heavily depends on K3bDvdJob and uses some of it's internals. + */ +class K3bVideoDvdJob : public K3bDvdJob +{ + Q_OBJECT + + public: + K3bVideoDvdJob( K3bVideoDvdDoc*, K3bJobHandler*, QObject* parent = 0 ); + virtual ~K3bVideoDvdJob(); + + virtual QString jobDescription() const; + virtual QString jobDetails() const; + + private: + bool prepareWriterJob(); + void prepareImager(); + + K3bVideoDvdDoc* m_doc; +}; + +#endif |