1 /* Converter.cpp */
2
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4 *
5 * This file is part of sayonara player
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "Converter.h"
22
23 #include "Utils/FileUtils.h"
24 #include "Utils/Logger/Logger.h"
25 #include "Utils/MetaData/MetaDataList.h"
26 #include "Utils/StandardPaths.h"
27 #include "Utils/Utils.h"
28
29 #include <QStringList>
30 #include <QProcess>
31 #include <QMap>
32 #include <QVariant>
33
34 using ProcessMap = QList<QStringList>;
35
36 struct Converter::Private
37 {
38 QStringList logFiles;
39 ProcessMap processes;
40 MetaDataList tracks;
41 QString targetDirectory;
42 QMap<int, QProcess*> runningProcesses;
43
44 int initialCount;
45 int currentIndex;
46 int commandCount;
47 int errorCount;
48 int processCount;
49 int quality;
50 bool stopped;
51
PrivateConverter::Private52 Private(int quality) :
53 initialCount(0),
54 currentIndex(0),
55 commandCount(0),
56 errorCount(0),
57 processCount(0),
58 quality(quality),
59 stopped(false)
60 {}
61 };
62
Converter(int quality,QObject * parent)63 Converter::Converter(int quality, QObject* parent) :
64 QObject(parent)
65 {
66 m = Pimpl::make<Private>(quality);
67
68 }
69
~Converter()70 Converter::~Converter()
71 {
72 Util::File::deleteFiles(m->logFiles);
73 }
74
loggingDirectory() const75 QString Converter::loggingDirectory() const
76 {
77 return Util::tempPath("encoder-logs");
78 }
79
targetDirectory() const80 QString Converter::targetDirectory() const
81 {
82 return m->targetDirectory;
83 }
84
addMetadata(const MetaDataList & tracks)85 void Converter::addMetadata(const MetaDataList& tracks)
86 {
87 m->initialCount = tracks.count();
88 m->tracks.clear();
89
90 QStringList formats = supportedInputFormats();
91 for(const MetaData& md : tracks)
92 {
93 const QString filepath(md.filepath());
94 for(const QString& format : formats)
95 {
96 if(filepath.endsWith(format, Qt::CaseInsensitive))
97 {
98 m->tracks << md;
99 break;
100 }
101 }
102 }
103 }
104
start(int numThreads,const QString & targetDirectoryectory)105 void Converter::start(int numThreads, const QString& targetDirectoryectory)
106 {
107 m->processCount = numThreads;
108 m->targetDirectory = targetDirectoryectory;
109 m->runningProcesses.clear();
110 m->errorCount = 0;
111 m->processes.clear();
112 m->currentIndex = 0;
113 m->stopped = false;
114
115 for(const MetaData& md : m->tracks)
116 {
117 m->processes << processEntry(md);
118 }
119
120 m->commandCount = m->tracks.count();
121
122 auto processCount = std::min(m->processes.size(), m->processCount);
123 for(int i = 0; i < processCount; i++)
124 {
125 QString process_name = binary();
126 QStringList arguments = m->processes.takeFirst();
127
128 startProcess(process_name, arguments);
129 }
130 }
131
stop()132 void Converter::stop()
133 {
134 m->stopped = true;
135
136 const auto processKeys = m->runningProcesses.keys();
137 for(int key : processKeys)
138 {
139 QProcess* p = m->runningProcesses.value(key);
140 if(p)
141 {
142 p->kill();
143 }
144 }
145 }
146
errorCount() const147 int Converter::errorCount() const
148 {
149 return m->errorCount;
150 }
151
quality() const152 int Converter::quality() const
153 {
154 return m->quality;
155 }
156
fileCount() const157 int Converter::fileCount() const
158 {
159 return m->tracks.count();
160 }
161
initialCount() const162 int Converter::initialCount() const
163 {
164 return m->initialCount;
165 }
166
isAvailable() const167 bool Converter::isAvailable() const
168 {
169 return QProcess::startDetached(binary(), {"--version"});
170 }
171
targetFile(const MetaData & md) const172 QString Converter::targetFile(const MetaData& md) const
173 {
174 const auto [dirname, filename] = Util::File::splitFilename(md.filepath());
175
176 QString target = Util::File::cleanFilename(m->targetDirectory + "/" + filename);
177 target = target.left(target.lastIndexOf(".")) + "." + extension();
178
179 return target;
180 }
181
startProcess(const QString & command,const QStringList & arguments)182 bool Converter::startProcess(const QString& command, const QStringList& arguments)
183 {
184 m->currentIndex++;
185
186 Util::File::createDir(loggingDirectory());
187 const auto logFile = QString("%1/encoder_%2_%3.out")
188 .arg(loggingDirectory())
189 .arg(binary())
190 .arg(m->currentIndex);
191
192 m->logFiles << logFile;
193
194 int id = Util::randomNumber(100, 1000000);
195
196 auto* process = new QProcess(this);
197 process->setStandardOutputFile(logFile);
198 process->setStandardErrorFile(logFile);
199 process->setProperty("id", id);
200 m->runningProcesses.insert(id, process);
201
202 connect(process,
203 static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
204 this,
205 &Converter::processFinished);
206 connect(process, &QProcess::errorOccurred, this, &Converter::errorOccured);
207
208 spLog(Log::Debug, this) << "Starting: " << command << " " << arguments.join(" ");
209 process->start(command, arguments);
210
211 return true;
212 }
213
processFinished(int ret,QProcess::ExitStatus exit_status)214 void Converter::processFinished(int ret, QProcess::ExitStatus exit_status)
215 {
216 Q_UNUSED(exit_status)
217
218 auto* process = static_cast<QProcess*>(sender());
219
220 spLog(Log::Debug, this) << "process finished";
221 if(ret != 0)
222 {
223 m->errorCount++;
224 spLog(Log::Warning, this) << "Encoding process failed with code " << ret << process->program();
225 }
226
227 int id = process->property("id").toInt();
228 m->runningProcesses.remove(id);
229
230 emit sigProgress(100 - (m->processes.size() * 100) / m->commandCount);
231
232 if(!m->processes.isEmpty() && !m->stopped)
233 {
234 QStringList arguments = m->processes.takeFirst();
235 startProcess(binary(), arguments);
236 }
237
238 else if(m->runningProcesses.isEmpty())
239 {
240 emit sigFinished();
241 }
242
243 else
244 {
245 spLog(Log::Warning, this) << "Something strange happened";
246 }
247 }
248
errorOccured(QProcess::ProcessError err)249 void Converter::errorOccured(QProcess::ProcessError err)
250 {
251 auto* p = static_cast<QProcess*>(this->sender());
252
253 spLog(Log::Warning, this) << p->program() << ": " << p->arguments().join(", ");
254 spLog(Log::Warning, this) << "Error: QProcess:ProcessError " << p->errorString();
255
256 processFinished(10000 + err, QProcess::ExitStatus::NormalExit);
257 }
258