Commits

Alexandre Macabies  committed 1942785

Initial commit

  • Participants

Comments (0)

Files changed (2)

+The MIT License (MIT)
+
+Copyright (c) [year] [fullname]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

File acapelabox.py

+#!/usr/bin/env python3
+
+from io import BytesIO
+from itertools import groupby
+from lxml import html
+from operator import itemgetter
+import requests
+import sys
+
+sess = requests.Session()
+sess.headers['user-agent'] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36"
+
+
+def get_voice_list():
+    page = sess.get('https://www.acapela-box.com/AcaBox/index.php')
+    soup = html.parse(BytesIO(page.content))
+    voices = []
+
+    for opt in soup.xpath('//select[@id="acaboxvoice_cb"]/option'):
+        lng, name = opt.text.split('-', 1)
+        voices.append((lng.strip(), name.strip(), opt.get('data-id')))
+
+    return voices
+
+
+def print_voice_list(all_voices, filters=None):
+    if filters is None:
+        filters = []
+    else:
+        filters = list(map(str.lower, filters))
+
+    for gpname, voices in groupby(all_voices, itemgetter(0)):
+        filtered = [(name, code) for g, name, code in voices if not filters or any(_ in name.lower() or _ in code.lower() or _ in gpname.lower() for _ in filters)]
+        if not filtered:
+            continue
+        print()
+        print((' %s ' % gpname).center(60, '='))
+        for name, code in filtered:
+            print('  {:>22}  {}'.format(code, name))
+
+
+def tts(out, text, voice, velocity, speed):
+    resp = sess.post('https://www.acapela-box.com/AcaBox/dovaas.php', data={
+        'text': r'\vct={velocity}\ \spd={speed}\ {text}'.format(velocity=velocity, speed=speed, text=text).encode('utf-8'),
+        'voice': voice,
+        'listen': '1',
+        'format': 'MP3',
+        'codecMP3': '1',
+        'spd': speed,
+        'vct': velocity,
+    })
+
+    url = resp.json()['snd_url']
+    for chunk in sess.get(url, stream=True).iter_content(1024, decode_unicode=False):
+        out.write(chunk)
+
+
+if __name__ == '__main__':
+    import argcomplete, argparse
+    from concurrent.futures import ThreadPoolExecutor
+    voices = set()
+    with ThreadPoolExecutor(max_workers=1) as e:
+        future_voices = e.submit(get_voice_list)
+
+    parser = argparse.ArgumentParser(description="Download AcapelaBox TTS sounds.")
+
+    subparsers = parser.add_subparsers()
+    listing = subparsers.add_parser('list', help="Display the list of available voices.")
+    listing.add_argument('search', nargs=argparse.REMAINDER, help="Query string to filter results.")
+
+    generate = subparsers.add_parser('speak', help="Generate the sound file from text.")
+    generate.add_argument('-v', '--velocity', type=int, default=100, help="Voice velocity.")
+    generate.add_argument('-s', '--speed', type=int, default=180, help="Voice speed.")
+    generate.add_argument('-o', '--output', type=argparse.FileType('wb'), default=sys.stdout.buffer, help="Output file (default to stdout).")
+    generate.add_argument('voice', help="The voice to use (use the `list` command for a list of available voices).")
+    generate.add_argument('text', nargs='+', help="The text to transform to speech.")
+
+    argcomplete.autocomplete(parser)
+    args = parser.parse_args()
+
+    if 'search' in args:
+        if not future_voices.done():
+            print("Retrieving voice list…\r")
+
+        voices = future_voices.result()
+        print_voice_list(voices, args.search)
+
+    elif 'text' in args:
+        voice = args.voice.lower()
+
+        all_voices = future_voices.result()
+        codes = list(map(itemgetter(2), all_voices))
+        if not voice in codes:
+            print("Voice `{v}` is not valid. Choices are:\n\t{all}".format(v=voice, all=codes), file=sys.stderr)
+
+        try:
+            tts(args.output, ' '.join(args.text), args.voice, args.velocity, args.speed)
+        except IOError as e:
+            print("Error while writing output file.", e, sep="\n", file=sys.stderr)
+        except (KeyError, ValueError):
+            print("Invalid response from AcapelaBox", file=sys.stderr)
+        except Exception as e:
+            print("Unknown error.", e, sep='\n', file=sys.stderr)