Skip to content

Commit ed99f80

Browse files
Merge pull request #203 from CPJKU/develop
Release 1.2.1
2 parents aa4e2d6 + 92eb8ac commit ed99f80

File tree

7 files changed

+311
-47
lines changed

7 files changed

+311
-47
lines changed

partitura/display.py

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@
1010
import warnings
1111
import os
1212
import subprocess
13+
import shutil
1314
from tempfile import NamedTemporaryFile, TemporaryFile
15+
from typing import Optional
1416

1517
from partitura import save_musicxml
1618
from partitura.io.musescore import render_musescore
19+
from partitura.score import ScoreLike
20+
21+
from partitura.utils.misc import PathLike, deprecated_alias
1722

1823

1924
__all__ = ["render"]
@@ -36,7 +41,13 @@
3641
# return s
3742

3843

39-
def render(part, fmt="png", dpi=90, out_fn=None):
44+
@deprecated_alias(out_fn="out", part="score_data")
45+
def render(
46+
score_data: ScoreLike,
47+
fmt: str = "png",
48+
dpi: int = 90,
49+
out: Optional[PathLike] = None,
50+
) -> None:
4051
"""Create a rendering of one or more parts or partgroups.
4152
4253
The function can save the rendered image to a file (when
@@ -49,25 +60,23 @@ def render(part, fmt="png", dpi=90, out_fn=None):
4960
5061
Parameters
5162
----------
52-
part : :class:`partitura.score.Part` or :class:`partitura.score.PartGroup`
53-
or a list of these
63+
score_data : ScoreLike
5464
The score content to be displayed
5565
fmt : {'png', 'pdf'}, optional
5666
The image format of the rendered material
5767
out_fn : str or None, optional
5868
The path of the image output file. If None, the rendering will
5969
be displayed in a viewer.
60-
6170
"""
6271

63-
img_fn = render_musescore(part, fmt, out_fn, dpi)
72+
img_fn = render_musescore(score_data, fmt, out, dpi)
6473

6574
if img_fn is None or not os.path.exists(img_fn):
66-
img_fn = render_lilypond(part, fmt)
75+
img_fn = render_lilypond(score_data, fmt)
6776
if img_fn is None or not os.path.exists(img_fn):
6877
return
6978

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

7988

80-
def render_lilypond(part, fmt="png"):
89+
@deprecated_alias(part="score_data")
90+
def render_lilypond(
91+
score_data,
92+
fmt="png",
93+
out=None,
94+
) -> Optional[PathLike]:
95+
"""
96+
Render a score-like object using Lilypond
97+
98+
Parameters
99+
----------
100+
score_data : ScoreLike
101+
Score-like object to be rendered
102+
fmt : {'png', 'pdf'}
103+
Output image format
104+
out : str or None, optional
105+
The path of the image output file, if not specified, the
106+
rendering will be saved to a temporary filename. Defaults to
107+
None.
108+
109+
Returns
110+
-------
111+
out : PathLike
112+
Path of the generated output image (or None if no image was generated).
113+
"""
81114
if fmt not in ("png", "pdf"):
82-
print("warning: unsupported output format")
115+
warnings.warn("warning: unsupported output format")
83116
return None
84117

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

91124
# save part to musicxml in file handle xml_fh
92-
save_musicxml(part, xml_fh)
125+
save_musicxml(score_data, xml_fh)
93126
# rewind read pointer of file handle before we pass it to musicxml2ly
94127
xml_fh.seek(0)
95128

@@ -141,4 +174,9 @@ def render_lilypond(part, fmt="png"):
141174
)
142175
return
143176

144-
return img_fh.name
177+
if out is not None:
178+
shutil.copy(img_fh.name, out)
179+
else:
180+
out = img_fh.name
181+
182+
return out

