File indexing completed on 2024-05-12 04:49:59
0001 /* 0002 Copyright (C) 2005 Benjamin Meyer <ben at meyerhome dot net> 0003 0004 This program is free software; you can redistribute it and/or modify 0005 it under the terms of the GNU General Public License as published by 0006 the Free Software Foundation; either version 2 of the License, or 0007 (at your option) any later version. 0008 0009 This program is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 GNU General Public License for more details. 0013 0014 You should have received a copy of the GNU General Public License 0015 along with this program; if not, write to the Free Software 0016 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 0017 USA. 0018 */ 0019 0020 #include <config-audiocd.h> 0021 0022 #include "audiocd_kio_debug.h" 0023 #include "audiocd_lame_encoder.h" 0024 #include "encoderlame.h" 0025 0026 #include <QDir> 0027 #include <QFileInfo> 0028 #include <QStandardPaths> 0029 #include <QStringList> 0030 #include <QTemporaryFile> 0031 0032 Q_LOGGING_CATEGORY(AUDIOCD_KIO_LOG, "kf.kio.workers.audiocd") 0033 0034 extern "C" { 0035 AUDIOCDPLUGINS_EXPORT void create_audiocd_encoders(KIO::WorkerBase *worker, QList<AudioCDEncoder *> &encoders) 0036 { 0037 encoders.append(new EncoderLame(worker)); 0038 } 0039 } 0040 0041 static int bitrates[] = {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}; 0042 0043 class EncoderLame::Private 0044 { 0045 public: 0046 int bitrate; 0047 bool waitingForWrite; 0048 bool processHasExited; 0049 QString lastErrorMessage; 0050 QStringList genreList; 0051 uint lastSize; 0052 KProcess *currentEncodeProcess; 0053 QTemporaryFile *tempFile; 0054 }; 0055 0056 EncoderLame::EncoderLame(KIO::WorkerBase *worker) 0057 : QObject() 0058 , AudioCDEncoder(worker) 0059 { 0060 d = new Private(); 0061 d->waitingForWrite = false; 0062 d->processHasExited = false; 0063 d->lastSize = 0; 0064 loadSettings(); 0065 } 0066 0067 EncoderLame::~EncoderLame() 0068 { 0069 delete d; 0070 } 0071 0072 QWidget *EncoderLame::getConfigureWidget(KConfigSkeleton **manager) const 0073 { 0074 (*manager) = Settings::self(); 0075 auto config = new EncoderLameConfig(); 0076 config->cbr_settings->hide(); 0077 return config; 0078 } 0079 0080 bool EncoderLame::init() 0081 { 0082 // Determine if lame is installed on the system or not. 0083 if (QStandardPaths::findExecutable(QStringLiteral("lame")).isEmpty()) 0084 return false; 0085 0086 // Ask lame for the list of genres it knows; otherwise it barfs when doing 0087 // e.g. lame --tg 'Vocal Jazz' 0088 KProcess proc; 0089 proc.setOutputChannelMode(KProcess::MergedChannels); 0090 proc << QStringLiteral("lame") << QStringLiteral("--genre-list"); 0091 proc.execute(); 0092 0093 if (proc.exitStatus() != QProcess::NormalExit) 0094 return false; 0095 0096 QByteArray array = proc.readAll(); 0097 QString str = QString::fromLocal8Bit(array); 0098 d->genreList = str.split(QLatin1Char('\n'), Qt::SkipEmptyParts); 0099 // Remove the numbers in front of every genre 0100 for (QStringList::Iterator it = d->genreList.begin(); it != d->genreList.end(); ++it) { 0101 QString &genre = *it; 0102 int i = 0; 0103 while (i < genre.length() && (genre[i].isSpace() || genre[i].isDigit())) 0104 ++i; 0105 genre = genre.mid(i); 0106 } 0107 // qCDebug(AUDIOCD_KIO_LOG) << "Available genres:" << d->genreList; 0108 0109 return true; 0110 } 0111 0112 void EncoderLame::loadSettings() 0113 { 0114 // Generate the command line arguments for the current settings 0115 args.clear(); 0116 0117 Settings *settings = Settings::self(); 0118 0119 // Should we bitswap? I'm unsure about the proper logic for this. 0120 // KDE3 always swapped on little-endian hosts, 0121 // while #171065 says we shouldn't always do so. 0122 // So... let's make it configurable, at least. 0123 0124 bool swap = false; 0125 switch (settings->byte_swap()) { 0126 case Settings::EnumByte_swap::Yes: 0127 swap = true; 0128 break; 0129 case Settings::EnumByte_swap::No: 0130 swap = false; 0131 break; 0132 default: 0133 #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN 0134 swap = false; // it looks like lame is now clever enough to do the right 0135 // thing by default? (#171065) 0136 #else 0137 swap = false; 0138 #endif 0139 } 0140 if (swap) 0141 args << QStringLiteral("-x"); 0142 0143 int quality = settings->quality(); 0144 if (quality < 0) 0145 quality = quality * -1; 0146 if (quality > 9) 0147 quality = 9; 0148 0149 int method = settings->bitrate_constant() ? 0 : 1; 0150 0151 if (method == 0) { 0152 // Constant Bitrate Encoding 0153 args.append(QStringLiteral("-b")); 0154 args.append(QStringLiteral("%1").arg(bitrates[settings->cbr_bitrate()])); 0155 d->bitrate = bitrates[settings->cbr_bitrate()]; 0156 args.append(QStringLiteral("-q")); 0157 args.append(QStringLiteral("%1").arg(quality)); 0158 } else { 0159 // Variable Bitrate Encoding 0160 if (settings->vbr_average_br()) { 0161 args.append(QStringLiteral("--abr")); 0162 args.append(QStringLiteral("%1").arg(bitrates[settings->vbr_mean_brate()])); 0163 d->bitrate = bitrates[settings->vbr_mean_brate()]; 0164 if (settings->vbr_min_br()) { 0165 args.append(QStringLiteral("-b")); 0166 args.append(QStringLiteral("%1").arg(bitrates[settings->vbr_min_brate()])); 0167 } 0168 if (settings->vbr_min_hard()) 0169 args.append(QStringLiteral("-F")); 0170 if (settings->vbr_max_br()) { 0171 args.append(QStringLiteral("-B")); 0172 args.append(QStringLiteral("%1").arg(bitrates[settings->vbr_max_brate()])); 0173 } 0174 } else { 0175 d->bitrate = 128; 0176 args.append(QStringLiteral("-V")); 0177 args.append(QStringLiteral("%1").arg(quality)); 0178 } 0179 if (!settings->vbr_xing_tag()) 0180 args.append(QStringLiteral("-t")); 0181 } 0182 0183 args.append(QStringLiteral("-m")); 0184 switch (settings->stereo()) { 0185 case 0: 0186 args.append(QStringLiteral("s")); 0187 break; 0188 case 1: 0189 args.append(QStringLiteral("j")); 0190 break; 0191 case 2: 0192 args.append(QStringLiteral("d")); 0193 break; 0194 case 3: 0195 args.append(QStringLiteral("m")); 0196 break; 0197 default: 0198 args.append(QStringLiteral("s")); 0199 break; 0200 } 0201 0202 if (settings->copyright()) 0203 args.append(QStringLiteral("-c")); 0204 if (!settings->original()) 0205 args.append(QStringLiteral("-o")); 0206 if (settings->iso()) 0207 args.append(QStringLiteral("--strictly-enforce-ISO")); 0208 if (settings->crc()) 0209 args.append(QStringLiteral("-p")); 0210 0211 if (settings->enable_lowpass()) { 0212 args.append(QStringLiteral("--lowpass")); 0213 args.append(QStringLiteral("%1").arg(settings->lowfilterfreq())); 0214 0215 if (settings->set_lpf_width()) { 0216 args.append(QStringLiteral("--lowpass-width")); 0217 args.append(QStringLiteral("%1").arg(settings->lowfilterwidth())); 0218 } 0219 } 0220 0221 if (settings->enable_highpass()) { 0222 args.append(QStringLiteral("--hipass")); 0223 args.append(QStringLiteral("%1").arg(settings->highfilterfreq())); 0224 0225 if (settings->set_hpf_width()) { 0226 args.append(QStringLiteral("--hipass-width")); 0227 args.append(QStringLiteral("%1").arg(settings->highfilterwidth())); 0228 } 0229 } 0230 } 0231 0232 unsigned long EncoderLame::size(long time_secs) const { 0233 return (time_secs * d->bitrate * 1000) / 8; 0234 } 0235 0236 long EncoderLame::readInit(long /*size*/) 0237 { 0238 // Create KProcess 0239 d->currentEncodeProcess = new KProcess(); 0240 d->tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/kaudiocd_XXXXXX") + QLatin1String(".mp3")); 0241 d->tempFile->open(); 0242 d->lastErrorMessage.clear(); 0243 d->processHasExited = false; 0244 0245 // -r raw/pcm 0246 // -s 44.1 (because it is raw you have to specify this) 0247 *(d->currentEncodeProcess) << QStringLiteral("lame") << QStringLiteral("--verbose") << QStringLiteral("-r") << QStringLiteral("-s") 0248 << QStringLiteral("44.1"); 0249 *(d->currentEncodeProcess) << args; 0250 if (Settings::self()->id3_tag()) 0251 *d->currentEncodeProcess << trackInfo; 0252 0253 // Read in stdin, output to the temp file 0254 *d->currentEncodeProcess << QStringLiteral("-") << d->tempFile->fileName(); 0255 0256 // qCDebug(AUDIOCD_KIO_LOG) << d->currentEncodeProcess->args(); 0257 0258 connect(d->currentEncodeProcess, &KProcess::readyReadStandardOutput, this, &EncoderLame::receivedStdout); 0259 connect(d->currentEncodeProcess, &KProcess::readyReadStandardError, this, &EncoderLame::receivedStderr); 0260 // connect(d->currentEncodeProcess, &KProcess::bytesWritten, 0261 // this, &EncoderLame::wroteStdin); 0262 0263 connect(d->currentEncodeProcess, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished), this, &EncoderLame::processExited); 0264 0265 // Launch! 0266 d->currentEncodeProcess->setOutputChannelMode(KProcess::SeparateChannels); 0267 d->currentEncodeProcess->start(); 0268 return 0; 0269 } 0270 0271 void EncoderLame::processExited(int exitCode, QProcess::ExitStatus /*status*/) 0272 { 0273 qCDebug(AUDIOCD_KIO_LOG) << "Lame Encoding process exited with: " << exitCode; 0274 d->processHasExited = true; 0275 } 0276 0277 void EncoderLame::receivedStderr() 0278 { 0279 QByteArray error = d->currentEncodeProcess->readAllStandardError(); 0280 qCDebug(AUDIOCD_KIO_LOG) << "Lame stderr: " << error; 0281 if (!d->lastErrorMessage.isEmpty()) 0282 d->lastErrorMessage += QLatin1Char('\t'); 0283 d->lastErrorMessage += QString::fromLocal8Bit(error); 0284 } 0285 0286 void EncoderLame::receivedStdout() 0287 { 0288 QString output = QString::fromLocal8Bit(d->currentEncodeProcess->readAllStandardOutput()); 0289 qCDebug(AUDIOCD_KIO_LOG) << "Lame stdout: " << output; 0290 } 0291 0292 // void EncoderLame::wroteStdin(){ 0293 // d->waitingForWrite = false; 0294 // } 0295 0296 long EncoderLame::read(qint16 *buf, int frames) 0297 { 0298 if (!d->currentEncodeProcess) 0299 return 0; 0300 if (d->processHasExited) 0301 return -1; 0302 0303 // Pipe the raw data to lame 0304 char *cbuf = reinterpret_cast<char *>(buf); 0305 d->currentEncodeProcess->write(cbuf, frames * 4); 0306 // We can't return until the buffer has been written 0307 d->currentEncodeProcess->waitForBytesWritten(-1); 0308 0309 // Determine the file size increase 0310 QFileInfo file(d->tempFile->fileName()); 0311 uint change = file.size() - d->lastSize; 0312 d->lastSize = file.size(); 0313 return change; 0314 } 0315 0316 long EncoderLame::readCleanup() 0317 { 0318 if (!d->currentEncodeProcess) 0319 return 0; 0320 0321 // Let lame tag the first frame of the mp3 0322 d->currentEncodeProcess->closeWriteChannel(); 0323 d->currentEncodeProcess->waitForFinished(-1); 0324 0325 // Now copy the file out of the temp into kio 0326 QFile file(d->tempFile->fileName()); 0327 if (file.open(QIODevice::ReadOnly)) { 0328 char data[1024]; 0329 while (!file.atEnd()) { 0330 uint read = file.read(data, 1024); 0331 QByteArray output(data, read); 0332 ioWorker->data(output); 0333 } 0334 file.close(); 0335 } 0336 0337 // cleanup the process and temp 0338 delete d->currentEncodeProcess; 0339 delete d->tempFile; 0340 d->lastSize = 0; 0341 0342 return 0; 0343 } 0344 0345 void EncoderLame::fillSongInfo(KCDDB::CDInfo info, int track, const QString &comment) 0346 { 0347 trackInfo.clear(); 0348 trackInfo.append(QStringLiteral("--tt")); 0349 trackInfo.append(info.track(track - 1).get(Title).toString()); 0350 0351 trackInfo.append(QStringLiteral("--ta")); 0352 trackInfo.append(info.track(track - 1).get(Artist).toString()); 0353 0354 trackInfo.append(QStringLiteral("--tl")); 0355 trackInfo.append(info.get(Title).toString()); 0356 0357 trackInfo.append(QStringLiteral("--ty")); 0358 trackInfo.append(QStringLiteral("%1").arg(info.get(Year).toString())); 0359 0360 trackInfo.append(QStringLiteral("--tc")); 0361 trackInfo.append(comment); 0362 0363 trackInfo.append(QStringLiteral("--tn")); 0364 trackInfo.append(QStringLiteral("%1").arg(track)); 0365 0366 const QString genre = info.get(Genre).toString(); 0367 if (d->genreList.indexOf(genre) != -1) { 0368 trackInfo.append(QStringLiteral("--tg")); 0369 trackInfo.append(genre); 0370 } 0371 } 0372 0373 QString EncoderLame::lastErrorMessage() const 0374 { 0375 return d->lastErrorMessage; 0376 } 0377 0378 #include "moc_encoderlame.cpp"