commit a01b5221b61131e4457d27633f272290db1e9dc4 Author: jocavdh Date: Sun May 5 21:29:52 2019 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e28dd3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,146 @@ + +# Created by https://www.gitignore.io/api/linux,python +# Edit at https://www.gitignore.io/?templates=linux,python + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don’t work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/linux,python + diff --git a/README.md b/README.md new file mode 100755 index 0000000..86e3d93 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Pick something random app for Snips + +[![Build status](https://api.travis-ci.com/koenvervloesem/snips-app-pick-something-random.svg?branch=master)](https://travis-ci.com/koenvervloesem/snips-app-pick-something-random) [![Maintainability](https://api.codeclimate.com/v1/badges/b02b8ff9a4ebd13f9e3f/maintainability)](https://codeclimate.com/github/koenvervloesem/snips-app-pick-something-random/maintainability) [![Code quality](https://api.codacy.com/project/badge/Grade/178255b06d224fabb2aae5b83827de3f)](https://www.codacy.com/app/koenvervloesem/snips-app-pick-something-random) [![Python versions](https://img.shields.io/badge/python-3.5|3.6|3.7-blue.svg)](https://www.python.org) [![GitHub license](https://img.shields.io/github/license/koenvervloesem/snips-app-pick-something-random.svg)](https://github.com/koenvervloesem/snips-app-pick-something-random/blob/master/LICENSE) [![Languages](https://img.shields.io/badge/i18n-en-brown.svg)](https://github.com/koenvervloesem/snips-app-pick-something-random/tree/master/translations) [![Snips App Store](https://img.shields.io/badge/snips-app-blue.svg)](https://console.snips.ai/store/en/skill_NmlgOeBBO13) + +With this [Snips](https://snips.ai/) app, you can ask for random numbers and dates, to flip a coin and to roll a die or multiple dice. + +## Installation + +The easiest way to install this app is by adding the Snips app [Pick something random](https://console.snips.ai/store/en/skill_NmlgOeBBO13) to your assistant in the [Snips Console](https://console.snips.ai). + +## Usage + +This app recognizes the following intents: + +* FlipCoin - The user asks to flip a coin. The app reasponds with heads or tails. +* RandomNumber - The user asks to pick a random number between two numbers. The app responds with a number. +* RandomDate - The user asks to pick a random date, optionally in a specific period. The app responds with a date in the specified time period. +* RollDice - The user asks to roll a die or multiple dice (specified in words or in [dice notation](https://en.wikipedia.org/wiki/Dice_notation)). The app responds with the numbers. + +## Copyright + +This app is provided by [Koen Vervloesem](mailto:koen@vervloesem.eu) as open source software. See LICENSE for more information. diff --git a/action-play.py b/action-play.py new file mode 100755 index 0000000..96d0f2f --- /dev/null +++ b/action-play.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +# Fuction recognize intents and connect them to the right actions +import paho.mqtt.client as mqtt +import json +from time import sleep +from logic import tts, read_script +from config import characters, directions + +HOST = 'localhost' +PORT = 1883 + + +def on_connect(client, userdata, flags, rc): + print("Connected to {0} with result code {1}".format(HOST, rc)) + # Subscribe to the text detected topic + client.subscribe("hermes/asr/textCaptured") + client.subscribe("hermes/nlu/intentNotRecognized") + client.subscribe('hermes/intent/jocavdh:ask') + client.subscribe('hermes/intent/jocavdh:answer_yes') + client.subprocess('hermes/dialogueManager/sessionEnded') + client.subprocess('hermes/dialogueManager/sessionStarted') + +def on_introduce(client,data,msg): + data = json.loads(msg.payload) + sessionId = data['sessionId'] + + for character, line, direction in read_script('play_scripts/demo.txt'): + input_text = line + voice = characters.get(character)[0] + speaker = characters.get(character)[1] + #speaker = 'default' + # Some way to do something with the stage directions will come here + action = directions.get(direction[0]) + tts(voice, input_text, speaker) + + client.publish('hermes/dialogueManager/endSession', json.dumps({ + 'sessionId': sessionId + })) + + client.publish('hermes/dialogueManager/startSession', json.dumps({ + 'siteId': 'default', + 'init': {'type': 'action', 'canBeEnqueued': False, 'intentFilter':['jocavdh:answer_yes']} + })) + + +def on_answer(client,data,msg): + + data = json.loads(msg.payload) + answer_value = data['slots'][0]['value']['value'] + print(answer_value) + + if answer_value == 'yes': + input_text = 'Lorem ipsum' + + if answer_value == 'no': + input_text = 'nope nope nope' + + voice = "dfki-obadiah" + speaker = 'default' + + tts(voice, input_text, speaker) + + print('The play is over.') + + +client = mqtt.Client() +client.connect(HOST, PORT, 60) +client.on_connect = on_connect +client.connected_flag=False +client.message_callback_add('hermes/intent/jocavdh:ask', on_introduce) +client.message_callback_add('hermes/intent/jocavdh:answer_yes', on_answer) +client.message_callback_add('hermes/intent/jocavdh:no', on_answer) +print('main') +listening = False + +client.loop_forever() \ No newline at end of file diff --git a/config.py b/config.py new file mode 100755 index 0000000..642b889 --- /dev/null +++ b/config.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# play_config.py +# This script contains the basic configuration of the play +# A list of the characters and their voices +# A list of stage directions which connect to formal instructions for the hardware +# --- + +# Define sound output device here +# sound_output_device = sc.get_speaker('Scarlett') + +# Dictionary to link characters to the right voice +characters = {"ROGUE":["dfki-prudence", "mono1"], "SAINT":["dfki-obadiah", "mono2"], "RASA":["dfki-poppy-hsmm", "mono3"] } + +# Dictionary to link stage directions to a particular formal action +directions = { + 'Wait for audience':'listen_audience', + 'Listens to Google Home':'listen_google_home', + 'Music':'audio' +} diff --git a/logic.py b/logic.py new file mode 100644 index 0000000..3470c93 --- /dev/null +++ b/logic.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +# play_logic.py +# This script contains the logic to turn the play_script into instructions for the hardware +# --- + +# 01 FUNTION TO PROCESS THEATRE SCRIPT +import re +from config import characters, directions + + +def read_script(filename): + lines = [] + + with open(filename, 'r') as f: + for line in f.readlines(): + #print(line) + parts = re.match(r'(?P^.+?):\s?(\[(?P[^]]+)\])?\s?(?P.*)', line) + parts_character = parts.group('character') + parts_text = parts.group('text') + parts_directions = str(parts.group('stage_directions')).split(".") + + lines.append((parts_character,parts_text,parts_directions)) + + #print(lines) + return lines; + + + +# 02 FUNCTION TO SYNTHESIZE TEXT +# based on https://github.com/marytts/marytts-txt2wav/tree/python + +# To play wave files +from subprocess import call +from time import sleep + +# Mary server informations +mary_host = "localhost" +mary_port = "59125" + +# HTTP + URL packages +import httplib2 +import os +from urllib.parse import urlencode, quote # For URL creation + +def tts(voice, input_text, speaker): + # Build the query + query_hash = {"INPUT_TEXT": input_text, + "INPUT_TYPE":"TEXT", # Input text + "LOCALE":"en_GB", + "VOICE": voice, # Voice informations (need to be compatible) + "OUTPUT_TYPE":"AUDIO", + "AUDIO":"WAVE", # Audio informations (need both) + } + query = urlencode(query_hash) + print("query = \"http://%s:%s/process?%s\"" % (mary_host, mary_port, query)) + + # Run the query to mary http server + h_mary = httplib2.Http() + resp, content = h_mary.request("http://%s:%s/process?" % (mary_host, mary_port), "POST", query) + + # Decode the wav file or raise an exception if no wav files + if (resp["content-type"] == "audio/x-wav"): + + # Write the wav file + fpath = os.path.join(os.path.dirname(__file__), '/tmp/output_wav.wav') + f = open(fpath, "wb") + f.write(content) + f.close() + + # aplay -D mono3 /tmp/output_wav.wav + + call(["aplay", fpath]) + + + else: + raise Exception(content) + + +# 03 FUNCTIONS TO RUN THE PLAY ON THE SPEAKERS +import paho.mqtt.client as mqtt +import json +from time import sleep + +HOST = 'localhost' +PORT = 1883 + + +def on_connect(client, userdata, flags, rc): + print("Connected to {0} with result code {1}".format(HOST, rc)) + # Subscribe to the text detected topic + client.subscribe("hermes/asr/textCaptured") + client.subscribe("hermes/nlu/intentNotRecognized") + client.subscribe('hermes/intent/jocavdh:ask') + client.subscribe('hermes/intent/jocavdh:answer_yes') + client.subprocess('hermes/dialogueManager/sessionEnded') + client.subprocess('hermes/dialogueManager/sessionStarted') + + +def on_message(client, userdata, msg): + print('Google Home is not speaking anymore') + client.connected_flag=True + +# def on_waiting(client, userdata, msg): +# sessionId = json.loads(id.payload) +# print('delete mistaken intent') +# client.publish("hermes/dialogueManager/endSession", json.dumps({ +# 'sessionId': sessionId, +# })) + +client = mqtt.Client() +client.connect(HOST, PORT, 60) +client.on_connect = on_connect +client.connected_flag=False + +listening = False + + +def play(): + for character, line, direction in read_script('play_scripts/demo.txt'): + input_text = line + voice = characters.get(character)[0] + speaker = characters.get(character)[1] + #speaker = 'default' + # Some way to do something with the stage directions will come here + action = directions.get(direction[0]) + tts(voice, input_text, speaker) + + if action == 'listen_google_home': + print('Waiting for the Google Home to finish its talk') + + # # start voice activity detection + # client.publish("hermes/asr/startListening", json.dumps({ + # 'siteId': 'default', + # 'init': { + # 'type': 'action', + # 'canBeEnqueued': True + # } + # })) + + client.publish("hermes/asr/startListening", json.dumps({ + 'siteId': 'default' + })) + + # create callback + client.on_message = on_message + listening = True + + while listening: + client.loop() + + #client.on_message = on_message + client.message_callback_add('hermes/asr/textCaptured', on_message) + + if client.connected_flag: + sleep(1) + print('Continue the play') + client.connected_flag = False + # client.message_callback_add('hermes/dialogueManager/sessionQueued', on_waiting) + break + + if action == 'audio': + print('play audioclip') + playing = True + + while playing: + call(["aplay", "-D", speaker, "/usr/share/snips/congress.wav"]) + playing = False + + if action == 'listen_audience': + print('ask the audience') + + + + + + + sleep(1) + + + print('This act is done.') + + return diff --git a/play_scripts/demo.txt b/play_scripts/demo.txt new file mode 100644 index 0000000..01f69ce --- /dev/null +++ b/play_scripts/demo.txt @@ -0,0 +1 @@ +ROGUE: Give me \ No newline at end of file diff --git a/play_scripts/introduction.txt b/play_scripts/introduction.txt new file mode 100755 index 0000000..1450220 --- /dev/null +++ b/play_scripts/introduction.txt @@ -0,0 +1 @@ +ROGUE: Hi, I am rogue. Are you Joca? diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 0000000..a7020c9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +# Needed for Snips platform 1.1.0 (0.61.1) +hermes-python>=0.3.3 +toml diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..e7ea6c4 --- /dev/null +++ b/setup.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -e + +# Copy config.ini.default if it exists and config.ini doesn't exist. +if [ -e config.ini.default ] && [ ! -e config.ini ]; then + cp config.ini.default config.ini + chmod a+w config.ini +fi + +PYTHON=$(command -v python3) +VENV=venv + +if [ -f "$PYTHON" ]; then + + if [ ! -d $VENV ]; then + # Create a virtual environment if it doesn't exist. + $PYTHON -m venv $VENV + else + if [ -e $VENV/bin/python2 ]; then + # If a Python2 environment exists, delete it first + # before creating a new Python 3 virtual environment. + rm -r $VENV + $PYTHON -m venv $VENV + fi + fi + + # Activate the virtual environment and install requirements. + # shellcheck disable=SC1090 + . $VENV/bin/activate + pip3 install -r requirements.txt + +else + >&2 echo "Cannot find Python 3. Please install it." +fi diff --git a/tmp/output_wav.wav b/tmp/output_wav.wav new file mode 100755 index 0000000..b279152 Binary files /dev/null and b/tmp/output_wav.wav differ