summaryrefslogtreecommitdiffstats
path: root/src/rip/k3baudioprojectconvertingthread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rip/k3baudioprojectconvertingthread.cpp')
-rw-r--r--src/rip/k3baudioprojectconvertingthread.cpp459
1 files changed, 459 insertions, 0 deletions
diff --git a/src/rip/k3baudioprojectconvertingthread.cpp b/src/rip/k3baudioprojectconvertingthread.cpp
new file mode 100644
index 0000000..8c175fb
--- /dev/null
+++ b/src/rip/k3baudioprojectconvertingthread.cpp
@@ -0,0 +1,459 @@
+/*
+ *
+ * $Id: k3baudioprojectconvertingthread.cpp 619556 2007-01-03 17:38:12Z trueg $
+ * Copyright (C) 2005 Sebastian Trueg <trueg@k3b.org>
+ *
+ * This file is part of the K3b project.
+ * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * See the file "COPYING" for the exact licensing terms.
+ */
+
+
+#include "k3baudioprojectconvertingthread.h"
+#include "k3bpatternparser.h"
+
+#include <k3bjob.h>
+#include <k3baudiodoc.h>
+#include <k3baudiotrack.h>
+#include <k3baudioencoder.h>
+#include <k3bwavefilewriter.h>
+#include "k3bcuefilewriter.h"
+
+#include <k3bglobals.h>
+
+#include <qfile.h>
+#include <qtimer.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+
+
+
+
+class K3bAudioProjectConvertingThread::Private
+{
+public:
+ Private()
+ : encoder(0),
+ waveFileWriter(0),
+ canceled(false) {
+ }
+
+ // the index of the currently ripped track in m_tracks
+ int currentTrackIndex;
+ long long overallBytesRead;
+ long long overallBytesToRead;
+
+ K3bAudioEncoder* encoder;
+ K3bWaveFileWriter* waveFileWriter;
+
+ bool canceled;
+
+ QString fileType;
+};
+
+
+K3bAudioProjectConvertingThread::K3bAudioProjectConvertingThread( K3bAudioDoc* doc )
+ : K3bThread(),
+ m_doc(doc)
+{
+ d = new Private();
+}
+
+
+K3bAudioProjectConvertingThread::~K3bAudioProjectConvertingThread()
+{
+ delete d->waveFileWriter;
+ delete d;
+}
+
+
+void K3bAudioProjectConvertingThread::setFileType( const QString& t )
+{
+ d->fileType = t;
+}
+
+
+void K3bAudioProjectConvertingThread::setEncoder( K3bAudioEncoder* f )
+{
+ d->encoder = f;
+}
+
+
+void K3bAudioProjectConvertingThread::init()
+{
+ d->canceled = false;
+}
+
+
+void K3bAudioProjectConvertingThread::run()
+{
+ emitStarted();
+ emitNewTask( i18n("Converting Audio Tracks") );
+
+ if( !d->encoder )
+ if( !d->waveFileWriter )
+ d->waveFileWriter = new K3bWaveFileWriter();
+
+
+ d->overallBytesRead = 0;
+ d->overallBytesToRead = m_doc->length().audioBytes();
+
+ if( m_singleFile ) {
+ QString& filename = m_tracks[0].second;
+
+ QString dir = filename.left( filename.findRev("/") );
+ if( !KStandardDirs::makeDir( dir ) ) {
+ emitInfoMessage( i18n("Unable to create directory %1").arg(dir), K3bJob::ERROR );
+ emitFinished(false);
+ return;
+ }
+
+ // initialize
+ bool isOpen = true;
+ if( d->encoder ) {
+ if( isOpen = d->encoder->openFile( d->fileType, filename, m_doc->length() ) ) {
+ // here we use cd Title and Artist
+ d->encoder->setMetaData( K3bAudioEncoder::META_TRACK_ARTIST, m_cddbEntry.cdArtist );
+ d->encoder->setMetaData( K3bAudioEncoder::META_TRACK_TITLE, m_cddbEntry.cdTitle );
+ d->encoder->setMetaData( K3bAudioEncoder::META_TRACK_COMMENT, m_cddbEntry.cdExtInfo );
+ d->encoder->setMetaData( K3bAudioEncoder::META_ALBUM_ARTIST, m_cddbEntry.cdArtist );
+ d->encoder->setMetaData( K3bAudioEncoder::META_ALBUM_TITLE, m_cddbEntry.cdTitle );
+ d->encoder->setMetaData( K3bAudioEncoder::META_ALBUM_COMMENT, m_cddbEntry.cdExtInfo );
+ d->encoder->setMetaData( K3bAudioEncoder::META_YEAR, QString::number(m_cddbEntry.year) );
+ d->encoder->setMetaData( K3bAudioEncoder::META_GENRE, m_cddbEntry.genre );
+ }
+ else
+ emitInfoMessage( d->encoder->lastErrorString(), K3bJob::ERROR );
+ }
+ else {
+ isOpen = d->waveFileWriter->open( filename );
+ }
+
+ if( !isOpen ) {
+ emitInfoMessage( i18n("Unable to open '%1' for writing.").arg(filename), K3bJob::ERROR );
+ emitFinished(false);
+ return;
+ }
+
+ emitInfoMessage( i18n("Converting to single file '%1'.").arg(filename), K3bJob::INFO );
+ }
+
+ bool success = true;
+ K3bAudioTrack* track = m_doc->firstTrack();
+ unsigned int i = 0;
+ while( track ) {
+ d->currentTrackIndex = i;
+ if( !convertTrack( track, m_singleFile ? m_tracks[0].second : m_tracks[i].second ) ) {
+ success = false;
+ break;
+ }
+
+ emitInfoMessage( i18n("Successfully converted track %1.").arg(i+1), K3bJob::INFO );
+
+ track = track->next();
+ ++i;
+ }
+
+ if( m_singleFile ) {
+ if( d->encoder )
+ d->encoder->closeFile();
+ else
+ d->waveFileWriter->close();
+ }
+
+ if( !d->canceled && success && m_writePlaylist ) {
+ success = success && writePlaylist();
+ }
+
+ if( !d->canceled && success && m_writeCueFile && m_singleFile ) {
+ success = success && writeCueFile();
+ }
+
+ if( d->canceled ) {
+ if( d->currentTrackIndex >= 0 && d->currentTrackIndex < (int)m_tracks.count() ) {
+ if( QFile::exists( m_tracks[d->currentTrackIndex].second ) ) {
+ QFile::remove( m_tracks[d->currentTrackIndex].second );
+ emitInfoMessage( i18n("Removed partial file '%1'.").arg(m_tracks[d->currentTrackIndex].second), K3bJob::INFO );
+ }
+ }
+
+ emitCanceled();
+ emitFinished(false);
+ }
+ else
+ emitFinished(success);
+}
+
+
+bool K3bAudioProjectConvertingThread::convertTrack( K3bAudioTrack* track, const QString& filename )
+{
+ QString dir = filename.left( filename.findRev("/") );
+ if( !KStandardDirs::makeDir( dir ) ) {
+ emitInfoMessage( i18n("Unable to create directory %1").arg(dir), K3bJob::ERROR );
+ return false;
+ }
+
+ // initialize
+ bool isOpen = true;
+ if( !m_singleFile ) {
+ if( d->encoder ) {
+ if( isOpen = d->encoder->openFile( d->fileType,
+ filename,
+ track->length() ) ) {
+
+ d->encoder->setMetaData( K3bAudioEncoder::META_TRACK_ARTIST, m_cddbEntry.artists[d->currentTrackIndex] );
+ d->encoder->setMetaData( K3bAudioEncoder::META_TRACK_TITLE, m_cddbEntry.titles[d->currentTrackIndex] );
+ d->encoder->setMetaData( K3bAudioEncoder::META_TRACK_COMMENT, m_cddbEntry.extInfos[d->currentTrackIndex] );
+ d->encoder->setMetaData( K3bAudioEncoder::META_TRACK_NUMBER, QString::number(d->currentTrackIndex+1).rightJustify( 2, '0' ) );
+ d->encoder->setMetaData( K3bAudioEncoder::META_ALBUM_ARTIST, m_cddbEntry.cdArtist );
+ d->encoder->setMetaData( K3bAudioEncoder::META_ALBUM_TITLE, m_cddbEntry.cdTitle );
+ d->encoder->setMetaData( K3bAudioEncoder::META_ALBUM_COMMENT, m_cddbEntry.cdExtInfo );
+ d->encoder->setMetaData( K3bAudioEncoder::META_YEAR, QString::number(m_cddbEntry.year) );
+ d->encoder->setMetaData( K3bAudioEncoder::META_GENRE, m_cddbEntry.genre );
+ }
+ else
+ emitInfoMessage( d->encoder->lastErrorString(), K3bJob::ERROR );
+ }
+ else {
+ isOpen = d->waveFileWriter->open( filename );
+ }
+
+ if( !isOpen ) {
+ emitInfoMessage( i18n("Unable to open '%1' for writing.").arg(filename), K3bJob::ERROR );
+ return false;
+ }
+ }
+
+
+ if( !m_cddbEntry.artists[d->currentTrackIndex].isEmpty() &&
+ !m_cddbEntry.titles[d->currentTrackIndex].isEmpty() )
+ emitNewSubTask( i18n("Converting track %1 (%2 - %3)")
+ .arg(d->currentTrackIndex+1)
+ .arg(m_cddbEntry.artists[d->currentTrackIndex])
+ .arg(m_cddbEntry.titles[d->currentTrackIndex]) );
+ else
+ emitNewSubTask( i18n("Converting track %1").arg(d->currentTrackIndex+1) );
+
+
+ // do the conversion
+ // ----------------------
+
+ char buffer[10*1024];
+ const int bufferLength = 10*1024;
+ int readLength = 0;
+ long long readFile = 0;
+ track->seek(0);
+ while( !d->canceled && ( readLength = track->read( buffer, bufferLength ) ) > 0 ) {
+
+ if( d->encoder ) {
+ // the tracks produce big endian samples
+ // so we need to swap the bytes here
+ char b;
+ for( int i = 0; i < bufferLength-1; i+=2 ) {
+ b = buffer[i];
+ buffer[i] = buffer[i+1];
+ buffer[i+1] = b;
+ }
+
+ if( d->encoder->encode( buffer, readLength ) < 0 ) {
+ kdDebug() << "(K3bAudioProjectConvertingThread) error while encoding." << endl;
+ emitInfoMessage( d->encoder->lastErrorString(), K3bJob::ERROR );
+ emitInfoMessage( i18n("Error while encoding track %1.").arg(d->currentTrackIndex+1), K3bJob::ERROR );
+ return false;
+ }
+ }
+ else {
+ d->waveFileWriter->write( buffer,
+ readLength,
+ K3bWaveFileWriter::BigEndian );
+ }
+
+ d->overallBytesRead += readLength;
+ readFile += readLength;
+ emitSubPercent( 100*readFile/track->size() );
+ emitPercent( 100*d->overallBytesRead/d->overallBytesToRead );
+ }
+
+ if( !m_singleFile ) {
+ if( d->encoder )
+ d->encoder->closeFile();
+ else
+ d->waveFileWriter->close();
+ }
+
+ return ( readLength == 0 );
+}
+
+
+void K3bAudioProjectConvertingThread::cancel()
+{
+ d->canceled = true;
+}
+
+
+bool K3bAudioProjectConvertingThread::writePlaylist()
+{
+ // this is an absolut path so there is always a "/"
+ QString playlistDir = m_playlistFilename.left( m_playlistFilename.findRev( "/" ) );
+
+ if( !KStandardDirs::makeDir( playlistDir ) ) {
+ emitInfoMessage( i18n("Unable to create directory %1").arg(playlistDir), K3bJob::ERROR );
+ return false;
+ }
+
+ emitInfoMessage( i18n("Writing playlist to %1.").arg( m_playlistFilename ), K3bJob::INFO );
+
+ QFile f( m_playlistFilename );
+ if( f.open( IO_WriteOnly ) ) {
+ QTextStream t( &f );
+
+ // format descriptor
+ t << "#EXTM3U" << endl;
+
+ // now write the entries (or the entry if m_singleFile)
+ if( m_singleFile ) {
+ // extra info
+ t << "#EXTINF:" << m_doc->length().lba() << ",";
+ if( !m_cddbEntry.cdArtist.isEmpty() && !m_cddbEntry.cdTitle.isEmpty() )
+ t << m_cddbEntry.cdArtist << " - " << m_cddbEntry.cdTitle << endl;
+ else
+ t << m_tracks[0].second.mid(m_tracks[0].second.findRev("/") + 1,
+ m_tracks[0].second.length() - m_tracks[0].second.findRev("/") - 5)
+ << endl; // filename without extension
+
+ // filename
+ if( m_relativePathInPlaylist )
+ t << findRelativePath( m_tracks[0].second, playlistDir )
+ << endl;
+ else
+ t << m_tracks[0].second << endl;
+ }
+ else {
+ for( unsigned int i = 0; i < m_tracks.count(); ++i ) {
+ int trackIndex = m_tracks[i].first-1;
+
+ // extra info
+ t << "#EXTINF:" << m_doc->length().totalFrames()/75 << ",";
+
+ if( !m_cddbEntry.artists[trackIndex].isEmpty() && !m_cddbEntry.titles[trackIndex].isEmpty() )
+ t << m_cddbEntry.artists[trackIndex] << " - " << m_cddbEntry.titles[trackIndex] << endl;
+ else
+ t << m_tracks[i].second.mid(m_tracks[i].second.findRev("/") + 1,
+ m_tracks[i].second.length()
+ - m_tracks[i].second.findRev("/") - 5)
+ << endl; // filename without extension
+
+ // filename
+ if( m_relativePathInPlaylist )
+ t << findRelativePath( m_tracks[i].second, playlistDir )
+ << endl;
+ else
+ t << m_tracks[i].second << endl;
+ }
+ }
+
+ return ( t.device()->status() == IO_Ok );
+ }
+ else {
+ emitInfoMessage( i18n("Unable to open '%1' for writing.").arg(m_playlistFilename), K3bJob::ERROR );
+ kdDebug() << "(K3bAudioProjectConvertingThread) could not open file " << m_playlistFilename << " for writing." << endl;
+ return false;
+ }
+}
+
+
+bool K3bAudioProjectConvertingThread::writeCueFile()
+{
+ K3bCueFileWriter cueWriter;
+
+ // create a new toc and cd-text
+ K3bDevice::Toc toc;
+ K3bDevice::CdText text;
+ text.setPerformer( m_cddbEntry.cdArtist );
+ text.setTitle( m_cddbEntry.cdTitle );
+ text.reserve( m_tracks.count() );
+ K3b::Msf currentSector;
+ K3bAudioTrack* track = m_doc->firstTrack();
+ int trackNum = 1;
+ while( track ) {
+
+ K3bDevice::Track newTrack( currentSector, (currentSector+=track->length()) - 1, K3bDevice::Track::AUDIO );
+ toc.append( newTrack );
+
+ K3bDevice::TrackCdText trackText;
+ trackText.setPerformer( m_cddbEntry.artists[trackNum-1] );
+ trackText.setTitle( m_cddbEntry.titles[trackNum-1] );
+ text.append( trackText );
+
+ track = track->next();
+ ++trackNum;
+ }
+
+ cueWriter.setData( toc );
+ cueWriter.setCdText( text );
+
+
+ // we always use a relative filename here
+ QString imageFile = m_tracks[0].second.section( '/', -1 );
+ cueWriter.setImage( imageFile, ( d->fileType.isEmpty() ? QString("WAVE") : d->fileType ) );
+
+ // use the same base name as the image file
+ QString cueFile = m_tracks[0].second;
+ cueFile.truncate( cueFile.findRev(".") );
+ cueFile += ".cue";
+
+ emitInfoMessage( i18n("Writing cue file to %1.").arg(cueFile), K3bJob::INFO );
+
+ return cueWriter.save( cueFile );
+}
+
+
+QString K3bAudioProjectConvertingThread::findRelativePath( const QString& absPath, const QString& baseDir )
+{
+ QString baseDir_ = K3b::prepareDir( K3b::fixupPath(baseDir) );
+ QString path = K3b::fixupPath( absPath );
+
+ // both paths have an equal beginning. That's just how it's configured by K3b
+ int pos = baseDir_.find( "/" );
+ int oldPos = pos;
+ while( pos != -1 && path.left( pos+1 ) == baseDir_.left( pos+1 ) ) {
+ oldPos = pos;
+ pos = baseDir_.find( "/", pos+1 );
+ }
+
+ // now the paths are equal up to oldPos, so that's how "deep" we go
+ path = path.mid( oldPos+1 );
+ baseDir_ = baseDir_.mid( oldPos+1 );
+ int numberOfDirs = baseDir_.contains( '/' );
+ for( int i = 0; i < numberOfDirs; ++i )
+ path.prepend( "../" );
+
+ return path;
+}
+
+
+QString K3bAudioProjectConvertingThread::jobDescription() const
+{
+ if( m_cddbEntry.cdTitle.isEmpty() )
+ return i18n("Converting Audio Tracks");
+ else
+ return i18n("Converting Audio Tracks From '%1'").arg(m_cddbEntry.cdTitle);
+}
+
+QString K3bAudioProjectConvertingThread::jobDetails() const
+{
+ if( d->encoder )
+ return i18n("1 track (encoding to %1)",
+ "%n tracks (encoding to %1)",
+ m_tracks.count() ).arg(d->encoder->fileTypeComment(d->fileType));
+ else
+ return i18n("1 track", "%n tracks", m_doc->numOfTracks() );
+}
+