1 /* BEGIN_COMMON_COPYRIGHT_HEADER
2 * (c)LGPL2+
3 *
4 * Flacon - audio File Encoder
5 * https://github.com/flacon/flacon
6 *
7 * Copyright: 2012-2013
8 * Alexander Sokoloff <sokoloff.a@gmail.com>
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 *
24 * END_COMMON_COPYRIGHT_HEADER */
25
26 #include "types.h"
27 #include "formats_in/informat.h"
28 #include "settings.h"
29 #include "inputaudiofile.h"
30 #include "formats_out/outformat.h"
31 #include "converter/sox.h"
32
33 #include <assert.h>
34 #include <QtGlobal>
35 #include <QDir>
36 #include <QDebug>
37 #include <QProcessEnvironment>
38 #include <QDir>
39 #include <QStandardPaths>
40 #include <QCoreApplication>
41 #include <QMetaEnum>
42
43 #ifdef Q_OS_WIN
44 #define PATH_ENV_SEPARATOR ';'
45 #define BINARY_EXT ".exe"
46
47 #elif defined(Q_OS_OS2)
48 #define PATH_ENV_SEPARATOR ';'
49 #define BINARY_EXT ".exe"
50
51 #else
52 #define PATH_ENV_SEPARATOR ':'
53 #define BINARY_EXT ""
54
55 #endif
56
57 #define PROFILES_PREFIX "Profiles"
58
59 QString Settings::mFileName;
60 Settings *Settings::mInstance = nullptr;
61
62 /************************************************
63 * Added on 6.0.0 release, remove after 8.0.0 release
64 ************************************************/
migrateKey(Settings * settings,const QString & oldKey,const QString & newKey)65 static void migrateKey(Settings *settings, const QString &oldKey, const QString &newKey)
66 {
67 if (settings->contains(oldKey) && !settings->contains(newKey))
68 settings->setValue(newKey, settings->value(oldKey));
69 }
70
71 /************************************************
72 * Added on 6.0.0 release, remove after 8.0.0 release
73 ************************************************/
migrateProfile(Settings * settings,const QString & formatId)74 static void migrateProfile(Settings *settings, const QString &formatId)
75 {
76 const OutFormat *format = OutFormat::formatForId(formatId);
77 assert(format != nullptr);
78 if (!format)
79 return;
80
81 QString group = QString("%1/%2/").arg(PROFILES_PREFIX).arg(format->id());
82 settings->setValue(group + "Format", format->id());
83 settings->setValue(group + "Name", format->name());
84
85 if (format->id() == "AAC") {
86 migrateKey(settings, "Aac/UseQuality", group + "UseQuality");
87 migrateKey(settings, "Aac/Quality", group + "Quality");
88 migrateKey(settings, "Aac/Bitrate", group + "Bitrate");
89 }
90
91 if (format->id() == "FLAC") {
92 migrateKey(settings, "Flac/Compression", group + "Compression");
93 migrateKey(settings, "Flac/ReplayGain", group + "ReplayGain");
94 }
95
96 if (format->id() == "MP3") {
97 migrateKey(settings, "Mp3/Preset", group + "Preset");
98 migrateKey(settings, "Mp3/Bitrate", group + "Bitrate");
99 migrateKey(settings, "Mp3/Quality", group + "Quality");
100 migrateKey(settings, "Mp3/ReplayGain", group + "ReplayGain");
101
102 // Replace vbrStandardFast to vbrStandard
103 if (settings->value(group + "Preset", "") == "vbrStandardFast") {
104 settings->setValue(group + "Preset", "vbrStandard");
105 }
106
107 // Replace vbrExtremeFast to vbrExtreme
108 if (settings->value(group + "Preset", "") == "vbrExtremeFast") {
109 settings->setValue(group + "Preset", "vbrExtreme");
110 }
111 }
112
113 if (format->id() == "OGG") {
114 migrateKey(settings, "Ogg/UseQuality", group + "UseQuality");
115 migrateKey(settings, "Ogg/Quality", group + "Quality");
116 migrateKey(settings, "Ogg/MinBitrate", group + "MinBitrate");
117 migrateKey(settings, "Ogg/NormBitrate", group + "NormBitrate");
118 migrateKey(settings, "Ogg/MaxBitrate", group + "MaxBitrate");
119 migrateKey(settings, "Ogg/ReplayGain", group + "ReplayGain");
120 }
121
122 if (format->id() == "OPUS") {
123 migrateKey(settings, "Opus/BitrateType", group + "BitrateType");
124 migrateKey(settings, "Opus/Bitrate", group + "Bitrate");
125 }
126
127 if (format->id() == "WAV") {
128 // No options
129 }
130
131 if (format->id() == "WV") {
132 migrateKey(settings, "WV/Compression", group + "Compression");
133 migrateKey(settings, "WV/ReplayGain", group + "ReplayGain");
134 }
135
136 migrateKey(settings, "OutFiles/Directory", group + Profile::OUT_DIRECTORY_KEY);
137 migrateKey(settings, "OutFiles/Pattern", group + Profile::OUT_PATTERN_KEY);
138
139 migrateKey(settings, "Resample/BitsPerSample", group + Profile::BITS_PER_SAMPLE_KEY);
140 migrateKey(settings, "Resample/SampleRate", group + Profile::SAMPLE_RATE_KEY);
141
142 migrateKey(settings, "PerTrackCue/Create", group + Profile::CREATE_CUE_KEY);
143 migrateKey(settings, "PerTrackCue/FileName", group + Profile::CUE_FILE_NAME_KEY);
144 migrateKey(settings, "PerTrackCue/Pregap", group + Profile::PREGAP_TYPE_KEY);
145 }
146
147 /************************************************
148 * Added on 6.0.0 release, remove after 8.0.0 release
149 ************************************************/
migrateProfiles(Settings * settings)150 static void migrateProfiles(Settings *settings)
151 {
152 settings->allKeys();
153 if (!settings->childGroups().contains(PROFILES_PREFIX)) {
154
155 migrateProfile(settings, "WAV");
156 migrateProfile(settings, "AAC");
157 migrateProfile(settings, "FLAC");
158 migrateProfile(settings, "MP3");
159 migrateProfile(settings, "OGG");
160 migrateProfile(settings, "OPUS");
161 migrateProfile(settings, "WAV");
162 migrateProfile(settings, "WV");
163 }
164
165 if (settings->contains("OutFiles/Format") && !settings->contains("OutFiles/Profile"))
166 settings->setValue("OutFiles/Profile", settings->value("OutFiles/Format"));
167 }
168
169 /************************************************
170
171 ************************************************/
i()172 Settings *Settings::i()
173 {
174 if (!mInstance) {
175 if (mFileName.isEmpty())
176 mInstance = new Settings("flacon", "flacon");
177 else
178 mInstance = new Settings(mFileName);
179 }
180
181 return mInstance;
182 }
183
184 /************************************************
185
186 ************************************************/
setFileName(const QString & fileName)187 void Settings::setFileName(const QString &fileName)
188 {
189 mFileName = fileName;
190 delete mInstance;
191 mInstance = nullptr;
192 }
193
194 /************************************************
195
196 ************************************************/
Settings(const QString & organization,const QString & application)197 Settings::Settings(const QString &organization, const QString &application) :
198 QSettings(organization, application)
199 {
200 setIniCodec("UTF-8");
201 init();
202 }
203
204 /************************************************
205
206 ************************************************/
Settings(const QString & fileName)207 Settings::Settings(const QString &fileName) :
208 QSettings(fileName, QSettings::IniFormat)
209 {
210 setIniCodec("UTF-8");
211 init();
212 }
213
214 /************************************************
215
216 ************************************************/
init()217 void Settings::init()
218 {
219 migrateProfiles(this);
220 if (value(Inet_CDDBHost) == "https://gnudb.gnudb.org/")
221 remove("Inet/CDDBHost");
222
223 setDefaultValue(Tags_DefaultCodepage, "AUTODETECT");
224
225 // Globals **********************************
226 setDefaultValue(Encoder_ThreadCount, 8);
227 setDefaultValue(Encoder_TmpDir, "");
228
229 // Out Files ********************************
230 setDefaultValue(OutFiles_Profile, "FLAC");
231
232 // Internet *********************************
233 setDefaultValue(Inet_CDDBHost, "https://gnudb.gnudb.org/");
234
235 // Misc *************************************
236 setDefaultValue(Misc_LastDir, QDir::homePath());
237
238 // Cover image **************************
239 setDefaultValue(Cover_Mode, coverModeToString(CoverMode::Scale));
240 setDefaultValue(Cover_Size, 500);
241
242 // Embedded Cover image ******************
243 setDefaultValue(EmbeddedCover_Mode, coverModeToString(CoverMode::Disable));
244 setDefaultValue(EmbeddedCover_Size, 500);
245
246 // ConfigureDialog **********************
247 setDefaultValue(ConfigureDialog_Width, 645);
248 setDefaultValue(ConfigureDialog_Height, 425);
249
250 mPrograms << Conv::Sox::programName();
251
252 foreach (OutFormat *format, OutFormat::allFormats()) {
253 mPrograms << format->encoderProgramName();
254 mPrograms << format->gainProgramName();
255 }
256
257 foreach (const InputFormat *format, InputFormat::allFormats()) {
258 mPrograms << format->decoderProgramName();
259 }
260
261 mPrograms.remove("");
262
263 foreach (QString program, mPrograms) {
264 if (!checkProgram(program))
265 setValue("Programs/" + program, findProgram(program));
266 }
267
268 if (!childGroups().contains(PROFILES_PREFIX)) {
269 foreach (OutFormat *format, OutFormat::allFormats()) {
270 QString group = QString("%1/%2/").arg(PROFILES_PREFIX, format->id());
271 setDefaultValue(group + "Format", format->id());
272 setDefaultValue(group + "Name", format->name());
273 }
274 }
275 }
276
277 /************************************************
278
279 ************************************************/
keyToString(Settings::Key key) const280 QString Settings::keyToString(Settings::Key key) const
281 {
282 switch (key) {
283 case Tags_DefaultCodepage:
284 return "Tags/DefaultCodepage";
285
286 // MainWindow **************************
287 case MainWindow_Width:
288 return "MainWindow/Width";
289 case MainWindow_Height:
290 return "MainWindow/Height";
291
292 // Globals *****************************
293 case Encoder_ThreadCount:
294 return "Encoder/ThreadCount";
295 case Encoder_TmpDir:
296 return "Encoder/TmpDir";
297
298 // Out Files ***************************
299 case OutFiles_Profile:
300 return "OutFiles/Profile";
301 case OutFiles_PatternHistory:
302 return "OutFiles/PatternHistory";
303 case OutFiles_DirectoryHistory:
304 return "OutFiles/DirectoryHistory";
305
306 // Internet ****************************
307 case Inet_CDDBHost:
308 return "Inet/CDDBHost";
309
310 // Misc *********************************
311 case Misc_LastDir:
312 return "Misc/LastDirectory";
313
314 // ConfigureDialog **********************
315 case ConfigureDialog_Width:
316 return "ConfigureDialog/Width";
317 case ConfigureDialog_Height:
318 return "ConfigureDialog/Height";
319
320 // Cover image **************************
321 case Cover_Mode:
322 return "Cover/Mode";
323 case Cover_Size:
324 return "Cover/Size";
325
326 // Embedded Cover image ******************
327 case EmbeddedCover_Mode:
328 return "EmbeddedCover/Mode";
329 case EmbeddedCover_Size:
330 return "EmbeddedCover/Size";
331 }
332
333 assert(false);
334 return "";
335 }
336
337 /************************************************
338
339 ************************************************/
~Settings()340 Settings::~Settings()
341 {
342 }
343
344 /************************************************
345
346 ************************************************/
value(Key key,const QVariant & defaultValue) const347 QVariant Settings::value(Key key, const QVariant &defaultValue) const
348 {
349 return value(keyToString(key), defaultValue);
350 }
351
352 /************************************************
353
354 ************************************************/
value(const QString & key,const QVariant & defaultValue) const355 QVariant Settings::value(const QString &key, const QVariant &defaultValue) const
356 {
357 return QSettings::value(key, defaultValue);
358 }
359
360 /************************************************
361 *
362 ************************************************/
groups(const QString & parentGroup) const363 QStringList Settings::groups(const QString &parentGroup) const
364 {
365 QStringList res;
366 for (const QString &key : allKeys()) {
367 if (key.startsWith(parentGroup)) {
368 res << key.section("/", 1, 1);
369 }
370 }
371 res.removeDuplicates();
372 return res;
373 }
374
375 /************************************************
376
377 ************************************************/
checkProgram(const QString & program) const378 bool Settings::checkProgram(const QString &program) const
379 {
380 QString val = programName(program);
381
382 if (val.isEmpty())
383 return false;
384
385 QFileInfo fi(val);
386 return fi.exists() && fi.isExecutable();
387 }
388
389 /************************************************
390
391 ************************************************/
programName(const QString & program) const392 QString Settings::programName(const QString &program) const
393 {
394 #ifdef MAC_BUNDLE
395 return QDir(qApp->applicationDirPath()).absoluteFilePath(program);
396 #else
397 return value("Programs/" + program).toString();
398 #endif
399 }
400
401 /************************************************
402
403 ************************************************/
findProgram(const QString & program) const404 QString Settings::findProgram(const QString &program) const
405 {
406 QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH").split(PATH_ENV_SEPARATOR);
407 foreach (QString path, paths) {
408 QFileInfo fi(path + QDir::separator() + program + BINARY_EXT);
409 if (fi.exists() && fi.isExecutable())
410 return fi.absoluteFilePath();
411 }
412 return "";
413 }
414
415 /************************************************
416 *
417 ************************************************/
outFormat() const418 OutFormat *Settings::outFormat() const
419 {
420 OutFormat *format = OutFormat::formatForId(currentProfile().formatId());
421 if (format)
422 return format;
423
424 return OutFormat::allFormats().first();
425 }
426
427 /************************************************
428 *
429 ************************************************/
tmpDir() const430 QString Settings::tmpDir() const
431 {
432 return value(Encoder_TmpDir).toString();
433 }
434
435 /************************************************
436 *
437 ************************************************/
setTmpDir(const QString & value)438 void Settings::setTmpDir(const QString &value)
439 {
440 setValue(Encoder_TmpDir, value);
441 }
442
443 /************************************************
444
445 ************************************************/
defaultCodepage() const446 QString Settings::defaultCodepage() const
447 {
448 return value(Tags_DefaultCodepage).toString();
449 }
450
451 /************************************************
452
453 ************************************************/
setDefaultCodepage(const QString & value)454 void Settings::setDefaultCodepage(const QString &value)
455 {
456 setValue(Tags_DefaultCodepage, value);
457 }
458
459 /************************************************
460 *
461 ************************************************/
coverMode() const462 CoverMode Settings::coverMode() const
463 {
464 return strToCoverMode(value(Cover_Mode).toString());
465 }
466
467 /************************************************
468 *
469 ************************************************/
setCoverMode(CoverMode value)470 void Settings::setCoverMode(CoverMode value)
471 {
472 setValue(Cover_Mode, coverModeToString(value));
473 }
474
475 /************************************************
476 *
477 ************************************************/
coverImageSize() const478 int Settings::coverImageSize() const
479 {
480 return value(Cover_Size).toInt();
481 }
482
483 /************************************************
484
485 ************************************************/
setCoverImageSize(int value)486 void Settings::setCoverImageSize(int value)
487 {
488 setValue(Cover_Size, value);
489 }
490
491 /************************************************
492
493 ************************************************/
embeddedCoverMode() const494 CoverMode Settings::embeddedCoverMode() const
495 {
496 return strToCoverMode(value(EmbeddedCover_Mode).toString());
497 }
498
499 /************************************************
500
501 ************************************************/
setEmbeddedCoverMode(CoverMode value)502 void Settings::setEmbeddedCoverMode(CoverMode value)
503 {
504 setValue(EmbeddedCover_Mode, coverModeToString(value));
505 }
506
507 /************************************************
508
509 ************************************************/
embeddedCoverImageSize() const510 int Settings::embeddedCoverImageSize() const
511 {
512 return value(EmbeddedCover_Size).toInt();
513 }
514
515 /************************************************
516
517 ************************************************/
setEmbeddedCoverImageSize(int value)518 void Settings::setEmbeddedCoverImageSize(int value)
519 {
520 setValue(EmbeddedCover_Size, value);
521 }
522
523 /************************************************
524
525 ************************************************/
setValue(Settings::Key key,const QVariant & value)526 void Settings::setValue(Settings::Key key, const QVariant &value)
527 {
528 setValue(keyToString(key), value);
529 emit changed();
530 }
531
532 /************************************************
533
534 ************************************************/
setValue(const QString & key,const QVariant & value)535 void Settings::setValue(const QString &key, const QVariant &value)
536 {
537 QSettings::setValue(key, value);
538 emit changed();
539 }
540
541 /************************************************
542
543 ************************************************/
setDefaultValue(Key key,const QVariant & defaultValue)544 void Settings::setDefaultValue(Key key, const QVariant &defaultValue)
545 {
546 setValue(key, value(key, defaultValue));
547 }
548
549 /************************************************
550
551 ************************************************/
setDefaultValue(const QString & key,const QVariant & defaultValue)552 void Settings::setDefaultValue(const QString &key, const QVariant &defaultValue)
553 {
554 setValue(key, value(key, defaultValue));
555 }
556
557 /************************************************
558
559 ************************************************/
profiles() const560 const Profiles &Settings::profiles() const
561 {
562 if (mProfiles.isEmpty()) {
563 const_cast<Settings *>(this)->loadProfiles();
564 }
565
566 return mProfiles;
567 }
568
569 /************************************************
570
571 ************************************************/
profiles()572 Profiles &Settings::profiles()
573 {
574 if (mProfiles.isEmpty()) {
575 const_cast<Settings *>(this)->loadProfiles();
576 }
577
578 return mProfiles;
579 }
580
581 /************************************************
582 *
583 ************************************************/
loadProfiles()584 void Settings::loadProfiles()
585 {
586 QSet<QString> loaded;
587 allKeys();
588 beginGroup(PROFILES_PREFIX);
589 for (QString id : childGroups()) {
590
591 Profile profile(id);
592 profile.load(*this, id);
593
594 if (profile.isValid()) {
595 mProfiles << profile;
596 loaded << profile.formatId();
597 }
598 }
599 endGroup();
600 }
601
602 /************************************************
603 *
604 ************************************************/
setProfiles(const Profiles & profiles)605 void Settings::setProfiles(const Profiles &profiles)
606 {
607 allKeys();
608 beginGroup(PROFILES_PREFIX);
609 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
610 QSet<QString> old = QSet<QString>::fromList(childGroups());
611 #else
612 // After 5.14.0, QT has stated range constructors are available and preferred.
613 // See: https://doc.qt.io/qt-5/qset.html#toList
614 QList<QString> groups = childGroups();
615 QSet<QString> old = QSet<QString>(groups.begin(), groups.end());
616 #endif
617
618 for (const Profile &profile : profiles) {
619 old.remove(profile.id());
620 profile.save(*this, profile.id());
621 }
622
623 for (const QString &id : old) {
624 remove(id);
625 }
626 endGroup();
627 mProfiles.clear();
628 }
629
630 /************************************************
631 *
632 ************************************************/
currentProfile() const633 const Profile &Settings::currentProfile() const
634 {
635 int n = profiles().indexOf(value(OutFiles_Profile).toString());
636 if (n > -1) {
637 return profiles()[qMax(0, n)];
638 }
639
640 return NullProfile();
641 }
642
643 /************************************************
644 *
645 ************************************************/
currentProfile()646 Profile &Settings::currentProfile()
647 {
648
649 int n = profiles().indexOf(value(OutFiles_Profile).toString());
650 if (n > -1) {
651 return profiles()[qMax(0, n)];
652 }
653
654 return NullProfile();
655 }
656
657 /************************************************
658 *
659 ************************************************/
selectProfile(const QString & profileId)660 bool Settings::selectProfile(const QString &profileId)
661 {
662 if (profiles().indexOf(profileId) < 0)
663 return false;
664
665 setValue(OutFiles_Profile, profileId);
666 return true;
667 }
668
669 /************************************************
670 *
671 ************************************************/
encoderThreadsCount() const672 uint Settings::encoderThreadsCount() const
673 {
674 return value(Encoder_ThreadCount).toUInt();
675 }
676
677 /************************************************
678 *
679 ************************************************/
setEncoderThreadsCount(uint value)680 void Settings::setEncoderThreadsCount(uint value)
681 {
682 setValue(Encoder_ThreadCount, value);
683 }
684
685 /************************************************
686 *
687 ************************************************/
cddbHost() const688 QString Settings::cddbHost() const
689 {
690 return value(Inet_CDDBHost).toString();
691 }
692
693 /************************************************
694 *
695 ************************************************/
setCddbHost(const QString & value)696 void Settings::setCddbHost(const QString &value)
697 {
698 setValue(Inet_CDDBHost, value);
699 }
700