partitura/io/exportparangonada.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,30 @@ def save_parangonada_csv(
147147
np.savetxt(
148148
os.path.join(outdir, "ppart.csv"),
149149
# outdir + os.path.sep + "perf_note_array.csv",
150-
perf_note_array,
150+
perf_note_array[
151+
[
152+
"onset_sec",
153+
"duration_sec",
154+
"pitch",
155+
"velocity",
156+
"track",
157+
"channel",
158+
"id",
159+
]
160+
],
151161
fmt="%.20s",
152162
delimiter=",",
153-
header=",".join(perf_note_array.dtype.names),
163+
header=",".join(
164+
[
165+
"onset_sec",
166+
"duration_sec",
167+
"pitch",
168+
"velocity",
169+
"track",
170+
"channel",
171+
"id",
172+
]
173+
),
154174
comments="",
155175
)
156176
np.savetxt(

partitura/io/musescore.py

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import platform
99
import warnings
10+
import glob
1011
import os
1112
import shutil
1213
import subprocess
@@ -16,12 +17,14 @@
1617

1718
from partitura.io.importmusicxml import load_musicxml
1819
from partitura.io.exportmusicxml import save_musicxml
19-
from partitura.score import Score
20+
from partitura.score import Score, ScoreLike
2021

2122
from partitura.utils.misc import (
2223
deprecated_alias,
2324
deprecated_parameter,
2425
PathLike,
26+
concatenate_images,
27+
PIL_EXISTS,
2528
)
2629

2730

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

6366
elif platform.system() == "Windows":
64-
result = shutil.which(r"C:\Program Files\MuseScore 3\bin\MuseScore.exe")
67+
result = shutil.which(r"C:\Program Files\MuseScore 3\bin\MuseScore3.exe")
6568

6669
return result
6770

@@ -108,52 +111,67 @@ def load_via_musescore(
108111

109112
raise MuseScoreNotFoundException()
110113

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

113-
cmd = [mscore_exec, "-o", xml_fh.name, filename]
116+
cmd = [mscore_exec, "-o", xml_fh, filename]
114117

115-
try:
118+
try:
116119

117-
ps = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
120+
ps = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
118121

119-
if ps.returncode != 0:
120-
121-
raise FileImportException(
122-
(
123-
"Command {} failed with code {}. MuseScore "
124-
"error messages:\n {}"
125-
).format(cmd, ps.returncode, ps.stderr.decode("UTF-8"))
126-
)
127-
except FileNotFoundError as f:
122+
if ps.returncode != 0:
128123

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

133-
return load_musicxml(
134-
filename=xml_fh.name,
135-
validate=validate,
136-
force_note_ids=force_note_ids,
132+
raise FileImportException(
133+
'Executing "{}" returned {}.'.format(" ".join(cmd), f)
137134
)
138135

139-
140-
def render_musescore(part, fmt, out_fn=None, dpi=90):
141-
"""Render a part using musescore.
136+
score = load_musicxml(
137+
filename=xml_fh,
138+
validate=validate,
139+
force_note_ids=force_note_ids,
140+
)
141+
142+
os.remove(xml_fh)
143+
144+
return score
145+
146+
147+
@deprecated_alias(out_fn="out", part="score_data")
148+
def render_musescore(
149+
score_data: ScoreLike,
150+
fmt: str,
151+
out: Optional[PathLike] = None,
152+
dpi: Optional[int] = 90,
153+
) -> Optional[PathLike]:
154+
"""
155+
Render a score-like object using musescore.
142156
143157
Parameters
144158
----------
145-
part : Part
146-
Part to be rendered
159+
score_data : ScoreLike
160+
Score-like object to be rendered
147161
fmt : {'png', 'pdf'}
148162
Output image format
149-
out_fn : str or None, optional
163+
out : str or None, optional
150164
The path of the image output file, if not specified, the
151165
rendering will be saved to a temporary filename. Defaults to
152166
None.
153167
dpi : int, optional
154168
Image resolution. This option is ignored when `fmt` is
155169
'pdf'. Defaults to 90.
156170
171+
Returns
172+
-------
173+
out : Optional[PathLike]
174+
Path to the output generated image (or None if no image was generated)
157175
"""
158176
mscore_exec = find_musescore3()
159177

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

175-
save_musicxml(part, xml_fh)
193+
save_musicxml(score_data, xml_fh)
176194

177195
cmd = [
178196
mscore_exec,
179197
"-T",
180198
"10",
181199
"-r",
182-
"{}".format(dpi),
200+
"{}".format(int(dpi)),
183201
"-o",
184202
os.fspath(img_fh),
185203
os.fspath(xml_fh),
@@ -215,12 +233,27 @@ def render_musescore(part, fmt, out_fn=None, dpi=90):
215233
# ps.stderr.decode('UTF-8')))
216234

217235
if fmt == "png":
218-
img_fh = (img_fh.parent / (img_fh.stem + "-1")).with_suffix(img_fh.suffix)
236+
237+
if PIL_EXISTS:
238+
# get all generated image files
239+
img_files = glob.glob(
240+
os.path.join(img_fh.parent, img_fh.stem + "-*.png")
241+
)
242+
concatenate_images(
243+
filenames=img_files,
244+
out=img_fh,
245+
concat_mode="vertical",
246+
)
247+
else:
248+
# The first image seems to be blank (MuseScore adds an empy page)
249+
img_fh = (img_fh.parent / (img_fh.stem + "-2")).with_suffix(
250+
img_fh.suffix
251+
)
219252

220253
if img_fh.is_file():
221-
if out_fn is None:
222-
out_fn = os.path.join(gettempdir(), "partitura_render_tmp.png")
223-
shutil.copy(img_fh, out_fn)
224-
return out_fn
254+
if out is None:
255+
out = os.path.join(gettempdir(), "partitura_render_tmp.png")
256+
shutil.copy(img_fh, out)
257+
return out
225258

226259
return None

0 commit comments

Comments
 (0)