LCOV - code coverage report
Current view: top level - src/app/internal - consoleapp.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 215 0
Test Date: 2026-11-05 04:07:02

            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 : }
        

Generated by: LCOV version 2.0-1