OSDN Git Service

Bump minimum supported QAAC add-in version.
[lamexp/LameXP.git] / src / Encoder_Opus.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2023 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU GENERAL PUBLIC LICENSE as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version; always including the non-optional
9 // LAMEXP GNU GENERAL PUBLIC LICENSE ADDENDUM. See "License.txt" file!
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 //
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
22
23 #include "Encoder_Opus.h"
24
25 //MUtils
26 #include <MUtils/Global.h>
27
28 //Internal
29 #include "Global.h"
30 #include "Model_Settings.h"
31 #include "MimeTypes.h"
32
33 //Qt
34 #include <QProcess>
35 #include <QDir>
36 #include <QUUid>
37
38 static const int g_opusBitrateLUT[30] = { 8, 9, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 144, 160, 176, 192, 208, 224, 240, 256 };
39
40 ///////////////////////////////////////////////////////////////////////////////
41 // Encoder Info
42 ///////////////////////////////////////////////////////////////////////////////
43
44 class OpusEncoderInfo : public AbstractEncoderInfo
45 {
46         virtual bool isModeSupported(int mode) const
47         {
48                 switch(mode)
49                 {
50                 case SettingsModel::VBRMode:
51                 case SettingsModel::ABRMode:
52                 case SettingsModel::CBRMode:
53                         return true;
54                         break;
55                 default:
56                         MUTILS_THROW("Bad RC mode specified!");
57                 }
58         }
59
60         virtual int valueCount(int mode) const
61         {
62                 switch(mode)
63                 {
64                 case SettingsModel::VBRMode:
65                 case SettingsModel::ABRMode:
66                 case SettingsModel::CBRMode:
67                         return 30;
68                         break;
69                 default:
70                         MUTILS_THROW("Bad RC mode specified!");
71                 }
72         }
73
74         virtual int valueAt(int mode, int index) const
75         {
76                 switch(mode)
77                 {
78                 case SettingsModel::VBRMode:
79                 case SettingsModel::ABRMode:
80                 case SettingsModel::CBRMode:
81                         return g_opusBitrateLUT[qBound(0, index, 29)];
82                         break;
83                 default:
84                         MUTILS_THROW("Bad RC mode specified!");
85                 }
86         }
87
88         virtual int valueType(int mode) const
89         {
90                 switch(mode)
91                 {
92                 case SettingsModel::VBRMode:
93                 case SettingsModel::ABRMode:
94                         return TYPE_APPROX_BITRATE;
95                         break;
96                 case SettingsModel::CBRMode:
97                         return TYPE_BITRATE;
98                         break;
99                 default:
100                         MUTILS_THROW("Bad RC mode specified!");
101                 }
102         }
103
104         virtual const char *description(void) const
105         {
106                 static const char* s_description = "Opus-Tools OpusEnc (libopus)";
107                 return s_description;
108         }
109
110         virtual const char *extension(void) const
111         {
112                 static const char* s_extension = "opus";
113                 return s_extension;
114         }
115
116         virtual bool isResamplingSupported(void) const
117         {
118                 return false;
119         }
120 }
121 static const g_opusEncoderInfo;
122
123 ///////////////////////////////////////////////////////////////////////////////
124 // Encoder implementation
125 ///////////////////////////////////////////////////////////////////////////////
126
127 OpusEncoder::OpusEncoder(void)
128 :
129         m_binary(lamexp_tools_lookup(L1S("opusenc.exe")))
130 {
131         if(m_binary.isEmpty())
132         {
133                 MUTILS_THROW("Error initializing Opus encoder. Tool 'opusenc.exe' is not registred!");
134         }
135
136         m_configOptimizeFor = 0;
137         m_configEncodeComplexity = 10;
138         m_configFrameSize = 3;
139 }
140
141 OpusEncoder::~OpusEncoder(void)
142 {
143 }
144
145 bool OpusEncoder::encode(const QString &sourceFile, const AudioFileModel_MetaInfo &metaInfo, const unsigned int /*duration*/, const unsigned int /*channels*/, const QString &outputFile, QAtomicInt &abortFlag)
146 {
147         QProcess process;
148         QStringList args;
149
150         switch(m_configRCMode)
151         {
152         case SettingsModel::VBRMode:
153                 args << L1S("--vbr");
154                 break;
155         case SettingsModel::ABRMode:
156                 args << L1S("--cvbr");
157                 break;
158         case SettingsModel::CBRMode:
159                 args << L1S("--hard-cbr");
160                 break;
161         default:
162                 MUTILS_THROW("Bad rate-control mode!");
163                 break;
164         }
165
166         args << "--comp" << QString::number(m_configEncodeComplexity);
167
168         switch(m_configFrameSize)
169         {
170         case 0:
171                 args << L1S("--framesize") << L1S("2.5");
172                 break;
173         case 1:
174                 args << L1S("--framesize") << L1S("5");
175                 break;
176         case 2:
177                 args << L1S("--framesize") << L1S("10");
178                 break;
179         case 3:
180                 args << L1S("--framesize") << L1S("20");
181                 break;
182         case 4:
183                 args << L1S("--framesize") << L1S("40");
184                 break;
185         case 5:
186                 args << L1S("--framesize") << L1S("60");
187                 break;
188         }
189         
190         args << L1S("--bitrate") << QString::number(g_opusBitrateLUT[qBound(0, m_configBitrate, 29)]);
191
192         if(!metaInfo.title().isEmpty())   args << L1S("--title")   << cleanTag(metaInfo.title());
193         if(!metaInfo.artist().isEmpty())  args << L1S("--artist")  << cleanTag(metaInfo.artist());
194         if(!metaInfo.album().isEmpty())   args << L1S("--album")   << cleanTag(metaInfo.album());
195         if(!metaInfo.genre().isEmpty())   args << L1S("--genre")   << cleanTag(metaInfo.genre());
196         if(metaInfo.year())               args << L1S("--date")    << QString::number(metaInfo.year());
197         if(metaInfo.position())           args << L1S("--comment") << QString("tracknumber=%1").arg(QString::number(metaInfo.position()));
198         if(!metaInfo.comment().isEmpty()) args << L1S("--comment") << QString("comment=%1").arg(cleanTag(metaInfo.comment()));
199         if(!metaInfo.cover().isEmpty())   args << L1S("--picture") << makeCoverParam(metaInfo.cover());
200
201         if(!m_configCustomParams.isEmpty()) args << m_configCustomParams.split(" ", QString::SkipEmptyParts);
202
203         args << QDir::toNativeSeparators(sourceFile);
204         args << QDir::toNativeSeparators(outputFile);
205
206         if(!startProcess(process, m_binary, args))
207         {
208                 return false;
209         }
210
211         int prevProgress = -1;
212         QRegExp regExp(L1S("\\[.\\]\\s+(\\d+)%"));
213
214         const result_t result = awaitProcess(process, abortFlag, [this, &prevProgress, &regExp](const QString &text)
215         {
216                 if (regExp.lastIndexIn(text) >= 0)
217                 {
218                         qint32 newProgress;
219                         if (MUtils::regexp_parse_int32(regExp, newProgress))
220                         {
221                                 if (newProgress > prevProgress)
222                                 {
223                                         emit statusUpdated(newProgress);
224                                         prevProgress = NEXT_PROGRESS(newProgress);
225                                 }
226                         }
227                         return true;
228                 }
229                 return false;
230         });
231         
232         return (result == RESULT_SUCCESS);
233 }
234
235 QString OpusEncoder::detectMimeType(const QString &coverFile)
236 {
237         const QString suffix = QFileInfo(coverFile).suffix();
238         for (size_t i = 0; MIME_TYPES[i].type; i++)
239         {
240                 for (size_t k = 0; MIME_TYPES[i].ext[k]; k++)
241                 {
242                         if (suffix.compare(QString::fromLatin1(MIME_TYPES[i].ext[k]), Qt::CaseInsensitive) == 0)
243                         {
244                                 return QString::fromLatin1(MIME_TYPES[i].type);
245                         }
246                 }
247         }
248
249         qWarning("Unknown MIME type for extension '%s' -> using default!", MUTILS_UTF8(coverFile));
250         return QString::fromLatin1(MIME_TYPES[0].type);
251 }
252
253 QString OpusEncoder::makeCoverParam(const QString &coverFile)
254 {
255         return QString("3|%1|||%2").arg(detectMimeType(coverFile), QDir::toNativeSeparators(coverFile));
256 }
257
258 void OpusEncoder::setOptimizeFor(int optimizeFor)
259 {
260         m_configOptimizeFor = qBound(0, optimizeFor, 2);
261 }
262
263 void OpusEncoder::setEncodeComplexity(int complexity)
264 {
265         m_configEncodeComplexity = qBound(0, complexity, 10);
266 }
267
268 void OpusEncoder::setFrameSize(int frameSize)
269 {
270         m_configFrameSize = qBound(0, frameSize, 5);
271 }
272
273 bool OpusEncoder::isFormatSupported(const QString &containerType, const QString& /*containerProfile*/, const QString &formatType, const QString& /*formatProfile*/, const QString& /*formatVersion*/)
274 {
275         if(containerType.compare(L1S("Wave"), Qt::CaseInsensitive) == 0)
276         {
277                 if(formatType.compare(L1S("PCM"), Qt::CaseInsensitive) == 0)
278                 {
279                         return true;
280                 }
281         }
282
283         return false;
284 }
285
286 const unsigned int *OpusEncoder::supportedChannelCount(void)
287 {
288         return NULL;
289 }
290
291 const unsigned int *OpusEncoder::supportedBitdepths(void)
292 {
293         static const unsigned int supportedBPS[] = {8, 16, 24, AudioFileModel::BITDEPTH_IEEE_FLOAT32, NULL};
294         return supportedBPS;
295 }
296
297 const bool OpusEncoder::needsTimingInfo(void)
298 {
299         return true;
300 }
301
302 const AbstractEncoderInfo *OpusEncoder::getEncoderInfo(void)
303 {
304         return &g_opusEncoderInfo;
305 }