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"