Spaces:
Sleeping
Sleeping
| #!@TARGET_PYTHON@ | |
| # Copyright (c) 2006--2020 Brailcom, o.p.s. | |
| # | |
| # Author: Milan Zamazal <pdm@brailcom.org> | |
| # | |
| # This file is part of LilyPond, the GNU music typesetter. | |
| # | |
| # LilyPond is free software: you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation, either version 3 of the License, or | |
| # (at your option) any later version. | |
| # | |
| # LilyPond is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with LilyPond. If not, see <http://www.gnu.org/licenses/>. | |
| import codecs | |
| import optparse | |
| import os | |
| import subprocess | |
| import sys | |
| import tempfile | |
| """ | |
| @relocate-preamble@ | |
| """ | |
| FESTIVAL_COMMAND = ['festival', '--pipe'] | |
| VOICE_CODINGS = {'voice_czech_ph': 'iso-8859-2'} | |
| _USAGE = """lilysong [-p PLAY-PROGRAM] FILE.xml [LANGUAGE-CODE-OR-VOICE [SPEEDUP]] | |
| lilysong FILE.ly [LANGUAGE-CODE-OR-VOICE] | |
| lilysong --list-voices | |
| lilysong --list-languages | |
| """ | |
| def usage(): | |
| print('Usage:', _USAGE) | |
| sys.exit(2) | |
| def process_options(args): | |
| parser = optparse.OptionParser(usage=_USAGE, version="@TOPLEVEL_VERSION@") | |
| parser.add_option('', '--list-voices', action='store_true', dest='list_voices', | |
| help="list available Festival voices") | |
| parser.add_option('', '--list-languages', action='store_true', dest='list_languages', | |
| help="list available Festival languages") | |
| parser.add_option('-p', '--play-program', metavar='PROGRAM', | |
| action='store', type='string', dest='play_program', | |
| help="use PROGRAM to play song immediately") | |
| options, args = parser.parse_args(args) | |
| return options, args | |
| def call_festival(scheme_code): | |
| p = subprocess.Popen(FESTIVAL_COMMAND, stdin=subprocess.PIPE, | |
| stdout=subprocess.PIPE, close_fds=True) | |
| p.stdin.write(scheme_code) | |
| p.stdin.close() | |
| answer = '' | |
| while True: | |
| process_output = p.stdout.read() | |
| if not process_output: | |
| break | |
| answer = answer + process_output | |
| return answer | |
| def select_voice(language_or_voice): | |
| if language_or_voice[:6] == 'voice_': | |
| voice = language_or_voice | |
| else: | |
| voice = call_festival(''' | |
| (let ((candidates '())) | |
| (mapcar (lambda (v) | |
| (if (eq (cadr (assoc 'language (cadr (voice.description v)))) '%s) | |
| (set! candidates (cons v candidates)))) | |
| (append (voice.list) (mapcar car Voice_descriptions))) | |
| (if candidates | |
| (format t "voice_%%s" (car candidates)) | |
| (format t "nil"))) | |
| ''' % (language_or_voice,)) | |
| if voice == 'nil': | |
| voice = None | |
| return voice | |
| def list_voices(): | |
| print(call_festival(''' | |
| (let ((voices (voice.list)) | |
| (print-voice (lambda (v) (format t "voice_%s\n" v)))) | |
| (mapcar print-voice voices) | |
| (mapcar (lambda (v) (if (not (member v voices)) (print-voice v))) | |
| (mapcar car Voice_descriptions))) | |
| ''')) | |
| def list_languages(): | |
| print(call_festival(''' | |
| (let ((languages '())) | |
| (let ((voices (voice.list)) | |
| (print-language (lambda (v) | |
| (let ((language (cadr (assoc 'language (cadr (voice.description v)))))) | |
| (if (and language (not (member language languages))) | |
| (begin | |
| (set! languages (cons language languages)) | |
| (print language))))))) | |
| (mapcar print-language voices) | |
| (mapcar (lambda (v) (if (not (member v voices)) (print-language v))) | |
| (mapcar car Voice_descriptions)))) | |
| ''')) | |
| def process_xml_file(file_name, voice, speedup, play_program): | |
| if speedup == 1: | |
| speedup = None | |
| coding = (VOICE_CODINGS.get(voice) or 'iso-8859-1') | |
| _, xml_temp_file = tempfile.mkstemp('.xml') | |
| try: | |
| # recode the XML file | |
| recodep = (coding != 'utf-8') | |
| if recodep: | |
| decode = codecs.getdecoder('utf-8') | |
| encode = codecs.getencoder(coding) | |
| input = open(file_name, encoding='utf8') | |
| output = open(xml_temp_file, 'w', encoding='utf8') | |
| while True: | |
| data = input.read() | |
| if not data: | |
| break | |
| if recodep: | |
| data = encode(decode(data)[0])[0] | |
| output.write(data) | |
| output.close() | |
| # synthesize | |
| wav_file = file_name[:-3] + 'wav' | |
| if speedup: | |
| _, wav_temp_file = tempfile.mkstemp('.wav') | |
| else: | |
| wav_temp_file = wav_file | |
| try: | |
| print("text2wave -eval '(%s)' -mode singing '%s' -o '%s'" % | |
| (voice, xml_temp_file, wav_temp_file,)) | |
| result = os.system("text2wave -eval '(%s)' -mode singing '%s' -o '%s'" % | |
| (voice, xml_temp_file, wav_temp_file,)) | |
| if result: | |
| sys.stdout.write("Festival processing failed.\n") | |
| return | |
| if speedup: | |
| result = os.system("sox '%s' '%s' speed '%f'" % | |
| (wav_temp_file, wav_file, speedup,)) | |
| if result: | |
| sys.stdout.write("Festival processing failed.\n") | |
| return | |
| finally: | |
| if speedup: | |
| try: | |
| os.delete(wav_temp_file) | |
| except OSError: | |
| pass | |
| sys.stdout.write("%s created.\n" % (wav_file,)) | |
| # play | |
| if play_program: | |
| os.system("%s '%s' >/dev/null" % (play_program, wav_file,)) | |
| finally: | |
| try: | |
| os.delete(xml_temp_file) | |
| except OSError: | |
| pass | |
| def process_ly_file(file_name, voice): | |
| result = os.system("lilypond '%s'" % (file_name,)) | |
| if result: | |
| return | |
| xml_file = None | |
| for f in os.listdir(os.path.dirname(file_name) or '.'): | |
| if (f[-4:] == '.xml' and | |
| (not xml_file or os.stat.st_mtime(f) > os.stat.st_mtime(xml_file))): | |
| xml_file = f | |
| if xml_file: | |
| process_xml_file(xml_file, voice, None, None) | |
| else: | |
| sys.stderr.write("No XML file found\n") | |
| def go(): | |
| options, args = process_options(sys.argv[1:]) | |
| if options.list_voices: | |
| list_voices() | |
| elif options.list_languages: | |
| list_languages() | |
| else: | |
| arglen = len(args) | |
| if arglen < 1: | |
| usage() | |
| file_name = args[0] | |
| if arglen > 1: | |
| language_or_voice = args[1] | |
| voice = select_voice(language_or_voice) | |
| else: | |
| voice = None | |
| if file_name[-3:] == '.ly': | |
| if arglen > 2: | |
| usage() | |
| process_ly_file(file_name, voice) | |
| else: | |
| if arglen > 3: | |
| usage() | |
| elif arglen == 3: | |
| try: | |
| speedup = float(args[2]) | |
| except ValueError: | |
| usage() | |
| else: | |
| speedup = None | |
| process_xml_file(file_name, voice, speedup, options.play_program) | |
| if __name__ == '__main__': | |
| go() | |