/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-Studio-CLA-applies
*
* MuseScore Studio
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore Limited and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "commandlineparser.h"
#include
#include "global/io/dir.h"
#include "global/internal/baseapplication.h"
#include "app_config.h"
#include "log.h"
using namespace muse;
using namespace mu::app;
static QStringList prepareArguments(int argc, char** argv)
{
QStringList args;
for (int i = 0; i < argc; ++i) {
QString arg = QString::fromUtf8(argv[i]);
#ifndef NDEBUG
if (arg.startsWith("-qmljsdebugger")) {
continue;
}
#endif
args << arg;
}
return args;
}
template
QCommandLineOption internalCommandLineOption(Args&& ... args)
{
QCommandLineOption option(std::forward(args)...);
option.setFlags(QCommandLineOption::HiddenFromHelp);
return option;
}
void CommandLineParser::init()
{
// Common
m_parser.addHelpOption(); // -?, -h, --help
m_parser.addVersionOption(); // -v, --version
m_parser.addPositionalArgument("scorefiles", "The files to open", "[scorefile...]");
m_parser.addOption(QCommandLineOption("long-version", "Print detailed version information"));
m_parser.addOption(QCommandLineOption({ "d", "debug" }, "Debug mode"));
m_parser.addOption(QCommandLineOption({ "D", "monitor-resolution" }, "Specify monitor resolution", "DPI"));
m_parser.addOption(QCommandLineOption({ "T", "trim-image" },
"Use with '-o .png' and '-o '. Trim exported image with specified margin (in pixels)",
"margin"));
m_parser.addOption(QCommandLineOption({ "b", "bitrate" }, "Use with '-o .mp3', sets bitrate, in kbps", "bitrate"));
m_parser.addOption(QCommandLineOption("template-mode", "Save template mode, no page size")); // and no platform and creationDate tags
m_parser.addOption(QCommandLineOption({ "t", "test-mode" }, "Set test mode flag for all files")); // this includes --template-mode
m_parser.addOption(QCommandLineOption("session-type", "Startup with given session type", "type")); // see StartupScenario::sessionTypeTromString
m_parser.addOption(QCommandLineOption("unroll-repeats", "Unroll repeats"));
// Converter mode
m_parser.addOption(QCommandLineOption({ "r", "image-resolution" }, "Set output resolution for image export", "DPI"));
m_parser.addOption(QCommandLineOption({ "o", "export-to" }, "Export to 'file'. Format depends on file's extension", "file"));
m_parser.addOption(QCommandLineOption({ "j", "job" }, "Process a conversion job", "file"));
m_parser.addOption(QCommandLineOption("extension", "Use extension to process a conversion job", "uri"));
m_parser.addOption(QCommandLineOption({ "F", "factory-settings" }, "Use factory settings"));
m_parser.addOption(QCommandLineOption({ "R", "revert-settings" }, "Revert to factory settings, but keep default preferences"));
m_parser.addOption(QCommandLineOption({ "M", "midi-operations" }, "Specify MIDI import operations file", "file"));
m_parser.addOption(QCommandLineOption({ "P", "export-score-parts" }, "Use with '-o .pdf', export score and parts"));
m_parser.addOption(QCommandLineOption({ "f", "force" },
"Use with '-o ', ignore warnings reg. score being corrupted or from wrong version"));
m_parser.addOption(QCommandLineOption("score-media",
"Export all media (excepting mp3) for a given score in a single JSON file and print it to stdout"));
m_parser.addOption(QCommandLineOption("highlight-config", "Set highlight to svg, generated from a given score", "highlight-config"));
m_parser.addOption(QCommandLineOption("score-meta", "Export score metadata to JSON document and print it to stdout"));
m_parser.addOption(QCommandLineOption("score-parts", "Generate parts data for the given score and save them to separate mscz files"));
m_parser.addOption(QCommandLineOption("score-parts-pdf",
"Generate parts data for the given score and export the data to a single JSON file, print it to stdout"));
m_parser.addOption(QCommandLineOption("score-transpose",
"Transpose the given score and export the data to a single JSON file, print it to stdout",
"options"));
m_parser.addOption(QCommandLineOption("score-elements",
"Scan the given score and export elements to a single JSON file, print it to stdout"));
m_parser.addOption(QCommandLineOption("source-update", "Update the source in the given score"));
m_parser.addOption(QCommandLineOption({ "S", "style" }, "Load style file", "style"));
m_parser.addOption(QCommandLineOption("sound-profile",
"Use with '-o .mp3' or with '-j ', override the sound profile in the given score(s). "
"Possible values: \"MuseScore Basic\", \"MuseSounds\"", "sound-profile"));
m_parser.addOption(QCommandLineOption("tracks-diff",
"Use with -o .mp3, write a diff of tracks before and after applying the sound profile. "
"The report contains two lists: oldTracks and newTracks",
"tracks-diff"));
m_parser.addOption(QCommandLineOption("transpose",
"Transpose the given score before executing the '-o' options",
"options"));
m_parser.addOption(QCommandLineOption("page",
"Use with '-o ', export only the specified page. "
"Supported output formats: SVG, PNG, PDF, MSCZ",
"options"));
m_parser.addOption(QCommandLineOption("region",
"Use with '-o ', export only the specified region to a separate mscz file. ",
"options"));
// MusicXML
m_parser.addOption(QCommandLineOption("musicxml-use-default-font",
"Apply default typeface (Edwin) to imported scores"));
m_parser.addOption(QCommandLineOption("musicxml-infer-text-type",
"Infer text type based on content where possible"));
// Video export
#ifdef MUE_BUILD_IMPEXP_VIDEOEXPORT_MODULE
m_parser.addOption(QCommandLineOption("score-video", "Generate video for the given score and export it to file"));
// not implemented
// m_parser.addOption(QCommandLineOption("view-mode",
// "View mode [paged-float, paged-original, paged-float-height, pano, auto]. Auto (default) will choose the best mode according to number of instruments etc... Will show piano for piano score only",
// "auto"));
// not implemented
// m_parser.addOption(QCommandLineOption("piano", "Show Piano, works only if one part and not auto or float modes"));
// m_parser.addOption(QCommandLineOption("piano-position", "Show Piano top or bottom. Default bottom", "bottom"));
m_parser.addOption(QCommandLineOption("resolution", "Resolution [2160p, 1440p, 1080p, 720p, 480p, 360p]", "1080p"));
m_parser.addOption(QCommandLineOption("fps", "Frame per second [60, 30, 24]", "24"));
m_parser.addOption(QCommandLineOption("ls", "Pause before playback in seconds (3.0)", "3.0"));
m_parser.addOption(QCommandLineOption("ts", "Pause before end of video in seconds (3.0)", "3.0"));
m_parser.addOption(QCommandLineOption("no-audio", "Export video without audio"));
#endif
m_parser.addOption(QCommandLineOption("gp-linked", "create tabulature linked staves for guitar pro"));
m_parser.addOption(QCommandLineOption("gp-experimental", "experimental features for guitar pro import"));
//! NOTE Currently only implemented `full` mode
m_parser.addOption(QCommandLineOption("migration", "Whether to do migration with given mode, `full` - full migration", "mode"));
// Diagnostic
m_parser.addOption(QCommandLineOption("diagnostic-output", "Diagnostic output", "output"));
m_parser.addOption(QCommandLineOption("diagnostic-gen-drawdata", "Generate engraving draw data", "scores-dir"));
m_parser.addOption(QCommandLineOption("diagnostic-com-drawdata", "Compare engraving draw data"));
m_parser.addOption(QCommandLineOption("diagnostic-drawdata-to-png", "Convert draw data to png", "file"));
m_parser.addOption(QCommandLineOption("diagnostic-drawdiff-to-png", "Convert draw diff to png"));
// Autobot
m_parser.addOption(QCommandLineOption("test-case", "Run test case by name or file", "nameOrFile"));
m_parser.addOption(QCommandLineOption("test-case-context", "Set test case context by name or file", "nameOrFile"));
m_parser.addOption(QCommandLineOption("test-case-context-value", "Set test case context value", "value"));
m_parser.addOption(QCommandLineOption("test-case-func", "Call test case function", "name"));
m_parser.addOption(QCommandLineOption("test-case-func-args", "Call test case function args", "args"));
// Audio plugins
m_parser.addOption(QCommandLineOption("register-audio-plugin",
"Check an audio plugin for compatibility with the application and register it", "path"));
m_parser.addOption(QCommandLineOption("register-failed-audio-plugin", "Register an incompatible audio plugin", "path"));
// Internal
m_parser.addOption(internalCommandLineOption("score-display-name-override",
"Display name to be shown in splash screen for the score that is being opened", "name"));
}
void CommandLineParser::parse(int argc, char** argv)
{
QStringList args = prepareArguments(argc, argv);
m_parser.parse(args);
auto floatValue = [this](const QString& name) -> std::optional {
bool ok = true;
float val = m_parser.value(name).toFloat(&ok);
if (ok) {
return val;
}
return std::nullopt;
};
auto doubleValue = [this](const QString& name) -> std::optional {
bool ok = true;
double val = m_parser.value(name).toDouble(&ok);
if (ok) {
return val;
}
return std::nullopt;
};
auto intValue = [this](const QString& name) -> std::optional {
bool ok = true;
int val = m_parser.value(name).toInt(&ok);
if (ok) {
return val;
}
return std::nullopt;
};
auto fromUserInputPath = [](const QString& path) -> QString {
return io::Dir::fromNativeSeparators(path).toQString();
};
QStringList scorefiles;
for (const QString& arg : m_parser.positionalArguments()) {
scorefiles << fromUserInputPath(arg);
}
if (m_parser.isSet("long-version")) {
printLongVersion();
exit(EXIT_SUCCESS);
}
if (m_parser.isSet("d")) {
m_options.app.loggerLevel = logger::Level::Debug;
}
if (m_parser.isSet("D")) {
std::optional val = doubleValue("D");
if (val) {
m_options.ui.physicalDotsPerInch = val;
} else {
LOGE() << "Option: -D not recognized DPI value: " << m_parser.value("D");
}
}
if (m_parser.isSet("T")) {
std::optional val = intValue("T");
if (val) {
m_options.exportImage.trimMarginPixelSize = val;
} else {
LOGE() << "Option: -T not recognized trim value: " << m_parser.value("T");
}
}
if (m_parser.isSet("M")) {
m_options.importMidi.operationsFile = fromUserInputPath(m_parser.value("M"));
}
if (m_parser.isSet("b")) {
std::optional val = intValue("b");
if (val) {
m_options.exportAudio.mp3Bitrate = val;
} else {
LOGE() << "Option: -b not recognized bitrate value: " << m_parser.value("b");
}
}
if (m_parser.isSet("template-mode")) {
m_options.notation.templateModeEnabled = true;
}
if (m_parser.isSet("t")) {
m_options.notation.testModeEnabled = true;
}
if (m_parser.isSet("session-type")) {
m_options.startup.type = m_parser.value("session-type").toStdString();
}
if (m_parser.isSet("register-audio-plugin")) {
m_options.runMode = IApplication::RunMode::AudioPluginRegistration;
m_options.audioPluginRegistration.pluginPath = fromUserInputPath(m_parser.value("register-audio-plugin"));
m_options.audioPluginRegistration.failedPlugin = false;
}
if (m_parser.isSet("register-failed-audio-plugin")) {
QStringList args1 = m_parser.positionalArguments();
m_options.runMode = IApplication::RunMode::AudioPluginRegistration;
m_options.audioPluginRegistration.pluginPath = fromUserInputPath(m_parser.value("register-failed-audio-plugin"));
m_options.audioPluginRegistration.failedPlugin = true;
m_options.audioPluginRegistration.failCode = !args1.empty() ? args1[0].toInt() : -1;
}
// Converter mode
if (m_parser.isSet("r")) {
std::optional val = floatValue("r");
if (val) {
m_options.exportImage.pngDpiResolution = val;
} else {
LOGE() << "Option: -r not recognized DPI value: " << m_parser.value("r");
}
}
if (m_parser.isSet("o")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::File;
if (scorefiles.size() < 1) {
LOGE() << "Option: -o no input file specified";
} else {
if (scorefiles.size() > 1) {
LOGW() << "Option: -o multiple input files specified; processing only the first one";
}
m_options.converterTask.inputFile = scorefiles[0];
m_options.converterTask.outputFile = fromUserInputPath(m_parser.value("o"));
// Only if "-o" is set "transpose" has some meaning
if (m_parser.isSet("transpose")) {
m_options.converterTask.params[CmdOptions::ParamKey::ScoreTransposeOptions] = m_parser.value("transpose");
}
if (m_parser.isSet("page")) {
m_options.converterTask.params[CmdOptions::ParamKey::PageNumber] = m_parser.value("page");
}
if (m_parser.isSet("region")) {
m_options.converterTask.params[CmdOptions::ParamKey::ScoreRegion] = m_parser.value("region");
}
}
}
if (m_parser.isSet("P")) {
if (m_options.converterTask.outputFile.isEmpty()) {
LOGE() << "Option: -R no output file specified";
} else {
m_options.converterTask.type = ConvertType::ConvertScoreParts;
}
}
if (m_parser.isSet("j")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::Batch;
m_options.converterTask.inputFile = fromUserInputPath(m_parser.value("j"));
}
if (m_parser.isSet("score-media")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::ExportScoreMedia;
m_options.converterTask.inputFile = scorefiles[0];
if (m_parser.isSet("highlight-config")) {
m_options.converterTask.params[CmdOptions::ParamKey::HighlightConfigPath]
= fromUserInputPath(m_parser.value("highlight-config"));
}
}
if (m_parser.isSet("score-meta")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::ExportScoreMeta;
m_options.converterTask.inputFile = scorefiles[0];
}
if (m_parser.isSet("score-parts")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::ExportScoreParts;
m_options.converterTask.inputFile = scorefiles[0];
}
if (m_parser.isSet("score-parts-pdf")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::ExportScorePartsPdf;
m_options.converterTask.inputFile = scorefiles[0];
}
if (m_parser.isSet("score-transpose")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::ExportScoreTranspose;
m_options.converterTask.inputFile = scorefiles[0];
m_options.converterTask.params[CmdOptions::ParamKey::ScoreTransposeOptions] = m_parser.value("score-transpose");
}
if (m_parser.isSet("score-elements")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::ExportScoreElements;
m_options.converterTask.inputFile = scorefiles[0];
}
if (m_parser.isSet("source-update")) {
QStringList args2 = m_parser.positionalArguments();
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::SourceUpdate;
m_options.converterTask.inputFile = fromUserInputPath(args2[0]);
if (args2.size() >= 2) {
m_options.converterTask.params[CmdOptions::ParamKey::ScoreSource] = args2[1];
} else {
LOGW() << "Option: --source-update no source specified";
}
}
// MusicXML
if (m_parser.isSet("musicxml-use-default-font")) {
m_options.importMusicXml.useDefaultFont = true;
}
if (m_parser.isSet("musicxml-infer-text-type")) {
m_options.importMusicXml.inferTextType = true;
}
// Video
#ifdef MUE_BUILD_IMPEXP_VIDEOEXPORT_MODULE
if (m_parser.isSet("score-video")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.converterTask.type = ConvertType::ExportScoreVideo;
m_options.converterTask.inputFile = scorefiles[0];
m_options.converterTask.outputFile = fromUserInputPath(m_parser.value("o"));
if (m_parser.isSet("view-mode")) {
NOT_IMPLEMENTED;
}
if (m_parser.isSet("piano")) {
NOT_IMPLEMENTED;
}
if (m_parser.isSet("piano-position")) {
NOT_IMPLEMENTED;
}
if (m_parser.isSet("resolution")) {
m_options.exportVideo.resolution = m_parser.value("resolution").toStdString();
}
if (m_parser.isSet("fps")) {
m_options.exportVideo.fps = intValue("fps");
}
if (m_parser.isSet("ls")) {
m_options.exportVideo.leadingSec = doubleValue("ls");
}
if (m_parser.isSet("ts")) {
m_options.exportVideo.trailingSec = doubleValue("ts");
}
if (m_parser.isSet("no-audio")) {
m_options.converterTask.params[CmdOptions::ParamKey::NoAudio] = true;
}
}
#endif
if (m_parser.isSet("F") || m_parser.isSet("R")) {
m_options.app.revertToFactorySettings = true;
}
if (m_parser.isSet("f")) {
m_options.converterTask.params[CmdOptions::ParamKey::ForceMode] = true;
}
if (m_parser.isSet("unroll-repeats")) {
m_options.converterTask.params[CmdOptions::ParamKey::UnrollRepeats] = true;
}
if (m_parser.isSet("S")) {
m_options.converterTask.params[CmdOptions::ParamKey::StylePath] = fromUserInputPath(m_parser.value("S"));
}
if (m_parser.isSet("sound-profile")) {
m_options.converterTask.params[CmdOptions::ParamKey::SoundProfile] = m_parser.value("sound-profile");
}
if (m_parser.isSet("tracks-diff")) {
m_options.converterTask.params[CmdOptions::ParamKey::TracksDiffPath] = m_parser.value("tracks-diff");
}
if (m_parser.isSet("extension")) {
m_options.converterTask.params[CmdOptions::ParamKey::ExtensionUri] = m_parser.value("extension");
}
if (m_parser.isSet("gp-linked")) {
m_options.guitarPro.linkedTabStaffCreated = true;
}
if (m_parser.isSet("gp-experimental")) {
m_options.guitarPro.experimental = true;
}
if (m_options.runMode == IApplication::RunMode::ConsoleApp) {
if (m_parser.isSet("migration")) {
QString val = m_parser.value("migration");
m_options.project.fullMigration = (val == "full") ? true : false;
}
}
// Diagnostic
if (m_parser.isSet("diagnostic-output")) {
m_options.diagnostic.output = m_parser.value("diagnostic-output");
}
if (m_parser.isSet("diagnostic-gen-drawdata")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.diagnostic.type = DiagnosticType::GenDrawData;
m_options.diagnostic.input << m_parser.value("diagnostic-gen-drawdata");
}
if (m_parser.isSet("diagnostic-com-drawdata")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.diagnostic.type = DiagnosticType::ComDrawData;
m_options.diagnostic.input = scorefiles;
}
if (m_parser.isSet("diagnostic-drawdata-to-png")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.diagnostic.type = DiagnosticType::DrawDataToPng;
m_options.diagnostic.input << m_parser.value("diagnostic-drawdata-to-png");
}
if (m_parser.isSet("diagnostic-drawdiff-to-png")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.diagnostic.type = DiagnosticType::DrawDiffToPng;
m_options.diagnostic.input = scorefiles;
}
// Autobot
if (m_parser.isSet("test-case")) {
m_options.runMode = IApplication::RunMode::ConsoleApp;
m_options.autobot.testCaseNameOrFile = fromUserInputPath(m_parser.value("test-case"));
}
if (m_parser.isSet("test-case-context")) {
m_options.autobot.testCaseContextNameOrFile = fromUserInputPath(m_parser.value("test-case-context"));
}
if (m_parser.isSet("test-case-context-value")) {
m_options.autobot.testCaseContextValue = m_parser.value("test-case-context-value");
}
if (m_parser.isSet("test-case-func")) {
m_options.autobot.testCaseFunc = m_parser.value("test-case-func");
}
if (m_parser.isSet("test-case-func-args")) {
m_options.autobot.testCaseFuncArgs = m_parser.value("test-case-func-args");
}
// Startup
if (m_options.runMode == IApplication::RunMode::GuiApp) {
if (!scorefiles.isEmpty()) {
m_options.startup.scoreUrl = QUrl::fromUserInput(scorefiles[0], QDir::currentPath(), QUrl::AssumeLocalFile);
}
if (m_parser.isSet("score-display-name-override")) {
m_options.startup.scoreDisplayNameOverride = m_parser.value("score-display-name-override");
}
}
}
void CommandLineParser::processBuiltinArgs(const QCoreApplication& app)
{
//! NOTE: some options require an instance of QCoreApplication
m_parser.process(app);
}
IApplication::RunMode CommandLineParser::runMode() const
{
return m_options.runMode;
}
const CmdOptions& CommandLineParser::options() const
{
return m_options;
}
void CommandLineParser::printLongVersion() const
{
if (BaseApplication::appUnstable()) {
printf("MuseScore: Music Score Editor\nUnstable Prerelease for Version %s; Build %s\n",
BaseApplication::appVersion().toStdString().c_str(), BaseApplication::appRevision().toStdString().c_str());
} else {
printf("MuseScore: Music Score Editor; Version %s; Build %s\n",
BaseApplication::appVersion().toStdString().c_str(), BaseApplication::appRevision().toStdString().c_str());
}
}