Source code for music21.musicxml.archiveTools
# -----------------------------------------------------------------------------
# Name: musicxml/archiveTools.py
# Purpose: Tools for compressing and decompressing MusicXML files
#
# Authors: Christopher Ariza
# Michael Scott Asato Cuthbert
#
# Copyright: Copyright © 2009, 2017-2024 Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# -----------------------------------------------------------------------------
'''
Tools for compressing and decompressing musicxml files.
'''
from __future__ import annotations
import os
import pathlib
import zipfile
from music21 import common
from music21 import environment
environLocal = environment.Environment('musicxml.archiveTools')
# -----------------------------------------------------------------------------
# compression
[docs]
def compressAllXMLFiles(*, deleteOriginal=False):
'''
Takes all filenames in corpus.paths and runs
:meth:`music21.musicxml.archiveTools.compressXML` on each. If the musicXML files are
compressed, the originals are deleted from the system.
'''
from music21.corpus.corpora import CoreCorpus
environLocal.warn('Compressing musicXML files...')
# this gets all .xml, .musicxml, .mxl etc.
for filename in CoreCorpus().getPaths(fileExtensions=('.xml',)):
compressXML(filename, deleteOriginal=deleteOriginal)
environLocal.warn(
'Compression complete. '
'Run the main test suite, fix bugs if necessary,'
'and then commit modified directories in corpus.'
)
[docs]
def compressXML(filename: str|pathlib.Path,
*,
deleteOriginal=False,
silent=False,
strictMxlCheck=True) -> bool:
'''
Takes a filename, and if the filename corresponds to a musicXML file with
an .musicxml or .xml extension, creates a corresponding compressed .mxl file in the same
directory.
If deleteOriginal is set to True, the original musicXML file is deleted
from the system.
If strictMxlCheck is False then any suffix will do.
Returns bool if successful.
'''
filename = str(filename)
if strictMxlCheck and not filename.endswith('.xml') and not filename.endswith('.musicxml'):
return False # not a musicXML file
fp = common.pathTools.cleanpath(filename, returnPathlib=True)
if not silent: # pragma: no cover
environLocal.warn(f'Updating file: {fp}')
newFilename = str(fp.with_suffix('.mxl'))
# contents of container.xml file in META-INF folder
container = f'''<?xml version="1.0" encoding="UTF-8"?>
<container>
<rootfiles>
<rootfile full-path="{fp.name}"/>
</rootfiles>
</container>
'''
# Export container and original xml file to system as a compressed XML.
with zipfile.ZipFile(
newFilename,
'w',
compression=zipfile.ZIP_DEFLATED,
) as myZip:
myZip.write(filename, fp.name)
myZip.writestr(
'META-INF' + os.path.sep + 'container.xml',
container,
)
# Delete uncompressed xml file from system
if deleteOriginal:
fp.unlink()
return True
[docs]
def uncompressMXL(filename: str|pathlib.Path,
*,
deleteOriginal=False,
silent=False,
strictMxlCheck=True) -> bool:
'''
Takes a filename, and if the filename corresponds to a compressed musicXML
file with an .mxl extension, creates a corresponding uncompressed .musicxml file
in the same directory.
If deleteOriginal is set to True, the original compressed musicXML file is
deleted from the system.
If strictMxlCheck is False then any type of file will attempt to be extracted.
Returns bool if successful.
'''
filename = str(filename)
if not filename.endswith('.mxl') and strictMxlCheck:
return False # not a compressed musicXML file
fp: pathlib.Path = common.pathTools.cleanpath(filename, returnPathlib=True)
if not silent: # pragma: no cover
environLocal.warn(f'Updating file: {fp}')
extractPath = str(fp.parent)
unarchivedName = fp.with_suffix('.musicxml').name
# Export container and original xml file to system as a compressed XML.
with zipfile.ZipFile(filename, 'r', compression=zipfile.ZIP_DEFLATED) as myZip:
try:
myZip.extract(member=unarchivedName, path=extractPath)
except KeyError:
try:
unarchivedName = unarchivedName.replace('.xml', '.musicxml')
myZip.extract(member=unarchivedName, path=extractPath)
except KeyError:
found_one_file = False
for storedName in myZip.namelist():
if 'META-INF' in storedName:
continue
myZip.extract(member=storedName, path=extractPath)
if not found_one_file:
# only rename one file: hope it is the right one.
extractPath_pathlib = pathlib.Path(extractPath)
wrongName = extractPath_pathlib / storedName
correctName = extractPath_pathlib / unarchivedName
wrongName.rename(correctName)
found_one_file = True
# Delete uncompressed xml file from system
if deleteOriginal:
fp.unlink()
return True
if __name__ == '__main__':
import sys
if len(sys.argv) >= 1:
for xmlName in sys.argv[1:]:
if xmlName.endswith('.xml'):
compressXML(xmlName)
elif xmlName.endswith('.musicxml'):
compressXML(xmlName)
elif xmlName.endswith('.mxl'):
uncompressMXL(xmlName)