Skip to content

Commit

Permalink
Merge pull request #203 from CPJKU/develop
Browse files Browse the repository at this point in the history
Release 1.2.1
  • Loading branch information
CarlosCancino-Chacon authored Feb 9, 2023
2 parents aa4e2d6 + 92eb8ac commit ed99f80
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 47 deletions.
60 changes: 49 additions & 11 deletions partitura/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@
import warnings
import os
import subprocess
import shutil
from tempfile import NamedTemporaryFile, TemporaryFile
from typing import Optional

from partitura import save_musicxml
from partitura.io.musescore import render_musescore
from partitura.score import ScoreLike

from partitura.utils.misc import PathLike, deprecated_alias


__all__ = ["render"]
Expand All @@ -36,7 +41,13 @@
# return s


def render(part, fmt="png", dpi=90, out_fn=None):
@deprecated_alias(out_fn="out", part="score_data")
def render(
score_data: ScoreLike,
fmt: str = "png",
dpi: int = 90,
out: Optional[PathLike] = None,
) -> None:
"""Create a rendering of one or more parts or partgroups.
The function can save the rendered image to a file (when
Expand All @@ -49,25 +60,23 @@ def render(part, fmt="png", dpi=90, out_fn=None):
Parameters
----------
part : :class:`partitura.score.Part` or :class:`partitura.score.PartGroup`
or a list of these
score_data : ScoreLike
The score content to be displayed
fmt : {'png', 'pdf'}, optional
The image format of the rendered material
out_fn : str or None, optional
The path of the image output file. If None, the rendering will
be displayed in a viewer.
"""

img_fn = render_musescore(part, fmt, out_fn, dpi)
img_fn = render_musescore(score_data, fmt, out, dpi)

if img_fn is None or not os.path.exists(img_fn):
img_fn = render_lilypond(part, fmt)
img_fn = render_lilypond(score_data, fmt)
if img_fn is None or not os.path.exists(img_fn):
return

if not out_fn:
if not out:
# NOTE: the temporary image file will not be deleted.
if platform.system() == "Linux":
subprocess.call(["xdg-open", img_fn])
Expand All @@ -77,9 +86,33 @@ def render(part, fmt="png", dpi=90, out_fn=None):
os.startfile(img_fn)


def render_lilypond(part, fmt="png"):
@deprecated_alias(part="score_data")
def render_lilypond(
score_data,
fmt="png",
out=None,
) -> Optional[PathLike]:
"""
Render a score-like object using Lilypond
Parameters
----------
score_data : ScoreLike
Score-like object to be rendered
fmt : {'png', 'pdf'}
Output image format
out : str or None, optional
The path of the image output file, if not specified, the
rendering will be saved to a temporary filename. Defaults to
None.
Returns
-------
out : PathLike
Path of the generated output image (or None if no image was generated).
"""
if fmt not in ("png", "pdf"):
print("warning: unsupported output format")
warnings.warn("warning: unsupported output format")
return None

prvw_sfx = ".preview.{}".format(fmt)
Expand All @@ -89,7 +122,7 @@ def render_lilypond(part, fmt="png"):
) as img_fh:

# save part to musicxml in file handle xml_fh
save_musicxml(part, xml_fh)
save_musicxml(score_data, xml_fh)
# rewind read pointer of file handle before we pass it to musicxml2ly
xml_fh.seek(0)

Expand Down Expand Up @@ -141,4 +174,9 @@ def render_lilypond(part, fmt="png"):
)
return

return img_fh.name
if out is not None:
shutil.copy(img_fh.name, out)
else:
out = img_fh.name

