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 "backendapi.h"
23 :
24 : #include <stdio.h>
25 :
26 : #include <QString>
27 : #include <QJsonDocument>
28 : #include <QJsonObject>
29 : #include <QJsonArray>
30 : #include <QJsonValue>
31 : #include <QRandomGenerator>
32 :
33 : #include "io/buffer.h"
34 :
35 : #include "engraving/infrastructure/mscwriter.h"
36 : #include "engraving/dom/excerpt.h"
37 : #include "engraving/dom/score.h"
38 : #include "engraving/rw/mscsaver.h"
39 : #include "engraving/types/typesconv.h"
40 :
41 : #include "notation/iexcerptnotation.h" // IWYU pragma: keep
42 : #include "notation/imasternotation.h"
43 : #include "notation/inotationelements.h" // IWYU pragma: keep
44 :
45 : #include "project/types/projectmeta.h"
46 :
47 : #include "internal/converterutils.h"
48 :
49 : #include "backendjsonwriter.h"
50 : #include "notationmeta.h"
51 : #include "scoreelementsscanner.h"
52 :
53 : #include "log.h"
54 :
55 : using namespace mu;
56 : using namespace mu::converter;
57 : using namespace mu::project;
58 : using namespace mu::notation;
59 : using namespace mu::engraving;
60 : using namespace muse;
61 : using namespace muse::io;
62 :
63 : static const std::string PNG_WRITER_NAME = "png";
64 : static const std::string SVG_WRITER_NAME = "svg";
65 : static const std::string SEGMENTS_POSITIONS_WRITER_NAME = "spos";
66 : static const std::string SEGMENTS_POSITIONS_TAG_NAME = "sposXML";
67 : static const std::string MEASURES_POSITIONS_WRITER_NAME = "mpos";
68 : static const std::string MEASURES_POSITIONS_TAG_NAME = "mposXML";
69 : static const std::string PDF_WRITER_NAME = "pdf";
70 : static const std::string MIDI_WRITER_NAME = "midi";
71 : static const std::string MUSICXML_WRITER_NAME = "mxl";
72 : static const std::string MUSICXML_JSON_NAME = "mxml";
73 : static const std::string META_DATA_NAME = "metadata";
74 : static const std::string DEV_INFO_NAME = "devinfo";
75 :
76 : static constexpr bool ADD_SEPARATOR = true;
77 :
78 0 : Ret BackendApi::exportScoreMedia(const muse::modularity::ContextPtr& iocCtx, const muse::io::path_t& in, const muse::io::path_t& out,
79 : const muse::io::path_t& highlightConfigPath, const OpenParams& openParams)
80 : {
81 0 : TRACEFUNC
82 :
83 0 : RetVal<INotationProjectPtr> prj = openProject(iocCtx, in, openParams);
84 0 : if (!prj.ret) {
85 0 : return prj.ret;
86 : }
87 :
88 0 : INotationPtr notation = prj.val->masterNotation()->notation();
89 :
90 0 : bool result = true;
91 :
92 0 : QFile outputFile;
93 0 : openOutputFile(outputFile, out);
94 :
95 0 : BackendJsonWriter jsonWriter(&outputFile);
96 :
97 0 : result &= exportScorePngs(notation, jsonWriter, ADD_SEPARATOR);
98 0 : result &= exportScoreSvgs(notation, highlightConfigPath, jsonWriter, ADD_SEPARATOR);
99 0 : result &= exportScoreElementsPositions(SEGMENTS_POSITIONS_WRITER_NAME, SEGMENTS_POSITIONS_TAG_NAME,
100 0 : notation, jsonWriter, ADD_SEPARATOR);
101 0 : result &= exportScoreElementsPositions(MEASURES_POSITIONS_WRITER_NAME, MEASURES_POSITIONS_TAG_NAME,
102 0 : notation, jsonWriter, ADD_SEPARATOR);
103 0 : result &= exportScorePdf(notation, jsonWriter, ADD_SEPARATOR);
104 0 : result &= exportScoreMidi(notation, jsonWriter, ADD_SEPARATOR);
105 0 : result &= exportScoreMusicXML(notation, jsonWriter, ADD_SEPARATOR);
106 0 : result &= exportScoreMetaData(notation, jsonWriter, ADD_SEPARATOR);
107 0 : result &= devInfo(notation, jsonWriter);
108 :
109 0 : return result ? make_ret(Ret::Code::Ok) : make_ret(Ret::Code::InternalError);
110 0 : }
111 :
112 0 : Ret BackendApi::exportScoreMeta(const muse::modularity::ContextPtr& iocCtx, const muse::io::path_t& in, const muse::io::path_t& out,
113 : const OpenParams& openParams)
114 : {
115 0 : TRACEFUNC
116 :
117 0 : RetVal<INotationProjectPtr> prj = openProject(iocCtx, in, openParams);
118 0 : if (!prj.ret) {
119 0 : return prj.ret;
120 : }
121 :
122 0 : INotationPtr notation = prj.val->masterNotation()->notation();
123 :
124 0 : QFile outputFile;
125 0 : openOutputFile(outputFile, out);
126 :
127 0 : BackendJsonWriter jsonWriter(&outputFile);
128 :
129 0 : bool result = exportScoreMetaData(notation, jsonWriter);
130 :
131 0 : return result ? make_ret(Ret::Code::Ok) : make_ret(Ret::Code::InternalError);
132 0 : }
133 :
134 0 : Ret BackendApi::exportScoreParts(const muse::modularity::ContextPtr& iocCtx, const muse::io::path_t& in, const muse::io::path_t& out,
135 : const OpenParams& openParams)
136 : {
137 0 : TRACEFUNC
138 :
139 0 : RetVal<INotationProjectPtr> prj = openProject(iocCtx, in, openParams);
140 0 : if (!prj.ret) {
141 0 : return prj.ret;
142 : }
143 :
144 0 : QFile outputFile;
145 0 : openOutputFile(outputFile, out);
146 :
147 0 : Ret ret = doExportScoreParts(iocCtx, prj.val->masterNotation(), outputFile);
148 :
149 0 : outputFile.close();
150 :
151 0 : return ret;
152 0 : }
153 :
154 0 : Ret BackendApi::exportScorePartsPdfs(const muse::modularity::ContextPtr& iocCtx, const muse::io::path_t& in, const muse::io::path_t& out,
155 : const OpenParams& openParams)
156 : {
157 0 : TRACEFUNC
158 :
159 0 : RetVal<INotationProjectPtr> prj = openProject(iocCtx, in, openParams);
160 0 : if (!prj.ret) {
161 0 : return prj.ret;
162 : }
163 :
164 0 : QFile outputFile;
165 0 : openOutputFile(outputFile, out);
166 :
167 0 : std::string scoreFileName = io::dirpath(in).toStdString() + "/" + io::filename(in, false).toStdString() + ".pdf";
168 :
169 0 : Ret ret = doExportScorePartsPdfs(prj.val->masterNotation(), outputFile, scoreFileName);
170 :
171 0 : outputFile.close();
172 :
173 0 : return ret;
174 0 : }
175 :
176 0 : Ret BackendApi::exportScoreTranspose(const muse::modularity::ContextPtr& iocCtx, const muse::io::path_t& in, const muse::io::path_t& out,
177 : const std::string& optionsJson, const OpenParams& openParams)
178 : {
179 0 : TRACEFUNC
180 :
181 0 : RetVal<INotationProjectPtr> prj = openProject(iocCtx, in, openParams);
182 0 : if (!prj.ret) {
183 0 : return prj.ret;
184 : }
185 :
186 0 : INotationPtr notation = prj.val->masterNotation()->notation();
187 :
188 0 : Ret ret = ConverterUtils::applyTranspose(notation, optionsJson);
189 0 : if (!ret) {
190 0 : return ret;
191 : }
192 :
193 0 : QFile outputFile;
194 0 : openOutputFile(outputFile, out);
195 :
196 0 : BackendJsonWriter jsonWriter(&outputFile);
197 :
198 0 : bool result = doExportScoreTranspose(iocCtx, notation, jsonWriter);
199 :
200 0 : return result ? make_ret(Ret::Code::Ok) : make_ret(Ret::Code::InternalError);
201 0 : }
202 :
203 0 : Ret BackendApi::exportScoreElements(const muse::modularity::ContextPtr& iocCtx, const muse::io::path_t& in, const muse::io::path_t& out,
204 : const OpenParams& openParams)
205 : {
206 0 : TRACEFUNC;
207 :
208 0 : RetVal<INotationProjectPtr> prj = openProject(iocCtx, in, openParams);
209 0 : if (!prj.ret) {
210 0 : return prj.ret;
211 : }
212 :
213 0 : INotationPtr notation = prj.val->masterNotation()->notation();
214 :
215 0 : QFile outputFile;
216 0 : openOutputFile(outputFile, out);
217 :
218 0 : return doExportScoreElements(notation, outputFile);
219 0 : }
220 :
221 0 : Ret BackendApi::openOutputFile(QFile& file, const muse::io::path_t& out)
222 : {
223 0 : bool ok = false;
224 0 : if (!out.empty()) {
225 0 : file.setFileName(out.toQString());
226 0 : ok = file.open(QFile::WriteOnly);
227 : } else {
228 0 : ok = file.open(stdout, QFile::WriteOnly);
229 : }
230 :
231 0 : return ok ? make_ret(Ret::Code::Ok) : make_ret(Ret::Code::InternalError);
232 : }
233 :
234 0 : RetVal<project::INotationProjectPtr> BackendApi::openProject(const muse::modularity::ContextPtr& iocCtx, const muse::io::path_t& path,
235 : const OpenParams& params)
236 : {
237 0 : TRACEFUNC
238 :
239 0 : auto notationProject = notationCreator()->newProject(iocCtx);
240 0 : IF_ASSERT_FAILED(notationProject) {
241 0 : return make_ret(Ret::Code::InternalError);
242 : }
243 :
244 0 : Ret ret = notationProject->load(path, params);
245 0 : if (!ret) {
246 0 : LOGE() << "failed load: " << path << ", ret: " << ret.toString();
247 0 : return make_ret(Ret::Code::InternalError);
248 : }
249 :
250 0 : IMasterNotationPtr masterNotation = notationProject->masterNotation();
251 0 : if (!masterNotation) {
252 0 : return make_ret(Ret::Code::InternalError);
253 : }
254 :
255 0 : INotationPtr notation = masterNotation->notation();
256 0 : if (!notation) {
257 0 : return make_ret(Ret::Code::InternalError);
258 : }
259 :
260 0 : switchToPageView(masterNotation);
261 0 : renderExcerptsContents(masterNotation);
262 :
263 0 : return RetVal<INotationProjectPtr>::make_ok(notationProject);
264 0 : }
265 :
266 0 : QVariantMap BackendApi::readBeatsColors(const muse::io::path_t& filePath)
267 : {
268 0 : TRACEFUNC
269 :
270 0 : if (filePath.empty()) {
271 0 : return QVariantMap();
272 : }
273 :
274 0 : RetVal<ByteArray> fileData = fileSystem()->readFile(filePath);
275 0 : if (!fileData.ret) {
276 0 : LOGW() << fileData.ret.toString();
277 0 : return QVariantMap();
278 : }
279 :
280 0 : const QJsonDocument document = QJsonDocument::fromJson(fileData.val.toQByteArrayNoCopy());
281 0 : const QJsonObject obj = document.object();
282 0 : const QJsonArray colors = obj.value("highlight").toArray();
283 :
284 0 : QVariantMap result;
285 :
286 0 : for (const auto colorObj : colors) {
287 0 : const QJsonArray beatsIndexes = colorObj[u"beats"].toArray();
288 0 : const QColor beatsColor = QColor(colorObj[u"color"].toString());
289 :
290 0 : for (const auto index : beatsIndexes) {
291 0 : result[index.toString()] = beatsColor;
292 : }
293 0 : }
294 :
295 0 : return result;
296 0 : }
297 :
298 0 : Ret BackendApi::exportScorePngs(const INotationPtr notation, BackendJsonWriter& jsonWriter, bool addSeparator)
299 : {
300 0 : TRACEFUNC
301 :
302 0 : auto pngWriter = writers()->writer(PNG_WRITER_NAME);
303 0 : if (!pngWriter) {
304 0 : LOGW() << "Not found writer " << PNG_WRITER_NAME;
305 0 : return make_ret(Ret::Code::InternalError);
306 : }
307 :
308 0 : jsonWriter.addKey("pngs");
309 0 : jsonWriter.openArray();
310 :
311 0 : const PageList& notationPages = notation->elements()->pages();
312 :
313 0 : bool result = true;
314 0 : for (size_t i = 0; i < notationPages.size(); ++i) {
315 0 : ByteArray pngData;
316 0 : auto pngDevice = Buffer::opened(IODevice::ReadWrite, &pngData);
317 :
318 : INotationWriter::Options options = {
319 0 : { INotationWriter::OptionKey::PAGE_NUMBER, Val(static_cast<int>(i)) },
320 0 : { INotationWriter::OptionKey::TRANSPARENT_BACKGROUND, Val(false) }
321 0 : };
322 :
323 0 : Ret writeRet = pngWriter->write(notation, pngDevice, options);
324 0 : if (!writeRet) {
325 0 : LOGW() << writeRet.toString();
326 0 : result = false;
327 : }
328 :
329 0 : bool lastArrayValue = ((notationPages.size() - 1) == i);
330 0 : jsonWriter.addValue(pngData.toQByteArrayNoCopy().toBase64(), !lastArrayValue);
331 0 : }
332 :
333 0 : jsonWriter.closeArray(addSeparator);
334 :
335 0 : return result ? make_ret(Ret::Code::Ok) : make_ret(Ret::Code::InternalError);
336 0 : }
337 :
338 0 : Ret BackendApi::exportScoreSvgs(const INotationPtr notation, const muse::io::path_t& highlightConfigPath, BackendJsonWriter& jsonWriter,
339 : bool addSeparator)
340 : {
341 0 : TRACEFUNC
342 :
343 0 : auto svgWriter = writers()->writer(SVG_WRITER_NAME);
344 0 : if (!svgWriter) {
345 0 : LOGW() << "Not found writer " << SVG_WRITER_NAME;
346 0 : return make_ret(Ret::Code::InternalError);
347 : }
348 :
349 0 : jsonWriter.addKey("svgs");
350 0 : jsonWriter.openArray();
351 :
352 0 : const PageList& notationPages = notation->elements()->pages();
353 0 : QVariantMap beatsColors = readBeatsColors(highlightConfigPath);
354 :
355 0 : bool result = true;
356 0 : for (size_t i = 0; i < notationPages.size(); ++i) {
357 0 : ByteArray svgData;
358 0 : auto svgDevice = Buffer::opened(IODevice::ReadWrite, &svgData);
359 :
360 : INotationWriter::Options options {
361 0 : { INotationWriter::OptionKey::PAGE_NUMBER, Val(static_cast<int>(i)) },
362 0 : { INotationWriter::OptionKey::TRANSPARENT_BACKGROUND, Val(false) },
363 0 : { INotationWriter::OptionKey::BEATS_COLORS, Val::fromQVariant(beatsColors) }
364 0 : };
365 :
366 0 : Ret writeRet = svgWriter->write(notation, svgDevice, options);
367 0 : if (!writeRet) {
368 0 : LOGW() << writeRet.toString();
369 0 : result = false;
370 : }
371 :
372 0 : bool lastArrayValue = ((notationPages.size() - 1) == i);
373 0 : jsonWriter.addValue(svgData.toQByteArrayNoCopy().toBase64(), !lastArrayValue);
374 0 : }
375 :
376 0 : jsonWriter.closeArray(addSeparator);
377 :
378 0 : return result ? make_ret(Ret::Code::Ok) : make_ret(Ret::Code::InternalError);
379 0 : }
380 :
381 0 : Ret BackendApi::exportScoreElementsPositions(const std::string& elementsPositionsWriterName, const std::string& elementsPositionsTagName,
382 : const INotationPtr notation, BackendJsonWriter& jsonWriter, bool addSeparator)
383 : {
384 0 : TRACEFUNC
385 :
386 0 : RetVal<QByteArray> writerRetVal = processWriter(elementsPositionsWriterName, notation);
387 0 : if (!writerRetVal.ret) {
388 0 : return writerRetVal.ret;
389 : }
390 :
391 0 : jsonWriter.addKey(elementsPositionsTagName.c_str());
392 0 : jsonWriter.addValue(writerRetVal.val, addSeparator, false);
393 :
394 0 : return make_ret(Ret::Code::Ok);
395 0 : }
396 :
397 0 : Ret BackendApi::exportScorePdf(const INotationPtr notation, BackendJsonWriter& jsonWriter, bool addSeparator)
398 : {
399 0 : TRACEFUNC
400 :
401 0 : RetVal<QByteArray> writerRetVal = processWriter(PDF_WRITER_NAME, notation);
402 0 : if (!writerRetVal.ret) {
403 0 : return writerRetVal.ret;
404 : }
405 :
406 0 : jsonWriter.addKey(PDF_WRITER_NAME.c_str());
407 0 : jsonWriter.addValue(writerRetVal.val, addSeparator);
408 :
409 0 : return make_ret(Ret::Code::Ok);
410 0 : }
411 :
412 0 : Ret BackendApi::exportScorePdf(const INotationPtr notation, QIODevice& destinationDevice)
413 : {
414 0 : TRACEFUNC
415 :
416 0 : RetVal<QByteArray> writerRetVal = processWriter(PDF_WRITER_NAME, notation);
417 0 : if (!writerRetVal.ret) {
418 0 : return writerRetVal.ret;
419 : }
420 :
421 0 : bool ok = destinationDevice.write(QJsonDocument::fromJson(writerRetVal.val).toJson(QJsonDocument::Compact)) != -1;
422 0 : destinationDevice.close();
423 :
424 0 : return ok ? make_ret(Ret::Code::Ok) : make_ret(Ret::Code::InternalError);
425 0 : }
426 :
427 0 : Ret BackendApi::exportScoreMidi(const INotationPtr notation, BackendJsonWriter& jsonWriter, bool addSeparator)
428 : {
429 0 : TRACEFUNC
430 :
431 0 : RetVal<QByteArray> writerRetVal = processWriter(MIDI_WRITER_NAME, notation);
432 0 : if (!writerRetVal.ret) {
433 0 : return writerRetVal.ret;
434 : }
435 :
436 0 : jsonWriter.addKey(MIDI_WRITER_NAME.c_str());
437 0 : jsonWriter.addValue(writerRetVal.val, addSeparator);
438 :
439 0 : return make_ret(Ret::Code::Ok);
440 0 : }
441 :
442 0 : Ret BackendApi::exportScoreMusicXML(const INotationPtr notation, BackendJsonWriter& jsonWriter, bool addSeparator)
443 : {
444 0 : TRACEFUNC
445 :
446 0 : RetVal<QByteArray> writerRetVal = processWriter(MUSICXML_WRITER_NAME, notation);
447 0 : if (!writerRetVal.ret) {
448 0 : return writerRetVal.ret;
449 : }
450 :
451 0 : jsonWriter.addKey(MUSICXML_JSON_NAME.c_str());
452 0 : jsonWriter.addValue(writerRetVal.val, addSeparator);
453 :
454 0 : return make_ret(Ret::Code::Ok);
455 0 : }
456 :
457 0 : Ret BackendApi::exportScoreMetaData(const INotationPtr notation, BackendJsonWriter& jsonWriter, bool addSeparator)
458 : {
459 0 : TRACEFUNC
460 :
461 0 : RetVal<std::string> meta = NotationMeta::metaJson(notation);
462 0 : if (!meta.ret) {
463 0 : LOGW() << meta.ret.toString();
464 0 : return meta.ret;
465 : }
466 :
467 0 : jsonWriter.addKey(META_DATA_NAME.c_str());
468 0 : jsonWriter.addValue(QString::fromStdString(meta.val).toUtf8(), addSeparator, true);
469 :
470 0 : return make_ret(Ret::Code::Ok);
471 0 : }
472 :
473 0 : Ret BackendApi::devInfo(const notation::INotationPtr notation, BackendJsonWriter& jsonWriter, bool addSeparator)
474 : {
475 : UNUSED(notation);
476 :
477 0 : TRACEFUNC
478 :
479 0 : QJsonObject infoObj;
480 0 : infoObj["version"] = String("%1(%2)").arg(application()->fullVersion().toString(),
481 0 : application()->revision()).toQString();
482 :
483 0 : jsonWriter.addKey(DEV_INFO_NAME.c_str());
484 0 : jsonWriter.addValue(QJsonDocument(infoObj).toJson(), addSeparator, true);
485 :
486 0 : return make_ret(Ret::Code::Ok);
487 0 : }
488 :
489 0 : RetVal<QByteArray> BackendApi::processWriter(const std::string& writerName, const INotationPtr notation)
490 : {
491 0 : auto writer = writers()->writer(writerName);
492 0 : if (!writer) {
493 0 : LOGW() << "Not found writer " << writerName;
494 0 : return make_ret(Ret::Code::InternalError);
495 : }
496 :
497 0 : ByteArray data;
498 0 : auto device = Buffer::opened(IODevice::ReadWrite, &data);
499 :
500 0 : Ret writeRet = writer->write(notation, device);
501 0 : if (!writeRet) {
502 0 : LOGW() << writeRet.toString();
503 0 : return writeRet;
504 : }
505 :
506 0 : RetVal<QByteArray> result;
507 0 : result.ret = make_ret(Ret::Code::Ok);
508 0 : result.val = data.toQByteArrayNoCopy().toBase64();
509 :
510 0 : device.close();
511 :
512 0 : return result;
513 0 : }
514 :
515 0 : RetVal<QByteArray> BackendApi::processWriter(const std::string& writerName, const INotationPtrList notations,
516 : const INotationWriter::Options& options)
517 : {
518 0 : auto writer = writers()->writer(writerName);
519 0 : if (!writer) {
520 0 : LOGW() << "Not found writer " << writerName;
521 0 : return make_ret(Ret::Code::InternalError);
522 : }
523 :
524 0 : ByteArray data;
525 0 : auto device = Buffer::opened(IODevice::ReadWrite, &data);
526 :
527 0 : Ret writeRet = writer->writeList(notations, device, options);
528 0 : if (!writeRet) {
529 0 : LOGW() << writeRet.toString();
530 0 : return writeRet;
531 : }
532 :
533 0 : RetVal<QByteArray> result;
534 0 : result.ret = make_ret(Ret::Code::Ok);
535 0 : result.val = data.toQByteArrayNoCopy().toBase64();
536 :
537 0 : device.close();
538 :
539 0 : return result;
540 0 : }
541 :
542 0 : Ret BackendApi::doExportScoreParts(const muse::modularity::ContextPtr& iocCtx, const IMasterNotationPtr masterNotation,
543 : QIODevice& destinationDevice)
544 : {
545 0 : QJsonArray partsObjList;
546 0 : QJsonArray partsMetaList;
547 0 : QJsonArray partsTitles;
548 :
549 0 : ExcerptNotationList excerpts = allExcerpts(masterNotation);
550 :
551 0 : for (const IExcerptNotationPtr& excerpt : excerpts) {
552 0 : mu::engraving::Score* partScore = excerpt->notation()->elements()->msScore();
553 0 : std::map<String, String> partMetaTags = partScore->metaTags();
554 :
555 0 : QJsonValue partTitle(partScore->name());
556 0 : partsTitles << partTitle;
557 :
558 0 : QVariantMap meta;
559 0 : for (const auto& [key, val] : partMetaTags) {
560 0 : meta[key] = val.toQString();
561 : }
562 :
563 0 : meta["open"] = partScore->isOpen();
564 :
565 0 : if (!partScore->eid().isValid()) {
566 0 : partScore->assignNewEID();
567 : }
568 0 : meta["id"] = QString::fromStdString(partScore->eid().toStdString());
569 :
570 0 : QJsonValue partMetaObj = QJsonObject::fromVariantMap(meta);
571 0 : partsMetaList << partMetaObj;
572 :
573 0 : std::string fileName = io::escapeFileName(partScore->name().toStdString()).toStdString() + ".mscz";
574 0 : QJsonValue partObj(QString::fromLatin1(scorePartJson(iocCtx, partScore, fileName).val));
575 0 : partsObjList << partObj;
576 0 : }
577 :
578 0 : QJsonObject json;
579 0 : json["parts"] = partsTitles;
580 0 : json["partsMeta"] = partsMetaList;
581 0 : json["partsBin"] = partsObjList;
582 :
583 0 : bool ok = destinationDevice.write(QJsonDocument(json).toJson(QJsonDocument::Compact)) != -1;
584 0 : destinationDevice.close();
585 :
586 0 : return ok ? make_ret(Ret::Code::Ok) : make_ret(Ret::Code::InternalError);
587 0 : }
588 :
589 0 : Ret BackendApi::doExportScorePartsPdfs(const IMasterNotationPtr masterNotation, QIODevice& destinationDevice,
590 : const std::string& scoreFileName)
591 : {
592 0 : QJsonObject jsonForPdfs;
593 0 : jsonForPdfs["score"] = QString::fromStdString(scoreFileName);
594 0 : QByteArray scoreBin = processWriter(PDF_WRITER_NAME, masterNotation->notation()).val;
595 0 : jsonForPdfs["scoreBin"] = QLatin1String(scoreBin);
596 :
597 0 : INotationPtrList notations;
598 0 : notations.push_back(masterNotation->notation());
599 :
600 0 : QJsonArray partsArray;
601 0 : QJsonArray partsNamesArray;
602 :
603 0 : ExcerptNotationList excerpts = allExcerpts(masterNotation);
604 :
605 0 : for (const IExcerptNotationPtr& e : excerpts) {
606 0 : QJsonValue partNameVal(e->name());
607 0 : partsNamesArray.append(partNameVal);
608 :
609 0 : QByteArray partBin = processWriter(PDF_WRITER_NAME, e->notation()).val;
610 0 : partsArray.append(QLatin1String(partBin));
611 :
612 0 : notations.push_back(e->notation());
613 0 : }
614 :
615 0 : jsonForPdfs["parts"] = partsNamesArray;
616 0 : jsonForPdfs["partsBin"] = partsArray;
617 :
618 0 : jsonForPdfs["scoreFullPostfix"] = QString("-Score_and_parts") + ".pdf";
619 :
620 : INotationWriter::Options options {
621 0 : { INotationWriter::OptionKey::UNIT_TYPE, Val(INotationWriter::UnitType::MULTI_PART) }
622 0 : };
623 :
624 0 : QByteArray fullScoreData = processWriter(PDF_WRITER_NAME, notations, options).val;
625 0 : jsonForPdfs["scoreFullBin"] = QLatin1String(fullScoreData);
626 :
627 0 : QJsonDocument jsonDoc(jsonForPdfs);
628 0 : bool ok = destinationDevice.write(QJsonDocument(jsonDoc).toJson(QJsonDocument::Compact)) != -1;
629 :
630 0 : return ok;
631 0 : }
632 :
633 0 : Ret BackendApi::doExportScoreTranspose(const muse::modularity::ContextPtr& iocCtx, const INotationPtr notation,
634 : BackendJsonWriter& jsonWriter, bool addSeparator)
635 : {
636 0 : mu::engraving::Score* score = notation->elements()->msScore();
637 :
638 0 : jsonWriter.addKey("mscz");
639 :
640 0 : std::string fileNumber = std::to_string(QRandomGenerator::global()->generate() % 1000000);
641 0 : std::string fileName = score->name().toStdString() + "_transposed." + fileNumber + ".mscz";
642 :
643 0 : RetVal<QByteArray> scoreJson = scorePartJson(iocCtx, score, fileName);
644 0 : if (!scoreJson.ret) {
645 0 : LOGW() << "Transpose: adding mscz failed";
646 : }
647 :
648 0 : jsonWriter.addValue(scoreJson.val, ADD_SEPARATOR);
649 :
650 0 : Ret ret = exportScorePdf(notation, jsonWriter, addSeparator);
651 0 : return ret;
652 0 : }
653 :
654 0 : muse::Ret BackendApi::doExportScoreElements(const notation::INotationPtr notation, QIODevice& out)
655 : {
656 0 : mu::engraving::Score* score = notation->elements()->msScore();
657 0 : ElementMap elements = ScoreElementScanner::scanElements(score);
658 :
659 0 : QJsonArray rootArray;
660 :
661 0 : auto writeLocation = [](const ElementInfo::Location& loc, QJsonObject& obj) {
662 0 : if (loc.staffIdx != muse::nidx) {
663 0 : obj["staffIdx"] = static_cast<int>(loc.staffIdx);
664 : }
665 :
666 0 : if (loc.voiceIdx != muse::nidx) {
667 0 : obj["voiceIdx"] = static_cast<int>(loc.voiceIdx);
668 : }
669 :
670 0 : if (loc.measureIdx != muse::nidx) {
671 0 : obj["measureIdx"] = static_cast<int>(loc.measureIdx);
672 : }
673 :
674 0 : if (RealIsEqualOrMore(loc.beat, 0.f)) {
675 0 : obj["beat"] = loc.beat;
676 : }
677 0 : };
678 :
679 0 : auto writeDuration = [](const ElementInfo::Duration& dur, QJsonObject& obj) {
680 0 : QJsonObject durObj;
681 0 : durObj["name"] = dur.name.toQString();
682 0 : if (dur.dots > 0) {
683 0 : durObj["dots"] = dur.dots;
684 : }
685 0 : obj["duration"] = durObj;
686 0 : };
687 :
688 0 : auto writeNotes = [](const ElementInfo::NoteList& notes, QJsonObject& obj) {
689 0 : QJsonArray noteArray;
690 :
691 0 : for (const ElementInfo::Note& note : notes) {
692 0 : QJsonObject noteObj;
693 0 : noteObj["name"] = note.name.toQString();
694 0 : for (const auto& [key, val] : note.data) {
695 0 : noteObj[key.toQString()] = val.toQString();
696 : }
697 0 : noteArray << noteObj;
698 0 : }
699 :
700 0 : obj["notes"] = noteArray;
701 0 : };
702 :
703 0 : for (const auto& instrumentPair : elements) {
704 0 : QJsonArray elementArray;
705 :
706 0 : for (const ElementInfo& element : instrumentPair.second) {
707 0 : QJsonObject obj;
708 0 : obj["type"] = mu::engraving::TConv::toXml(element.type).ascii();
709 :
710 0 : if (!element.name.empty()) {
711 0 : obj["name"] = element.name.toQString();
712 : }
713 :
714 0 : if (!element.notes.empty()) {
715 0 : writeNotes(element.notes, obj);
716 : }
717 :
718 0 : if (!element.duration.name.empty()) {
719 0 : writeDuration(element.duration, obj);
720 : }
721 :
722 0 : for (const auto& [key, val] : element.data) {
723 0 : obj[key.toQString()] = val.toQString();
724 : }
725 :
726 0 : if (element.start == element.end) {
727 0 : writeLocation(element.start, obj);
728 : } else {
729 0 : QJsonObject start, end;
730 0 : writeLocation(element.start, start);
731 0 : writeLocation(element.end, end);
732 0 : obj["start"] = start;
733 0 : obj["end"] = end;
734 0 : }
735 :
736 0 : elementArray << obj;
737 0 : }
738 :
739 0 : QJsonObject instrumentObj;
740 0 : instrumentObj["instrumentId"] = instrumentPair.first.instrumentId.toQString();
741 0 : instrumentObj["partId"] = instrumentPair.first.partId.toQString();
742 0 : instrumentObj["elements"] = elementArray;
743 0 : rootArray << instrumentObj;
744 0 : }
745 :
746 0 : out.write(QJsonDocument(rootArray).toJson(QJsonDocument::Compact));
747 :
748 0 : return make_ok();
749 0 : }
750 :
751 0 : RetVal<QByteArray> BackendApi::scorePartJson(const muse::modularity::ContextPtr& iocCtx, mu::engraving::Score* score,
752 : const std::string& fileName)
753 : {
754 0 : ByteArray scoreData;
755 0 : Buffer buf(&scoreData);
756 :
757 0 : MscWriter::Params params;
758 0 : params.device = &buf;
759 0 : params.filePath = QString::fromStdString(fileName);
760 0 : params.mode = MscIoMode::Zip;
761 :
762 0 : MscWriter mscWriter(params);
763 0 : mscWriter.open();
764 :
765 0 : bool ok = MscSaver(iocCtx).exportPart(score, mscWriter);
766 0 : if (!ok) {
767 0 : LOGW() << "Error save mscz file";
768 : }
769 :
770 0 : mscWriter.close();
771 0 : if (mscWriter.hasError()) {
772 0 : ok = false;
773 0 : LOGW() << "Error write mscz file";
774 : }
775 :
776 0 : QByteArray ba = QByteArray::fromRawData(reinterpret_cast<const char*>(scoreData.constData()), static_cast<int>(scoreData.size()));
777 :
778 0 : RetVal<QByteArray> result;
779 0 : result.ret = ok ? make_ret(Ret::Code::Ok) : make_ret(Ret::Code::InternalError);
780 0 : result.val = ba.toBase64();
781 :
782 0 : return result;
783 0 : }
784 :
785 0 : void BackendApi::switchToPageView(IMasterNotationPtr masterNotation)
786 : {
787 : //! NOTE: All operations must be done in page view mode
788 0 : masterNotation->notation()->setViewMode(ViewMode::PAGE);
789 0 : for (const IExcerptNotationPtr& excerpt : masterNotation->excerpts()) {
790 0 : excerpt->notation()->setViewMode(ViewMode::PAGE);
791 : }
792 0 : }
793 :
794 0 : void BackendApi::renderExcerptsContents(IMasterNotationPtr masterNotation)
795 : {
796 : //! NOTE: Due to optimization, only the master score is layouted
797 : //! Let's layout all the scores of the excerpts
798 0 : for (const IExcerptNotationPtr& excerpt : masterNotation->excerpts()) {
799 0 : Score* score = excerpt->notation()->elements()->msScore();
800 0 : if (!score->autoLayoutEnabled()) {
801 0 : score->doLayout();
802 : }
803 : }
804 0 : }
805 :
806 0 : ExcerptNotationList BackendApi::allExcerpts(notation::IMasterNotationPtr masterNotation)
807 : {
808 0 : initPotentialExcerpts(masterNotation);
809 :
810 0 : ExcerptNotationList excerpts = masterNotation->excerpts();
811 0 : ExcerptNotationList potentialExcerpts = masterNotation->potentialExcerpts();
812 0 : excerpts.insert(excerpts.end(), potentialExcerpts.begin(), potentialExcerpts.end());
813 :
814 0 : masterNotation->sortExcerpts(excerpts);
815 :
816 0 : return excerpts;
817 0 : }
818 :
819 0 : void BackendApi::initPotentialExcerpts(notation::IMasterNotationPtr masterNotation)
820 : {
821 0 : ExcerptNotationList potentialExcerpts = masterNotation->potentialExcerpts();
822 :
823 0 : masterNotation->initExcerpts(potentialExcerpts);
824 0 : renderExcerptsContents(masterNotation);
825 0 : }
826 :
827 0 : Ret BackendApi::updateSource(const muse::modularity::ContextPtr& iocCtx, const muse::io::path_t& in, const std::string& newSource,
828 : bool forceMode)
829 : {
830 0 : OpenParams openParams;
831 0 : openParams.forceMode = forceMode;
832 :
833 0 : RetVal<INotationProjectPtr> project = openProject(iocCtx, in, openParams);
834 0 : if (!project.ret) {
835 0 : return project.ret;
836 : }
837 :
838 0 : ProjectMeta meta = project.val->metaInfo();
839 0 : meta.source = QString::fromStdString(newSource);
840 :
841 0 : project.val->setMetaInfo(meta);
842 :
843 0 : return project.val->save();
844 0 : }
|