summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorschneefux <schneefux+commit@schneefux.xyz>2015-01-19 12:15:23 +0100
committerschneefux <schneefux+commit@schneefux.xyz>2015-01-19 12:15:23 +0100
commit9fa55058c99fd9ef461b1a7b422eb53b5839783e (patch)
treebfa85322d3744fca01732a7adb68f00ee4f30a3f
parentb7113990da5e446f7b371a846c314181fc961aa6 (diff)
parentf8ad3c9a982f8c4407931b513e6069289d561972 (diff)
downloadjasper-client-9fa55058c99fd9ef461b1a7b422eb53b5839783e.tar.gz
jasper-client-9fa55058c99fd9ef461b1a7b422eb53b5839783e.zip
Merge pull request #285 from Holzhaus/julius-stt-engine
Added basic Julius STT engine and vocabcompiler
-rw-r--r--client/stt.py93
-rw-r--r--client/vocabcompiler.py152
2 files changed, 244 insertions, 1 deletions
diff --git a/client/stt.py b/client/stt.py
index 95ed487..c192e6a 100644
--- a/client/stt.py
+++ b/client/stt.py
@@ -7,6 +7,8 @@ import tempfile
import logging
import urllib
import urlparse
+import re
+import subprocess
from abc import ABCMeta, abstractmethod
import requests
import yaml
@@ -176,6 +178,97 @@ class PocketSphinxSTT(AbstractSTTEngine):
return diagnose.check_python_import('pocketsphinx')
+class JuliusSTT(AbstractSTTEngine):
+ """
+ A very basic Speech-to-Text engine using Julius.
+ """
+
+ SLUG = 'julius'
+ VOCABULARY_TYPE = vocabcompiler.JuliusVocabulary
+
+ def __init__(self, vocabulary=None, hmmdefs="/usr/share/voxforge/julius/" +
+ "acoustic_model_files/hmmdefs", tiedlist="/usr/share/" +
+ "voxforge/julius/acoustic_model_files/tiedlist"):
+ self._logger = logging.getLogger(__name__)
+ self._vocabulary = vocabulary
+ self._hmmdefs = hmmdefs
+ self._tiedlist = tiedlist
+ self._pattern = re.compile(r'sentence(\d+): <s> (.+) </s>')
+
+ # Inital test run: we run this command once to log errors/warnings
+ cmd = ['julius',
+ '-input', 'stdin',
+ '-dfa', self._vocabulary.dfa_file,
+ '-v', self._vocabulary.dict_file,
+ '-h', self._hmmdefs,
+ '-hlist', self._tiedlist,
+ '-forcedict']
+ cmd = [str(x) for x in cmd]
+ self._logger.debug('Executing: %r', cmd)
+ with tempfile.SpooledTemporaryFile() as out_f:
+ with tempfile.SpooledTemporaryFile() as f:
+ with tempfile.SpooledTemporaryFile() as err_f:
+ subprocess.call(cmd, stdin=f, stdout=out_f, stderr=err_f)
+ out_f.seek(0)
+ for line in out_f.read().splitlines():
+ line = line.strip()
+ if len(line) > 7 and line[:7].upper() == 'ERROR: ':
+ if not line[7:].startswith('adin_'):
+ self._logger.error(line[7:])
+ elif len(line) > 9 and line[:9].upper() == 'WARNING: ':
+ self._logger.warning(line[9:])
+ elif len(line) > 6 and line[:6].upper() == 'STAT: ':
+ self._logger.debug(line[6:])
+
+ @classmethod
+ def get_config(cls):
+ # FIXME: Replace this as soon as we have a config module
+ config = {}
+ # HMM dir
+ # Try to get hmm_dir from config
+ profile_path = jasperpath.config('profile.yml')
+
+ if os.path.exists(profile_path):
+ with open(profile_path, 'r') as f:
+ profile = yaml.safe_load(f)
+ if 'julius' in profile:
+ if 'hmmdefs' in profile['julius']:
+ config['hmmdefs'] = profile['julius']['hmmdefs']
+ if 'tiedlist' in profile['julius']:
+ config['tiedlist'] = profile['julius']['tiedlist']
+ return config
+
+ def transcribe(self, fp, mode=None):
+ cmd = ['julius',
+ '-quiet',
+ '-nolog',
+ '-input', 'stdin',
+ '-dfa', self._vocabulary.dfa_file,
+ '-v', self._vocabulary.dict_file,
+ '-h', self._hmmdefs,
+ '-hlist', self._tiedlist,
+ '-forcedict']
+ cmd = [str(x) for x in cmd]
+ self._logger.debug('Executing: %r', cmd)
+ with tempfile.SpooledTemporaryFile() as out_f:
+ with tempfile.SpooledTemporaryFile() as err_f:
+ subprocess.call(cmd, stdin=fp, stdout=out_f, stderr=err_f)
+ out_f.seek(0)
+ results = [(int(i), text) for i, text in
+ self._pattern.findall(out_f.read())]
+ transcribed = [text for i, text in
+ sorted(results, key=lambda x: x[0])
+ if text]
+ if not transcribed:
+ transcribed.append('')
+ self._logger.info('Transcribed: %r', transcribed)
+ return transcribed
+
+ @classmethod
+ def is_available(cls):
+ return diagnose.check_executable('julius')
+
+
class GoogleSTT(AbstractSTTEngine):
"""
Speech-To-Text implementation which relies on the Google Speech API.
diff --git a/client/vocabcompiler.py b/client/vocabcompiler.py
index ed9f425..0f8a648 100644
--- a/client/vocabcompiler.py
+++ b/client/vocabcompiler.py
@@ -8,7 +8,13 @@ import os
import tempfile
import logging
import hashlib
+import subprocess
+import tarfile
+import re
+import contextlib
+import shutil
from abc import ABCMeta, abstractmethod, abstractproperty
+import yaml
import brain
import jasperpath
@@ -325,6 +331,151 @@ class PocketsphinxVocabulary(AbstractVocabulary):
f.write(line)
+class JuliusVocabulary(AbstractVocabulary):
+ class VoxForgeLexicon(object):
+ def __init__(self, fname, membername=None):
+ self._dict = {}
+ self.parse(fname, membername)
+
+ @contextlib.contextmanager
+ def open_dict(self, fname, membername=None):
+ if tarfile.is_tarfile(fname):
+ if not membername:
+ raise ValueError('archive membername not set!')
+ tf = tarfile.open(fname)
+ f = tf.extractfile(membername)
+ yield f
+ f.close()
+ tf.close()
+ else:
+ with open(fname) as f:
+ yield f
+
+ def parse(self, fname, membername=None):
+ pattern = re.compile(r'\[(.+)\]\W(.+)')
+ with self.open_dict(fname, membername=membername) as f:
+ for line in f:
+ matchobj = pattern.search(line)
+ if matchobj:
+ word, phoneme = [x.strip() for x in matchobj.groups()]
+ if word in self._dict:
+ self._dict[word].append(phoneme)
+ else:
+ self._dict[word] = [phoneme]
+
+ def translate_word(self, word):
+ if word in self._dict:
+ return self._dict[word]
+ else:
+ return []
+
+ PATH_PREFIX = 'julius-vocabulary'
+
+ @property
+ def dfa_file(self):
+ """
+ Returns:
+ The path of the the julius dfa file as string
+ """
+ return os.path.join(self.path, 'dfa')
+
+ @property
+ def dict_file(self):
+ """
+ Returns:
+ The path of the the julius dict file as string
+ """
+ return os.path.join(self.path, 'dict')
+
+ @property
+ def is_compiled(self):
+ return (super(self.__class__, self).is_compiled and
+ os.access(self.dfa_file, os.R_OK) and
+ os.access(self.dict_file, os.R_OK))
+
+ def _get_grammar(self, phrases):
+ return {'S': [['NS_B', 'WORD_LOOP', 'NS_E']],
+ 'WORD_LOOP': [['WORD_LOOP', 'WORD'], ['WORD']]}
+
+ def _get_word_defs(self, lexicon, phrases):
+ word_defs = {'NS_B': [('<s>', 'sil')],
+ 'NS_E': [('</s>', 'sil')],
+ 'WORD': []}
+
+ words = []
+ for phrase in phrases:
+ if ' ' in phrase:
+ for word in phrase.split(' '):
+ words.append(word)
+ else:
+ words.append(phrase)
+
+ for word in words:
+ for phoneme in lexicon.translate_word(word):
+ word_defs['WORD'].append((word, phoneme))
+ return word_defs
+
+ def _compile_vocabulary(self, phrases):
+ prefix = 'jasper'
+ tmpdir = tempfile.mkdtemp()
+
+ lexicon_file = jasperpath.data('julius-stt', 'VoxForge.tgz')
+ lexicon_archive_member = 'VoxForge/VoxForgeDict'
+ profile_path = jasperpath.config('profile.yml')
+ if os.path.exists(profile_path):
+ with open(profile_path, 'r') as f:
+ profile = yaml.safe_load(f)
+ if 'julius' in profile:
+ if 'lexicon' in profile['julius']:
+ lexicon_file = profile['julius']['lexicon']
+ if 'lexicon_archive_member' in profile['julius']:
+ lexicon_archive_member = \
+ profile['julius']['lexicon_archive_member']
+
+ lexicon = JuliusVocabulary.VoxForgeLexicon(lexicon_file,
+ lexicon_archive_member)
+
+ # Create grammar file
+ tmp_grammar_file = os.path.join(tmpdir,
+ os.extsep.join([prefix, 'grammar']))
+ with open(tmp_grammar_file, 'w') as f:
+ grammar = self._get_grammar(phrases)
+ for definition in grammar.pop('S'):
+ f.write("%s: %s\n" % ('S', ' '.join(definition)))
+ for name, definitions in grammar.items():
+ for definition in definitions:
+ f.write("%s: %s\n" % (name, ' '.join(definition)))
+
+ # Create voca file
+ tmp_voca_file = os.path.join(tmpdir, os.extsep.join([prefix, 'voca']))
+ with open(tmp_voca_file, 'w') as f:
+ for category, words in self._get_word_defs(lexicon,
+ phrases).items():
+ f.write("%% %s\n" % category)
+ for word, phoneme in words:
+ f.write("%s\t\t\t%s\n" % (word, phoneme))
+
+ # mkdfa.pl
+ olddir = os.getcwd()
+ os.chdir(tmpdir)
+ cmd = ['mkdfa.pl', str(prefix)]
+ with tempfile.SpooledTemporaryFile() as out_f:
+ subprocess.call(cmd, stdout=out_f, stderr=out_f)
+ out_f.seek(0)
+ for line in out_f.read().splitlines():
+ line = line.strip()
+ if line:
+ self._logger.debug(line)
+ os.chdir(olddir)
+
+ tmp_dfa_file = os.path.join(tmpdir, os.extsep.join([prefix, 'dfa']))
+ tmp_dict_file = os.path.join(tmpdir, os.extsep.join([prefix, 'dict']))
+ shutil.move(tmp_dfa_file, self.dfa_file)
+ shutil.move(tmp_dict_file, self.dict_file)
+
+ shutil.rmtree(tmpdir)
+
+
def get_phrases_from_module(module):
"""
Gets phrases from a module.
@@ -373,7 +524,6 @@ def get_all_phrases():
return sorted(list(set(phrases)))
if __name__ == '__main__':
- import shutil
import argparse
parser = argparse.ArgumentParser(description='Vocabcompiler Demo')