3 Implements a 'music' directive that accepts musical passages written
4 in ABC notation. Requires that abcm2ps and Ghostscript be installed.
8 % Sample file to test various features of abc2ps
17 import os
, sys
, tempfile
, popen2
19 from docutils
import nodes
20 from docutils
.parsers
.rst
.directives
import register_directive
24 def ABCDirective (name
, arguments
, options
, content
, lineno
,
25 content_offset
, block_text
, state
, state_machine
):
26 abc_temp
= tempfile
.NamedTemporaryFile("wt")
27 abc_temp
.write("\n".join(content
))
29 fd
, ps_temp
= tempfile
.mkstemp()
31 output_path
= state
.document
.settings
._destination
or '.'
32 output_name
= os
.path
.join(output_path
, 'music-%i.png' % music_counter
)
34 sts1
= sts2
= sts3
= 0
35 pipe
= popen2
.Popen3('abcm2ps -O/dev/stdout - < %s > %s'
36 % (abc_temp
.name
, ps_temp
),
41 errors
= pipe
.childerr
.read()
42 errors
= parse_errors(errors
, lineno
)
45 sts2
= os
.system('gs -q -sOutputFile=%s -sDEVICE=pngmono - <%s'
46 % (output_name
, ps_temp
))
48 sts3
= os
.system('gs -q -sOutputFile=%s -sDEVICE=pngmono - <%s'
49 % (output_name
, ps_temp
))
56 if sts1
!= 0 or sts2
!= 0:
57 error
= state_machine
.reporter
.error(
58 'Error processing music directive:\n\t' +
59 ('\n\t'.join(errors
)),
60 nodes
.literal_block(block_text
, block_text
),
64 # Crop excess whitespace from the image
65 crop_image(output_name
)
67 # Return an image directive.
68 options
['uri'] = os
.path
.basename(output_name
)
69 return [nodes
.image(output_name
, **options
)]
72 error_pat
= re
.compile('(in line )(\d+)')
73 def parse_errors (errors
, line_start
):
74 lines
= errors
.split('\n')
75 lines
= [ line
for line
in lines
if line
.startswith('Error')]
77 return m
.group(1) + str(line_start
+int(m
.group(2)))
78 lines
= [ error_pat
.sub(f
, line
) for line
in lines
]
82 def crop_image (filename
):
83 """Reads the image specified by filename, crops excess space off it,
84 and writes it out to the same file."""
86 image
= Image
.open(filename
)
88 image
= _crop(image
, edge
)
91 def _crop (image
, edge
):
92 im_x
, im_y
= image
.size
95 scan_increment
= (1,0)
96 line_increment
= (0,1)
99 scan_increment
= (1,0)
100 line_increment
= (0,-1)
103 scan_increment
= (0,1)
104 line_increment
= (-1,0)
107 scan_increment
= (0,1)
108 line_increment
= (1,0)
110 # Look for blank lines
111 def is_white (color
):
115 x_inc
, y_inc
= scan_increment
116 while 0 <= x
< im_x
and 0 <= y
< im_y
:
117 point
= image
.getpixel((x
,y
))
118 if not is_white(point
):
124 # Look for blank edges, jumping in increments of JUMP pixels.
127 x_inc
, y_inc
= line_increment
132 # Found a non-blank line, so scan back
135 while not is_blank(x
,y
):
139 # OK; x,y are now on the first blank line, so crop it
141 box
= (0,y
, im_x
, im_y
)
147 box
= (x
,0, im_x
, im_y
)
149 image
= image
.crop(box
)
154 ABCDirective
.content
= True
157 register_directive('music', ABCDirective
)