return out
24 changes: 22 additions & 2 deletions partitura/io/exportparangonada.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,30 @@ def save_parangonada_csv(
np.savetxt(
os.path.join(outdir, "ppart.csv"),
# outdir + os.path.sep + "perf_note_array.csv",
perf_note_array,
perf_note_array[
[
"onset_sec",
"duration_sec",
"pitch",
"velocity",
"track",
"channel",
"id",
]
],
fmt="%.20s",
delimiter=",",
header=",".join(perf_note_array.dtype.names),
header=",".join(
[
"onset_sec",
"duration_sec",
"pitch",
"velocity",
"track",
"channel",
"id",
]
),
comments="",
)
np.savetxt(
Expand Down
99 changes: 66 additions & 33 deletions partitura/io/musescore.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import platform
import warnings
import glob
import os
import shutil
import subprocess
Expand All @@ -16,12 +17,14 @@

from partitura.io.importmusicxml import load_musicxml
from partitura.io.exportmusicxml import save_musicxml
from partitura.score import Score
from partitura.score import Score, ScoreLike

from partitura.utils.misc import (
deprecated_alias,
deprecated_parameter,
PathLike,
concatenate_images,
PIL_EXISTS,
)


Expand Down Expand Up @@ -61,7 +64,7 @@ def find_musescore3():
result = shutil.which("/Applications/MuseScore 3.app/Contents/MacOS/mscore")

elif platform.system() == "Windows":
result = shutil.which(r"C:\Program Files\MuseScore 3\bin\MuseScore.exe")
result = shutil.which(r"C:\Program Files\MuseScore 3\bin\MuseScore3.exe")

return result

Expand Down Expand Up @@ -108,52 +111,67 @@ def load_via_musescore(

raise MuseScoreNotFoundException()

with NamedTemporaryFile(suffix=".musicxml") as xml_fh:
xml_fh = os.path.splitext(os.path.basename(filename))[0] + ".musicxml"

cmd = [mscore_exec, "-o", xml_fh.name, filename]
cmd = [mscore_exec, "-o", xml_fh, filename]

try:
try:

ps = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
ps = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)

if ps.returncode != 0:

raise FileImportException(
(
"Command {} failed with code {}. MuseScore "
"error messages:\n {}"
).format(cmd, ps.returncode, ps.stderr.decode("UTF-8"))
)
except FileNotFoundError as f:
if ps.returncode != 0:

raise FileImportException(
'Executing "{}" returned {}.'.format(" ".join(cmd), f)
(
"Command {} failed with code {}. MuseScore "
"error messages:\n {}"
).format(cmd, ps.returncode, ps.stderr.decode("UTF-8"))
)
except FileNotFoundError as f:

return load_musicxml(
filename=xml_fh.name,
validate=validate,
force_note_ids=force_note_ids,
raise FileImportException(
'Executing "{}" returned {}.'.format(" ".join(cmd), f)
)


def render_musescore(part, fmt, out_fn=None, dpi=90):
"""Render a part using musescore.
score = load_musicxml(
filename=xml_fh,
validate=validate,
force_note_ids=force_note_ids,
)

os.remove(xml_fh)

return score


@deprecated_alias(out_fn="out", part="score_data")
def render_musescore(
score_data: ScoreLike,
fmt: str,
out: Optional[PathLike] = None,
dpi: Optional[int] = 90,
) -> Optional[PathLike]:
"""
Render a score-like object using musescore.
Parameters
----------
part : Part
Part to be rendered
score_data : ScoreLike
Score-like object to be rendered
fmt : {'png', 'pdf'}
Output image format
out_fn : str or None, optional
out : str or None, optional
The path of the image output file, if not specified, the
rendering will be saved to a temporary filename. Defaults to
None.
dpi : int, optional
Image resolution. This option is ignored when `fmt` is
'pdf'. Defaults to 90.
Returns
-------
out : Optional[PathLike]
Path to the output generated image (or None if no image was generated)
"""
mscore_exec = find_musescore3()

Expand All @@ -172,14 +190,14 @@ def render_musescore(part, fmt, out_fn=None, dpi=90):
xml_fh = Path(tmpdir) / "score.musicxml"
img_fh = Path(tmpdir) / f"score.{fmt}"

save_musicxml(part, xml_fh)
save_musicxml(score_data, xml_fh)

cmd = [
mscore_exec,
"-T",
"10",
"-r",
"{}".format(dpi),
"{}".format(int(dpi)),
"-o",
os.fspath(img_fh),
os.fspath(xml_fh),
Expand Down Expand Up @@ -215,12 +233,27 @@ def render_musescore(part, fmt, out_fn=None, dpi=90):
# ps.stderr.decode('UTF-8')))

if fmt == "png":
img_fh = (img_fh.parent / (img_fh.stem + "-1")).with_suffix(img_fh.suffix)

if PIL_EXISTS:
# get all generated image files
img_files = glob.glob(
os.path.join(img_fh.parent, img_fh.stem + "-*.png")
)
concatenate_images(
filenames=img_files,
out=img_fh,
concat_mode="vertical",
)
else:
# The first image seems to be blank (MuseScore adds an empy page)
img_fh = (img_fh.parent / (img_fh.stem + "-2")).with_suffix(
img_fh.suffix
)

if img_fh.is_file():
if out_fn is None:
out_fn = os.path.join(gettempdir(), "partitura_render_tmp.png")
shutil.copy(img_fh, out_fn)
return out_fn
if out is None:
out = os.path.join(gettempdir(), "partitura_render_tmp.png")
shutil.copy(img_fh, out)
return out

return None
Loading

0 comments on commit ed99f80

Please sign in to comment.