/* * * $Id: k3baudioprojectconvertingthread.cpp 619556 2007-01-03 17:38:12Z trueg $ * Copyright (C) 2005 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 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 "k3baudioprojectconvertingthread.h" #include "k3bpatternparser.h" #include #include #include #include #include #include "k3bcuefilewriter.h" #include #include #include #include #include #include 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; TQString fileType; }; K3bAudioProjectConvertingThread::K3bAudioProjectConvertingThread( K3bAudioDoc* doc ) : K3bThread(), m_doc(doc) { d = new Private(); } K3bAudioProjectConvertingThread::~K3bAudioProjectConvertingThread() { delete d->waveFileWriter; delete d; } void K3bAudioProjectConvertingThread::setFileType( const TQString& 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 ) { TQString& filename = m_tracks[0].second; TQString dir = filename.left( filename.findRev("/") ); if( !TDEStandardDirs::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, TQString::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( TQFile::exists( m_tracks[d->currentTrackIndex].second ) ) { TQFile::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 TQString& filename ) { TQString dir = filename.left( filename.findRev("/") ); if( !TDEStandardDirs::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, TQString::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, TQString::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 "/" TQString playlistDir = m_playlistFilename.left( m_playlistFilename.findRev( "/" ) ); if( !TDEStandardDirs::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 ); TQFile f( m_playlistFilename ); if( f.open( IO_WriteOnly ) ) { TQTextStream 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 TQString imageFile = m_tracks[0].second.section( '/', -1 ); cueWriter.setImage( imageFile, ( d->fileType.isEmpty() ? TQString("WAVE") : d->fileType ) ); // use the same base name as the image file TQString 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 ); } TQString K3bAudioProjectConvertingThread::findRelativePath( const TQString& absPath, const TQString& baseDir ) { TQString baseDir_ = K3b::prepareDir( K3b::fixupPath(baseDir) ); TQString 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; } TQString 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); } TQString 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() ); }