diff --git a/libk3b/plugin/k3baudiodecoder.cpp b/libk3b/plugin/k3baudiodecoder.cpp index deb5bfdb1..e6bb8e97d 100644 --- a/libk3b/plugin/k3baudiodecoder.cpp +++ b/libk3b/plugin/k3baudiodecoder.cpp @@ -1,634 +1,641 @@ /* * * Copyright (C) 2003-2009 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 #include "k3bcore.h" #include "k3baudiodecoder.h" #include "k3bpluginmanager.h" #include "k3b_i18n.h" #include #include #include #include #include #include #include #include #include #include #if !(HAVE_LRINT && HAVE_LRINTF) #define lrint(dbl) ((int) (dbl+0.5)) #define lrintf(flt) ((int) (flt+0.5)) #endif // use a one second buffer static const int DECODING_BUFFER_SIZE = 75*2352; namespace { typedef QMap MetaInfoMap; class ExtractionResult : public KFileMetaData::ExtractionResult { public: ExtractionResult( const QString& filename, const QString& mimetype, MetaInfoMap& metaInfoMap ) : KFileMetaData::ExtractionResult( filename, mimetype, KFileMetaData::ExtractionResult::ExtractMetaData ), metaInfoMap_( metaInfoMap ) {} void append(const QString& /*text*/) override {} void addType(KFileMetaData::Type::Type /*type*/) override {} void add(KFileMetaData::Property::Property property, const QVariant& value) override { switch(property) { case KFileMetaData::Property::Title: metaInfoMap_.insert( K3b::AudioDecoder::META_TITLE, value.toString() ); break; case KFileMetaData::Property::Artist: metaInfoMap_.insert( K3b::AudioDecoder::META_ARTIST, value.toString() ); break; case KFileMetaData::Property::Lyricist: metaInfoMap_.insert( K3b::AudioDecoder::META_SONGWRITER, value.toString() ); break; case KFileMetaData::Property::Composer: metaInfoMap_.insert( K3b::AudioDecoder::META_COMPOSER, value.toString() ); break; case KFileMetaData::Property::Comment: metaInfoMap_.insert( K3b::AudioDecoder::META_COMMENT, value.toString() ); break; default: break; } } private: MetaInfoMap& metaInfoMap_; }; } // namespace class K3b::AudioDecoder::Private { public: Private() : resampleState(0), resampleData(0), inBuffer(0), inBufferPos(0), inBufferFill(0), outBuffer(0), monoBuffer(0), decodingBufferPos(0), decodingBufferFill(0), - valid(true) { + valid(true), + metaDataCollection(NULL) { } // the current position of the decoder // This does NOT include the decodingBuffer K3b::Msf currentPos; // since the current position above is measured in frames // there might be a little offset since the decoded data is not // always a multiple of 2353 bytes int currentPosOffset; // already decoded bytes from last init or last seek // TODO: replace alreadyDecoded with currentPos unsigned long alreadyDecoded; K3b::Msf decodingStartPos; - KFileMetaData::ExtractorCollection metaDataCollection; + KFileMetaData::ExtractorCollection *metaDataCollection; QMimeDatabase mimeDatabase; QMimeType mimeType; // set to true once decodeInternal() returned 0 bool decoderFinished; // resampling SRC_STATE* resampleState; SRC_DATA* resampleData; float* inBuffer; float* inBufferPos; int inBufferFill; float* outBuffer; int samplerate; int channels; // mono -> stereo conversion char* monoBuffer; char decodingBuffer[DECODING_BUFFER_SIZE]; char* decodingBufferPos; int decodingBufferFill; QMap technicalInfoMap; MetaInfoMap metaInfoMap; bool valid; }; K3b::AudioDecoder::AudioDecoder( QObject* parent ) : QObject( parent ) { d = new Private(); } K3b::AudioDecoder::~AudioDecoder() { cleanup(); if( d->inBuffer ) delete [] d->inBuffer; if( d->outBuffer ) delete [] d->outBuffer; if( d->monoBuffer ) delete [] d->monoBuffer; delete d->resampleData; if( d->resampleState ) src_delete( d->resampleState ); delete d; } void K3b::AudioDecoder::setFilename( const QString& filename ) { m_fileName = filename; d->mimeType = QMimeType(); } bool K3b::AudioDecoder::isValid() const { return d->valid; } bool K3b::AudioDecoder::analyseFile() { d->technicalInfoMap.clear(); d->metaInfoMap.clear(); d->mimeType = QMimeType(); cleanup(); bool ret = analyseFileInternal( m_length, d->samplerate, d->channels ); if( ret && ( d->channels == 1 || d->channels == 2 ) && m_length > 0 ) { d->valid = initDecoder(); return d->valid; } else { d->valid = false; return false; } } bool K3b::AudioDecoder::initDecoder( const K3b::Msf& startOffset ) { if( initDecoder() ) { if( startOffset > 0 ) return seek( startOffset ); else return true; } else return false; } bool K3b::AudioDecoder::initDecoder() { cleanup(); if( d->resampleState ) src_reset( d->resampleState ); d->alreadyDecoded = 0; d->currentPos = 0; d->currentPosOffset = 0; d->decodingBufferFill = 0; d->decodingBufferPos = 0; d->decodingStartPos = 0; d->inBufferFill = 0; d->decoderFinished = false; return initDecoderInternal(); } int K3b::AudioDecoder::decode( char* _data, int maxLen ) { unsigned long lengthToDecode = (m_length - d->decodingStartPos).audioBytes(); if( d->alreadyDecoded >= lengthToDecode ) return 0; if( maxLen <= 0 ) return 0; int read = 0; if( d->decodingBufferFill == 0 ) { // // now we decode into the decoding buffer // to ensure a minimum buffer size // d->decodingBufferFill = 0; d->decodingBufferPos = d->decodingBuffer; if( !d->decoderFinished ) { if( d->samplerate != 44100 ) { // check if we have data left from some previous conversion if( d->inBufferFill > 0 ) { read = resample( d->decodingBuffer, DECODING_BUFFER_SIZE ); } else { if( !d->inBuffer ) { d->inBuffer = new float[DECODING_BUFFER_SIZE/2]; } if( (read = decodeInternal( d->decodingBuffer, DECODING_BUFFER_SIZE )) == 0 ) d->decoderFinished = true; d->inBufferFill = read/2; d->inBufferPos = d->inBuffer; from16bitBeSignedToFloat( d->decodingBuffer, d->inBuffer, d->inBufferFill ); read = resample( d->decodingBuffer, DECODING_BUFFER_SIZE ); } } else if( d->channels == 1 ) { if( !d->monoBuffer ) { d->monoBuffer = new char[DECODING_BUFFER_SIZE/2]; } // we simply duplicate every frame if( (read = decodeInternal( d->monoBuffer, DECODING_BUFFER_SIZE/2 )) == 0 ) d->decoderFinished = true; for( int i = 0; i < read; i+=2 ) { d->decodingBuffer[2*i] = d->decodingBuffer[2*i+2] = d->monoBuffer[i]; d->decodingBuffer[2*i+1] = d->decodingBuffer[2*i+3] = d->monoBuffer[i+1]; } read *= 2; } else { if( (read = decodeInternal( d->decodingBuffer, DECODING_BUFFER_SIZE )) == 0 ) d->decoderFinished = true; } } if( read < 0 ) { return -1; } else if( read == 0 ) { // check if we need to pad int bytesToPad = lengthToDecode - d->alreadyDecoded; if( bytesToPad > 0 ) { qDebug() << "(K3b::AudioDecoder) track length: " << lengthToDecode << "; decoded module data: " << d->alreadyDecoded << "; we need to pad " << bytesToPad << " bytes." << endl; if( DECODING_BUFFER_SIZE < bytesToPad ) bytesToPad = DECODING_BUFFER_SIZE; ::memset( d->decodingBuffer, 0, bytesToPad ); qDebug() << "(K3b::AudioDecoder) padded " << bytesToPad << " bytes."; read = bytesToPad; } else { qDebug() << "(K3b::AudioDecoder) decoded " << d->alreadyDecoded << " bytes."; return 0; } } else { // check if we decoded too much if( d->alreadyDecoded + read > lengthToDecode ) { qDebug() << "(K3b::AudioDecoder) we decoded too much. Cutting output by " << (read + d->alreadyDecoded - lengthToDecode) << endl; read = lengthToDecode - d->alreadyDecoded; } } d->decodingBufferFill = read; } // clear out the decoding buffer read = qMin( maxLen, d->decodingBufferFill ); ::memcpy( _data, d->decodingBufferPos, read ); d->decodingBufferPos += read; d->decodingBufferFill -= read; d->alreadyDecoded += read; d->currentPos += (read+d->currentPosOffset)/2352; d->currentPosOffset = (read+d->currentPosOffset)%2352; return read; } // resample data in d->inBufferPos and save the result to data // // int K3b::AudioDecoder::resample( char* data, int maxLen ) { if( !d->resampleState ) { d->resampleState = src_new( SRC_SINC_MEDIUM_QUALITY, d->channels, 0 ); if( !d->resampleState ) { qDebug() << "(K3b::AudioDecoder) unable to initialize resampler."; return -1; } d->resampleData = new SRC_DATA; } if( !d->outBuffer ) { d->outBuffer = new float[DECODING_BUFFER_SIZE/2]; } d->resampleData->data_in = d->inBufferPos; d->resampleData->data_out = d->outBuffer; d->resampleData->input_frames = d->inBufferFill/d->channels; d->resampleData->output_frames = maxLen/2/2; // in case of mono files we need the space anyway d->resampleData->src_ratio = 44100.0/(double)d->samplerate; if( d->inBufferFill == 0 ) d->resampleData->end_of_input = 1; // this should force libsamplerate to output the last frames else d->resampleData->end_of_input = 0; int len = 0; if( (len = src_process( d->resampleState, d->resampleData ) ) ) { qDebug() << "(K3b::AudioDecoder) error while resampling: " << src_strerror(len); return -1; } if( d->channels == 2 ) fromFloatTo16BitBeSigned( d->outBuffer, data, d->resampleData->output_frames_gen*d->channels ); else { for( int i = 0; i < d->resampleData->output_frames_gen; ++i ) { fromFloatTo16BitBeSigned( &d->outBuffer[i], &data[4*i], 1 ); fromFloatTo16BitBeSigned( &d->outBuffer[i], &data[4*i+2], 1 ); } } d->inBufferPos += d->resampleData->input_frames_used*d->channels; d->inBufferFill -= d->resampleData->input_frames_used*d->channels; if( d->inBufferFill <= 0 ) { d->inBufferPos = d->inBuffer; d->inBufferFill = 0; } // 16 bit frames, so we need to multiply by 2 // and we always have two channels return d->resampleData->output_frames_gen*2*2; } void K3b::AudioDecoder::from16bitBeSignedToFloat( char* src, float* dest, int samples ) { while( samples ) { samples--; dest[samples] = static_cast( qint16(((src[2*samples]<<8)&0xff00)|(src[2*samples+1]&0x00ff)) / 32768.0 ); } } void K3b::AudioDecoder::fromFloatTo16BitBeSigned( float* src, char* dest, int samples ) { while( samples ) { samples--; float scaled = src[samples] * 32768.0; qint16 val = 0; // clipping if( scaled >= ( 1.0 * 0x7FFF ) ) val = 32767; else if( scaled <= ( -8.0 * 0x1000 ) ) val = -32768; else val = lrintf(scaled); dest[2*samples] = val>>8; dest[2*samples+1] = val; } } void K3b::AudioDecoder::from8BitTo16BitBeSigned( char* src, char* dest, int samples ) { while( samples ) { samples--; float scaled = static_cast(quint8(src[samples])-128) / 128.0 * 32768.0; qint16 val = 0; // clipping if( scaled >= ( 1.0 * 0x7FFF ) ) val = 32767; else if( scaled <= ( -8.0 * 0x1000 ) ) val = -32768; else val = lrintf(scaled); dest[2*samples] = val>>8; dest[2*samples+1] = val; } } bool K3b::AudioDecoder::seek( const K3b::Msf& pos ) { qDebug() << "(K3b::AudioDecoder) seek from " << d->currentPos.toString() << " (+" << d->currentPosOffset << ") to " << pos.toString() << endl; if( pos > length() ) return false; d->decoderFinished = false; if( pos == d->currentPos && d->currentPosOffset == 0 ) return true; if( pos == 0 ) return initDecoder(); bool success = false; // // First check if we may do a "perfect seek". // We cannot rely on the decoding plugins to seek perfectly. Especially // the mp3 decoder does not. But in case we want to split a live recording // it is absolutely nesseccary to perform a perfect seek. // So if we did not already decode past the seek position and the difference // between the current position and the seek position is less than some fixed // value we simply decode up to the seek position. // if( ( pos > d->currentPos || ( pos == d->currentPos && d->currentPosOffset == 0 ) ) && ( pos - d->currentPos < K3b::Msf(0,10,0) ) ) { // < 10 seconds is ok qDebug() << "(K3b::AudioDecoder) performing perfect seek from " << d->currentPos.toString() << " to " << pos.toString() << ". :)" << endl; qint64 bytesToDecode = pos.audioBytes() - d->currentPos.audioBytes() - d->currentPosOffset; qDebug() << "(K3b::AudioDecoder) seeking " << bytesToDecode << " bytes."; char buffi[10*2352]; while( bytesToDecode > 0 ) { int read = decode( buffi, qMin(( qint64 )( 10*2352 ), bytesToDecode) ); if( read <= 0 ) return false; bytesToDecode -= read; } qDebug() << "(K3b::AudioDecoder) perfect seek done."; success = true; } else { // // Here we have to reset the resampling stuff since we restart decoding at another position. // if( d->resampleState ) src_reset( d->resampleState ); d->inBufferFill = 0; // // And also reset the decoding buffer to not return any garbage from previous decoding. // d->decodingBufferFill = 0; success = seekInternal( pos ); } d->alreadyDecoded = 0; d->currentPos = d->decodingStartPos = pos; d->currentPosOffset = 0; return success; } void K3b::AudioDecoder::cleanup() { + if (d->metaDataCollection) { + delete d->metaDataCollection; + d->metaDataCollection = NULL; + } } QString K3b::AudioDecoder::metaInfo( MetaDataField f ) { if( d->metaInfoMap.contains( f ) ) return d->metaInfoMap[f]; // fall back to KFileMetaData if( !d->mimeType.isValid() ) { d->mimeType = d->mimeDatabase.mimeTypeForFile( m_fileName ); - for( KFileMetaData::Extractor* plugin : d->metaDataCollection.fetchExtractors( d->mimeType.name() ) ) + if (!d->metaDataCollection) + d->metaDataCollection = new KFileMetaData::ExtractorCollection; + for( KFileMetaData::Extractor* plugin : d->metaDataCollection->fetchExtractors( d->mimeType.name() ) ) { ExtractionResult extractionResult(m_fileName, d->mimeType.name(), d->metaInfoMap); plugin->extract(&extractionResult); } if( d->metaInfoMap.contains( f ) ) return d->metaInfoMap[f]; } return QString(); } void K3b::AudioDecoder::addMetaInfo( MetaDataField f, const QString& value ) { if( !value.isEmpty() ) d->metaInfoMap[f] = value; else qDebug() << "(K3b::AudioDecoder) empty meta data field."; } QStringList K3b::AudioDecoder::supportedTechnicalInfos() const { QStringList l; for( QMap::const_iterator it = d->technicalInfoMap.constBegin(); it != d->technicalInfoMap.constEnd(); ++it ) l.append( it.key() ); return l; } QString K3b::AudioDecoder::technicalInfo( const QString& key ) const { return d->technicalInfoMap[key]; } void K3b::AudioDecoder::addTechnicalInfo( const QString& key, const QString& value ) { d->technicalInfoMap[key] = value; } K3b::AudioDecoder* K3b::AudioDecoderFactory::createDecoder( const QUrl& url ) { qDebug() << "(K3b::AudioDecoderFactory::createDecoder( " << url.toLocalFile() << " )"; QList fl = k3bcore->pluginManager()->plugins( "AudioDecoder" ); // first search for a single format decoder Q_FOREACH( K3b::Plugin* plugin, fl ) { K3b::AudioDecoderFactory* f = dynamic_cast( plugin ); if( f && !f->multiFormatDecoder() && f->canDecode( url ) ) { qDebug() << "1"; return f->createDecoder();} } // no single format decoder. Search for a multi format decoder Q_FOREACH( K3b::Plugin* plugin, fl ) { K3b::AudioDecoderFactory* f = dynamic_cast( plugin ); if( f && f->multiFormatDecoder() && f->canDecode( url ) ) { qDebug() << "2"; return f->createDecoder();} } qDebug() << "(K3b::AudioDecoderFactory::createDecoder( " << url.toLocalFile() << " ) no success"; // nothing found return 0; } QString K3b::AudioDecoderFactory::categoryName() const { return i18nc( "plugin type", "Audio Decoder" ); } diff --git a/libk3b/projects/audiocd/k3baudiotrack.cpp b/libk3b/projects/audiocd/k3baudiotrack.cpp index e16c6bbae..b484d0985 100644 --- a/libk3b/projects/audiocd/k3baudiotrack.cpp +++ b/libk3b/projects/audiocd/k3baudiotrack.cpp @@ -1,809 +1,809 @@ /* * * Copyright (C) 2003-2008 Sebastian Trueg * Copyright (C) 2009 Gustavo Pichorim Boiko * Copyright (C) 2010 Michal Malek * * This file is part of the K3b project. * Copyright (C) 1998-2009 Sebastian Trueg * * 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 "k3baudiotrackreader.h" #include "k3baudiodecoder.h" #include "k3bcore.h" #include "k3bcdtextvalidator.h" #include #include class K3b::AudioTrack::Private { public: Private( AudioDoc* p = 0 ) : parent(p), copy(false), preEmp(false), index0Offset(150), prev(0), next(0), firstSource(0), currentlyDeleting(false) { cdTextValidator = new K3b::CdTextValidator(); } ~Private() { delete cdTextValidator; } AudioDoc* parent; /** copy protection */ bool copy; bool preEmp; Msf index0Offset; Device::TrackCdText cdText; // list AudioTrack* prev; AudioTrack* next; AudioDataSource* firstSource; bool currentlyDeleting; K3b::CdTextValidator* cdTextValidator; }; K3b::AudioTrack::AudioTrack() : QObject(), d( new Private ) { } K3b::AudioTrack::AudioTrack( K3b::AudioDoc* parent ) : QObject(), d( new Private( parent ) ) { } K3b::AudioTrack::~AudioTrack() { qDebug() << this; d->currentlyDeleting = true; // fix the list take(); qDebug() << "deleting sources."; // delete all sources while( d->firstSource ) - delete d->firstSource; + delete d->firstSource->take(); qDebug() << "finished"; delete d; } K3b::AudioDoc* K3b::AudioTrack::doc() const { return d->parent; } void K3b::AudioTrack::emitChanged() { emit changed(); if( d->parent && !d->currentlyDeleting ) d->parent->slotTrackChanged( this ); } void K3b::AudioTrack::setArtist( const QString& a ) { setPerformer( a ); } void K3b::AudioTrack::setPerformer( const QString& a ) { if( performer() != a ) { QString s( a ); d->cdTextValidator->fixup( s ); d->cdText.setPerformer(s); emitChanged(); } } void K3b::AudioTrack::setTitle( const QString& t ) { if( title() != t ) { QString s( t ); d->cdTextValidator->fixup( s ); d->cdText.setTitle(s); emitChanged(); } } void K3b::AudioTrack::setArranger( const QString& t ) { if( arranger() != t ) { QString s( t ); d->cdTextValidator->fixup( s ); d->cdText.setArranger(s); emitChanged(); } } void K3b::AudioTrack::setSongwriter( const QString& t ) { if( songwriter() != t ) { QString s( t ); d->cdTextValidator->fixup( s ); d->cdText.setSongwriter(s); emitChanged(); } } void K3b::AudioTrack::setComposer( const QString& t ) { if( composer() != t ) { QString s( t ); d->cdTextValidator->fixup( s ); d->cdText.setComposer(s); emitChanged(); } } void K3b::AudioTrack::setIsrc( const QString& t ) { if( isrc() != t ) { d->cdText.setIsrc(t); emitChanged(); } } void K3b::AudioTrack::setCdTextMessage( const QString& t ) { if( cdTextMessage() != t ) { QString s( t ); d->cdTextValidator->fixup( s ); d->cdText.setMessage(s); emitChanged(); } } void K3b::AudioTrack::setCdText( const K3b::Device::TrackCdText& cdtext ) { d->cdText = cdtext; emitChanged(); } void K3b::AudioTrack::setPreEmp( bool b ) { if( d->preEmp != b ) { d->preEmp = b; emitChanged(); } } void K3b::AudioTrack::setCopyProtection( bool b ) { if( d->copy != b ) { d->copy = b; emitChanged(); } } K3b::AudioDataSource* K3b::AudioTrack::firstSource() const { return d->firstSource; } K3b::AudioDataSource* K3b::AudioTrack::lastSource() const { K3b::AudioDataSource* s = d->firstSource; while( s && s->next() ) s = s->next(); return s; } bool K3b::AudioTrack::inList() const { if( doc() ) return ( doc()->firstTrack() == this || d->prev != 0 ); else return false; } K3b::Msf K3b::AudioTrack::length() const { K3b::Msf length; K3b::AudioDataSource* source = d->firstSource; while( source ) { length += source->length(); source = source->next(); } return length; } KIO::filesize_t K3b::AudioTrack::size() const { return length().audioBytes(); } QString K3b::AudioTrack::artist() const { return d->cdText.performer(); } QString K3b::AudioTrack::performer() const { return d->cdText.performer(); } QString K3b::AudioTrack::title() const { return d->cdText.title(); } QString K3b::AudioTrack::arranger() const { return d->cdText.arranger(); } QString K3b::AudioTrack::songwriter() const { return d->cdText.songwriter(); } QString K3b::AudioTrack::composer() const { return d->cdText.composer(); } QString K3b::AudioTrack::isrc() const { return d->cdText.isrc(); } QString K3b::AudioTrack::cdTextMessage() const { return d->cdText.message(); } K3b::Device::TrackCdText K3b::AudioTrack::cdText() const { return d->cdText; } bool K3b::AudioTrack::copyProtection() const { return d->copy; } bool K3b::AudioTrack::preEmp() const { return d->preEmp; } unsigned int K3b::AudioTrack::trackNumber() const { if( d->prev ) return d->prev->trackNumber() + 1; else return 1; } K3b::Msf K3b::AudioTrack::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() - d->index0Offset; } K3b::Msf K3b::AudioTrack::postGap() const { if( next() ) return d->index0Offset; else return 0; } void K3b::AudioTrack::setIndex0( const K3b::Msf& msf ) { if( msf == 0 ) d->index0Offset = 0; else d->index0Offset = length() - msf; } K3b::AudioTrack* K3b::AudioTrack::take() { if( inList() ) { const int position = trackNumber() - 1; if ( doc() ) emit doc()->trackAboutToBeRemoved( position ); if( !d->prev ) doc()->setFirstTrack( d->next ); if( !d->next ) doc()->setLastTrack( d->prev ); if( d->prev ) d->prev->d->next = d->next; if( d->next ) d->next->d->prev = d->prev; d->prev = d->next = 0; // remove from doc if( doc() ) doc()->slotTrackRemoved( position ); d->parent = 0; } return this; } void K3b::AudioTrack::moveAfter( K3b::AudioTrack* track ) { qDebug() << "(K3b::AudioTrack::moveAfter( " << track << " )"; if( !track ) { if( !doc() ) { qDebug() << "(K3b::AudioTrack::moveAfter) no parent set"; return; } // make sure we do not mess up the list if( doc()->lastTrack() ) moveAfter( doc()->lastTrack() ); else { emit doc()->trackAboutToBeAdded( 0 ); doc()->setFirstTrack( take() ); doc()->setLastTrack( this ); emit doc()->trackAdded( 0 ); } } else if( track == this ) { qDebug() << "(K3b::AudioTrack::moveAfter) trying to move this after this."; return; } else { // remove this from the list take(); emit track->doc()->trackAboutToBeAdded( track->trackNumber()-1 ); // set the new parent doc d->parent = track->doc(); K3b::AudioTrack* oldNext = track->d->next; // set track as prev track->d->next = this; d->prev = track; // set oldNext as next if( oldNext ) oldNext->d->prev = this; d->next = oldNext; if( !d->prev ) doc()->setFirstTrack( this ); if( !d->next ) doc()->setLastTrack( this ); emit doc()->trackAdded( track->trackNumber()-1 ); } emitChanged(); } void K3b::AudioTrack::moveAhead( K3b::AudioTrack* track ) { if( !track ) { if( !doc() ) { qDebug() << "(K3b::AudioTrack::moveAfter) no parent set"; return; } // make sure we do not mess up the list if( doc()->firstTrack() ) moveAhead( doc()->firstTrack() ); else { emit doc()->trackAboutToBeAdded( 0 ); doc()->setFirstTrack( take() ); doc()->setLastTrack( this ); emit doc()->trackAdded( 0 ); } } else if( track == this ) { qDebug() << "(K3b::AudioTrack::moveAhead) trying to move this ahead of this."; return; } else { // remove this from the list take(); emit track->doc()->trackAboutToBeAdded( track->trackNumber()-1 ); // set the new parent doc d->parent = track->doc(); K3b::AudioTrack* oldPrev = track->d->prev; // set track as next d->next = track; track->d->prev = this; // set oldPrev as prev d->prev = oldPrev; if( oldPrev ) oldPrev->d->next = this; if( !d->prev ) doc()->setFirstTrack( this ); if( !d->next ) doc()->setLastTrack( this ); emit doc()->trackAdded( track->trackNumber()-1 ); } emitChanged(); } void K3b::AudioTrack::merge( K3b::AudioTrack* trackToMerge, K3b::AudioDataSource* sourceAfter ) { qDebug() << "(K3b::AudioTrack::merge) " << trackToMerge << " into " << this; if( this == trackToMerge ) { qDebug() << "(K3b::AudioTrack::merge) trying to merge this with this."; 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 ) { qDebug() << "(K3b::AudioTrack::merge) merging " << trackToMerge->firstSource(); if( d->firstSource ) { trackToMerge->firstSource()->moveAhead( d->firstSource ); } else { addSource( trackToMerge->firstSource()->take() ); } sourceAfter = d->firstSource; } qDebug() << "(K3b::AudioTrack::merge) now merge the other sources."; // now merge all sources into this track while( trackToMerge->firstSource() ) { K3b::AudioDataSource* s = trackToMerge->firstSource(); qDebug() << "(K3b::AudioTrack::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; qDebug() << "(K3b::AudioTrack::merge) finished"; emitChanged(); } K3b::AudioTrack* K3b::AudioTrack::prev() const { return d->prev; } K3b::AudioTrack* K3b::AudioTrack::next() const { return d->next; } void K3b::AudioTrack::setFirstSource( K3b::AudioDataSource* source ) { d->firstSource = source; while( source ) { source->m_track = this; source = source->next(); } emitChanged(); } void K3b::AudioTrack::addSource( K3b::AudioDataSource* source ) { if( !source ) return; K3b::AudioDataSource* s = d->firstSource; while( s && s->next() ) s = s->next(); if( s ) source->moveAfter( s ); else setFirstSource( source->take() ); } void K3b::AudioTrack::sourceChanged( K3b::AudioDataSource* ) { if( d->currentlyDeleting ) return; // TODO: update indices if( d->index0Offset > length() ) d->index0Offset = length()-1; emitChanged(); } int K3b::AudioTrack::numberSources() const { K3b::AudioDataSource* source = d->firstSource; int i = 0; while( source ) { source = source->next(); ++i; } return i; } K3b::AudioTrack* K3b::AudioTrack::copy() const { K3b::AudioTrack* track = new K3b::AudioTrack(); track->d->copy = d->copy; track->d->preEmp = d->preEmp; track->d->index0Offset = d->index0Offset; track->d->cdText = d->cdText; K3b::AudioDataSource* source = d->firstSource; while( source ) { track->addSource( source->copy() ); source = source->next(); } return track; } K3b::AudioTrack* K3b::AudioTrack::split( const K3b::Msf& pos ) { if( pos < length() ) { // search the source // pos will be the first sector of the new track K3b::Msf currentPos; K3b::AudioDataSource* source = firstSource(); while( source && currentPos + source->length() <= pos ) { currentPos += source->length(); source = source->next(); } K3b::AudioDataSource* splitSource = 0; if( currentPos > 0 && currentPos == pos ) { // no need to split a source splitSource = source; } else { if (source) splitSource = source->split( pos - currentPos ); } // the new track should include all sources from splitSource and below K3b::AudioTrack* splitTrack = new K3b::AudioTrack(); splitTrack->d->cdText = d->cdText; source = splitSource; while( source ) { K3b::AudioDataSource* addSource = source; source = source->next(); splitTrack->addSource( addSource ); } qDebug() << "(K3b::AudioTrack) moving track " << splitTrack << " after this (" << this << ") with parent " << doc(); splitTrack->moveAfter( this ); return splitTrack; } else return 0; } QIODevice* K3b::AudioTrack::createReader( QObject* parent ) { return new AudioTrackReader( *this, parent ); } K3b::Device::Track K3b::AudioTrack::toCdTrack() const { if( !inList() ) return K3b::Device::Track(); K3b::Msf firstSector; K3b::AudioTrack* track = doc()->firstTrack(); while( track != this ) { firstSector += track->length(); track = track->next(); } K3b::Device::Track cdTrack( firstSector, firstSector + length() - 1, K3b::Device::Track::TYPE_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 K3b::AudioTrack::debug() { qDebug() << "Track " << this << endl << " Prev: " << d->prev << endl << " Next: " << d->next << endl << " Sources:" << endl; K3b::AudioDataSource* s = d->firstSource; while( s ) { qDebug() << " " << s << " - Prev: " << s->prev() << " Next: " << s->next(); s = s->next(); } } K3b::AudioDataSource* K3b::AudioTrack::getSource( int index ) const { int i = 0; K3b::AudioDataSource* source = firstSource(); while ( source && i < index ) { source = source->next(); ++i; } return source; } void K3b::AudioTrack::emitSourceAboutToBeRemoved( AudioDataSource* source ) { emit sourceAboutToBeRemoved( source->sourceIndex() ); if ( doc() ) { emit doc()->sourceAboutToBeRemoved( this, source->sourceIndex() ); } } void K3b::AudioTrack::emitSourceRemoved( K3b::AudioDataSource* source ) { if ( doc() ) { // set the first source by hand (without using setFirstSource() ) // just to avoid the model to read invalid firstSources if ( !source->prev() ) d->firstSource = source->next(); emit doc()->sourceRemoved( this, source->sourceIndex() ); } emit sourceRemoved( source->sourceIndex() ); // and now call the setFirstSource() to make sure the proper signals // are emitted if ( !source->prev() ) setFirstSource( source->next() ); } void K3b::AudioTrack::emitSourceAboutToBeAdded( int position ) { emit sourceAboutToBeAdded( position ); if ( doc() ) { emit doc()->sourceAboutToBeAdded( this, position ); } } void K3b::AudioTrack::emitSourceAdded( AudioDataSource* source ) { if ( doc() ) { emit doc()->sourceAdded( this, source->sourceIndex() ); doc()->slotTrackChanged( this ); } else { emit sourceAdded( source->sourceIndex() ); } } void K3b::AudioTrack::setIndex0Offset( const Msf& index0Offset ) { d->index0Offset = index0Offset; } void K3b::AudioTrack::setParent( K3b::AudioDoc* parent ) { d->parent = parent; }