File indexing completed on 2024-05-12 04:51:00

0001 /*
0002     SPDX-FileCopyrightText: 2003-2008 Sebastian Trueg <trueg@k3b.org>
0003     SPDX-FileCopyrightText: 2009 Gustavo Pichorim Boiko <gustavo.boiko@kdemail.net>
0004     SPDX-FileCopyrightText: 2010 Michal Malek <michalm@jabster.pl>
0005     SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "k3bglobals.h"
0011 #include "k3baudiodoc.h"
0012 #include "k3baudiotrack.h"
0013 #include "k3baudiojob.h"
0014 #include "k3baudiofile.h"
0015 #include "k3baudiozerodata.h"
0016 #include "k3baudiocdtracksource.h"
0017 #include "k3brawaudiodatasource.h"
0018 #include "k3bcuefileparser.h"
0019 #include "k3bcdtextvalidator.h"
0020 #include "k3bcore.h"
0021 #include "k3baudiodecoder.h"
0022 #include "k3b_i18n.h"
0023 
0024 #include <KConfig>
0025 #include <KIO/Global>
0026 
0027 #include <QDataStream>
0028 #include <QDebug>
0029 #include <QDir>
0030 #include <QFile>
0031 #include <QFileInfo>
0032 #include <QStringList>
0033 #include <QTextStream>
0034 #include <QDomElement>
0035 
0036 
0037 class K3b::AudioDoc::Private
0038 {
0039 public:
0040     Private()
0041     :
0042         firstTrack( 0 ),
0043         lastTrack( 0 ),
0044         cdTextValidator( new K3b::CdTextValidator() )
0045     {
0046     }
0047 
0048     ~Private() {
0049         delete cdTextValidator;
0050     }
0051 
0052     AudioTrack* firstTrack;
0053     AudioTrack* lastTrack;
0054 
0055     bool hideFirstTrack;
0056     bool normalize;
0057 
0058     // CD-Text
0059     // --------------------------------------------------
0060     Device::CdText cdTextData;
0061     bool cdText;
0062     // --------------------------------------------------
0063 
0064     // Audio ripping
0065     int audioRippingParanoiaMode;
0066     int audioRippingRetries;
0067     bool audioRippingIgnoreReadErrors;
0068 
0069     //
0070     // decoder housekeeping
0071     // --------------------------------------------------
0072     // used to check if we may delete a decoder
0073     QMap<AudioDecoder*, int> decoderUsageCounterMap;
0074     // used to check if we already have a decoder for a specific file
0075     QMap<QString, AudioDecoder*> decoderPresenceMap;
0076 
0077     K3b::CdTextValidator* cdTextValidator;
0078 };
0079 
0080 
0081 K3b::AudioDoc::AudioDoc( QObject* parent )
0082     : K3b::Doc( parent )
0083 {
0084     d = new Private;
0085 }
0086 
0087 K3b::AudioDoc::~AudioDoc()
0088 {
0089     // delete all tracks
0090     int i = 1;
0091     int cnt = numOfTracks();
0092     while( d->firstTrack ) {
0093         qDebug() << "(K3b::AudioDoc::AudioDoc) deleting track " << i << " of " << cnt;
0094         delete d->firstTrack->take();
0095         qDebug() << "(K3b::AudioDoc::AudioDoc) deleted.";
0096         ++i;
0097     }
0098 
0099     delete d;
0100 }
0101 
0102 bool K3b::AudioDoc::newDocument()
0103 {
0104     clear();
0105     d->normalize = false;
0106     d->hideFirstTrack = false;
0107     d->cdText = false;
0108     d->cdTextData.clear();
0109     d->audioRippingParanoiaMode = 0;
0110     d->audioRippingRetries = 5;
0111     d->audioRippingIgnoreReadErrors = true;
0112 
0113     return K3b::Doc::newDocument();
0114 }
0115 
0116 
0117 void K3b::AudioDoc::clear()
0118 {
0119     // delete all tracks
0120     while( d->firstTrack )
0121         delete d->firstTrack->take();
0122 }
0123 
0124 
0125 QString K3b::AudioDoc::name() const
0126 {
0127     if( !d->cdTextData.title().isEmpty() )
0128         return d->cdTextData.title();
0129     else
0130         return K3b::Doc::name();
0131 }
0132 
0133 
0134 K3b::AudioTrack* K3b::AudioDoc::firstTrack() const
0135 {
0136     return d->firstTrack;
0137 }
0138 
0139 
0140 K3b::AudioTrack* K3b::AudioDoc::lastTrack() const
0141 {
0142     return d->lastTrack;
0143 }
0144 
0145 
0146 // this one is called by K3b::AudioTrack to update the list
0147 void K3b::AudioDoc::setFirstTrack( K3b::AudioTrack* track )
0148 {
0149     d->firstTrack = track;
0150 }
0151 
0152 // this one is called by K3b::AudioTrack to update the list
0153 void K3b::AudioDoc::setLastTrack( K3b::AudioTrack* track )
0154 {
0155     d->lastTrack = track;
0156 }
0157 
0158 
0159 KIO::filesize_t K3b::AudioDoc::size() const
0160 {
0161     // This is not really correct but what the user expects ;)
0162     return length().mode1Bytes();
0163 }
0164 
0165 
0166 K3b::Msf K3b::AudioDoc::length() const
0167 {
0168     K3b::Msf length = 0;
0169     K3b::AudioTrack* track = d->firstTrack;
0170     while( track ) {
0171         length += track->length();
0172         track = track->next();
0173     }
0174 
0175     return length;
0176 }
0177 
0178 
0179 bool K3b::AudioDoc::cdText() const
0180 {
0181     return d->cdText;
0182 }
0183 
0184 
0185 QString K3b::AudioDoc::title() const
0186 {
0187     return d->cdTextData.title();
0188 }
0189 
0190 
0191 QString K3b::AudioDoc::artist() const
0192 {
0193     return d->cdTextData.performer();
0194 }
0195 
0196 
0197 QString K3b::AudioDoc::disc_id() const
0198 {
0199     return d->cdTextData.discId();
0200 }
0201 
0202 
0203 QString K3b::AudioDoc::arranger() const
0204 {
0205     return d->cdTextData.arranger();
0206 }
0207 
0208 
0209 QString K3b::AudioDoc::songwriter() const
0210 {
0211     return d->cdTextData.songwriter();
0212 }
0213 
0214 
0215 QString K3b::AudioDoc::composer() const
0216 {
0217     return d->cdTextData.composer();
0218 }
0219 
0220 
0221 QString K3b::AudioDoc::upc_ean() const
0222 {
0223     return d->cdTextData.upcEan();
0224 }
0225 
0226 
0227 QString K3b::AudioDoc::cdTextMessage() const
0228 {
0229     return d->cdTextData.message();
0230 }
0231 
0232 
0233 void K3b::AudioDoc::addUrls( const QList<QUrl>& urls )
0234 {
0235     // make sure we add them at the end even if urls are in the queue
0236     addTracks( urls, 99 );
0237 }
0238 
0239 
0240 void K3b::AudioDoc::addTracks( const QList<QUrl>& urls, int position )
0241 {
0242     QList<QUrl> allUrls = extractUrlList( K3b::convertToLocalUrls(urls) );
0243     QList<QUrl>::iterator end( allUrls.end());
0244     for( QList<QUrl>::iterator it = allUrls.begin(); it != end; it++, position++ ) {
0245         QUrl& url = *it;
0246         if( url.toLocalFile().right(3).toLower() == "cue" ) {
0247             // try adding a cue file
0248             if( K3b::AudioTrack* newAfter = importCueFile( url.toLocalFile(), getTrack(position) ) ) {
0249                 position = newAfter->trackNumber();
0250                 continue;
0251             }
0252         }
0253 
0254         if( K3b::AudioTrack* track = createTrack( url ) ) {
0255             addTrack( track, position );
0256 
0257             K3b::AudioDecoder* dec = static_cast<K3b::AudioFile*>( track->firstSource() )->decoder();
0258             track->setTitle( dec->metaInfo( K3b::AudioDecoder::META_TITLE ) );
0259             track->setArtist( dec->metaInfo( K3b::AudioDecoder::META_ARTIST ) );
0260             track->setSongwriter( dec->metaInfo( K3b::AudioDecoder::META_SONGWRITER ) );
0261             track->setComposer( dec->metaInfo( K3b::AudioDecoder::META_COMPOSER ) );
0262             track->setCdTextMessage( dec->metaInfo( K3b::AudioDecoder::META_COMMENT ) );
0263         }
0264     }
0265 
0266     emit changed();
0267 }
0268 
0269 
0270 QList<QUrl> K3b::AudioDoc::extractUrlList( const QList<QUrl>& urls )
0271 {
0272     QList<QUrl> files;
0273 
0274     Q_FOREACH( const QUrl& url, urls ) {
0275 
0276         QFileInfo fi( url.toLocalFile() );
0277 
0278         if( fi.isDir() ) {
0279             // add all files in the dir
0280             QDir dir( fi.filePath() );
0281 
0282             const QStringList entries = dir.entryList( QDir::Files, QDir::Name | QDir::LocaleAware );
0283             Q_FOREACH( const QString& entry, entries )
0284             {
0285                 files.push_back( QUrl::fromLocalFile( dir.filePath( entry ) ) );
0286             }
0287         }
0288         else {
0289             QList<QUrl> playlistFiles;
0290             if( readPlaylistFile( url, playlistFiles ) ) {
0291                 files += playlistFiles;
0292             }
0293             else {
0294                 files.push_back( url );
0295             }
0296         }
0297     }
0298 
0299     return files;
0300 }
0301 
0302 
0303 bool K3b::AudioDoc::readPlaylistFile( const QUrl& url, QList<QUrl>& playlist )
0304 {
0305     const QDir playlistDirectory( url.adjusted(QUrl::RemoveFilename).path() );
0306 
0307     // check if the file is a m3u playlist
0308     // and if so add all listed files
0309 
0310     QFile f( url.toLocalFile() );
0311     if( !f.open( QIODevice::ReadOnly ) )
0312         return false;
0313 
0314     QByteArray buf = f.read( 7 );
0315     if( buf.size() != 7 || QString::fromLatin1( buf ) != "#EXTM3U" )
0316         return false;
0317     f.seek( 0 );
0318 
0319     QTextStream t( &f );
0320 
0321     // skip the first line
0322     t.readLine();
0323 
0324     // read the file
0325     while( !t.atEnd() ) {
0326         QString line = t.readLine();
0327         if( line[0] != '#' ) {
0328             QUrl mp3url;
0329             QFileInfo pathInfo(line);
0330             if (pathInfo.isRelative())
0331                 mp3url = QUrl::fromLocalFile( QDir::cleanPath( playlistDirectory.filePath( line ) ) );
0332             else
0333                 mp3url = QUrl::fromLocalFile( line );
0334 
0335             playlist.append( mp3url );
0336         }
0337     }
0338 
0339     return true;
0340 }
0341 
0342 
0343 void K3b::AudioDoc::addSources( K3b::AudioTrack* parent,
0344                               const QList<QUrl>& urls,
0345                               K3b::AudioDataSource* sourceAfter )
0346 {
0347     qDebug() << "(K3b::AudioDoc::addSources( " << parent << ", "
0348              << urls.first().toLocalFile() << ", "
0349              << sourceAfter << " )" << Qt::endl;
0350     QList<QUrl> allUrls = extractUrlList( urls );
0351     QList<QUrl>::const_iterator end(allUrls.constEnd());
0352     for( QList<QUrl>::const_iterator it = allUrls.constBegin(); it != end; ++it ) {
0353         if( K3b::AudioFile* file = createAudioFile( *it ) ) {
0354             if( sourceAfter )
0355                 file->moveAfter( sourceAfter );
0356             else
0357                 file->moveAhead( parent->firstSource() );
0358             sourceAfter = file;
0359         }
0360     }
0361 
0362     qDebug() << "(K3b::AudioDoc::addSources) finished.";
0363 }
0364 
0365 
0366 K3b::AudioTrack* K3b::AudioDoc::importCueFile( const QString& cuefile, K3b::AudioTrack* after, K3b::AudioDecoder* decoder )
0367 {
0368     if( !after )
0369         after = d->lastTrack;
0370 
0371     qDebug() << "(K3b::AudioDoc::importCueFile( " << cuefile << ", " << after << ")";
0372     K3b::CueFileParser parser( cuefile );
0373     if( parser.isValid() && parser.toc().contentType() == K3b::Device::AUDIO ) {
0374 
0375         qDebug() << "(K3b::AudioDoc::importCueFile) parsed with image: " << parser.imageFilename();
0376 
0377         // global cd-text
0378         if( !parser.cdText().title().isEmpty() )
0379             setTitle( parser.cdText().title() );
0380         if( !parser.cdText().performer().isEmpty() )
0381             setPerformer( parser.cdText().performer() );
0382 
0383         bool isBin = parser.imageFileType() == QLatin1String( "bin" );
0384 
0385         bool reused = true;
0386         if( !decoder && !isBin )
0387             if ( !( decoder = getDecoderForUrl( QUrl::fromLocalFile(parser.imageFilename()), &reused ) ) )
0388                 return 0;
0389 
0390         AudioDataSource* source = 0;
0391         int i = 0;
0392         foreach( const K3b::Device::Track& track, parser.toc() ) {
0393             if ( isBin ) {
0394                 source = new RawAudioDataSource( parser.imageFilename() );
0395             }
0396             else {
0397                 if( !reused )
0398                     decoder->analyseFile();
0399 
0400                 source = new K3b::AudioFile( decoder, this );
0401             }
0402 
0403             source->setStartOffset( track.firstSector() );
0404             source->setEndOffset( track.lastSector()+1 );
0405 
0406             K3b::AudioTrack* newTrack = new K3b::AudioTrack( this );
0407             newTrack->addSource( source );
0408             newTrack->moveAfter( after );
0409 
0410             // we do not know the length of the source yet so we have to force the index value
0411             if( track.index0() > 0 )
0412                 newTrack->setIndex0Offset( track.length() - track.index0() );
0413             else
0414                 newTrack->setIndex0Offset( 0 );
0415 
0416             // cd-text
0417             newTrack->setTitle( parser.cdText()[i].title() );
0418             newTrack->setPerformer( parser.cdText()[i].performer() );
0419 
0420             // add the next track after this one
0421             after = newTrack;
0422             ++i;
0423         }
0424 
0425         // let the last source use the data up to the end of the file
0426         if( source )
0427             source->setEndOffset(0);
0428 
0429         return after;
0430     }
0431 
0432     return 0;
0433 }
0434 
0435 
0436 K3b::AudioDecoder* K3b::AudioDoc::getDecoderForUrl( const QUrl& url, bool* reused )
0437 {
0438     K3b::AudioDecoder* decoder = 0;
0439 
0440     // check if we already have a proper decoder
0441     if( d->decoderPresenceMap.contains( url.toLocalFile() ) ) {
0442         decoder = d->decoderPresenceMap[url.toLocalFile()];
0443         *reused = true;
0444     }
0445     else if( (decoder = K3b::AudioDecoderFactory::createDecoder( url )) ) {
0446         qDebug() << "(K3b::AudioDoc) using " << decoder->metaObject()->className()
0447                  << " for decoding of " << url.toLocalFile() << Qt::endl;
0448 
0449         decoder->setFilename( url.toLocalFile() );
0450         *reused = false;
0451     }
0452 
0453     return decoder;
0454 }
0455 
0456 
0457 K3b::AudioFile* K3b::AudioDoc::createAudioFile( const QUrl& url )
0458 {
0459     if( !QFile::exists( url.toLocalFile() ) ) {
0460         qDebug() << "(K3b::AudioDoc) could not find file " << url.toLocalFile();
0461         return 0;
0462     }
0463 
0464     bool reused;
0465     K3b::AudioDecoder* decoder = getDecoderForUrl( url, &reused );
0466     if( decoder ) {
0467         if( !reused )
0468             decoder->analyseFile();
0469         return new K3b::AudioFile( decoder, this );
0470     }
0471     else {
0472         qDebug() << "(K3b::AudioDoc) unknown file type in file " << url.toLocalFile();
0473         return 0;
0474     }
0475 }
0476 
0477 
0478 K3b::AudioTrack* K3b::AudioDoc::createTrack( const QUrl& url )
0479 {
0480     qDebug() << "(K3b::AudioDoc::createTrack( " << url.toLocalFile() << " )";
0481     if( K3b::AudioFile* file = createAudioFile( url ) ) {
0482         K3b::AudioTrack* newTrack = new K3b::AudioTrack( this );
0483         newTrack->setFirstSource( file );
0484         return newTrack;
0485     }
0486     else
0487         return 0;
0488 }
0489 
0490 
0491 void K3b::AudioDoc::addTrack( const QUrl& url, int position )
0492 {
0493     addTracks( QList<QUrl>() << url, position );
0494 }
0495 
0496 
0497 
0498 K3b::AudioTrack* K3b::AudioDoc::getTrack( int trackNum )
0499 {
0500     K3b::AudioTrack* track = d->firstTrack;
0501     int i = 1;
0502     while( track ) {
0503         if( i == trackNum )
0504             return track;
0505         track = track->next();
0506         ++i;
0507     }
0508 
0509     return 0;
0510 }
0511 
0512 
0513 void K3b::AudioDoc::addTrack( K3b::AudioTrack* track, int position )
0514 {
0515     qDebug() << "(" << track << "," << position << ")";
0516 
0517     track->setParent( this );
0518     if( !d->firstTrack ) {
0519         emit trackAboutToBeAdded( 0 );
0520         d->firstTrack = d->lastTrack = track;
0521         emit trackAdded( 0 );
0522     } else if( position == 0 ) {
0523         track->moveAhead( d->firstTrack );
0524     } else {
0525         K3b::AudioTrack* after = getTrack( position );
0526         if( after )
0527             track->moveAfter( after );
0528         else
0529             track->moveAfter( d->lastTrack );  // just to be sure it's anywhere...
0530     }
0531 
0532     emit changed();
0533 }
0534 
0535 
0536 void K3b::AudioDoc::removeTrack( K3b::AudioTrack* track )
0537 {
0538     delete track;
0539 }
0540 
0541 
0542 void K3b::AudioDoc::moveTrack( K3b::AudioTrack* track, K3b::AudioTrack* after )
0543 {
0544     track->moveAfter( after );
0545 }
0546 
0547 
0548 void K3b::AudioDoc::setHideFirstTrack( bool b )
0549 {
0550     d->hideFirstTrack = b;
0551 }
0552 
0553 
0554 void K3b::AudioDoc::setNormalize( bool b )
0555 {
0556     d->normalize = b;
0557 }
0558 
0559 
0560 void K3b::AudioDoc::writeCdText( bool b )
0561 {
0562     d->cdText = b;
0563 }
0564 
0565 
0566 bool K3b::AudioDoc::loadDocumentData( QDomElement* root )
0567 {
0568     newDocument();
0569 
0570     // we will parse the dom-tree and create a K3b::AudioTrack for all entries immediately
0571     // this should not take long and so not block the gui
0572 
0573     QDomNodeList nodes = root->childNodes();
0574 
0575     for( int i = 0; i < nodes.count(); i++ ) {
0576 
0577         QDomElement e = nodes.item(i).toElement();
0578 
0579         if( e.isNull() )
0580             return false;
0581 
0582         if( e.nodeName() == "general" ) {
0583             if( !readGeneralDocumentData( e ) )
0584                 return false;
0585         }
0586 
0587         else if( e.nodeName() == "normalize" )
0588             setNormalize( e.text() == "yes" );
0589 
0590         else if( e.nodeName() == "hide_first_track" )
0591             setHideFirstTrack( e.text() == "yes" );
0592 
0593         else if( e.nodeName() == "audio_ripping" ) {
0594             QDomNodeList ripNodes = e.childNodes();
0595             for( int j = 0; j < ripNodes.length(); j++ ) {
0596                 if( ripNodes.item(j).nodeName() == "paranoia_mode" )
0597                     setAudioRippingParanoiaMode( ripNodes.item(j).toElement().text().toInt() );
0598                 else if( ripNodes.item(j).nodeName() == "read_retries" )
0599                     setAudioRippingRetries( ripNodes.item(j).toElement().text().toInt() );
0600                 else if( ripNodes.item(j).nodeName() == "ignore_read_errors" )
0601                     setAudioRippingIgnoreReadErrors( ripNodes.item(j).toElement().text() == "yes" );
0602             }
0603         }
0604 
0605         // parse cd-text
0606         else if( e.nodeName() == "cd-text" ) {
0607             if( !e.hasAttribute( "activated" ) )
0608                 return false;
0609 
0610             writeCdText( e.attributeNode( "activated" ).value() == "yes" );
0611 
0612             QDomNodeList cdTextNodes = e.childNodes();
0613             for( int j = 0; j < cdTextNodes.length(); j++ ) {
0614                 if( cdTextNodes.item(j).nodeName() == "title" )
0615                     setTitle( cdTextNodes.item(j).toElement().text() );
0616 
0617                 else if( cdTextNodes.item(j).nodeName() == "artist" )
0618                     setArtist( cdTextNodes.item(j).toElement().text() );
0619 
0620                 else if( cdTextNodes.item(j).nodeName() == "arranger" )
0621                     setArranger( cdTextNodes.item(j).toElement().text() );
0622 
0623                 else if( cdTextNodes.item(j).nodeName() == "songwriter" )
0624                     setSongwriter( cdTextNodes.item(j).toElement().text() );
0625 
0626                 else if( cdTextNodes.item(j).nodeName() == "composer" )
0627                     setComposer( cdTextNodes.item(j).toElement().text() );
0628 
0629                 else if( cdTextNodes.item(j).nodeName() == "disc_id" )
0630                     setDisc_id( cdTextNodes.item(j).toElement().text() );
0631 
0632                 else if( cdTextNodes.item(j).nodeName() == "upc_ean" )
0633                     setUpc_ean( cdTextNodes.item(j).toElement().text() );
0634 
0635                 else if( cdTextNodes.item(j).nodeName() == "message" )
0636                     setCdTextMessage( cdTextNodes.item(j).toElement().text() );
0637             }
0638         }
0639 
0640         else if( e.nodeName() == "contents" ) {
0641 
0642             QDomNodeList contentNodes = e.childNodes();
0643 
0644             for( int j = 0; j< contentNodes.length(); j++ ) {
0645 
0646                 QDomElement trackElem = contentNodes.item(j).toElement();
0647 
0648                 // first of all we need a track
0649                 K3b::AudioTrack* track = new K3b::AudioTrack();
0650 
0651 
0652                 // backwards compatibility
0653                 // -----------------------------------------------------------------------------------------------------
0654                 QDomAttr oldUrlAttr = trackElem.attributeNode( "url" );
0655                 if( !oldUrlAttr.isNull() ) {
0656                     if( K3b::AudioFile* file =
0657                         createAudioFile( QUrl::fromLocalFile( oldUrlAttr.value() ) ) ) {
0658                         track->addSource( file );
0659                     }
0660                 }
0661                 // -----------------------------------------------------------------------------------------------------
0662 
0663 
0664                 QDomNodeList trackNodes = trackElem.childNodes();
0665                 for( int trackJ = 0; trackJ < trackNodes.length(); trackJ++ ) {
0666 
0667                     if( trackNodes.item(trackJ).nodeName() == "sources" ) {
0668                         QDomNodeList sourcesNodes = trackNodes.item(trackJ).childNodes();
0669                         for( int sourcesIndex = 0; sourcesIndex < sourcesNodes.length(); sourcesIndex++ ) {
0670                             QDomElement sourceElem = sourcesNodes.item(sourcesIndex).toElement();
0671                             if( sourceElem.nodeName() == "file" ) {
0672                                 if( K3b::AudioFile* file =
0673                                     createAudioFile( QUrl::fromLocalFile( sourceElem.attributeNode( "url" ).value() ) ) ) {
0674                                     file->setStartOffset( K3b::Msf::fromString( sourceElem.attributeNode( "start_offset" ).value() ) );
0675                                     file->setEndOffset( K3b::Msf::fromString( sourceElem.attributeNode( "end_offset" ).value() ) );
0676                                     track->addSource( file );
0677                                 }
0678                             }
0679                             else if( sourceElem.nodeName() == "silence" ) {
0680                                 K3b::AudioZeroData* zero = new K3b::AudioZeroData();
0681                                 zero->setLength( K3b::Msf::fromString( sourceElem.attributeNode( "length" ).value() ) );
0682                                 track->addSource( zero );
0683                             }
0684                             else if( sourceElem.nodeName() == "cdtrack" ) {
0685                                 K3b::Msf length = K3b::Msf::fromString( sourceElem.attributeNode( "length" ).value() );
0686                                 int titlenum = 0;
0687                                 int discid = 0;
0688                                 QString title, artist, cdTitle, cdArtist;
0689 
0690                                 QDomNodeList cdTrackSourceNodes = sourceElem.childNodes();
0691                                 for( int cdTrackSourceIndex = 0; cdTrackSourceIndex < cdTrackSourceNodes.length(); ++cdTrackSourceIndex ) {
0692                                     QDomElement cdTrackSourceItemElem = cdTrackSourceNodes.item(cdTrackSourceIndex).toElement();
0693                                     if( cdTrackSourceItemElem.nodeName() == "title_number" )
0694                                         titlenum = cdTrackSourceItemElem.text().toInt();
0695                                     else if( cdTrackSourceItemElem.nodeName() == "disc_id" )
0696                                         discid = cdTrackSourceItemElem.text().toUInt( 0, 16 );
0697                                     else if( cdTrackSourceItemElem.nodeName() == "title" )
0698                                         title = QString::number(cdTrackSourceItemElem.text().toInt());
0699                                     else if( cdTrackSourceItemElem.nodeName() == "artist" )
0700                                         artist = QString::number(cdTrackSourceItemElem.text().toInt());
0701                                     else if( cdTrackSourceItemElem.nodeName() == "cdtitle" )
0702                                         cdTitle = QString::number(cdTrackSourceItemElem.text().toInt());
0703                                     else if( cdTrackSourceItemElem.nodeName() == "cdartist" )
0704                                         cdArtist = QString::number(cdTrackSourceItemElem.text().toInt());
0705                                 }
0706 
0707                                 if( discid != 0 && titlenum > 0 ) {
0708                                     K3b::AudioCdTrackSource* cdtrack = new K3b::AudioCdTrackSource( discid, length, titlenum,
0709                                                                                                 artist, title,
0710                                                                                                 cdArtist, cdTitle );
0711                                     cdtrack->setStartOffset( K3b::Msf::fromString( sourceElem.attributeNode( "start_offset" ).value() ) );
0712                                     cdtrack->setEndOffset( K3b::Msf::fromString( sourceElem.attributeNode( "end_offset" ).value() ) );
0713                                     track->addSource( cdtrack );
0714                                 }
0715                                 else {
0716                                     qDebug() << "(K3b::AudioDoc) invalid cdtrack source.";
0717                                     delete track;
0718                                     return false;
0719                                 }
0720                             }
0721                             else {
0722                                 qDebug() << "(K3b::AudioDoc) unknown source type: " << sourceElem.nodeName();
0723                                 delete track;
0724                                 return false;
0725                             }
0726                         }
0727                     }
0728 
0729                     // load cd-text
0730                     else if( trackNodes.item(trackJ).nodeName() == "cd-text" ) {
0731                         QDomNodeList cdTextNodes = trackNodes.item(trackJ).childNodes();
0732                         for( int trackCdTextJ = 0; trackCdTextJ < cdTextNodes.length(); trackCdTextJ++ ) {
0733                             if( cdTextNodes.item(trackCdTextJ).nodeName() == "title" )
0734                                 track->setTitle( cdTextNodes.item(trackCdTextJ).toElement().text() );
0735 
0736                             else if( cdTextNodes.item(trackCdTextJ).nodeName() == "artist" )
0737                                 track->setArtist( cdTextNodes.item(trackCdTextJ).toElement().text() );
0738 
0739                             else if( cdTextNodes.item(trackCdTextJ).nodeName() == "arranger" )
0740                                 track->setArranger( cdTextNodes.item(trackCdTextJ).toElement().text() );
0741 
0742                             else if( cdTextNodes.item(trackCdTextJ).nodeName() == "songwriter" )
0743                                 track->setSongwriter( cdTextNodes.item(trackCdTextJ).toElement().text() );
0744 
0745                             else if( cdTextNodes.item(trackCdTextJ).nodeName() == "composer" )
0746                                 track->setComposer( cdTextNodes.item(trackCdTextJ).toElement().text() );
0747 
0748                             else if( cdTextNodes.item(trackCdTextJ).nodeName() == "isrc" )
0749                                 track->setIsrc( cdTextNodes.item(trackCdTextJ).toElement().text() );
0750 
0751                             else if( cdTextNodes.item(trackCdTextJ).nodeName() == "message" )
0752                                 track->setCdTextMessage( cdTextNodes.item(trackCdTextJ).toElement().text() );
0753                         }
0754                     }
0755 
0756                     else if( trackNodes.item(trackJ).nodeName() == "index0" )
0757                         track->setIndex0( K3b::Msf::fromString( trackNodes.item(trackJ).toElement().text() ) );
0758 
0759                     // TODO: load other indices
0760 
0761                     // load options
0762                     else if( trackNodes.item(trackJ).nodeName() == "copy_protection" )
0763                         track->setCopyProtection( trackNodes.item(trackJ).toElement().text() == "yes" );
0764 
0765                     else if( trackNodes.item(trackJ).nodeName() == "pre_emphasis" )
0766                         track->setPreEmp( trackNodes.item(trackJ).toElement().text() == "yes" );
0767                 }
0768 
0769                 // add the track
0770                 if( track->numberSources() > 0 )
0771                     addTrack( track, 99 ); // append to the end // TODO improve
0772                 else {
0773                     qDebug() << "(K3b::AudioDoc) no sources. deleting track " << track;
0774                     delete track;
0775                 }
0776             }
0777         }
0778     }
0779 
0780     setModified(false);
0781 
0782     return true;
0783 }
0784 
0785 bool K3b::AudioDoc::saveDocumentData( QDomElement* docElem )
0786 {
0787     QDomDocument doc = docElem->ownerDocument();
0788     saveGeneralDocumentData( docElem );
0789 
0790     // add normalize
0791     QDomElement normalizeElem = doc.createElement( "normalize" );
0792     normalizeElem.appendChild( doc.createTextNode( normalize() ? "yes" : "no" ) );
0793     docElem->appendChild( normalizeElem );
0794 
0795     // add hide track
0796     QDomElement hideFirstTrackElem = doc.createElement( "hide_first_track" );
0797     hideFirstTrackElem.appendChild( doc.createTextNode( hideFirstTrack() ? "yes" : "no" ) );
0798     docElem->appendChild( hideFirstTrackElem );
0799 
0800     // save the audio cd ripping settings
0801     // paranoia mode, read retries, and ignore read errors
0802     // ------------------------------------------------------------
0803     QDomElement ripMain = doc.createElement( "audio_ripping" );
0804     docElem->appendChild( ripMain );
0805 
0806     QDomElement ripElem = doc.createElement( "paranoia_mode" );
0807     ripElem.appendChild( doc.createTextNode( QString::number( audioRippingParanoiaMode() ) ) );
0808     ripMain.appendChild( ripElem );
0809 
0810     ripElem = doc.createElement( "read_retries" );
0811     ripElem.appendChild( doc.createTextNode( QString::number( audioRippingRetries() ) ) );
0812     ripMain.appendChild( ripElem );
0813 
0814     ripElem = doc.createElement( "ignore_read_errors" );
0815     ripElem.appendChild( doc.createTextNode( audioRippingIgnoreReadErrors() ? "yes" : "no" ) );
0816     ripMain.appendChild( ripElem );
0817     // ------------------------------------------------------------
0818 
0819     // save disc cd-text
0820     // -------------------------------------------------------------
0821     QDomElement cdTextMain = doc.createElement( "cd-text" );
0822     cdTextMain.setAttribute( "activated", cdText() ? "yes" : "no" );
0823     QDomElement cdTextElem = doc.createElement( "title" );
0824     cdTextElem.appendChild( doc.createTextNode( (title())) );
0825     cdTextMain.appendChild( cdTextElem );
0826 
0827     cdTextElem = doc.createElement( "artist" );
0828     cdTextElem.appendChild( doc.createTextNode( (artist())) );
0829     cdTextMain.appendChild( cdTextElem );
0830 
0831     cdTextElem = doc.createElement( "arranger" );
0832     cdTextElem.appendChild( doc.createTextNode( (arranger())) );
0833     cdTextMain.appendChild( cdTextElem );
0834 
0835     cdTextElem = doc.createElement( "songwriter" );
0836     cdTextElem.appendChild( doc.createTextNode( (songwriter())) );
0837     cdTextMain.appendChild( cdTextElem );
0838 
0839     cdTextElem = doc.createElement( "composer" );
0840     cdTextElem.appendChild( doc.createTextNode( composer()) );
0841     cdTextMain.appendChild( cdTextElem );
0842 
0843     cdTextElem = doc.createElement( "disc_id" );
0844     cdTextElem.appendChild( doc.createTextNode( (disc_id())) );
0845     cdTextMain.appendChild( cdTextElem );
0846 
0847     cdTextElem = doc.createElement( "upc_ean" );
0848     cdTextElem.appendChild( doc.createTextNode( (upc_ean())) );
0849     cdTextMain.appendChild( cdTextElem );
0850 
0851     cdTextElem = doc.createElement( "message" );
0852     cdTextElem.appendChild( doc.createTextNode( (cdTextMessage())) );
0853     cdTextMain.appendChild( cdTextElem );
0854 
0855     docElem->appendChild( cdTextMain );
0856     // -------------------------------------------------------------
0857 
0858     // save the tracks
0859     // -------------------------------------------------------------
0860     QDomElement contentsElem = doc.createElement( "contents" );
0861 
0862     for( K3b::AudioTrack* track = firstTrack(); track != 0; track = track->next() ) {
0863 
0864         QDomElement trackElem = doc.createElement( "track" );
0865 
0866         // add sources
0867         QDomElement sourcesParent = doc.createElement( "sources" );
0868 
0869         for( K3b::AudioDataSource* source = track->firstSource(); source; source = source->next() ) {
0870             // TODO: save a source element with a type attribute and start- and endoffset
0871             //       then distinct between the different source types.
0872             if( K3b::AudioFile* file = dynamic_cast<K3b::AudioFile*>(source) ) {
0873                 QDomElement sourceElem = doc.createElement( "file" );
0874                 sourceElem.setAttribute( "url", file->filename() );
0875                 sourceElem.setAttribute( "start_offset", file->startOffset().toString() );
0876                 sourceElem.setAttribute( "end_offset", file->endOffset().toString() );
0877                 sourcesParent.appendChild( sourceElem );
0878             }
0879             else if( K3b::AudioZeroData* zero = dynamic_cast<K3b::AudioZeroData*>(source) ) {
0880                 QDomElement sourceElem = doc.createElement( "silence" );
0881                 sourceElem.setAttribute( "length", zero->length().toString() );
0882                 sourcesParent.appendChild( sourceElem );
0883             }
0884             else if( K3b::AudioCdTrackSource* cdTrack = dynamic_cast<K3b::AudioCdTrackSource*>(source) ) {
0885                 QDomElement sourceElem = doc.createElement( "cdtrack" );
0886                 sourceElem.setAttribute( "length", cdTrack->originalLength().toString() );
0887                 sourceElem.setAttribute( "start_offset", cdTrack->startOffset().toString() );
0888                 sourceElem.setAttribute( "end_offset", cdTrack->endOffset().toString() );
0889 
0890                 QDomElement subElem = doc.createElement( "title_number" );
0891                 subElem.appendChild( doc.createTextNode( QString::number(cdTrack->cdTrackNumber()) ) );
0892                 sourceElem.appendChild( subElem );
0893 
0894                 subElem = doc.createElement( "disc_id" );
0895                 subElem.appendChild( doc.createTextNode( QString::number(cdTrack->discId(), 16) ) );
0896                 sourceElem.appendChild( subElem );
0897 
0898                 subElem = doc.createElement( "title" );
0899                 subElem.appendChild( doc.createTextNode( cdTrack->title() ) );
0900                 sourceElem.appendChild( subElem );
0901 
0902                 subElem = doc.createElement( "artist" );
0903                 subElem.appendChild( doc.createTextNode( cdTrack->artist() ) );
0904                 sourceElem.appendChild( subElem );
0905 
0906                 subElem = doc.createElement( "cdtitle" );
0907                 subElem.appendChild( doc.createTextNode( cdTrack->cdTitle() ) );
0908                 sourceElem.appendChild( subElem );
0909 
0910                 subElem = doc.createElement( "cdartist" );
0911                 subElem.appendChild( doc.createTextNode( cdTrack->cdArtist() ) );
0912                 sourceElem.appendChild( subElem );
0913 
0914                 sourcesParent.appendChild( sourceElem );
0915             }
0916             else {
0917                 qCritical() << "(K3b::AudioDoc) saving sources other than file or zero not supported yet." << Qt::endl;
0918                 return false;
0919             }
0920         }
0921         trackElem.appendChild( sourcesParent );
0922 
0923         // index 0
0924         QDomElement index0Elem = doc.createElement( "index0" );
0925         index0Elem.appendChild( doc.createTextNode( track->index0().toString() ) );
0926         trackElem.appendChild( index0Elem );
0927 
0928         // TODO: other indices
0929 
0930         // add cd-text
0931         cdTextMain = doc.createElement( "cd-text" );
0932         cdTextElem = doc.createElement( "title" );
0933         cdTextElem.appendChild( doc.createTextNode( (track->title())) );
0934         cdTextMain.appendChild( cdTextElem );
0935 
0936         cdTextElem = doc.createElement( "artist" );
0937         cdTextElem.appendChild( doc.createTextNode( (track->artist())) );
0938         cdTextMain.appendChild( cdTextElem );
0939 
0940         cdTextElem = doc.createElement( "arranger" );
0941         cdTextElem.appendChild( doc.createTextNode( (track->arranger()) ) );
0942         cdTextMain.appendChild( cdTextElem );
0943 
0944         cdTextElem = doc.createElement( "songwriter" );
0945         cdTextElem.appendChild( doc.createTextNode( (track->songwriter()) ) );
0946         cdTextMain.appendChild( cdTextElem );
0947 
0948         cdTextElem = doc.createElement( "composer" );
0949         cdTextElem.appendChild( doc.createTextNode( (track->composer()) ) );
0950         cdTextMain.appendChild( cdTextElem );
0951 
0952         cdTextElem = doc.createElement( "isrc" );
0953         cdTextElem.appendChild( doc.createTextNode( ( track->isrc()) ) );
0954         cdTextMain.appendChild( cdTextElem );
0955 
0956         cdTextElem = doc.createElement( "message" );
0957         cdTextElem.appendChild( doc.createTextNode( (track->cdTextMessage()) ) );
0958         cdTextMain.appendChild( cdTextElem );
0959 
0960         trackElem.appendChild( cdTextMain );
0961 
0962         // add copy protection
0963         QDomElement copyElem = doc.createElement( "copy_protection" );
0964         copyElem.appendChild( doc.createTextNode( track->copyProtection() ? "yes" : "no" ) );
0965         trackElem.appendChild( copyElem );
0966 
0967         // add pre emphasis
0968         copyElem = doc.createElement( "pre_emphasis" );
0969         copyElem.appendChild( doc.createTextNode( track->preEmp() ? "yes" : "no" ) );
0970         trackElem.appendChild( copyElem );
0971 
0972         contentsElem.appendChild( trackElem );
0973     }
0974     // -------------------------------------------------------------
0975 
0976     docElem->appendChild( contentsElem );
0977 
0978     return true;
0979 }
0980 
0981 
0982 bool K3b::AudioDoc::hideFirstTrack() const
0983 {
0984     return d->hideFirstTrack;
0985 }
0986 
0987 
0988 int K3b::AudioDoc::numOfTracks() const
0989 {
0990     return ( d->lastTrack ? d->lastTrack->trackNumber() : 0 );
0991 }
0992 
0993 
0994 bool K3b::AudioDoc::normalize() const
0995 {
0996     return d->normalize;
0997 }
0998 
0999 
1000 K3b::BurnJob* K3b::AudioDoc::newBurnJob( K3b::JobHandler* hdl, QObject* parent )
1001 {
1002     return new K3b::AudioJob( this, hdl, parent );
1003 }
1004 
1005 
1006 void K3b::AudioDoc::slotTrackChanged( K3b::AudioTrack* track )
1007 {
1008     qDebug() << "(K3b::AudioDoc::slotTrackChanged " << track;
1009     setModified( true );
1010     // if the track is empty now we simply delete it
1011     if( track->firstSource() ) {
1012         emit trackChanged( track );
1013         emit changed();
1014     }
1015     else {
1016         qDebug() << "(K3b::AudioDoc::slotTrackChanged) track " << track << " empty. Deleting.";
1017         delete track; // this will emit the proper signal
1018     }
1019     qDebug() << "(K3b::AudioDoc::slotTrackChanged done";
1020 }
1021 
1022 
1023 void K3b::AudioDoc::slotTrackRemoved( int position )
1024 {
1025     setModified( true );
1026     emit trackRemoved( position );
1027     emit changed();
1028 }
1029 
1030 void K3b::AudioDoc::increaseDecoderUsage( K3b::AudioDecoder* decoder )
1031 {
1032     qDebug() << "(K3b::AudioDoc::increaseDecoderUsage)";
1033     if( !d->decoderUsageCounterMap.contains( decoder ) ) {
1034         d->decoderUsageCounterMap[decoder] = 1;
1035         d->decoderPresenceMap[decoder->filename()] = decoder;
1036     }
1037     else
1038         d->decoderUsageCounterMap[decoder]++;
1039     qDebug() << "(K3b::AudioDoc::increaseDecoderUsage) finished";
1040 }
1041 
1042 
1043 void K3b::AudioDoc::decreaseDecoderUsage( K3b::AudioDecoder* decoder )
1044 {
1045     d->decoderUsageCounterMap[decoder]--;
1046     if( d->decoderUsageCounterMap[decoder] <= 0 ) {
1047         d->decoderUsageCounterMap.remove(decoder);
1048         d->decoderPresenceMap.remove(decoder->filename());
1049         delete decoder;
1050     }
1051 }
1052 
1053 
1054 K3b::Device::CdText K3b::AudioDoc::cdTextData() const
1055 {
1056     K3b::Device::CdText text( d->cdTextData );
1057     K3b::AudioTrack* track = firstTrack();
1058     int i = 0;
1059     while( track ) {
1060         text.track( i++ ) = track->cdText();
1061         track = track->next();
1062     }
1063     return text;
1064 }
1065 
1066 
1067 int K3b::AudioDoc::audioRippingParanoiaMode() const
1068 {
1069     return d->audioRippingParanoiaMode;
1070 }
1071 
1072 
1073 int K3b::AudioDoc::audioRippingRetries() const
1074 {
1075     return d->audioRippingRetries;
1076 }
1077 
1078 
1079 bool K3b::AudioDoc::audioRippingIgnoreReadErrors() const
1080 {
1081     return d->audioRippingIgnoreReadErrors;
1082 }
1083 
1084 
1085 K3b::Device::Toc K3b::AudioDoc::toToc() const
1086 {
1087     K3b::Device::Toc toc;
1088 
1089     // FIXME: add MCN
1090 
1091     K3b::AudioTrack* track = firstTrack();
1092     K3b::Msf pos = 0;
1093     while( track ) {
1094         toc.append( track->toCdTrack() );
1095         track = track->next();
1096     }
1097 
1098     return toc;
1099 }
1100 
1101 
1102 void K3b::AudioDoc::setTitle( const QString& v )
1103 {
1104     d->cdTextData.setTitle( v );
1105     emit changed();
1106 }
1107 
1108 
1109 void K3b::AudioDoc::setArtist( const QString& v )
1110 {
1111     setPerformer( v );
1112 }
1113 
1114 
1115 void K3b::AudioDoc::setPerformer( const QString& v )
1116 {
1117     QString s( v );
1118     d->cdTextValidator->fixup( s );
1119     d->cdTextData.setPerformer( s );
1120     emit changed();
1121 }
1122 
1123 
1124 void K3b::AudioDoc::setDisc_id( const QString& v )
1125 {
1126     QString s( v );
1127     d->cdTextValidator->fixup( s );
1128     d->cdTextData.setDiscId( s );
1129     emit changed();
1130 }
1131 
1132 
1133 void K3b::AudioDoc::setArranger( const QString& v )
1134 {
1135     QString s( v );
1136     d->cdTextValidator->fixup( s );
1137     d->cdTextData.setArranger( s );
1138     emit changed();
1139 }
1140 
1141 
1142 void K3b::AudioDoc::setSongwriter( const QString& v )
1143 {
1144     QString s( v );
1145     d->cdTextValidator->fixup( s );
1146     d->cdTextData.setSongwriter( s );
1147     emit changed();
1148 }
1149 
1150 
1151 void K3b::AudioDoc::setComposer( const QString& v )
1152 {
1153     QString s( v );
1154     d->cdTextValidator->fixup( s );
1155     d->cdTextData.setComposer( s );
1156     emit changed();
1157 }
1158 
1159 
1160 void K3b::AudioDoc::setUpc_ean( const QString& v )
1161 {
1162     QString s( v );
1163     d->cdTextValidator->fixup( s );
1164     d->cdTextData.setUpcEan( s );
1165     emit changed();
1166 }
1167 
1168 
1169 void K3b::AudioDoc::setCdTextMessage( const QString& v )
1170 {
1171     QString s( v );
1172     d->cdTextValidator->fixup( s );
1173     d->cdTextData.setMessage( s );
1174     emit changed();
1175 }
1176 
1177 
1178 void K3b::AudioDoc::setAudioRippingParanoiaMode( int i )
1179 {
1180     d->audioRippingParanoiaMode = i;
1181 }
1182 
1183 
1184 void K3b::AudioDoc::setAudioRippingRetries( int r )
1185 {
1186     d->audioRippingRetries = r;
1187 }
1188 
1189 
1190 void K3b::AudioDoc::setAudioRippingIgnoreReadErrors( bool b )
1191 {
1192     d->audioRippingIgnoreReadErrors = b;
1193 }
1194 
1195 
1196 K3b::Device::MediaTypes K3b::AudioDoc::supportedMediaTypes() const
1197 {
1198     return K3b::Device::MEDIA_WRITABLE_CD;
1199 }
1200 
1201 #include "moc_k3baudiodoc.cpp"