LCOV - code coverage report
Current view: top level - src/converter/internal - convertercontroller.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 38.7 % 416 161
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              : #include "convertercontroller.h"
      23              : 
      24              : #include <QJsonDocument>
      25              : #include <QJsonObject>
      26              : #include <QJsonArray>
      27              : #include <QJsonParseError>
      28              : 
      29              : #include "global/defer.h"
      30              : #include "global/io/file.h"
      31              : #include "global/io/filestream.h"
      32              : #include "global/io/dir.h"
      33              : 
      34              : #include "engraving/dom/masterscore.h"
      35              : #include "engraving/infrastructure/mscio.h"
      36              : 
      37              : #include "notation/iexcerptnotation.h" // IWYU pragma: keep
      38              : #include "notation/imasternotation.h"
      39              : #include "notation/inotation.h"
      40              : #include "notation/inotationelements.h" // IWYU pragma: keep
      41              : #include "notation/inotationinteraction.h"
      42              : #include "notation/inotationselectionfilter.h" // IWYU pragma: keep
      43              : 
      44              : #include "project/iprojectaudiosettings.h" // IWYU pragma: keep
      45              : 
      46              : #include "convertercodes.h"
      47              : #include "compat/backendapi.h"
      48              : #include "compat/notationmeta.h"
      49              : #include "converterutils.h"
      50              : 
      51              : #include "log.h"
      52              : 
      53              : using namespace mu::converter;
      54              : using namespace mu::project;
      55              : using namespace mu::notation;
      56              : using namespace muse;
      57              : using namespace muse::io;
      58              : 
      59              : static const std::string PDF_SUFFIX = "pdf";
      60              : static const std::string PNG_SUFFIX = "png";
      61              : static const std::string SVG_SUFFIX = "svg";
      62              : static const std::string MP3_SUFFIX = "mp3";
      63              : 
      64              : //! NOTE: Returns the dirs of the path that don't exist yet, from the deepest one to the topmost one
      65           16 : static paths_t missingDirs(const path_t& dirPath)
      66              : {
      67           16 :     paths_t missing;
      68           16 :     path_t dir = dirPath;
      69              : 
      70           21 :     while (!dir.empty() && !Dir(dir).exists()) {
      71            5 :         missing.push_back(dir);
      72              : 
      73            5 :         const path_t parent = io::absoluteDirpath(dir);
      74            5 :         if (parent == dir) {
      75            0 :             break;
      76              :         }
      77              : 
      78            5 :         dir = parent;
      79            5 :     }
      80              : 
      81           16 :     return missing;
      82           16 : }
      83              : 
      84              : //! NOTE: Write directly to the file, some writers (e.g. video) write to the "file_path" themselves
      85           16 : Ret ConverterController::writeNotationToFile(INotationWriterPtr writer, INotationPtr notation, const path_t& filePath,
      86              :                                              const INotationWriter::Options& options, const path_t& dirPath) const
      87              : {
      88           16 :     const path_t outDir = io::absoluteDirpath(filePath);
      89           16 :     const paths_t createdDirs = missingDirs(outDir);
      90              : 
      91           16 :     Ret ret = Dir::mkpath(outDir);
      92           16 :     if (!ret) {
      93            0 :         LOGE() << "failed to create dir: " << outDir << ", err: " << ret.toString();
      94            0 :         return make_ret(Err::OutFileFailedOpen);
      95              :     }
      96              : 
      97              :     //! NOTE: Don't leave behind the dirs created for the output file if the convert fails.
      98              :     //!       Remove them bottom-up and only while they are empty, so that files written there by others are kept
      99            6 :     auto removeCreatedDirs = [this, &createdDirs]() {
     100            9 :         for (const path_t& dir : createdDirs) {
     101            4 :             if (!fileSystem()->remove(dir, /*onlyIfEmpty*/ true)) {
     102            1 :                 break;
     103              :             }
     104              :         }
     105            6 :     };
     106              : 
     107           16 :     FileStream outFile(filePath);
     108           32 :     outFile.setMeta("file_path", filePath.toStdString());
     109           16 :     if (!dirPath.empty()) {
     110            6 :         outFile.setMeta("dir_path", dirPath.toStdString());
     111              :     }
     112              : 
     113           16 :     if (!outFile.open(IODevice::WriteOnly)) {
     114            1 :         LOGE() << "failed to open file: " << filePath;
     115            1 :         removeCreatedDirs();
     116            1 :         return make_ret(Err::OutFileFailedOpen);
     117              :     }
     118              : 
     119           15 :     ret = writer->write(notation, outFile, options);
     120           15 :     outFile.close();
     121              : 
     122           15 :     if (!ret || outFile.hasError()) {
     123            5 :         LOGE() << "failed write, err: " << (ret ? outFile.errorString() : ret.toString()) << ", path: " << filePath;
     124            5 :         File::remove(filePath);
     125            5 :         removeCreatedDirs();
     126            5 :         return make_ret(Err::OutFileFailedWrite);
     127              :     }
     128              : 
     129           10 :     return make_ok();
     130           16 : }
     131              : 
     132            0 : Ret ConverterController::batchConvert(const path_t& batchJobFile, const OpenParams& openParams,
     133              :                                       const String& soundProfile, const UriQuery& extensionUri,
     134              :                                       ProgressPtr progress)
     135              : {
     136            0 :     TRACEFUNC;
     137              : 
     138            0 :     if (progress) {
     139            0 :         progress->start();
     140              :     }
     141              : 
     142            0 :     RetVal<BatchJob> batchJob = parseBatchJob(batchJobFile);
     143            0 :     if (!batchJob.ret) {
     144            0 :         LOGE() << "failed parse batch job file, err: " << batchJob.ret.toString();
     145            0 :         if (progress) {
     146            0 :             progress->finish(ProgressResult(batchJob.ret));
     147              :         }
     148            0 :         return batchJob.ret;
     149              :     }
     150              : 
     151            0 :     StringList errors;
     152              : 
     153            0 :     int64_t current = 0;
     154            0 :     int64_t total = batchJob.val.size();
     155            0 :     for (const Job& job : batchJob.val) {
     156            0 :         if (progress) {
     157            0 :             ++current;
     158            0 :             progress->progress(current, total, job.in.toStdString());
     159              :         }
     160              : 
     161            0 :         Ret ret = convertFile(job.in, job.out, openParams, soundProfile, job.tracksDiffPath, extensionUri, job.transposeOptions,
     162            0 :                               job.pageNum, job.visibleParts,
     163            0 :                               job.copyright);
     164            0 :         if (!ret) {
     165            0 :             errors.emplace_back(String(u"failed convert, err: %1, in: %2, out: %3")
     166            0 :                                 .arg(String::fromStdString(ret.toString())).arg(job.in.toString()).arg(job.out.toString()));
     167              :         }
     168            0 :     }
     169              : 
     170            0 :     Ret ret;
     171            0 :     if (!errors.empty()) {
     172            0 :         ret = make_ret(Err::ConvertFailed, errors.join(u"\n").toStdString());
     173              :     } else {
     174            0 :         ret = make_ret(Ret::Code::Ok);
     175              :     }
     176              : 
     177            0 :     if (progress) {
     178            0 :         progress->finish(ProgressResult(ret));
     179              :     }
     180              : 
     181            0 :     return ret;
     182            0 : }
     183              : 
     184           16 : Ret ConverterController::fileConvert(const path_t& in, const path_t& out,
     185              :                                      const OpenParams& openParams,
     186              :                                      const String& soundProfile,
     187              :                                      const path_t& tracksDiffPath,
     188              :                                      const UriQuery& extensionUri,
     189              :                                      const std::string& transposeOptionsJson,
     190              :                                      const std::optional<ConvertTarget>& target)
     191              : {
     192           16 :     std::optional<TransposeOptions> transposeOptions;
     193              : 
     194           16 :     if (!transposeOptionsJson.empty()) {
     195            0 :         RetVal<TransposeOptions> transposeOptionsRet = ConverterUtils::parseTransposeOptions(transposeOptionsJson);
     196            0 :         if (!transposeOptionsRet.ret) {
     197            0 :             return transposeOptionsRet.ret;
     198              :         }
     199              : 
     200            0 :         transposeOptions = transposeOptionsRet.val;
     201            0 :     }
     202              : 
     203           16 :     return convertFile(in, out, openParams, soundProfile, tracksDiffPath, extensionUri, transposeOptions, target);
     204              : }
     205              : 
     206           16 : Ret ConverterController::convertFile(const muse::io::path_t& in, const muse::io::path_t& out,
     207              :                                      const OpenParams& openParams,
     208              :                                      const String& soundProfile,
     209              :                                      const path_t& tracksDiffPath,
     210              :                                      const muse::UriQuery& extensionUri,
     211              :                                      const std::optional<notation::TransposeOptions>& transposeOptions,
     212              :                                      const std::optional<ConvertTarget>& target,
     213              :                                      const std::vector<size_t>& visibleParts,
     214              :                                      const CopyrightInfo& copyright)
     215              : {
     216           17 :     TRACEFUNC;
     217              : 
     218           16 :     LOGI() << "in: " << in << ", out: " << out;
     219              : 
     220           16 :     std::string suffix = io::suffix(out);
     221              : 
     222           16 :     auto writer = writers()->writer(suffix);
     223           16 :     if (!writer) {
     224            1 :         return make_ret(Err::ConvertTypeUnknown);
     225              :     }
     226              : 
     227           15 :     auto notationProject = notationCreator()->newProject(iocContext());
     228           15 :     IF_ASSERT_FAILED(notationProject) {
     229            0 :         return make_ret(Err::UnknownError);
     230              :     }
     231              : 
     232           30 :     Ret ret = notationProject->load(in, openParams);
     233           15 :     if (!ret) {
     234            1 :         LOGE() << "failed load notation, err: " << ret.toString() << ", path: " << in;
     235            1 :         return make_ret(Err::InFileFailedLoad);
     236              :     }
     237              : 
     238           14 :     QJsonArray oldTracks;
     239           14 :     if (!tracksDiffPath.empty()) {
     240            0 :         oldTracks = NotationMeta::tracksJsonArray(notationProject->masterNotation()->notation());
     241              :     }
     242              : 
     243           14 :     if (!soundProfile.isEmpty()) {
     244            0 :         notationProject->audioSettings()->clearTrackInputParams();
     245            0 :         notationProject->audioSettings()->setActiveSoundProfile(soundProfile);
     246              :     }
     247              : 
     248           14 :     if (transposeOptions.has_value()) {
     249            0 :         ret = ConverterUtils::applyTranspose(notationProject->masterNotation()->notation(), transposeOptions.value());
     250            0 :         if (!ret) {
     251            0 :             LOGE() << "Failed to apply transposition, err: " << ret.toString();
     252            0 :             return ret;
     253              :         }
     254              :     }
     255              : 
     256           14 :     if (!visibleParts.empty()) {
     257            0 :         ConverterUtils::setVisibleParts(notationProject->masterNotation()->notation(), visibleParts);
     258              :     }
     259              : 
     260           14 :     if (!copyright.text.isEmpty()) {
     261            0 :         auto masterScore = notationProject->masterNotation()->masterScore();
     262              : 
     263            0 :         engraving::MStyle& style = masterScore->style();
     264            0 :         String footerOdd = style.value(engraving::Sid::oddFooterC).value<String>();
     265            0 :         String footerEven = style.value(engraving::Sid::evenFooterC).value<String>();
     266            0 :         footerOdd += copyright.text;
     267            0 :         footerEven += copyright.text;
     268            0 :         style.set(engraving::Sid::oddFooterC, footerOdd);
     269            0 :         style.set(engraving::Sid::evenFooterC, footerEven);
     270              : 
     271            0 :         masterScore->doLayout();
     272            0 :     }
     273              : 
     274           14 :     projectSession()->setProject(notationProject);
     275              : 
     276           14 :     DEFER {
     277           14 :         projectSession()->setProject(nullptr);
     278           28 :     };
     279              : 
     280              :     // Check if this is a part conversion job
     281           14 :     QString baseName = QString::fromStdString(io::completeBasename(out).toStdString());
     282           14 :     if (baseName.contains('*')) {
     283            2 :         return convertScoreParts(writer, notationProject->masterNotation(), out);
     284              :     }
     285              : 
     286              :     // use a extension for convert
     287           12 :     if (extensionUri.isValid()) {
     288            0 :         ret = convertByExtension(writer, notationProject->masterNotation()->notation(), out, extensionUri);
     289            0 :         if (!ret) {
     290            0 :             LOGE() << "Failed to convert by extension, err: " << ret.toString();
     291              :         }
     292              :     }
     293              :     // standart convert
     294              :     else {
     295           12 :         const bool pageNumIsSet = target.has_value() && std::holds_alternative<page_num_t>(target.value());
     296           12 :         const bool regionIsSet = target.has_value() && std::holds_alternative<ConvertRegionJson>(target.value());
     297              : 
     298           12 :         if (suffix == engraving::MSCZ || suffix == engraving::MSCX || suffix == engraving::MSCS) {
     299            1 :             if (pageNumIsSet) {
     300            0 :                 return notationProject->savePage(out, std::get<page_num_t>(target.value()));
     301            1 :             } else if (regionIsSet) {
     302            0 :                 return saveRegion(notationProject, std::get<ConvertRegionJson>(target.value()), out);
     303              :             }
     304              : 
     305            1 :             return notationProject->save(out);
     306              :         }
     307              : 
     308           11 :         if (pageNumIsSet || isConvertPageByPage(suffix)) {
     309            2 :             if (pageNumIsSet) {
     310            1 :                 page_num_t pageNum = std::get<page_num_t>(target.value());
     311            1 :                 ret = convertPage(writer, notationProject->masterNotation()->notation(), pageNum, out);
     312              :             } else {
     313            1 :                 ret = convertPageByPage(writer, notationProject->masterNotation()->notation(), out);
     314              :             }
     315              : 
     316            2 :             if (!ret) {
     317            0 :                 LOGE() << "Failed to convert page by page, err: " << ret.toString();
     318              :             }
     319              :         } else {
     320            9 :             ret = convertFullNotation(writer, notationProject->masterNotation()->notation(), out);
     321            9 :             if (!ret) {
     322            5 :                 LOGE() << "Failed to convert full notation, err: " << ret.toString();
     323              :             }
     324              :         }
     325              :     }
     326              : 
     327           11 :     if (ret && !tracksDiffPath.empty()) {
     328            0 :         ret = writeTracksDiff(notationProject, oldTracks, tracksDiffPath);
     329              :     }
     330              : 
     331           11 :     return ret;
     332           16 : }
     333              : 
     334            0 : Ret ConverterController::convertScoreParts(const muse::io::path_t& in, const muse::io::path_t& out, const OpenParams& openParams)
     335              : {
     336            0 :     TRACEFUNC;
     337              : 
     338            0 :     auto notationProject = notationCreator()->newProject(iocContext());
     339            0 :     IF_ASSERT_FAILED(notationProject) {
     340            0 :         return make_ret(Err::UnknownError);
     341              :     }
     342              : 
     343            0 :     std::string suffix = io::suffix(out);
     344            0 :     auto writer = writers()->writer(suffix);
     345            0 :     if (!writer) {
     346            0 :         return make_ret(Err::ConvertTypeUnknown);
     347              :     }
     348              : 
     349            0 :     Ret ret = notationProject->load(in, openParams);
     350            0 :     if (!ret) {
     351            0 :         LOGE() << "failed load notation, err: " << ret.toString() << ", path: " << in;
     352            0 :         return make_ret(Err::InFileFailedLoad);
     353              :     }
     354              : 
     355            0 :     ret = convertScoreParts(writer, notationProject->masterNotation(), out);
     356              : 
     357            0 :     return ret;
     358            0 : }
     359              : 
     360            2 : Ret ConverterController::convertScoreParts(INotationWriterPtr writer, IMasterNotationPtr masterNotation, const muse::io::path_t& out)
     361              : {
     362            2 :     std::string suffix = io::suffix(out);
     363              : 
     364            2 :     if (suffix == PDF_SUFFIX) {
     365            1 :         return convertScorePartsToPdf(writer, masterNotation, out);
     366            1 :     } else if (suffix == PNG_SUFFIX) {
     367            0 :         return convertScorePartsToPngs(writer, masterNotation, out);
     368            1 :     } else if (suffix == MP3_SUFFIX) {
     369            1 :         return convertScorePartsToMp3(writer, masterNotation, out);
     370              :     }
     371              : 
     372            0 :     return make_ret(Ret::Code::NotSupported);
     373            2 : }
     374              : 
     375            0 : RetVal<ConverterController::BatchJob> ConverterController::parseBatchJob(const muse::io::path_t& batchJobFile) const
     376              : {
     377            0 :     TRACEFUNC;
     378              : 
     379            0 :     RetVal<BatchJob> rv;
     380            0 :     QFile file(batchJobFile.toQString());
     381            0 :     if (!file.open(QIODevice::ReadOnly)) {
     382            0 :         rv.ret = make_ret(Err::BatchJobFileFailedOpen, file.errorString().toStdString());
     383            0 :         return rv;
     384              :     }
     385              : 
     386            0 :     const QByteArray data = file.readAll();
     387              : 
     388            0 :     QJsonParseError err;
     389            0 :     const QJsonDocument doc = QJsonDocument::fromJson(data, &err);
     390            0 :     if (err.error != QJsonParseError::NoError || !doc.isArray()) {
     391            0 :         rv.ret = make_ret(Err::BatchJobFileFailedParse, err.errorString().toStdString());
     392            0 :         return rv;
     393              :     }
     394              : 
     395            0 :     const QJsonArray arr = doc.array();
     396              : 
     397            0 :     auto correctUserInputPath = [](const QString& path) -> QString {
     398            0 :         return io::Dir::fromNativeSeparators(path).toQString();
     399              :     };
     400              : 
     401            0 :     for (const auto obj : arr) {
     402            0 :         Job job;
     403            0 :         job.in = correctUserInputPath(obj[u"in"].toString());
     404              : 
     405            0 :         QJsonObject transposeOptionsObj = obj[u"transpose"].toObject();
     406            0 :         if (!transposeOptionsObj.isEmpty()) {
     407            0 :             RetVal<TransposeOptions> transposeOptions = ConverterUtils::parseTransposeOptions(transposeOptionsObj);
     408            0 :             if (!transposeOptions.ret) {
     409            0 :                 rv.ret = transposeOptions.ret;
     410            0 :                 return rv;
     411              :             }
     412            0 :             job.transposeOptions = transposeOptions.val;
     413            0 :         }
     414              : 
     415            0 :         QJsonValue pageVal = obj[u"page"];
     416            0 :         if (!pageVal.isUndefined()) {
     417            0 :             job.pageNum = pageVal.toInt() - 1;
     418              :         }
     419              : 
     420            0 :         QJsonValue visibleParts = obj[u"visibleParts"];
     421            0 :         if (!visibleParts.isUndefined()) {
     422            0 :             if (visibleParts.isArray()) {
     423            0 :                 const QJsonArray partsArray = visibleParts.toArray();
     424            0 :                 for (const auto part : partsArray) {
     425            0 :                     if (part.isDouble()) {
     426            0 :                         job.visibleParts.push_back(part.toInt());
     427              :                     } else {
     428            0 :                         LOGE() << "Visible parts value must be a Number";
     429              :                     }
     430              :                 }
     431            0 :             } else {
     432            0 :                 rv.ret = make_ret(Err::BatchJobFileFailedParse, err.errorString().toStdString());
     433            0 :                 return rv;
     434              :             }
     435              :         }
     436              : 
     437            0 :         QJsonValue copyright = obj[u"copyright"];
     438            0 :         if (!copyright.isUndefined()) {
     439            0 :             if (copyright.isObject()) {
     440            0 :                 QJsonValue copyrightText = copyright[u"text"];
     441            0 :                 if (copyrightText.isUndefined()) {
     442            0 :                     rv.ret = make_ret(Err::BatchJobFileFailedParse, err.errorString().toStdString());
     443            0 :                     return rv;
     444              :                 }
     445              : 
     446            0 :                 job.copyright.text = copyrightText.toString();
     447              : 
     448            0 :                 QJsonValue showOnAllPages = copyright[u"showOnAllPages"];
     449            0 :                 if (!showOnAllPages.isUndefined() && showOnAllPages.isBool()) {
     450            0 :                     job.copyright.showOnAllPages = showOnAllPages.toBool();
     451              :                 }
     452            0 :             } else {
     453            0 :                 rv.ret = make_ret(Err::BatchJobFileFailedParse, err.errorString().toStdString());
     454            0 :                 return rv;
     455              :             }
     456              :         }
     457              : 
     458            0 :         const QJsonValue tracksDiffValue = obj[u"tracksDiff"];
     459            0 :         if (tracksDiffValue.isString()) {
     460            0 :             job.tracksDiffPath = correctUserInputPath(tracksDiffValue.toString());
     461              :         }
     462              : 
     463            0 :         const QJsonValue outValue = obj[u"out"];
     464            0 :         if (outValue.isString()) {
     465            0 :             job.out = correctUserInputPath(outValue.toString());
     466            0 :             rv.val.push_back(std::move(job));
     467            0 :         } else if (outValue.isArray()) {
     468            0 :             const QJsonArray outArray = outValue.toArray();
     469            0 :             for (const auto outItem : outArray) {
     470            0 :                 Job partJob = job; // Copy the input path
     471            0 :                 if (outItem.isString()) {
     472            0 :                     partJob.out = correctUserInputPath(outItem.toString());
     473            0 :                 } else if (outItem.isArray() && outItem.toArray().size() == 2) {
     474            0 :                     const QJsonArray partOutArray = outItem.toArray();
     475            0 :                     const QString prefix = correctUserInputPath(partOutArray[0].toString());
     476            0 :                     const QString suffix = partOutArray[1].toString();
     477            0 :                     partJob.out = prefix + "*" + suffix; // Use "*" as a placeholder for part names
     478            0 :                 }
     479            0 :                 rv.val.push_back(std::move(partJob));
     480            0 :             }
     481            0 :         }
     482            0 :     }
     483              : 
     484            0 :     rv.ret = make_ret(Ret::Code::Ok);
     485            0 :     return rv;
     486            0 : }
     487              : 
     488            0 : Ret ConverterController::convertByExtension(INotationWriterPtr writer, INotationPtr notation, const muse::io::path_t& out,
     489              :                                             const muse::UriQuery& extensionUri)
     490              : {
     491            0 :     extensions::ExtensionUri uri = extensionUri.uri();
     492            0 :     extensions::ExtensionActionCode actionCode = extensionUri.param("action", Val("main")).toString();
     493              : 
     494              :     //! NOTE First we do the extension, it can modify the notation (score)
     495            0 :     Ret ret = extensionsProvider()->perform(uri, actionCode);
     496            0 :     if (!ret) {
     497            0 :         return ret;
     498              :     }
     499              : 
     500            0 :     return writeNotationToFile(writer, notation, out);
     501            0 : }
     502              : 
     503           10 : bool ConverterController::isConvertPageByPage(const std::string& suffix) const
     504              : {
     505              :     static const std::unordered_set<std::string> TYPES {
     506              :         PNG_SUFFIX,
     507              :         SVG_SUFFIX,
     508           14 :     };
     509              : 
     510           10 :     return muse::contains(TYPES, suffix);
     511            1 : }
     512              : 
     513            1 : Ret ConverterController::convertPageByPage(INotationWriterPtr writer, INotationPtr notation, const muse::io::path_t& out) const
     514              : {
     515            2 :     TRACEFUNC;
     516              : 
     517            3 :     for (size_t i = 0; i < notation->elements()->pages().size(); i++) {
     518            6 :         const String filePath = muse::io::path_t(io::dirpath(out) + "/"
     519            8 :                                                  + io::completeBasename(out) + "-%1."
     520            6 :                                                  + io::suffix(out)).toString().arg(i + 1);
     521              : 
     522            2 :         Ret ret = convertPage(writer, notation, i, filePath, out);
     523            2 :         if (!ret) {
     524            0 :             return ret;
     525              :         }
     526            4 :     }
     527              : 
     528            1 :     return make_ret(Ret::Code::Ok);
     529            1 : }
     530              : 
     531            3 : muse::Ret ConverterController::convertPage(INotationWriterPtr writer, INotationPtr notation, const size_t pageNum,
     532              :                                            const muse::io::path_t& filePath, const muse::io::path_t& dirPath) const
     533              : {
     534            4 :     TRACEFUNC;
     535              : 
     536              :     const INotationWriter::Options options {
     537            3 :         { INotationWriter::OptionKey::PAGE_NUMBER, Val(static_cast<int>(pageNum)) },
     538            9 :     };
     539              : 
     540            6 :     return writeNotationToFile(writer, notation, filePath, options, dirPath);
     541            6 : }
     542              : 
     543           10 : Ret ConverterController::convertFullNotation(INotationWriterPtr writer, INotationPtr notation, const muse::io::path_t& out,
     544              :                                              const project::INotationWriter::Options& options) const
     545              : {
     546           10 :     return writeNotationToFile(writer, notation, out, options);
     547              : }
     548              : 
     549            1 : Ret ConverterController::convertScorePartsToPdf(INotationWriterPtr writer, IMasterNotationPtr masterNotation,
     550              :                                                 const muse::io::path_t& out) const
     551              : {
     552            2 :     TRACEFUNC;
     553              : 
     554              :     const INotationWriter::Options options {
     555            1 :         { INotationWriter::OptionKey::UNIT_TYPE, Val(INotationWriter::UnitType::PER_PART) },
     556            3 :     };
     557              : 
     558            3 :     for (const IExcerptNotationPtr& e : masterNotation->excerpts()) {
     559            2 :         QString partName = e->notation()->name();
     560            2 :         QString baseName = QString::fromStdString(io::completeBasename(out).toStdString());
     561            2 :         muse::io::path_t partOut = io::dirpath(out) + "/" + baseName.replace("*", partName).toStdString() + ".pdf";
     562              : 
     563            2 :         Ret ret = writeNotationToFile(writer, e->notation(), partOut, options);
     564            2 :         if (!ret) {
     565            0 :             return ret;
     566              :         }
     567            4 :     }
     568              : 
     569            1 :     return make_ret(Ret::Code::Ok);
     570            2 : }
     571              : 
     572            0 : Ret ConverterController::convertScorePartsToPngs(INotationWriterPtr writer, mu::notation::IMasterNotationPtr masterNotation,
     573              :                                                  const muse::io::path_t& out) const
     574              : {
     575            0 :     TRACEFUNC;
     576              : 
     577            0 :     for (const IExcerptNotationPtr& e : masterNotation->excerpts()) {
     578            0 :         QString partName = e->notation()->name();
     579            0 :         QString baseName = QString::fromStdString(io::completeBasename(out).toStdString());
     580            0 :         muse::io::path_t pngFilePath = io::dirpath(out) + "/" + baseName.replace("*", partName).toStdString() + ".png";
     581            0 :         Ret ret = convertPageByPage(writer, e->notation(), pngFilePath);
     582            0 :         if (!ret) {
     583            0 :             return ret;
     584              :         }
     585            0 :     }
     586              : 
     587            0 :     return make_ret(Ret::Code::Ok);
     588            0 : }
     589              : 
     590            1 : Ret ConverterController::convertScorePartsToMp3(INotationWriterPtr writer, IMasterNotationPtr masterNotation,
     591              :                                                 const muse::io::path_t& out) const
     592              : {
     593            2 :     TRACEFUNC;
     594              : 
     595              :     const INotationWriter::Options options {
     596            1 :         { INotationWriter::OptionKey::UNIT_TYPE, Val(INotationWriter::UnitType::PER_PART) },
     597            3 :     };
     598              : 
     599            1 :     for (const IExcerptNotationPtr& e : masterNotation->excerpts()) {
     600            1 :         QString partName = e->notation()->name();
     601            1 :         QString baseName = QString::fromStdString(io::completeBasename(out).toStdString());
     602            1 :         muse::io::path_t partOut = io::dirpath(out) + "/" + baseName.replace("*", partName).toStdString() + ".mp3";
     603              : 
     604            1 :         Ret ret = writeNotationToFile(writer, e->notation(), partOut, options);
     605            1 :         if (!ret) {
     606            1 :             return ret;
     607              :         }
     608            3 :     }
     609              : 
     610            0 :     return make_ret(Ret::Code::Ok);
     611            2 : }
     612              : 
     613            0 : muse::Ret ConverterController::saveRegion(INotationProjectPtr project, const ConvertRegionJson& regionJson, const path_t& out) const
     614              : {
     615            0 :     RetVal<ConvertRegion> region = ConverterUtils::parseRegion(regionJson);
     616            0 :     if (!region.ret) {
     617            0 :         return region.ret;
     618              :     }
     619              : 
     620            0 :     INotationPtr notation = project->masterNotation()->notation();
     621            0 :     const mu::engraving::Score* score = notation->elements()->msScore();
     622              : 
     623            0 :     Measure* startMeasure = score->crMeasure(static_cast<int>(region.val.start.measureIdx));
     624            0 :     if (!startMeasure) {
     625            0 :         startMeasure = score->firstMeasure();
     626              :     }
     627              : 
     628            0 :     Measure* endMeasure = score->crMeasure(static_cast<int>(region.val.end.measureIdx));
     629            0 :     if (!endMeasure) {
     630            0 :         endMeasure = score->lastMeasure();
     631              :     }
     632              : 
     633            0 :     IF_ASSERT_FAILED(startMeasure && endMeasure) {
     634            0 :         return make_ret(Err::UnknownError);
     635              :     }
     636              : 
     637            0 :     INotationInteractionPtr interaction = notation->interaction();
     638              : 
     639            0 :     if (!region.val.voiceIdxSet.empty()) {
     640              :         using VoiceFilterType = mu::engraving::VoicesSelectionFilterTypes;
     641              :         const std::vector<std::pair<size_t, mu::engraving::VoicesSelectionFilterTypes> > VOICE_FILTERS {
     642              :             { 0, VoiceFilterType::FIRST_VOICE },
     643              :             { 1, VoiceFilterType::SECOND_VOICE },
     644              :             { 2, VoiceFilterType::THIRD_VOICE },
     645              :             { 3, VoiceFilterType::FOURTH_VOICE },
     646            0 :         };
     647              : 
     648            0 :         for (const auto& pair : VOICE_FILTERS) {
     649            0 :             const bool voiceAccepted = muse::contains(region.val.voiceIdxSet, pair.first);
     650            0 :             interaction->selectionFilter()->setSelectionTypeFiltered(pair.second, voiceAccepted);
     651              :         }
     652            0 :     }
     653              : 
     654            0 :     interaction->select({ startMeasure }, SelectType::RANGE, region.val.start.staffIdx);
     655            0 :     interaction->select({ endMeasure }, SelectType::RANGE, region.val.end.staffIdx);
     656              : 
     657            0 :     return project->save(out, SaveMode::SaveSelection);
     658            0 : }
     659              : 
     660            0 : Ret ConverterController::writeTracksDiff(INotationProjectPtr project, const QJsonArray& oldTracks, const path_t& path) const
     661              : {
     662            0 :     TRACEFUNC;
     663              : 
     664            0 :     QJsonObject root;
     665            0 :     root["oldTracks"] = oldTracks;
     666            0 :     root["newTracks"] = NotationMeta::tracksJsonArray(project->masterNotation()->notation());
     667              : 
     668            0 :     QJsonDocument document(root);
     669            0 :     QByteArray qJson = document.toJson(QJsonDocument::Compact);
     670            0 :     ByteArray json = ByteArray::fromQByteArrayNoCopy(qJson);
     671              : 
     672            0 :     return File::writeFile(path, json);
     673            0 : }
     674              : 
     675            0 : Ret ConverterController::exportScoreMedia(const muse::io::path_t& in, const muse::io::path_t& out,
     676              :                                           const OpenParams& openParams,
     677              :                                           const muse::io::path_t& highlightConfigPath)
     678              : {
     679            0 :     TRACEFUNC;
     680              : 
     681            0 :     return BackendApi::exportScoreMedia(iocContext(), in, out, highlightConfigPath, openParams);
     682            0 : }
     683              : 
     684            0 : Ret ConverterController::exportScoreMeta(const muse::io::path_t& in, const muse::io::path_t& out, const OpenParams& openParams)
     685              : {
     686            0 :     TRACEFUNC;
     687              : 
     688            0 :     return BackendApi::exportScoreMeta(iocContext(), in, out, openParams);
     689            0 : }
     690              : 
     691            0 : Ret ConverterController::exportScoreParts(const muse::io::path_t& in, const muse::io::path_t& out, const OpenParams& openParams)
     692              : {
     693            0 :     TRACEFUNC;
     694              : 
     695            0 :     return BackendApi::exportScoreParts(iocContext(), in, out, openParams);
     696            0 : }
     697              : 
     698            0 : Ret ConverterController::exportScorePartsPdfs(const muse::io::path_t& in, const muse::io::path_t& out, const OpenParams& openParams)
     699              : {
     700            0 :     TRACEFUNC;
     701              : 
     702            0 :     return BackendApi::exportScorePartsPdfs(iocContext(), in, out, openParams);
     703            0 : }
     704              : 
     705            0 : Ret ConverterController::exportScoreTranspose(const muse::io::path_t& in, const muse::io::path_t& out, const std::string& optionsJson,
     706              :                                               const OpenParams& openParams)
     707              : {
     708            0 :     TRACEFUNC;
     709              : 
     710            0 :     return BackendApi::exportScoreTranspose(iocContext(), in, out, optionsJson, openParams);
     711            0 : }
     712              : 
     713            0 : Ret ConverterController::exportScoreElements(const muse::io::path_t& in, const muse::io::path_t& out,
     714              :                                              const OpenParams& openParams)
     715              : {
     716            0 :     TRACEFUNC;
     717              : 
     718            0 :     return BackendApi::exportScoreElements(iocContext(), in, out, openParams);
     719            0 : }
     720              : 
     721            1 : Ret ConverterController::exportScoreVideo(const muse::io::path_t& in, const muse::io::path_t& out, const OpenParams& openParams,
     722              :                                           bool withAudio)
     723              : {
     724            2 :     TRACEFUNC;
     725              : 
     726            1 :     auto notationProject = notationCreator()->newProject(iocContext());
     727            1 :     IF_ASSERT_FAILED(notationProject) {
     728            0 :         return make_ret(Err::UnknownError);
     729              :     }
     730              : 
     731            1 :     std::string suffix = io::suffix(out);
     732            1 :     auto writer = writers()->writer(suffix);
     733            1 :     if (!writer) {
     734            0 :         return make_ret(Err::ConvertTypeUnknown);
     735              :     }
     736              : 
     737            2 :     Ret ret = notationProject->load(in, openParams);
     738            1 :     if (!ret) {
     739            0 :         LOGE() << "failed load notation, err: " << ret.toString() << ", path: " << in;
     740            0 :         return make_ret(Err::InFileFailedLoad);
     741              :     }
     742              : 
     743            1 :     projectSession()->setProject(notationProject);
     744              : 
     745            1 :     DEFER {
     746            1 :         projectSession()->setProject(nullptr);
     747            2 :     };
     748              : 
     749              :     const INotationWriter::Options options {
     750            1 :         { INotationWriter::OptionKey::WITH_AUDIO, Val(withAudio) },
     751            3 :     };
     752              : 
     753            1 :     return convertFullNotation(writer, notationProject->masterNotation()->notation(), out, options);
     754            2 : }
     755              : 
     756            0 : Ret ConverterController::updateSource(const muse::io::path_t& in, const std::string& newSource, bool forceMode)
     757              : {
     758            0 :     TRACEFUNC;
     759              : 
     760            0 :     return BackendApi::updateSource(iocContext(), in, newSource, forceMode);
     761            0 : }
        

Generated by: LCOV version 2.0-1