Skip to content

[MusicXML] export images#33539

Open
rettinghaus wants to merge 4 commits into
musescore:mainfrom
rettinghaus:xml/image
Open

[MusicXML] export images#33539
rettinghaus wants to merge 4 commits into
musescore:mainfrom
rettinghaus:xml/image

Conversation

@rettinghaus
Copy link
Copy Markdown
Contributor

Resolves: #32688

This adds support for images to the MusicXML export. It checks the type of the imageStore for correctly assigning the mime type (a small method has been added there). If the suffix isn't sufficient, it tries to get it from the magic byte.
Images are stored in compressed MusicXML files.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds image export to MusicXML. The ImageStoreItem class gains a type() accessor exposing image metadata. ExportMusicXml introduces an image() method and a .mxl mode flag to emit MusicXML direction structures with image source, MIME type, dimensions, and positioning; getImageInfo() derives source and MIME (suffix + magic-byte fallback). The export dispatcher routes Image elements to the new exporter, several text exporters switch to .empty() checks, and .mxl archive generation packs referenced image binaries under computed source keys.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR adds image export for compressed MusicXML [#32688], but does not implement user warnings for uncompressed format or XML indicators when images are omitted. Add warnings when exporting uncompressed XML with images and implement XML indicators for omitted images as specified in issue #32688.
Docstring Coverage ⚠️ Warning Docstring coverage is 64.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title '[MusicXML] export images' accurately and concisely describes the main change—adding image export functionality to the MusicXML export feature.
Description check ✅ Passed The description mentions the linked issue and explains the core functionality added (MIME type checking and magic byte fallback, image storage in compressed MusicXML), but omits several checklist items expected in the template.
Out of Scope Changes check ✅ Passed Changes are tightly focused on image export: a new type() accessor in ImageStoreItem, image export logic in ExportMusicXml, and empty-text checks in various annotation exports are all related to the MusicXML image export objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/importexport/musicxml/internal/export/exportmusicxml.cpp`:
- Around line 5193-5228: The image() method in ExportMusicXml currently always
emits an image source attribute (via getImageInfo) even when the plain .musicxml
export path (saveXml) will not materialize binary assets, causing broken
references; modify ExportMusicXml::image to check the export mode (the flag used
by saveXml to decide .mxl packaging) and: if exporting plain XML, either (A)
write the image binary out as a sidecar file next to the XML and set the source
attribute to that relative path (use ImageStoreItem/getImageInfo to obtain
binary and filename), or (B) omit emitting the <image> tag and call the existing
warning/logging path to surface the omission; ensure this same gate is applied
to the code block around the saveXml/.mxl handling (the code referenced near
lines ~8960) so behavior is consistent between compressed and uncompressed
exports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 59f40812-2310-4c5a-9b4c-6909e1b3bca8

📥 Commits

Reviewing files that changed from the base of the PR and between dd2d974 and e5d4d52.

📒 Files selected for processing (2)
  • src/engraving/dom/imageStore.h
  • src/importexport/musicxml/internal/export/exportmusicxml.cpp

Comment thread src/importexport/musicxml/internal/export/exportmusicxml.cpp
@mathesoncalum mathesoncalum requested a review from miiizen May 22, 2026 08:36
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/importexport/musicxml/internal/export/exportmusicxml.cpp (1)

5228-5232: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Encode plain-XML image omission explicitly.

The plain .musicxml branch currently writes only the hashed source text in <other-direction>, which is not an explicit/machine-readable “image omitted” marker for downstream consumers.

💡 Suggested local change
     } else {
         String otherTag = u"other-direction";
         otherTag += positioningAttributes(img);
-        m_xml.tagRaw(otherTag, XmlWriter::xmlString(source));
+        const String omittedInfo = String(u"musescore:image-omitted source=\"%1\" type=\"%2\"")
+                                       .arg(XmlWriter::xmlString(source), XmlWriter::xmlString(type));
+        m_xml.tagRaw(otherTag, omittedInfo);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/importexport/musicxml/internal/export/exportmusicxml.cpp` around lines
5228 - 5232, The current else branch writes only the hashed source via
m_xml.tagRaw(otherTag, XmlWriter::xmlString(source)), which is not an explicit
machine-readable “image omitted” marker; update the code in that branch (around
otherTag, positioningAttributes(img), m_xml.tagRaw and
XmlWriter::xmlString(source)) to emit a clear, standardized marker (for example
an explicit <image-omitted/> element or a named token inside <other-direction>)
while preserving the original hash for traceability (include the hash as an
attribute or child element) so downstream consumers can detect omitted images
reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/importexport/musicxml/internal/export/exportmusicxml.cpp`:
- Around line 5228-5232: The current else branch writes only the hashed source
via m_xml.tagRaw(otherTag, XmlWriter::xmlString(source)), which is not an
explicit machine-readable “image omitted” marker; update the code in that branch
(around otherTag, positioningAttributes(img), m_xml.tagRaw and
XmlWriter::xmlString(source)) to emit a clear, standardized marker (for example
an explicit <image-omitted/> element or a named token inside <other-direction>)
while preserving the original hash for traceability (include the hash as an
attribute or child element) so downstream consumers can detect omitted images
reliably.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fd863681-b995-4c45-8485-1fbb7e5d76c5

📥 Commits

Reviewing files that changed from the base of the PR and between e5d4d52 and 96ac90d.

📒 Files selected for processing (1)
  • src/importexport/musicxml/internal/export/exportmusicxml.cpp

*/

if (text->plainText() == "") {
if (text->plainText() == u"") {
Copy link
Copy Markdown
Contributor

@Jojo-Schmitz Jojo-Schmitz May 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not if (text->plainText().isEmpty()) {?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were more cases, so I unified them all.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/importexport/musicxml/internal/export/exportmusicxml.cpp (2)

5154-5188: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add TIFF magic-byte detection to the fallback.

tif/tiff is only recognized from ImageStoreItem::type(). If the suffix is missing or wrong, this path falls back to application/octet-stream, so TIFF images lose their real MIME type even though TIFF is otherwise supported.

Suggested fix
     if (type.isEmpty()) {
         const ByteArray& ba = isi->buffer();
         if (ba.size() >= 4) {
             if (ba.at(0) == 0x89 && ba.at(1) == 0x50 && ba.at(2) == 0x4E && ba.at(3) == 0x47) {
                 type = u"image/png";
                 if (suffix.isEmpty()) {
                     source += u".png";
                 }
             } else if (ba.at(0) == 0xFF && ba.at(1) == 0xD8 && ba.at(2) == 0xFF) {
                 type = u"image/jpeg";
                 if (suffix.isEmpty()) {
                     source += u".jpg";
                 }
             } else if (ba.at(0) == 0x47 && ba.at(1) == 0x49 && ba.at(2) == 0x46 && ba.at(3) == 0x38) {
                 type = u"image/gif";
                 if (suffix.isEmpty()) {
                     source += u".gif";
                 }
             } else if (ba.at(0) == 0x42 && ba.at(1) == 0x4D) {
                 type = u"image/bmp";
                 if (suffix.isEmpty()) {
                     source += u".bmp";
                 }
+            } else if ((ba.at(0) == 0x49 && ba.at(1) == 0x49 && ba.at(2) == 0x2A && ba.at(3) == 0x00)
+                       || (ba.at(0) == 0x4D && ba.at(1) == 0x4D && ba.at(2) == 0x00 && ba.at(3) == 0x2A)) {
+                type = u"image/tiff";
+                if (suffix.isEmpty()) {
+                    source += u".tif";
+                }
             } else if (ba.at(0) == 0x3C && (ba.at(1) == 0x3F || ba.at(1) == 0x73)) {
                 type = u"image/svg+xml";
                 if (suffix.isEmpty()) {
                     source += u".svg";
                 }
             }
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/importexport/musicxml/internal/export/exportmusicxml.cpp` around lines
5154 - 5188, The fallback MIME sniffing block (using isi->buffer(), type,
suffix, source) lacks TIFF detection; add an else-if that checks the first four
bytes for TIFF signatures (little-endian: 0x49,0x49,0x2A,0x00 or big-endian:
0x4D,0x4D,0x00,0x2A) and set type = u"image/tiff" and, if suffix.isEmpty(),
append an appropriate extension to source (e.g., u".tif" or u".tiff"); keep this
branch alongside the other image checks before the final fallback to
application/octet-stream.

5228-5235: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Expose plain-XML image omissions so the caller can warn.

This branch drops the binary image and writes only an other-direction placeholder, but it never records that an omission happened. saveXml() only returns bool, so the exporter gives callers no way to surface the warning required by #32688 without duplicating a separate score scan.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/importexport/musicxml/internal/export/exportmusicxml.cpp` around lines
5228 - 5235, The export currently writes an "other-direction" placeholder for
omitted binary images in the branch that calls
m_xml.tagRaw(u"other-direction"...) but never records that an omission occurred,
so callers of saveXml() cannot warn; modify the export path to set a persistent
omission indicator (e.g., a member bool like imageOmitted or an
out-parameter/status return from saveXml()) when the code in exportmusicxml.cpp
that constructs otherTag and calls m_xml.tagRaw runs, and propagate that flag
back to the public saveXml() caller (or change saveXml() to return a status/enum
instead of plain bool) so callers can detect and surface the image-omission
warning; ensure the flag is reset/initialized appropriately and referenced by
downstream callers instead of duplicating score scans.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/importexport/musicxml/internal/export/exportmusicxml.cpp`:
- Around line 5154-5188: The fallback MIME sniffing block (using isi->buffer(),
type, suffix, source) lacks TIFF detection; add an else-if that checks the first
four bytes for TIFF signatures (little-endian: 0x49,0x49,0x2A,0x00 or
big-endian: 0x4D,0x4D,0x00,0x2A) and set type = u"image/tiff" and, if
suffix.isEmpty(), append an appropriate extension to source (e.g., u".tif" or
u".tiff"); keep this branch alongside the other image checks before the final
fallback to application/octet-stream.
- Around line 5228-5235: The export currently writes an "other-direction"
placeholder for omitted binary images in the branch that calls
m_xml.tagRaw(u"other-direction"...) but never records that an omission occurred,
so callers of saveXml() cannot warn; modify the export path to set a persistent
omission indicator (e.g., a member bool like imageOmitted or an
out-parameter/status return from saveXml()) when the code in exportmusicxml.cpp
that constructs otherTag and calls m_xml.tagRaw runs, and propagate that flag
back to the public saveXml() caller (or change saveXml() to return a status/enum
instead of plain bool) so callers can detect and surface the image-omission
warning; ensure the flag is reset/initialized appropriately and referenced by
downstream callers instead of duplicating score scans.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bd9a9bdd-f6dc-4bba-8e48-68a06f2dfa8e

📥 Commits

Reviewing files that changed from the base of the PR and between 96ac90d and 5a1bf4d.

📒 Files selected for processing (1)
  • src/importexport/musicxml/internal/export/exportmusicxml.cpp

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/importexport/musicxml/internal/export/exportmusicxml.cpp (1)

5234-5238: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Make the plain .musicxml fallback explicit and user-visible.

On Lines 5234-5238, the non-.mxl path still silently drops the binary and emits only the source string inside other-direction. That misses the uncompressed-export contract from the linked issue: callers have no warning to surface, and consumers cannot reliably tell this is an omitted-image placeholder or recover the original MIME type. Please thread a warning out of the plain-export path and serialize a stable omission marker that includes at least source and type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/importexport/musicxml/internal/export/exportmusicxml.cpp` around lines
5234 - 5238, The non-.mxl branch in exportmusicxml.cpp currently writes only the
raw source string into other-direction (see otherTag,
positioningAttributes(img), m_xml.tagRaw(XmlWriter::xmlString(source))) and must
instead emit an explicit omission marker and a user-visible warning; update that
branch to (1) log a warning via the module's logging facility indicating the
image was omitted from uncompressed .musicxml export, and (2) serialize a stable
omission element inside other-direction that includes at minimum the original
source and the image MIME/type (obtainable from img or its metadata) so
consumers can detect the omission and recover type information instead of seeing
an ambiguous raw source string. Ensure you modify the code that builds otherTag
and the m_xml.tagRaw call to write the structured omission marker (source +
type) and to call the logger.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/importexport/musicxml/internal/export/exportmusicxml.cpp`:
- Around line 5234-5238: The non-.mxl branch in exportmusicxml.cpp currently
writes only the raw source string into other-direction (see otherTag,
positioningAttributes(img), m_xml.tagRaw(XmlWriter::xmlString(source))) and must
instead emit an explicit omission marker and a user-visible warning; update that
branch to (1) log a warning via the module's logging facility indicating the
image was omitted from uncompressed .musicxml export, and (2) serialize a stable
omission element inside other-direction that includes at minimum the original
source and the image MIME/type (obtainable from img or its metadata) so
consumers can detect the omission and recover type information instead of seeing
an ambiguous raw source string. Ensure you modify the code that builds otherTag
and the m_xml.tagRaw call to write the structured omission marker (source +
type) and to call the logger.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23ed7484-ab85-4c0c-aaa8-2cf5fbae3e52

📥 Commits

Reviewing files that changed from the base of the PR and between 5a1bf4d and d5cab93.

📒 Files selected for processing (1)
  • src/importexport/musicxml/internal/export/exportmusicxml.cpp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Musicxml Export] - Pictures should be exported

4 participants