summaryrefslogtreecommitdiff
path: root/sandbox/amk/abc/abc.py
blob: f663cd765fc242247ac9e254b5e4ec0caa109ee3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""abc.py

Implements a 'music' directive that accepts musical passages written
in ABC notation.  Requires that abcm2ps and Ghostscript be installed.

.. music:: 

    % Sample file to test various features of abc2ps
    X:1
    T:Scale
    M:C
    K: clef=bass
     "C,"C,"D,"D,

"""

import os, sys, tempfile, popen2

from docutils import nodes
from docutils.parsers.rst.directives import register_directive

music_counter = 1

def ABCDirective (name, arguments, options, content, lineno,
                  content_offset, block_text, state, state_machine):
    abc_temp = tempfile.NamedTemporaryFile("wt")
    abc_temp.write("\n".join(content))
    abc_temp.flush()
    fd, ps_temp = tempfile.mkstemp()
    global music_counter
    output_path = state.document.settings._destination or '.' 
    output_name = os.path.join(output_path, 'music-%i.png' % music_counter)
    music_counter += 1
    sts1 = sts2 = sts3 = 0
    pipe = popen2.Popen3('abcm2ps -O/dev/stdout - < %s > %s'
                         % (abc_temp.name, ps_temp),
                         capturestderr=True
                         )
    pipe.tochild.close()
    pipe.fromchild.read()
    errors = pipe.childerr.read()
    errors = parse_errors(errors, lineno)
    sts1 = pipe.wait()
    if sts1 == 0:
        sts2 = os.system('gs -q -sOutputFile=%s -sDEVICE=pngmono - <%s'
                    % (output_name, ps_temp))
    if sts2 == 0:
        sts3 = os.system('gs -q -sOutputFile=%s -sDEVICE=pngmono - <%s'
                    % (output_name, ps_temp))
    try:
        os.unlink(ps_temp)
    except os.error:
        pass
    
    abc_temp.close()
    if sts1 != 0 or sts2 != 0:
        error = state_machine.reporter.error(
                  'Error processing music directive:\n\t' +
                  ('\n\t'.join(errors)),
                  nodes.literal_block(block_text, block_text),
                  line=lineno)
        return [error]
    else:
        # Crop excess whitespace from the image
        crop_image(output_name)
        
        # Return an image directive.
        options['uri'] = os.path.basename(output_name)
        return [nodes.image(output_name, **options)]

import re
error_pat = re.compile('(in line )(\d+)')
def parse_errors (errors, line_start):
    lines = errors.split('\n')
    lines =  [ line for line in lines if line.startswith('Error')]
    def f (m):
        return m.group(1) + str(line_start+int(m.group(2)))
    lines = [ error_pat.sub(f, line) for line in lines]
    return lines


def crop_image (filename):
    """Reads the image specified by filename, crops excess space off it,
    and writes it out to the same file."""
    from PIL import Image
    image = Image.open(filename)
    for edge in 'NSWE':
        image = _crop(image, edge)
    image.save(filename)   

def _crop (image, edge):
    im_x, im_y = image.size
    if edge == 'N':
        start = (0,0)
        scan_increment = (1,0)
        line_increment = (0,1)
    elif edge == 'S':
        start = (0,im_y-1)
        scan_increment = (1,0)
        line_increment = (0,-1)
    elif edge == 'E':
        start = (im_x-1,0)
        scan_increment = (0,1)
        line_increment = (-1,0)
    elif edge == 'W':
        start = (0,0)
        scan_increment = (0,1)
        line_increment = (1,0)

    # Look for blank lines
    def is_white (color):
        return color == 255
    
    def is_blank (x,y):
        x_inc, y_inc = scan_increment
        while 0 <= x < im_x and 0 <= y < im_y:
            point = image.getpixel((x,y))
            if not is_white(point):
                return False
            x += x_inc
            y += y_inc
        return True

    # Look for blank edges, jumping in increments of JUMP pixels.
    JUMP = 5
    x, y = start
    x_inc, y_inc = line_increment
    while is_blank(x,y):
        x += x_inc * JUMP
        y += y_inc * JUMP

    # Found a non-blank line, so scan back
    x -= x_inc
    y -= y_inc
    while not is_blank(x,y):
        x -= x_inc
        y -= y_inc

    # OK; x,y are now on the first blank line, so crop it
    if edge == 'N':
        box = (0,y, im_x, im_y)
    elif edge == 'S':
        box = (0,0, im_x, y)
    elif edge == 'E':
        box = (0,0, x, im_y)
    elif edge == 'W':
        box = (x,0, im_x, im_y)
        
    image = image.crop(box)
    image.load()
    return image
        
        
ABCDirective.content = True

def register():
    register_directive('music', ABCDirective)