]> git.donarmstrong.com Git - lilypond.git/blob - scripts/lilysong.py
Merge branch 'master' of git+ssh://jneem@git.sv.gnu.org/srv/git/lilypond
[lilypond.git] / scripts / lilysong.py
1 #!@TARGET_PYTHON@
2
3 # Copyright (C) 2006, 2007 Brailcom, o.p.s.
4 #
5 # Author: Milan Zamazal <pdm@brailcom.org>
6 #
7 # COPYRIGHT NOTICE
8 #
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.
13 #
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.
18 #
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.
22
23
24 import codecs
25 import optparse
26 import os
27 import popen2
28 import sys
29 import tempfile
30
31 """
32 @relocate-preamble@
33 """
34
35
36 FESTIVAL_COMMAND = 'festival --pipe'
37 VOICE_CODINGS = {'voice_czech_ph': 'iso-8859-2'}
38
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 """
44
45 def usage ():
46     print 'usage:', _USAGE
47     sys.exit (2)
48
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
60
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
72
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
90
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 ''')
99
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 ''')
114
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
169
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")
183
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)
215
216 if __name__ == '__main__':
217     go ()