File indexing completed on 2024-05-19 04:51:10

0001 /*
0002   Copyright (C) 2005 Benjamin Meyer <ben at meyerhome dot net>
0003   Copyright (C) 2018 Yuri Chornoivan <yurchor@mageia.org>
0004 
0005   This program is free software; you can redistribute it and/or modify
0006   it under the terms of the GNU General Public License as published by
0007   the Free Software Foundation; either version 2 of the License, or
0008   (at your option) any later version.
0009 
0010   This program is distributed in the hope that it will be useful,
0011   but WITHOUT ANY WARRANTY; without even the implied warranty of
0012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0013   GNU General Public License for more details.
0014 
0015   You should have received a copy of the GNU General Public License
0016   along with this program; if not, write to the Free Software
0017   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
0018   USA.
0019 */
0020 
0021 #include <config-audiocd.h>
0022 
0023 #include "audiocd_kio_debug.h"
0024 #include "audiocd_opus_encoder.h"
0025 #include "encoderopus.h"
0026 
0027 #include <QDir>
0028 #include <QFileInfo>
0029 #include <QStandardPaths>
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 EncoderOpus(worker));
0038 }
0039 }
0040 
0041 static const int bitrates[] = {6, 12, 24, 48, 64, 80, 96, 128, 160, 192, 256};
0042 
0043 class EncoderOpus::Private
0044 {
0045 public:
0046     int bitrate;
0047     bool write_opus_comments;
0048     bool waitingForWrite;
0049     bool processHasExited;
0050     QString lastErrorMessage;
0051     uint lastSize;
0052     KProcess *currentEncodeProcess = nullptr;
0053     QTemporaryFile *tempFile = nullptr;
0054 };
0055 
0056 EncoderOpus::EncoderOpus(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 EncoderOpus::~EncoderOpus()
0068 {
0069     delete d;
0070 }
0071 
0072 QWidget *EncoderOpus::getConfigureWidget(KConfigSkeleton **manager) const
0073 {
0074     (*manager) = Settings::self();
0075     auto config = new EncoderOpusConfig();
0076     config->kcfg_opus_complexity->setRange(0, 10);
0077     config->kcfg_opus_complexity->setSingleStep(1);
0078     config->opus_bitrate_settings->hide();
0079     return config;
0080 }
0081 
0082 bool EncoderOpus::init()
0083 {
0084     // Determine if opusenc is installed on the system or not.
0085     if (QStandardPaths::findExecutable(QStringLiteral("opusenc")).isEmpty())
0086         return false;
0087 
0088     return true;
0089 }
0090 
0091 void EncoderOpus::loadSettings()
0092 {
0093     // Generate the command line arguments for the current settings
0094     args.clear();
0095 
0096     Settings *settings = Settings::self();
0097 
0098     if (settings->opus_enc_complexity()) {
0099         args.append(QStringLiteral("--comp"));
0100         args.append(QStringLiteral("%1").arg(settings->opus_complexity()));
0101     } else {
0102         // Constant Bitrate Encoding
0103         if (settings->set_opus_cbr()) {
0104             args.append(QStringLiteral("--bitrate"));
0105             args.append(QStringLiteral("%1").arg(bitrates[settings->opus_cbr()]));
0106             d->bitrate = settings->opus_cbr();
0107             args.append(QStringLiteral("--hard-cbr"));
0108         };
0109         // Constrained Variable Bitrate Encoding
0110         if (settings->set_opus_cvbr()) {
0111             args.append(QStringLiteral("--bitrate"));
0112             args.append(QStringLiteral("%1").arg(bitrates[settings->opus_cvbr()]));
0113             d->bitrate = bitrates[settings->opus_cvbr()];
0114             args.append(QStringLiteral("--cvbr"));
0115         };
0116         // Average Variable Bitrate Encoding
0117         if (settings->set_opus_vbr()) {
0118             args.append(QStringLiteral("--bitrate"));
0119             args.append(QStringLiteral("%1").arg(bitrates[settings->opus_vbr()]));
0120             d->bitrate = bitrates[settings->opus_vbr()];
0121             args.append(QStringLiteral("--vbr"));
0122         };
0123     };
0124 
0125     d->write_opus_comments = settings->opus_comments();
0126 }
0127 
0128 unsigned long EncoderOpus::size(long time_secs) const {
0129     return (time_secs * d->bitrate * 1000) / 8;
0130 }
0131 
0132 long EncoderOpus::readInit(long /*size*/)
0133 {
0134     // Create KProcess
0135     d->currentEncodeProcess = new KProcess();
0136     d->tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/kaudiocd_XXXXXX") + QLatin1String(".opus"));
0137     d->tempFile->open();
0138     d->lastErrorMessage.clear();
0139     d->processHasExited = false;
0140 
0141     // --raw raw/pcm
0142     // --raw-rate 44100 (because it is raw you have to specify this)
0143     *(d->currentEncodeProcess) << QStringLiteral("opusenc") << QStringLiteral("--raw") << QStringLiteral("--raw-rate") << QStringLiteral("44100");
0144     *(d->currentEncodeProcess) << args;
0145     *d->currentEncodeProcess << trackInfo;
0146 
0147     // Read in stdin, output to the temp file
0148     *d->currentEncodeProcess << QStringLiteral("-") << d->tempFile->fileName();
0149 
0150     // qCDebug(AUDIOCD_KIO_LOG) << args;
0151 
0152     connect(d->currentEncodeProcess, &KProcess::readyReadStandardOutput, this, &EncoderOpus::receivedStdout);
0153     connect(d->currentEncodeProcess, &KProcess::readyReadStandardError, this, &EncoderOpus::receivedStderr);
0154     connect(d->currentEncodeProcess, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished), this, &EncoderOpus::processExited);
0155 
0156     // Launch!
0157     d->currentEncodeProcess->setOutputChannelMode(KProcess::SeparateChannels);
0158     d->currentEncodeProcess->start();
0159     return 0;
0160 }
0161 
0162 void EncoderOpus::processExited(int exitCode, QProcess::ExitStatus /*status*/)
0163 {
0164     qCDebug(AUDIOCD_KIO_LOG) << "Opusenc Encoding process exited with: " << exitCode;
0165     d->processHasExited = true;
0166 }
0167 
0168 void EncoderOpus::receivedStderr()
0169 {
0170     QByteArray error = d->currentEncodeProcess->readAllStandardError();
0171     qCDebug(AUDIOCD_KIO_LOG) << "Opusenc stderr: " << error;
0172     if (!d->lastErrorMessage.isEmpty())
0173         d->lastErrorMessage += QLatin1Char('\t');
0174     d->lastErrorMessage += QString::fromLocal8Bit(error);
0175 }
0176 
0177 void EncoderOpus::receivedStdout()
0178 {
0179     QString output = QString::fromLocal8Bit(d->currentEncodeProcess->readAllStandardOutput());
0180     qCDebug(AUDIOCD_KIO_LOG) << "Opusenc stdout: " << output;
0181 }
0182 
0183 long EncoderOpus::read(qint16 *buf, int frames)
0184 {
0185     if (!d->currentEncodeProcess)
0186         return 0;
0187     if (d->processHasExited)
0188         return -1;
0189 
0190     // Pipe the raw data to opusenc
0191     char *cbuf = reinterpret_cast<char *>(buf);
0192     d->currentEncodeProcess->write(cbuf, frames * 4);
0193     // We can't return until the buffer has been written
0194     d->currentEncodeProcess->waitForBytesWritten(-1);
0195 
0196     // Determine the file size increase
0197     QFileInfo file(d->tempFile->fileName());
0198     uint change = file.size() - d->lastSize;
0199     d->lastSize = file.size();
0200     return change;
0201 }
0202 
0203 long EncoderOpus::readCleanup()
0204 {
0205     if (!d->currentEncodeProcess)
0206         return 0;
0207 
0208     // Let opusenc tag the first frame of the opus
0209     d->currentEncodeProcess->closeWriteChannel();
0210     d->currentEncodeProcess->waitForFinished(-1);
0211 
0212     // Now copy the file out of the temp into kio
0213     QFile file(d->tempFile->fileName());
0214     if (file.open(QIODevice::ReadOnly)) {
0215         char data[1024];
0216         while (!file.atEnd()) {
0217             uint read = file.read(data, 1024);
0218             QByteArray output(data, read);
0219             ioWorker->data(output);
0220         }
0221         file.close();
0222     }
0223 
0224     // cleanup the process and temp
0225     delete d->currentEncodeProcess;
0226     delete d->tempFile;
0227     d->lastSize = 0;
0228 
0229     return 0;
0230 }
0231 
0232 void EncoderOpus::fillSongInfo(KCDDB::CDInfo info, int track, const QString &comment)
0233 {
0234     trackInfo.clear();
0235 
0236     if (!d->write_opus_comments)
0237         return;
0238 
0239     trackInfo.append(QStringLiteral("--album"));
0240     trackInfo.append(info.get(Title).toString());
0241 
0242     trackInfo.append(QStringLiteral("--artist"));
0243     trackInfo.append(info.track(track - 1).get(Artist).toString());
0244 
0245     trackInfo.append(QStringLiteral("--title"));
0246     trackInfo.append(info.track(track - 1).get(Title).toString());
0247 
0248     trackInfo.append(QStringLiteral("--date"));
0249     trackInfo.append(QDate(info.get(Year).toInt(), 1, 1).toString(Qt::ISODate));
0250 
0251     trackInfo.append(QStringLiteral("--comment"));
0252     trackInfo.append(QStringLiteral("DESCRIPTION=") + comment);
0253 
0254     trackInfo.append(QStringLiteral("--comment"));
0255     trackInfo.append(QStringLiteral("TRACKNUMBER=") + QString::number(track));
0256 
0257     trackInfo.append(QStringLiteral("--genre"));
0258     trackInfo.append(QStringLiteral("%1").arg(info.get(Genre).toString()));
0259 }
0260 
0261 QString EncoderOpus::lastErrorMessage() const
0262 {
0263     return d->lastErrorMessage;
0264 }
0265 
0266 #include "moc_encoderopus.cpp"