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