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"