Add script that produces a human-readable grammar from Bison output
[lilypond.git] / scripts / lilysong.py
blobf02aca7903abac1d6e4b96fa8be23a6a4ecc7205
1 #!@TARGET_PYTHON@
3 # Copyright (c) 2006--2009 Brailcom, o.p.s.
5 # Author: Milan Zamazal <pdm@brailcom.org>
7 # COPYRIGHT NOTICE
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful, but
15 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 # for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
24 import codecs
25 import optparse
26 import os
27 import popen2
28 import sys
29 import tempfile
31 """
32 @relocate-preamble@
33 """
36 FESTIVAL_COMMAND = 'festival --pipe'
37 VOICE_CODINGS = {'voice_czech_ph': 'iso-8859-2'}
39 _USAGE = """lilysong [-p PLAY-PROGRAM] FILE.xml [LANGUAGE-CODE-OR-VOICE [SPEEDUP]]
40 lilysong FILE.ly [LANGUAGE-CODE-OR-VOICE]
41 lilysong --list-voices
42 lilysong --list-languages
43 """
45 def usage ():
46 print 'Usage:', _USAGE
47 sys.exit (2)
49 def process_options (args):
50 parser = optparse.OptionParser (usage=_USAGE, version="@TOPLEVEL_VERSION@")
51 parser.add_option ('', '--list-voices', action='store_true', dest='list_voices',
52 help="list available Festival voices")
53 parser.add_option ('', '--list-languages', action='store_true', dest='list_languages',
54 help="list available Festival languages")
55 parser.add_option ('-p', '--play-program', metavar='PROGRAM',
56 action='store', type='string', dest='play_program',
57 help="use PROGRAM to play song immediately")
58 options, args = parser.parse_args (args)
59 return options, args
61 def call_festival (scheme_code):
62 in_, out = popen2.popen2 (FESTIVAL_COMMAND)
63 out.write (scheme_code)
64 out.close ()
65 answer = ''
66 while True:
67 process_output = in_.read ()
68 if not process_output:
69 break
70 answer = answer + process_output
71 return answer
73 def select_voice (language_or_voice):
74 if language_or_voice[:6] == 'voice_':
75 voice = language_or_voice
76 else:
77 voice = call_festival ('''
78 (let ((candidates '()))
79 (mapcar (lambda (v)
80 (if (eq (cadr (assoc 'language (cadr (voice.description v)))) '%s)
81 (set! candidates (cons v candidates))))
82 (append (voice.list) (mapcar car Voice_descriptions)))
83 (if candidates
84 (format t "voice_%%s" (car candidates))
85 (format t "nil")))
86 ''' % (language_or_voice,))
87 if voice == 'nil':
88 voice = None
89 return voice
91 def list_voices ():
92 print call_festival ('''
93 (let ((voices (voice.list))
94 (print-voice (lambda (v) (format t "voice_%s\n" v))))
95 (mapcar print-voice voices)
96 (mapcar (lambda (v) (if (not (member v voices)) (print-voice v)))
97 (mapcar car Voice_descriptions)))
98 ''')
100 def list_languages ():
101 print call_festival ('''
102 (let ((languages '()))
103 (let ((voices (voice.list))
104 (print-language (lambda (v)
105 (let ((language (cadr (assoc 'language (cadr (voice.description v))))))
106 (if (and language (not (member language languages)))
107 (begin
108 (set! languages (cons language languages))
109 (print language)))))))
110 (mapcar print-language voices)
111 (mapcar (lambda (v) (if (not (member v voices)) (print-language v)))
112 (mapcar car Voice_descriptions))))
113 ''')
115 def process_xml_file (file_name, voice, speedup, play_program):
116 if speedup == 1:
117 speedup = None
118 coding = (VOICE_CODINGS.get (voice) or 'iso-8859-1')
119 _, xml_temp_file = tempfile.mkstemp ('.xml')
120 try:
121 # recode the XML file
122 recodep = (coding != 'utf-8')
123 if recodep:
124 decode = codecs.getdecoder ('utf-8')
125 encode = codecs.getencoder (coding)
126 input = open (file_name)
127 output = open (xml_temp_file, 'w')
128 while True:
129 data = input.read ()
130 if not data:
131 break
132 if recodep:
133 data = encode (decode (data)[0])[0]
134 output.write (data)
135 output.close ()
136 # synthesize
137 wav_file = file_name[:-3] + 'wav'
138 if speedup:
139 _, wav_temp_file = tempfile.mkstemp ('.wav')
140 else:
141 wav_temp_file = wav_file
142 try:
143 print "text2wave -eval '(%s)' -mode singing '%s' -o '%s'" % (voice, xml_temp_file, wav_temp_file,)
144 result = os.system ("text2wave -eval '(%s)' -mode singing '%s' -o '%s'" %
145 (voice, xml_temp_file, wav_temp_file,))
146 if result:
147 sys.stdout.write ("Festival processing failed.\n")
148 return
149 if speedup:
150 result = os.system ("sox '%s' '%s' speed '%f'" % (wav_temp_file, wav_file, speedup,))
151 if result:
152 sys.stdout.write ("Festival processing failed.\n")
153 return
154 finally:
155 if speedup:
156 try:
157 os.delete (wav_temp_file)
158 except:
159 pass
160 sys.stdout.write ("%s created.\n" % (wav_file,))
161 # play
162 if play_program:
163 os.system ("%s '%s' >/dev/null" % (play_program, wav_file,))
164 finally:
165 try:
166 os.delete (xml_temp_file)
167 except:
168 pass
170 def process_ly_file (file_name, voice):
171 result = os.system ("lilypond '%s'" % (file_name,))
172 if result:
173 return
174 xml_file = None
175 for f in os.listdir (os.path.dirname (file_name) or '.'):
176 if (f[-4:] == '.xml' and
177 (not xml_file or os.stat.st_mtime (f) > os.stat.st_mtime (xml_file))):
178 xml_file = f
179 if xml_file:
180 process_xml_file (xml_file, voice, None, None)
181 else:
182 sys.stderr.write ("No XML file found\n")
184 def go ():
185 options, args = process_options (sys.argv[1:])
186 if options.list_voices:
187 list_voices ()
188 elif options.list_languages:
189 list_languages ()
190 else:
191 arglen = len (args)
192 if arglen < 1:
193 usage ()
194 file_name = args[0]
195 if arglen > 1:
196 language_or_voice = args[1]
197 voice = select_voice (language_or_voice)
198 else:
199 voice = None
200 if file_name[-3:] == '.ly':
201 if arglen > 2:
202 usage ()
203 process_ly_file (file_name, voice)
204 else:
205 if arglen > 3:
206 usage ()
207 elif arglen == 3:
208 try:
209 speedup = float (args[2])
210 except ValueError:
211 usage ()
212 else:
213 speedup = None
214 process_xml_file (file_name, voice, speedup, options.play_program)
216 if __name__ == '__main__':
217 go ()