Source code for mscxyz.meta

"""Class for metadata maniplation"""

from __future__ import annotations

import typing
from typing import Optional

import tmep
from lxml.etree import Element, _Element

if typing.TYPE_CHECKING:
    from mscxyz.score import Score


[docs] class UnmatchedFormatStringError(Exception): def __init__(self, format_string: str, input_string: str) -> None: self.msg = f"Your format string “{format_string}” doesn’t match on this input string: “{input_string}”" Exception.__init__(self, self.msg)
[docs] class FormatStringNoFieldError(Exception): def __init__(self, format_string: str) -> None: self.msg = f"No fields found in your format string “{format_string}”!" Exception.__init__(self, self.msg)
[docs] class Metatag: """ The class provides access to the MuseScore metadata fields. The class should not be renamed to ``MetaTag`` because it would conflict with the naming scheme of the fields ``metatag_title`` etc. :see: `MuseScore Handbook <https://musescore.org/en/handbook/4/project-properties>`_ The available ``metaTag`` fields are: * `arranger` * `audioComUrl` (new in v4) * `composer` * `copyright` * `creationDate` * `lyricist` * `movementNumber` * `movementTitle` * `mscVersion` * `platform` * `poet` (not in v4) * `source` * `sourceRevisionId` * `subtitle` * `translator` * `workNumber` * `workTitle` Project properties dialog (4.7.3): * Work title * Subtitle * Composer * Arranger * Lyricist * Translator * Copyright * Work number * Movement title * Movement number * Creation date * Platfrom * Source * Audio.com URL version 4 .. code-block:: xml <museScore version="4.20"> <Score> <metaTag name="arranger"></metaTag> <metaTag name="audioComUrl"></metaTag> <metaTag name="composer">Composer / arranger</metaTag> <metaTag name="copyright"></metaTag> <metaTag name="creationDate">2024-01-05</metaTag> <metaTag name="lyricist"></metaTag> <metaTag name="movementNumber"></metaTag> <metaTag name="movementTitle"></metaTag> <metaTag name="platform">Linux</metaTag> <metaTag name="source"></metaTag> <metaTag name="sourceRevisionId"></metaTag> <metaTag name="subtitle">Subtitle</metaTag> <metaTag name="translator"></metaTag> <metaTag name="workNumber"></metaTag> <metaTag name="workTitle">Untitled score</metaTag> 4.7.3: New Score: .. code-block:: xml <museScore version="4.70"> <Score> <metaTag name="arranger"></metaTag> <metaTag name="composer">Composer / arran <metaTag name="arranger"></metaTag> <metaTag name="composer">Composer / arranger</metaTag> <metaTag name="copyright"></metaTag> <metaTag name="creationDate">2026-06-14</metaTag> <metaTag name="lyricist"></metaTag> <metaTag name="movementNumber"></metaTag> <metaTag name="movementTitle"></metaTag> <metaTag name="platform">Linux</metaTag> <metaTag name="source"></metaTag> <metaTag name="translator"></metaTag> <metaTag name="workNumber"></metaTag> <metaTag name="workTitle">Untitled score</metaTag> """ fields = ( "arranger", "audio_com_url", "composer", "copyright", "creation_date", "lyricist", "movement_number", "movement_title", "msc_version", "platform", "poet", "source", "source_revision_id", "subtitle", "translator", "work_number", "work_title", ) score: "Score" xml_root: _Element def __init__(self, score: "Score") -> None: self.score = score self.xml_root = score.xml_root def __get_element(self, field: str) -> _Element: score_element: _Element = self.score.xml.find_safe("Score") element: _Element | None = self.score.xml.xpath( '//metaTag[@name="' + field + '"]' ) if element is None: _, element = self.score.xml.create_sub_element( score_element, "metaTag", "", attrib={"name": field} ) return element def __get_text(self, field: str) -> Optional[str]: element: _Element | None = self.__get_element(field) return self.score.xml.get_text(element) def __set_text(self, field: str, value: Optional[str]) -> None: element: _Element = self.__get_element(field) element.text = value @property def arranger(self) -> Optional[str]: """ .. code-block:: xml <metaTag name="arranger">...</metaTag> """ return self.__get_text("arranger") @arranger.setter def arranger(self, value: Optional[str]) -> None: self.__set_text("arranger", value) @property def audio_com_url(self) -> Optional[str]: """ .. code-block:: xml <metaTag name="audioComUrl">...</metaTag> """ return self.__get_text("audioComUrl") @audio_com_url.setter def audio_com_url(self, value: Optional[str]) -> None: self.__set_text("audioComUrl", value) @property def composer(self) -> Optional[str]: """Same text as "Composer" on the first page of the score .. code-block:: xml <metaTag name="composer">...</metaTag> """ return self.__get_text("composer") @composer.setter def composer(self, value: Optional[str]) -> None: self.__set_text("composer", value) @property def copyright(self) -> Optional[str]: """Same text as "Copyright" on the first page of the score. .. code-block:: xml <metaTag name="copyright">...</metaTag> """ return self.__get_text("copyright") @copyright.setter def copyright(self, value: Optional[str]) -> None: self.__set_text("copyright", value) @property def creation_date(self) -> Optional[str]: """ https://github.com/musescore/MuseScore/blob/06793ff5ff3065fe87fe9a8a651a6d20f49fd28c/src/engraving/dom/masterscore.cpp#L93 .. code-block:: xml <metaTag name="creationDate">2024-01-05</metaTag> """ return self.__get_text("creationDate") @creation_date.setter def creation_date(self, value: Optional[str]) -> None: self.__set_text("creationDate", value) @property def lyricist(self) -> Optional[str]: """Same text as “Lyricist” on the first page of the score. .. code-block:: xml <metaTag name="lyricist">...</metaTag> """ return self.__get_text("lyricist") @lyricist.setter def lyricist(self, value: Optional[str]) -> None: self.__set_text("lyricist", value) @property def movement_number(self) -> Optional[str]: """ .. code-block:: xml <metaTag name="movementNumber">...</metaTag> """ return self.__get_text("movementNumber") @movement_number.setter def movement_number(self, value: Optional[str]) -> None: self.__set_text("movementNumber", value) @property def movement_title(self) -> Optional[str]: """ .. code-block:: xml <metaTag name="movementTitle">...</metaTag> """ return self.__get_text("movementTitle") @movement_title.setter def movement_title(self, value: Optional[str]) -> None: self.__set_text("movementTitle", value) @property def msc_version(self) -> Optional[str]: """ .. code-block:: xml <metaTag name="mscVersion">4.20</metaTag> """ return self.__get_text("mscVersion") @msc_version.setter def msc_version(self, value: Optional[str]) -> None: self.__set_text("mscVersion", value) @property def platform(self) -> Optional[str]: """The computing platform the score was created on. This might be empty if the score was saved in test mode. https://github.com/musescore/MuseScore/blob/06793ff5ff3065fe87fe9a8a651a6d20f49fd28c/src/engraving/dom/masterscore.cpp#L74-L81 .. code-block:: xml <metaTag name="platform">Linux</metaTag> <metaTag name="platform">Apple Macintosh</metaTag> """ return self.__get_text("platform") @platform.setter def platform(self, value: Optional[str]) -> None: self.__set_text("platform", value) @property def poet(self) -> Optional[str]: """ .. code-block:: xml <metaTag name="poet">...</metaTag> """ return self.__get_text("poet") @poet.setter def poet(self, value: Optional[str]) -> None: self.__set_text("poet", value) @property def source(self) -> Optional[str]: """May contain a URL if the score was downloaded from or Publish to MuseScore.com. .. code-block:: xml <metaTag name="source">http://musescore.com/isaacweiss/getting-started</metaTag> <metaTag name="source">http://musescore.com/score/111410</metaTag> """ return self.__get_text("source") @source.setter def source(self, value: Optional[str]) -> None: self.__set_text("source", value) @property def source_revision_id(self) -> Optional[str]: """ .. code-block:: xml <metaTag name="sourceRevisionId">...</metaTag> """ return self.__get_text("sourceRevisionId") @source_revision_id.setter def source_revision_id(self, value: Optional[str]) -> None: self.__set_text("sourceRevisionId", value) @property def subtitle(self) -> Optional[str]: """ The subtitle. It has the same text as “Subtitle” on the first page of the score. .. code-block:: xml <metaTag name="subtitle">Subtitle</metaTag> """ return self.__get_text("subtitle") @subtitle.setter def subtitle(self, value: Optional[str]) -> None: self.__set_text("subtitle", value) @property def translator(self) -> Optional[str]: """ .. code-block:: xml <metaTag name="translator">...</metaTag> """ return self.__get_text("translator") @translator.setter def translator(self, value: Optional[str]) -> None: self.__set_text("translator", value) @property def work_number(self) -> Optional[str]: """ .. code-block:: xml <metaTag name="workNumber">...</metaTag> """ return self.__get_text("workNumber") @work_number.setter def work_number(self, value: Optional[str]) -> None: self.__set_text("workNumber", value) @property def work_title(self) -> Optional[str]: """ The Work Title. It has the same text as “Title” on the first page of the score. .. code-block:: xml <metaTag name="workTitle">Untitled score</metaTag> """ return self.__get_text("workTitle") @work_title.setter def work_title(self, value: Optional[str]) -> None: self.__set_text("workTitle", value)
[docs] def clean(self) -> None: for field in self.fields: setattr(self, field, None)
[docs] class VboxText: """ Manage one ``<Text>`` entry inside a ``<VBox>`` element. .. code-block:: xml <Text> <eid>hibWj5obkBO_6AUar0D+y6K</eid> <style>title</style> <text>Untitled score</text> </Text> Text style overrides: ===================== Global overrides: ----------------- .. code-block:: xml <Text> <eid>MX+29xsCL0G_gQ+qjPGU4EO</eid> <style>title</style> <family>FreeSans</family> <bold>1</bold> <italic>1</italic> <text><b><i><font face="FreeSans"/>Untitled score</i></b></text> </Text> Inline overrides: ----------------- .. code-block:: xml <Text> <eid>0iZtR+qEd6C_jIRPV50caLG</eid> <style>composer</style> <text><i>Composer</i> / <b>arranger</b></text> </Text> :param style: The style name used in the ``<style>...</style>`` element. :param parent_vbox: The parent ``<VBox>`` element where ``<Text>`` lives. :param container: The existing ``<Text>`` element or ``None``. """ __parent_vbox: _Element """The parent vbox element.""" def __init__( self, style: str, parent_vbox: _Element, container: Optional[_Element], ) -> None: self.__style = style self.__parent_vbox = parent_vbox self.__container = container self.__style_element = None self.__text_element = None if self.__container is not None: self.__style_element = self.__container.find("style") self.__text_element = self.__container.find("text")
[docs] def exists(self) -> bool: """Checks whether the ``<Text>...</Text>`` element with the given style name exists. :return: ``True`` if the ``</Text>`` element with the given style name, otherwise ``False``. """ return self.__container is not None
[docs] def reset_style(self) -> None: """Reset the text style overrides. This method removes style override tags from the ``<Text>`` container and keeps only ``<eid>``, ``<style>``, and ``<text>`` tags. Before: .. code-block:: xml <Text> <eid>MX+29xsCL0G_gQ+qjPGU4EO</eid> <style>title</style> <family>FreeSans</family> <bold>1</bold> <italic>1</italic> <text><b><i><font face="FreeSans"/>Untitled score</i></b></text> </Text> After: .. code-block:: xml <Text> <eid>MX+29xsCL0G_gQ+qjPGU4EO</eid> <style>title</style> <text>Untitled score</text> </Text> """ if self.__container is None: return for element in list(self.__container): if element.tag not in ("eid", "style", "text"): self.__container.remove(element) # The get the plain text and remove the HMTL style tags. self.text = self.text
[docs] def remove(self) -> None: """Remove the container element ``<Text>...</Text>`` from the parent ``<Vbox>...</Vbox>`` element.""" if self.__container is not None: self.__parent_vbox.remove(self.__container) self.__container = None self.__style_element = None self.__text_element = None return None
__container: Optional[_Element] """The surrounding text element in uppercase letters (``<Text>...</Text>``).""" @property def _container(self) -> _Element: """The surrounding text element in uppercase letters (``<Text>...</Text>``).""" if self.__container is None: self.__container = Element("Text") self.__parent_vbox.append(self.__container) return self.__container __style_element: Optional[_Element] """The style element, for example ``<style>title</style>``.""" @property def _style_element(self) -> _Element: """The style element, for example ``<style>title</style>``.""" if self.__style_element is None: self.__style_element = Element("style") self.__style_element.text = self.__style self._container.append(self.__style_element) return self.__style_element __style: str """The name of the style.""" @property def style(self) -> str: """The name of the style. In the XML markup ``<style>title</style>``, for example, the style name is ``title``.""" return self.__style @style.setter def style(self, style: str) -> None: self.__style = style self._style_element.text = style __text_element: Optional[_Element] @property def _text_element(self) -> _Element: """The text element in lowercase letters inside the container (``<text>...</text>``).""" if self.__text_element is None: self.__text_element = Element("text") self._container.append(self.__text_element) return self.__text_element @property def text(self) -> Optional[str]: """ The plain text content. Setting ``text`` to ``None`` removes the entire ``<Text>`` container.""" if self.__container is None: return None # To get the content of all child elements, # for example: ``<text><b><i><font face="FreeSans"/>Untitled score</i></b></text>`` content = self._text_element.xpath(".//text()") if ( isinstance(content, bool) or isinstance(content, float) or isinstance(content, int) ): return str(content) elements: list[str] = [] for i in content: elements.append(str(i)) if len(elements) == 0: return None return "".join(elements) @text.setter def text(self, content: Optional[str]) -> None: if content is None: self.remove() return None # To create the style-tag self.style = self.__style self._text_element.clear() self._text_element.text = content
[docs] class Vbox: """The first `vertical` box or frame of a score. Available fields: * `title`: Title * `subtitle`: Subtitle * `composer`: Composer * `lyricist` (poet): Lyricist * `instrument_excerpt`: Part name Version 2, 3 .. code-block:: xml <Staff id="1"> <VBox> <height>10</height> <Text> <style>Title</style> <text>Title</text> </Text> <Text> <style>Composer</style> <text>Composer</text> </Text> </VBox> </Staff> Version 4 .. code-block:: xml <Staff id="1"> <VBox> <height>10</height> <boxAutoSize>0</boxAutoSize> <eid>4294967418</eid> <Text> <eid>8589934598</eid> <style>title</style> <text>Title</text> </Text> <Text> <eid>12884901894</eid> <style>composer</style> <text>Composer</text> </Text> </VBox> </Staff> Version 4.6.5 (lyricist is now poet) .. code-block:: xml <Staff id="1"> <VBox> <height>10</height> <eid>3yH8HKTgwb_p8uM4j9efcE</eid> <Text> <eid>hibWj5obkBO_6AUar0D+y6K</eid> <style>title</style> <text>Untitled score</text> </Text> <Text> <eid>mDB0b0Sa0SM_73LrivvAWEC</eid> <style>subtitle</style> <text>Subtitle</text> </Text> <Text> <eid>13Y3EfRgceC_QRif2MiL9ZM</eid> <style>composer</style> <text>Composer / arranger</text> </Text> <Text> <eid>5bIbUnhSBCP_qqv84cvOnMO</eid> <style>poet</style> <text>Lyricist</text> </Text> </VBox> </Staff> """ # eid # eid Refactor EID into a pseudo-UUID https://github.com/musescore/MuseScore/commit/a6784896ae10a08559ac48ca4fba9ba63d2b471d # https://github.com/musescore/MuseScore/blob/master/src/engraving/infrastructure/eid.cpp # https://github.com/musescore/MuseScore/blob/master/src/engraving/infrastructure/eidregister.cpp # https://musescore.org/en/node/379559 # However, in 4.4.4 it is of the 'numeric' type, while in 4.5.2 it is of the 'alphanumeric' type. # I believe their main purpose is to link elements between main score and parts. # EID: same examples # TVJRSgwuZD_EwSwNORnKz # pYMYtt7e63I_bcIcXkGdYWH # /da47B/ELqH_MFU63hCcg7F # OTzv11PTP6E_aqOziUGyuBF # UUID: 907a3f91-5c2a-441e-98c2-7c65d4b4b0b5 # as base64: b8tRS7h4TJ2Vt43Dp85v2A # as uuid : 6fcb514b-b878-4c9d-95b7-8dc3a7ce6fd8 fields = ( "composer", "instrument_excerpt", "lyricist", "subtitle", "title", ) _score: "Score" _vbox: _Element def __init__(self, score: "Score") -> None: self._score = score xpath = '/museScore/Score/Staff[@id="1"]' vbox = self._score.xml.xpath(xpath + "/VBox") if vbox is None: vbox, _ = self._score.xml.create_sub_element("VBox", "height", "10") self._score.xml.xpath_safe(xpath).insert(0, vbox) self._vbox = vbox self.migrate_lyricist() def __normalize_style_name(self, style: str) -> str: """ :param style: The string inside the ``<style>`` tags, for example ``Title`` or ``Composer`` or for v4 ``title`` or ``composer``. """ if self._score.version_major in (2, 3): style = style.title() elif self._score.version_major == 4: style = style.lower() return style def __get_container(self, style: str) -> Optional[_Element]: """ Get the ``<Text>...</Text>``container XML element by the style name. :param style: The string inside the ``<style>`` tag, for example ``Title`` or ``Composer`` or for v4 ``title`` or ``composer``. :return: The ``<Text>...</Text>``container XML element or ``None`` if the XML element doen’t exist. """ for element in self._vbox: s = element.find("style") if s is not None and s.text == self.__normalize_style_name(style): return element return None def __create_vbox_text(self, style_name: str) -> VboxText: return VboxText( self.__normalize_style_name(style_name), self._vbox, self.__get_container(style_name), ) __title: Optional[VboxText] = None
[docs] def set_text(self, style_name: str, text: str) -> VboxText: """ Add a ``<Text>`` element to the first VBox of a score, or set it if it already exists. If a ``<Text>`` element already exists, its content is updated; otherwise, the ``<Text>`` element is created. :param style_name: The style name used in the ``<style>...</style>`` element. :return: A :class:`VBoxText` wrapper instance that provides further, more fine-grained access to the text element. """ element = self.__create_vbox_text(style_name) element.text = text return element
[docs] def get_text(self, style_name: str) -> Optional[str]: """Retrieve the text content of a ``<Text>`` element by its style name. :param style_name: The style name used in the ``<style>...</style>`` element. :return: The text content of the ``<Text>`` element """ element = self.__create_vbox_text(style_name) return element.text
[docs] def rename_style(self, old: str, new: str) -> VboxText: """Rename the style name of a ``<Text>`` element. :param old: The old style name used in the ``<style>...</style>`` element. :param new: The new style name used in the ``<style>...</style>`` element. :return: A :class:`VBoxText` wrapper instance that provides further, more fine-grained access to the text element. """ element = self.__create_vbox_text(old) element.style = new return element
[docs] def remove_text(self, style_name: str) -> VboxText: """Remove a ``<Text>`` element from the first VBox in a score by its style name. :param style_name: The style name used in the ``<style>...</style>`` element. :return: A :class:`VBoxText` wrapper instance that provides further, more fine-grained access to the text element. """ element = self.__create_vbox_text(style_name) element.remove() return element
[docs] def reset_text_style(self, style_name: str) -> VboxText: """:param style_name: The style name used in the ``<style>...</style>`` element. :return: A :class:`VBoxText` wrapper instance that provides further, more fine-grained access to the text element. """ element = self.__create_vbox_text(style_name) element.reset_style() return element
@property def title_element(self) -> VboxText: """ The title element of the first `vertical` box or frame of a score. .. code-block:: xml <Staff id="1"> <VBox> <height>10</height> <boxAutoSize>0</boxAutoSize> <eid>3yH8HKTgwb_p8uM4j9efcE</eid> <Text> <eid>9EZXpseYfo_z5oSRZ7xoaL</eid> <style>title</style> <text>Mondscheinsonate</text> </Text> </VBox> </Staff> """ if self.__title is None: self.__title = self.__create_vbox_text("title") return self.__title @property def title(self) -> Optional[str]: """ The title text field of the first `vertical` box or frame of a score. .. code-block:: xml <Staff id="1"> <VBox> <height>10</height> <boxAutoSize>0</boxAutoSize> <eid>3yH8HKTgwb_p8uM4j9efcE</eid> <Text> <eid>9EZXpseYfo_z5oSRZ7xoaL</eid> <style>title</style> <text>Mondscheinsonate</text> </Text> </VBox> </Staff> """ return self.title_element.text @title.setter def title(self, value: Optional[str]) -> None: """ The title plain text content of the first `vertical` box or frame of a score. Setting this field to ``None`` will delete the corresponding XML element from the score. If this field is ``None``, the corresponding XML element does not exist. """ self.title_element.text = value __subtitle: Optional[VboxText] = None @property def subtitle_element(self) -> VboxText: """ The subtitle element of the first `vertical` box or frame of a score. .. code-block:: xml <Staff id="1"> <VBox> <height>10</height> <boxAutoSize>0</boxAutoSize> <eid>3yH8HKTgwb_p8uM4j9efcE</eid> <Text> <eid>W2TmIWNulw_3xwF7Hs22D</eid> <style>subtitle</style> <text>1. Satz</text> </Text> </VBox> </Staff> """ if self.__subtitle is None: self.__subtitle = self.__create_vbox_text("subtitle") return self.__subtitle @property def subtitle(self) -> Optional[str]: """ The subtitle plain text content of the first `vertical` box or frame of a score. Setting this field to ``None`` will delete the corresponding XML element from the score. If this field is ``None``, the corresponding XML element does not exist. """ return self.subtitle_element.text @subtitle.setter def subtitle(self, value: Optional[str]) -> None: self.subtitle_element.text = value __composer: Optional[VboxText] = None @property def composer_element(self) -> VboxText: """ The composer element of the first `vertical` box or frame of a score. .. code-block:: xml <Staff id="1"> <VBox> <height>10</height> <boxAutoSize>0</boxAutoSize> <eid>3yH8HKTgwb_p8uM4j9efcE</eid> <Text> <eid>TzY/P+VBDFG_lyazF6x468M</eid> <style>composer</style> <text>Ludwig van Beethoven</text> </Text> </VBox> </Staff> """ if self.__composer is None: self.__composer = self.__create_vbox_text("composer") return self.__composer @property def composer(self) -> Optional[str]: """ The composer plain text content of the first `vertical` box or frame of a score. Setting this field to ``None`` will delete the corresponding XML element from the score. If this field is ``None``, the corresponding XML element does not exist. """ return self.composer_element.text @composer.setter def composer(self, value: Optional[str]) -> None: self.composer_element.text = value __lyricist: Optional[VboxText] = None @property def _legacy_lyricist_element(self) -> VboxText: if self.__lyricist is None: self.__lyricist = self.__create_vbox_text("lyricist") return self.__lyricist __poet: Optional[VboxText] = None @property def lyricist_element(self) -> VboxText: """ The lyricist element of the first `vertical` box or frame of a score. .. code-block:: xml <Staff id="1"> <VBox> <height>10</height> <boxAutoSize>0</boxAutoSize> <eid>3yH8HKTgwb_p8uM4j9efcE</eid> <Text> <eid>iklYO/iK0CE_kALp5O5iuVN</eid> <style>poet</style> <text>Johann Wolfgang von Goethe</text> </Text> </VBox> </Staff> """ if self.__poet is None: self.__poet = self.__create_vbox_text("poet") return self.__poet
[docs] def migrate_lyricist(self) -> None: """Migrate the lyricist text element by renaming the style from ``lyricist`` to ``poet``.""" if ( self._legacy_lyricist_element.exists() and not self.lyricist_element.exists() ): self._legacy_lyricist_element.style = "poet" self.__poet = self.__lyricist self.__lyricist = None elif self._legacy_lyricist_element.exists() and self.lyricist_element.exists(): self._legacy_lyricist_element.remove()
@property def lyricist(self) -> Optional[str]: """ The lyricist plain text content of the first `vertical` box or frame of a score. Setting this field to ``None`` will delete the corresponding XML element from the score. If this field is ``None``, the corresponding XML element does not exist. """ return self.lyricist_element.text @lyricist.setter def lyricist(self, value: Optional[str]) -> None: self.lyricist_element.text = value __instrument_excerpt: Optional[VboxText] = None @property def instrument_excerpt_element(self) -> VboxText: if self.__instrument_excerpt is None: self.__instrument_excerpt = self.__create_vbox_text("instrument_excerpt") return self.__instrument_excerpt @property def instrument_excerpt(self) -> Optional[str]: """ The instrument excerpt text field of the first `vertical` box or frame of a score. .. code-block:: xml <Staff id="1"> <VBox> <height>10</height> <boxAutoSize>0</boxAutoSize> <eid>4294967418</eid> <Text> <eid>fx/RTjxo4CE_M0P0jIU0j3L</eid> <style>instrument_excerpt</style> <text>Instrument Name</text> </Text> </VBox> </Staff> """ return self.instrument_excerpt_element.text @instrument_excerpt.setter def instrument_excerpt(self, value: Optional[str]) -> None: self.instrument_excerpt_element.text = value
[docs] def clean(self) -> None: """Remove the text elements :attr:`title` :attr:`subtitle`, :attr:`composer`, :attr:`lyricist` and :attr:`instrument_excerpt` from the vbox.""" self.title_element.remove() self.subtitle_element.remove() self.composer_element.remove() self.lyricist_element.remove() self.instrument_excerpt_element.remove()
[docs] def reset_style(self) -> None: """Reset the text style overrides of the elements :attr:`title` :attr:`subtitle`, :attr:`composer`, :attr:`lyricist` and :attr:`instrument_excerpt` from the vbox. """ self.title_element.reset_style() self.subtitle_element.reset_style() self.composer_element.reset_style() self.lyricist_element.reset_style() self.instrument_excerpt_element.reset_style()
[docs] class Meta: """ High-level interface for score metadata. This class combines metadata stored in MuseScore ``metaTag`` elements (:class:`Metatag`) and text objects in the first vertical frame (:class:`Vbox`) and exposes them through a unified API. MuseScore version 4.7.2 metatag_lyricist == vbox_poet """ score: "Score" metatag: Metatag vbox: Vbox def __init__(self, score: "Score") -> None: """Initialize metadata helpers for a score.""" self.score = score self.metatag = Metatag(self.score) self.vbox = Vbox(self.score)
[docs] def sync_fields(self) -> None: """ Re-assign key fields to trigger internal synchronization. This forces current values to be written back through property setters, ensuring ``metaTag`` and ``VBox`` representations are aligned. """ self.title = self.title self.subtitle = self.subtitle self.composer = self.composer self.lyricist = self.lyricist
[docs] def write_to_log_file(self, log_file: str, format_string: str) -> None: """ Write formatted exported score fields to a log file. :param log_file: Path of the output log file. :param format_string: Template string parsed with exported fields. """ log = open(log_file, "w") log.write(tmep.parse(format_string, self.score.fields.export_to_dict()) + "\n") log.close()
[docs] def clean(self) -> None: """ Clean all metadata fields of the object. """ self.metatag.clean() self.vbox.clean()
[docs] def delete_duplicates(self) -> None: """ Delete duplicates in the metadata. This method checks if the :attr:`lyricist` and :attr:`composer` are the same, and if so, it sets :attr:`lyricist` to an empty string. It also checks if :attr:`title` is empty but :attr:`subtitle` is not, and if so, it sets :attr:`title` to :attr:`subtitle`. Finally, it checks if :attr:`subtitle` is the same as :attr:`title`, and if so, it sets :attr:`subtitle` to an empty string. """ if self.lyricist == self.composer: self.lyricist = None if not self.title and self.subtitle: self.title = self.subtitle if self.subtitle == self.title: self.subtitle = None
[docs] def reload(self, save: bool = False) -> Meta: """ Reload the MuseScore file. :param save: Whether to save the changes before reloading. Default is False. :return: The reloaded Meta object. :see: :meth:`mscxyz.Score.reload` """ return self.score.reload(save).meta
def __pick_value(self, *values: Optional[str]) -> Optional[str]: for value in values: if value: return value return None @property def title(self) -> Optional[str]: """ Get and set the value of :attr:`Vbox.title` and :attr:`Metatag.work_title` all at once. If the attributes have different values, then the attribute :attr:`Vbox.title` is preferred. """ return self.__pick_value(self.vbox.title, self.metatag.work_title) @title.setter def title(self, value: Optional[str]) -> None: self.vbox.title = self.metatag.work_title = value @property def subtitle(self) -> Optional[str]: """ Get and set the value of :attr:`Vbox.subtitle`, :attr:`Metatag.subtitle` and :attr:`Metatag.movement_title` all at once. If the attributes have different values, then the attribute :attr:`Vbox.subtitle` is preferred. """ return self.__pick_value( self.vbox.subtitle, self.metatag.subtitle, self.metatag.movement_title ) @subtitle.setter def subtitle(self, value: Optional[str]) -> None: self.vbox.subtitle = self.metatag.subtitle = self.metatag.movement_title = value @property def composer(self) -> Optional[str]: """ Get and set the value of :attr:`Vbox.composer` and :attr:`Metatag.composer` all at once. If the attributes have different values, then the attribute :attr:`Vbox.composer` is preferred. """ return self.__pick_value(self.vbox.composer, self.metatag.composer) @composer.setter def composer(self, value: Optional[str]) -> None: self.vbox.composer = self.metatag.composer = value @property def lyricist(self) -> Optional[str]: """ Get and set the value of :attr:`Vbox.lyricist` and :attr:`Metatag.lyricist` all at once. If the attributes have different values, then the attribute :attr:`Vbox.lyricist` is preferred. """ return self.__pick_value(self.vbox.lyricist, self.metatag.lyricist) @lyricist.setter def lyricist(self, value: Optional[str]) -> None: self.vbox.lyricist = self.metatag.lyricist = value