/* Copyright (C) 2005 Benjamin Meyer 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "encoderlame.h" #include "encoderlameconfig.h" #include "audiocd_lame_encoder.h" #include #include #include #include #include #include #include #include #include #include #include "collectingprocess.h" extern "C" { KDE_EXPORT void create_audiocd_encoders(TDEIO::SlaveBase *slave, TQPtrList &encoders) { encoders.append(new EncoderLame(slave)); } } static int bitrates[] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 }; class EncoderLame::Private { public: int bitrate; bool waitingForWrite; bool processHasExited; TQString lastErrorMessage; TQStringList genreList; uint lastSize; TDEProcess *currentEncodeProcess; KTempFile *tempFile; }; EncoderLame::EncoderLame(TDEIO::SlaveBase *slave) : TQObject(), AudioCDEncoder(slave) { d = new Private(); d->waitingForWrite = false; d->processHasExited = false; d->lastSize = 0; loadSettings(); } EncoderLame::~EncoderLame(){ delete d; } TQWidget* EncoderLame::getConfigureWidget(TDEConfigSkeleton** manager) const { (*manager) = Settings::self(); TDEGlobal::locale()->insertCatalogue("audiocd_encoder_lame"); EncoderLameConfig *config = new EncoderLameConfig(); config->cbr_settings->hide(); return config; } bool EncoderLame::init(){ // Determine if lame is installed on the system or not. if ( KStandardDirs::findExe( "lame" ).isEmpty() ) return false; // Ask lame for the list of genres it knows; otherwise it barfs when doing // e.g. lame --tg 'Vocal Jazz' CollectingProcess proc; proc << "lame" << "--genre-list"; proc.start(TDEProcess::Block, TDEProcess::Stdout); if(proc.exitStatus() != 0) return false; const TQByteArray data = proc.collectedStdout(); TQString str; if ( !data.isEmpty() ) str = TQString::fromLocal8Bit( data, data.size() ); d->genreList = TQStringList::split( '\n', str ); // Remove the numbers in front of every genre for( TQStringList::Iterator it = d->genreList.begin(); it != d->genreList.end(); ++it ) { TQString& genre = *it; uint i = 0; while ( i < genre.length() && ( genre[i].isSpace() || genre[i].isDigit() ) ) ++i; genre = genre.mid( i ); } //kdDebug(7117) << "Available genres:" << d->genreList << endl; return true; } void EncoderLame::loadSettings(){ // Generate the command line arguments for the current settings args.clear(); Settings *settings = Settings::self(); int quality = settings->quality(); if (quality < 0 ) quality = quality *-1; if (quality > 9) quality = 9; int method = settings->bitrate_constant() ? 0 : 1 ; if (method == 0) { // Constant Bitrate Encoding args.append("-b"); args.append(TQString("%1").arg(bitrates[settings->cbr_bitrate()])); d->bitrate = bitrates[settings->cbr_bitrate()]; args.append("-q"); args.append(TQString("%1").arg(quality)); } else { // Variable Bitrate Encoding if (settings->vbr_average_br()) { args.append("--abr"); args.append(TQString("%1").arg(bitrates[settings->vbr_mean_brate()])); d->bitrate = bitrates[settings->vbr_mean_brate()]; if (settings->vbr_min_br()){ args.append("-b"); args.append(TQString("%1").arg(bitrates[settings->vbr_min_brate()])); } if (settings->vbr_min_hard()) args.append("-F"); if (settings->vbr_max_br()){ args.append("-B"); args.append(TQString("%1").arg(bitrates[settings->vbr_max_brate()])); } } else { d->bitrate = 128; args.append("-V"); args.append(TQString("%1").arg(quality)); } if ( !settings->vbr_xing_tag() ) args.append("-t"); } args.append("-m"); switch ( settings->stereo() ) { case 0: args.append("s"); break; case 1: args.append("j"); break; case 2: args.append("d"); break; case 3: args.append("m"); break; default: args.append("s"); break; } if(settings->copyright()) args.append("-c"); if(!settings->original()) args.append("-o"); if(settings->iso()) args.append("--strictly-enforce-ISO"); if(settings->crc()) args.append("-p"); if ( settings->enable_lowpass() ) { args.append("--lowpass"); args.append(TQString("%1").arg(settings->lowfilterfreq())); if (settings->set_lpf_width()){ args.append("--lowpass-width"); args.append(TQString("%1").arg(settings->lowfilterwidth())); } } if ( settings->enable_highpass()) { args.append("--hipass"); args.append(TQString("%1").arg(settings->highfilterfreq())); if (settings->set_hpf_width()){ args.append("--hipass-width"); args.append(TQString("%1").arg(settings->highfilterwidth())); } } } unsigned long EncoderLame::size(long time_secs) const { return (time_secs * d->bitrate * 1000)/8; } long EncoderLame::readInit(long /*size*/){ // Create TDEProcess d->currentEncodeProcess = new TDEProcess(0); TQString prefix = locateLocal("tmp", ""); d->tempFile = new KTempFile(prefix, ".mp3"); d->tempFile->setAutoDelete(true); d->lastErrorMessage = TQString(); d->processHasExited = false; // -x bitswap // -r raw/pcm // -s 44.1 (because it is raw you have to specify this) // #if __BYTE_ORDER == __LITTLE_ENDIAN // *(d->currentEncodeProcess) << "lame" << "--verbose" << "-x" << "-r" << "-s" << "44.1"; // #else *(d->currentEncodeProcess) << "lame" << "--verbose" << "-r" << "-s" << "44.1"; // #endif *(d->currentEncodeProcess) << args; if(Settings::self()->id3_tag()) *d->currentEncodeProcess << trackInfo; // Read in stdin, output to the temp file *d->currentEncodeProcess << "-" << d->tempFile->name().latin1(); //kdDebug(7117) << d->currentEncodeProcess->args() << endl; connect(d->currentEncodeProcess, TQT_SIGNAL(receivedStdout(TDEProcess *, char *, int)), this, TQT_SLOT(receivedStdout(TDEProcess *, char *, int))); connect(d->currentEncodeProcess, TQT_SIGNAL(receivedStderr(TDEProcess *, char *, int)), this, TQT_SLOT(receivedStderr(TDEProcess *, char *, int))); connect(d->currentEncodeProcess, TQT_SIGNAL(wroteStdin(TDEProcess *)), this, TQT_SLOT(wroteStdin(TDEProcess *))); connect(d->currentEncodeProcess, TQT_SIGNAL(processExited(TDEProcess *)), this, TQT_SLOT(processExited(TDEProcess *))); // Launch! d->currentEncodeProcess->start(TDEProcess::NotifyOnExit, KShellProcess::All); return 0; } void EncoderLame::processExited ( TDEProcess *process ){ kdDebug(7117) << "Lame Encoding process exited with: " << process->exitStatus() << endl; d->processHasExited = true; } void EncoderLame::receivedStderr( TDEProcess * /*process*/, char *buffer, int /*buflen*/ ){ kdDebug(7117) << "Lame stderr: " << buffer << endl; if ( !d->lastErrorMessage.isEmpty() ) d->lastErrorMessage += '\t'; d->lastErrorMessage += TQString::fromLocal8Bit( buffer ); } void EncoderLame::receivedStdout( TDEProcess * /*process*/, char *buffer, int /*length*/ ){ kdDebug(7117) << "Lame stdout: " << buffer << endl; } void EncoderLame::wroteStdin( TDEProcess * /*procces*/ ){ d->waitingForWrite = false; } long EncoderLame::read(int16_t *buf, int frames){ if(!d->currentEncodeProcess) return 0; if (d->processHasExited) return -1; // Pipe the raw data to lame char * cbuf = reinterpret_cast(buf); d->currentEncodeProcess->writeStdin( cbuf, frames*4); // We can't return until the buffer has been written d->waitingForWrite = true; while(d->waitingForWrite && d->currentEncodeProcess->isRunning()){ kapp->processEvents(); usleep(1); } // Determine the file size increase TQFileInfo file(d->tempFile->name()); uint change = file.size() - d->lastSize; d->lastSize = file.size(); return change; } long EncoderLame::readCleanup(){ if(!d->currentEncodeProcess) return 0; // Let lame tag the first frame of the mp3 d->currentEncodeProcess->closeStdin(); while( d->currentEncodeProcess->isRunning()){ kapp->processEvents(); usleep(1); } // Now copy the file out of the temp into tdeio TQFile file( d->tempFile->name() ); if ( file.open( IO_ReadOnly ) ) { TQByteArray output; char data[1024]; while ( !file.atEnd() ) { uint read = file.readBlock(data, 1024); output.setRawData(data, read); ioslave->data(output); output.resetRawData(data, read); } file.close(); } // cleanup the process and temp delete d->currentEncodeProcess; delete d->tempFile; d->lastSize = 0; return 0; } void EncoderLame::fillSongInfo( KCDDB::CDInfo info, int track, const TQString &comment ){ trackInfo.clear(); trackInfo.append("--tt"); trackInfo.append(info.trackInfoList[track].get("title").toString()); trackInfo.append("--ta"); trackInfo.append(info.get("artist").toString()); trackInfo.append("--tl"); trackInfo.append(info.get("title").toString()); trackInfo.append("--ty"); trackInfo.append(TQString("%1").arg(info.get("year").toString())); trackInfo.append("--tc"); trackInfo.append(comment); trackInfo.append("--tn"); trackInfo.append(TQString("%1").arg(track+1)); const TQString genre = info.get( "genre" ).toString(); if ( d->genreList.find( genre ) != d->genreList.end() ) { trackInfo.append("--tg"); trackInfo.append(genre); } } TQString EncoderLame::lastErrorMessage() const { return d->lastErrorMessage; } #include "encoderlame.moc"