Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "consoleapp.h"
24 :
25 : #include <QCoreApplication>
26 :
27 : #include "modularity/ioc.h"
28 : #include "audioplugins/iregisteraudiopluginsscenario.h"
29 :
30 : #include "muse_framework_config.h"
31 : #include "app_config.h"
32 :
33 : #include "log.h"
34 : #include "settings.h"
35 :
36 : using namespace muse;
37 : using namespace mu::app;
38 : using namespace mu::converter;
39 :
40 0 : static std::optional<ConvertTarget> parseTarget(const QMap<MuseScoreCmdOptions::ParamKey, QVariant>& params)
41 : {
42 0 : auto it = params.find(MuseScoreCmdOptions::ParamKey::ScoreRegion);
43 0 : if (it != params.end()) {
44 0 : return it.value().toString().toStdString();
45 : }
46 :
47 0 : it = params.find(MuseScoreCmdOptions::ParamKey::PageNumber);
48 0 : if (it == params.end()) {
49 0 : return std::nullopt;
50 : }
51 :
52 0 : bool ok = true;
53 0 : page_num_t num = it.value().toULongLong(&ok) - 1;
54 :
55 0 : if (!ok) {
56 0 : LOGE() << "Invalid page, ignoring...";
57 0 : return std::nullopt;
58 : }
59 :
60 0 : return num;
61 : }
62 :
63 0 : MuseScoreConsoleApp::MuseScoreConsoleApp(const std::shared_ptr<MuseScoreCmdOptions>& options)
64 0 : : muse::ConsoleApplication(options)
65 : {
66 0 : }
67 :
68 0 : void MuseScoreConsoleApp::showSplash()
69 : {
70 0 : std::cout << "================================================" << std::endl;
71 0 : std::cout << "The MuseScore console application is starting..." << std::endl;
72 0 : std::cout << "================================================" << std::endl;
73 0 : }
74 :
75 0 : void MuseScoreConsoleApp::applyCommandLineOptions(const std::shared_ptr<muse::CmdOptions>& opt)
76 : {
77 0 : IF_ASSERT_FAILED(opt) {
78 0 : return;
79 : }
80 :
81 0 : muse::ConsoleApplication::applyCommandLineOptions(opt);
82 :
83 0 : if (opt->runMode == IApplication::RunMode::AudioPluginRegistration) {
84 0 : return;
85 : }
86 :
87 0 : const std::shared_ptr<MuseScoreCmdOptions> options = std::dynamic_pointer_cast<MuseScoreCmdOptions>(opt);
88 0 : IF_ASSERT_FAILED(options) {
89 0 : return;
90 : }
91 :
92 0 : uiConfiguration()->setCustomPhysicalDotsPerInch(options->ui.physicalDotsPerInch);
93 :
94 0 : notationConfiguration()->setTemplateModeEnabled(options->notation.templateModeEnabled);
95 0 : notationConfiguration()->setTestModeEnabled(options->notation.testModeEnabled);
96 :
97 0 : if (options->runMode == IApplication::RunMode::ConsoleApp) {
98 0 : project::MigrationOptions migration;
99 0 : migration.appVersion = mu::engraving::Constants::MSC_VERSION;
100 :
101 : //! NOTE Don't ask about migration in convert mode
102 0 : migration.isAskAgain = false;
103 :
104 0 : if (options->project.fullMigration) {
105 0 : bool isMigration = options->project.fullMigration.value();
106 0 : migration.isApplyMigration = isMigration;
107 0 : migration.isApplyEdwin = isMigration;
108 0 : migration.isApplyLeland = isMigration;
109 0 : migration.isRemapPercussion = isMigration;
110 : }
111 :
112 : //! NOTE Don't write to settings, just on current session
113 0 : for (project::MigrationType type : project::allMigrationTypes()) {
114 0 : projectConfiguration()->setMigrationOptions(type, migration, false);
115 : }
116 : }
117 :
118 : #ifdef MUE_BUILD_IMPEXP_IMAGESEXPORT_MODULE
119 0 : imagesExportConfiguration()->setTrimMarginPixelSize(options->exportImage.trimMarginPixelSize);
120 0 : imagesExportConfiguration()->setExportPngDpiResolutionOverride(options->exportImage.pngDpiResolution);
121 : #endif
122 :
123 : #ifdef MUE_BUILD_IMPEXP_VIDEOEXPORT_MODULE
124 0 : videoExportConfiguration()->setResolution(options->exportVideo.resolution);
125 0 : videoExportConfiguration()->setFps(options->exportVideo.fps);
126 0 : videoExportConfiguration()->setLeadingSec(options->exportVideo.leadingSec);
127 0 : videoExportConfiguration()->setTrailingSec(options->exportVideo.trailingSec);
128 : #endif
129 :
130 : #ifdef MUE_BUILD_IMPEXP_MIDI_MODULE
131 0 : midiImportExportConfiguration()->setMidiImportOperationsFile(options->importMidi.operationsFile);
132 : #endif
133 : #ifdef MUE_BUILD_IMPEXP_MUSICXML_MODULE
134 0 : musicXmlConfiguration()->setNeedUseDefaultFontOverride(options->importMusicXml.useDefaultFont);
135 0 : musicXmlConfiguration()->setInferTextTypeOverride(options->importMusicXml.inferTextType);
136 : #endif
137 : #ifdef MUE_BUILD_IMPEXP_AUDIOEXPORT_MODULE
138 0 : audioExportConfiguration()->setExportMp3BitrateOverride(options->exportAudio.mp3Bitrate);
139 : #endif
140 : #ifdef MUE_BUILD_IMPEXP_GUITARPRO_MODULE
141 0 : guitarProConfiguration()->setLinkedTabStaffCreated(options->guitarPro.linkedTabStaffCreated);
142 0 : guitarProConfiguration()->setExperimental(options->guitarPro.experimental);
143 : #endif
144 0 : if (options->app.revertToFactorySettings) {
145 0 : settings()->reset(options->app.revertToFactorySettings.value());
146 : }
147 0 : }
148 :
149 0 : void MuseScoreConsoleApp::doStartupScenario(const muse::modularity::ContextPtr& ctxId)
150 : {
151 0 : const std::shared_ptr<MuseScoreCmdOptions> options = std::dynamic_pointer_cast<MuseScoreCmdOptions>(contextData(ctxId).options);
152 0 : IF_ASSERT_FAILED(options) {
153 0 : return;
154 : }
155 :
156 0 : switch (options->runMode) {
157 0 : case IApplication::RunMode::ConsoleApp: {
158 : // ====================================================
159 : // Process Testflow
160 : // ====================================================
161 0 : MuseScoreCmdOptions::Testflow testflowOptions = options->testflow;
162 0 : if (!testflowOptions.testCaseNameOrFile.isEmpty()) {
163 0 : processTestflow(testflowOptions, ctxId);
164 : } else {
165 : // ====================================================
166 : // Process Diagnostic
167 : // ====================================================
168 0 : MuseScoreCmdOptions::Diagnostic diagnostic = options->diagnostic;
169 0 : if (diagnostic.type != DiagnosticType::Undefined) {
170 0 : int code = processDiagnostic(diagnostic, ctxId);
171 0 : qApp->exit(code);
172 : } else {
173 : // ====================================================
174 : // Process Converter
175 : // ====================================================
176 0 : MuseScoreCmdOptions::ConverterTask task = options->converterTask;
177 0 : int code = processConverter(task, ctxId);
178 0 : qApp->exit(code);
179 0 : }
180 0 : }
181 0 : } break;
182 0 : case IApplication::RunMode::AudioPluginRegistration: {
183 0 : MuseScoreCmdOptions::AudioPluginRegistration pluginRegistration = options->audioPluginRegistration;
184 :
185 0 : int code = processAudioPluginRegistration(pluginRegistration, ctxId);
186 0 : qApp->exit(code);
187 0 : } break;
188 0 : default: {
189 0 : UNREACHABLE;
190 : }
191 : }
192 0 : }
193 :
194 0 : int MuseScoreConsoleApp::processConverter(const MuseScoreCmdOptions::ConverterTask& task, const muse::modularity::ContextPtr& ctx)
195 : {
196 0 : muse::ContextInject<converter::IConverterController> converter = { ctx };
197 0 : muse::ContextInject<playback::ISoundProfilesRepository> soundProfilesRepository = { ctx };
198 :
199 0 : Ret ret = make_ret(Ret::Code::Ok);
200 0 : String soundProfile = task.params[MuseScoreCmdOptions::ParamKey::SoundProfile].toString();
201 0 : UriQuery extensionUri = UriQuery(task.params[MuseScoreCmdOptions::ParamKey::ExtensionUri].toString().toStdString());
202 :
203 0 : if (!soundProfile.isEmpty() && !soundProfilesRepository()->containsProfile(soundProfile)) {
204 0 : LOGE() << "Unknown sound profile: " << soundProfile;
205 0 : soundProfile.clear();
206 : }
207 :
208 0 : converter::OpenParams openParams;
209 0 : openParams.stylePath = task.params[MuseScoreCmdOptions::ParamKey::StylePath].toString();
210 0 : openParams.forceMode = task.params[MuseScoreCmdOptions::ParamKey::ForceMode].toBool();
211 0 : openParams.unrollRepeats = task.params[MuseScoreCmdOptions::ParamKey::UnrollRepeats].toBool();
212 :
213 0 : switch (task.type) {
214 0 : case ConvertType::Batch:
215 0 : ret = converter()->batchConvert(task.inputFile, openParams, soundProfile, extensionUri);
216 0 : break;
217 0 : case ConvertType::File: {
218 0 : std::string transposeOptionsJson = task.params[MuseScoreCmdOptions::ParamKey::ScoreTransposeOptions].toString().toStdString();
219 0 : std::optional<ConvertTarget> target = parseTarget(task.params);
220 0 : io::path_t tracksDiffPath = task.params[MuseScoreCmdOptions::ParamKey::TracksDiffPath].toString();
221 0 : ret = converter()->fileConvert(task.inputFile, task.outputFile, openParams, soundProfile, tracksDiffPath,
222 0 : extensionUri, transposeOptionsJson, target);
223 0 : } break;
224 0 : case ConvertType::ConvertScoreParts:
225 0 : ret = converter()->convertScoreParts(task.inputFile, task.outputFile, openParams);
226 0 : break;
227 0 : case ConvertType::ExportScoreMedia: {
228 0 : muse::io::path_t highlightConfigPath = task.params[MuseScoreCmdOptions::ParamKey::HighlightConfigPath].toString();
229 0 : ret = converter()->exportScoreMedia(task.inputFile, task.outputFile, openParams, highlightConfigPath);
230 0 : } break;
231 0 : case ConvertType::ExportScoreMeta:
232 0 : ret = converter()->exportScoreMeta(task.inputFile, task.outputFile, openParams);
233 0 : break;
234 0 : case ConvertType::ExportScoreParts:
235 0 : ret = converter()->exportScoreParts(task.inputFile, task.outputFile, openParams);
236 0 : break;
237 0 : case ConvertType::ExportScorePartsPdf:
238 0 : ret = converter()->exportScorePartsPdfs(task.inputFile, task.outputFile, openParams);
239 0 : break;
240 0 : case ConvertType::ExportScoreTranspose: {
241 0 : std::string scoreTranspose = task.params[MuseScoreCmdOptions::ParamKey::ScoreTransposeOptions].toString().toStdString();
242 0 : ret = converter()->exportScoreTranspose(task.inputFile, task.outputFile, scoreTranspose, openParams);
243 0 : } break;
244 0 : case ConvertType::ExportScoreElements: {
245 0 : ret = converter()->exportScoreElements(task.inputFile, task.outputFile, openParams);
246 0 : } break;
247 0 : case ConvertType::ExportScoreVideo: {
248 0 : bool withAudio = !task.params[MuseScoreCmdOptions::ParamKey::NoAudio].toBool();
249 0 : ret = converter()->exportScoreVideo(task.inputFile, task.outputFile, openParams, withAudio);
250 0 : } break;
251 0 : case ConvertType::SourceUpdate: {
252 0 : std::string scoreSource = task.params[MuseScoreCmdOptions::ParamKey::ScoreSource].toString().toStdString();
253 0 : ret = converter()->updateSource(task.inputFile, scoreSource, openParams.forceMode);
254 0 : } break;
255 : }
256 :
257 0 : if (!ret) {
258 0 : LOGE() << "failed convert, error: " << ret.toString();
259 : }
260 :
261 0 : return ret.code();
262 0 : }
263 :
264 0 : int MuseScoreConsoleApp::processDiagnostic(const MuseScoreCmdOptions::Diagnostic& task, const muse::modularity::ContextPtr&)
265 : {
266 0 : if (!diagnosticDrawProvider()) {
267 0 : return make_ret(Ret::Code::NotSupported);
268 : }
269 :
270 0 : Ret ret = make_ret(Ret::Code::Ok);
271 :
272 0 : if (task.input.isEmpty()) {
273 0 : return make_ret(Ret::Code::UnknownError);
274 : }
275 :
276 0 : io::paths_t input;
277 0 : for (const QString& p : task.input) {
278 0 : input.push_back(p);
279 : }
280 :
281 0 : muse::io::path_t output = task.output;
282 :
283 0 : if (output.empty()) {
284 0 : output = "./";
285 : }
286 :
287 0 : switch (task.type) {
288 0 : case DiagnosticType::GenDrawData:
289 0 : ret = diagnosticDrawProvider()->generateDrawData(input.front(), output);
290 0 : break;
291 0 : case DiagnosticType::ComDrawData: {
292 0 : IF_ASSERT_FAILED(input.size() == 2) {
293 0 : return make_ret(Ret::Code::UnknownError);
294 : }
295 0 : ret = diagnosticDrawProvider()->compareDrawData(input.at(0), input.at(1), output);
296 0 : } break;
297 0 : case DiagnosticType::DrawDataToPng:
298 0 : ret = diagnosticDrawProvider()->drawDataToPng(input.front(), output);
299 0 : break;
300 0 : case DiagnosticType::DrawDiffToPng: {
301 0 : muse::io::path_t diffPath = input.at(0);
302 0 : muse::io::path_t refPath;
303 0 : if (input.size() > 1) {
304 0 : refPath = input.at(1);
305 : }
306 0 : ret = diagnosticDrawProvider()->drawDiffToPng(diffPath, refPath, output);
307 0 : } break;
308 0 : default:
309 0 : break;
310 : }
311 :
312 0 : if (!ret) {
313 0 : LOGE() << "diagnostic ret: " << ret.toString();
314 : }
315 :
316 0 : return ret.code();
317 0 : }
318 :
319 0 : int MuseScoreConsoleApp::processAudioPluginRegistration(const MuseScoreCmdOptions::AudioPluginRegistration& task,
320 : const muse::modularity::ContextPtr& ctx)
321 : {
322 0 : Ret ret = make_ret(Ret::Code::Ok);
323 :
324 0 : muse::ContextInject<audioplugins::IRegisterAudioPluginsScenario> registerAudioPluginsScenario = { ctx };
325 :
326 0 : if (!task.outputFile.empty()) {
327 0 : ret = registerAudioPluginsScenario()->validatePlugin(task.pluginPath, task.outputFile);
328 : } else {
329 0 : ret = registerAudioPluginsScenario()->registerPlugin(task.pluginPath);
330 : }
331 :
332 0 : if (!ret) {
333 0 : LOGE() << ret.toString();
334 : }
335 :
336 0 : return ret.code();
337 0 : }
338 :
339 0 : void MuseScoreConsoleApp::processTestflow(const MuseScoreCmdOptions::Testflow& task, const muse::modularity::ContextPtr& ctx)
340 : {
341 : using namespace muse::testflow;
342 0 : muse::ContextInject<ITestflow> testflow = { ctx };
343 :
344 0 : muse::async::Channel<StepInfo, Ret> stepCh = testflow()->stepStatusChanged();
345 0 : stepCh.onReceive(nullptr, [](const StepInfo& step, const Ret& ret) {
346 0 : if (!ret) {
347 0 : LOGE() << "failed step: " << step.name << ", ret: " << ret.toString();
348 0 : qApp->exit(ret.code());
349 : } else {
350 0 : LOGI() << "success step: " << step.name << ", ret: " << ret.toString();
351 : }
352 0 : });
353 :
354 0 : muse::async::Channel<muse::io::path_t, ITestflow::Status> statusCh = testflow()->statusChanged();
355 0 : statusCh.onReceive(nullptr, [](const muse::io::path_t& path, ITestflow::Status st) {
356 0 : if (st == ITestflow::Status::Finished) {
357 0 : LOGI() << "success finished, path: " << path;
358 0 : qApp->exit(0);
359 : }
360 0 : });
361 :
362 0 : ITestflow::Options opt;
363 0 : opt.context = task.testCaseContextNameOrFile;
364 0 : opt.contextVal = task.testCaseContextValue.toStdString();
365 0 : opt.func = task.testCaseFunc.toStdString();
366 0 : opt.funcArgs = task.testCaseFuncArgs.toStdString();
367 :
368 0 : testflow()->execScript(task.testCaseNameOrFile, opt);
369 0 : }
|