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"