2
* Copyright © 2012 Canonical Ltd.
4
* This program is free software: you can redistribute it and/or modify it
5
* under the terms of the GNU General Public License version 3, as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranties of
10
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11
* PURPOSE. See the GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License along
14
* with this program. If not, see <http://www.gnu.org/licenses/>.
17
#define _POSIX_SOURCE 1
18
#define G_LOG_DOMAIN "hudjulius"
20
#include "hudjulius.h"
21
#include "hudsource.h"
22
#include "pronounce-dict.h"
23
#include "hud-query-iface.h"
25
#include <glib/gstdio.h>
31
GObject parent_instance;
33
HudQueryIfaceComCanonicalHudQuery * skel;
35
GRegex * alphanumeric_regex;
39
gboolean listen_emitted;
41
gboolean heard_something_emitted;
53
hud_julius_error_quark(void)
55
static GQuark quark = 0;
57
quark = g_quark_from_static_string ("hud-julius-error-quark");
62
hud_julius_alphanumeric_regex_new (void)
64
GRegex *alphanumeric_regex = NULL;
67
alphanumeric_regex = g_regex_new("…|\\.\\.\\.", 0, 0, &error);
68
if (alphanumeric_regex == NULL) {
69
g_error("Compiling regex failed: [%s]", error->message);
73
return alphanumeric_regex;
76
static void rm_rf(const gchar *path);
78
typedef GObjectClass HudJuliusClass;
80
static void hud_julius_finalize (GObject *object);
82
static gboolean hud_julius_voice_query (HudVoice *self, HudSource *source,
83
gchar **result, GError **error);
85
static void hud_julius_iface_init (HudVoiceInterface *iface);
87
G_DEFINE_TYPE_WITH_CODE (HudJulius, hud_julius, G_TYPE_OBJECT,
88
G_IMPLEMENT_INTERFACE (HUD_TYPE_VOICE, hud_julius_iface_init))
90
static void hud_julius_iface_init (HudVoiceInterface *iface)
92
iface->query = hud_julius_voice_query;
96
hud_julius_class_init (HudJuliusClass *klass)
98
klass->finalize = hud_julius_finalize;
102
hud_julius_init (HudJulius *self)
104
self->alphanumeric_regex = hud_julius_alphanumeric_regex_new();
108
hud_julius_finalize (GObject *object)
110
HudJulius *self = HUD_JULIUS (object);
112
g_clear_object(&self->skel);
113
g_clear_pointer(&self->alphanumeric_regex, g_regex_unref);
115
G_OBJECT_CLASS (hud_julius_parent_class)
120
hud_julius_get_daemon_path ()
122
return g_build_filename (LIBEXECDIR, "hud", "hud-julius-listen", NULL );
126
timeout_quit_func (gpointer user_data)
128
g_assert(HUD_IS_JULIUS(user_data));
130
HudJulius *self = HUD_JULIUS(user_data);
131
g_debug("Query timeout");
132
*self->query = g_strdup("");
133
g_source_remove(self->watch_source);
134
g_main_loop_quit(self->mainloop);
139
watch_function (GIOChannel *channel, GIOCondition condition,
142
g_assert(HUD_IS_JULIUS(user_data));
144
HudJulius *self = HUD_JULIUS(user_data);
146
if ((condition & G_IO_IN) != 0)
148
GString *line = g_string_sized_new (100);
156
status = g_io_channel_read_line_string (channel, line, NULL,
159
while (status == G_IO_STATUS_AGAIN);
161
if (status != G_IO_STATUS_NORMAL)
165
g_warning("IO ERROR(): %s", (*self->error)->message);
170
if (g_str_has_prefix (line->str, "<voice-query-listening/>"))
172
if (!self->listen_emitted)
174
self->listen_emitted = TRUE;
175
hud_query_iface_com_canonical_hud_query_emit_voice_query_listening (
176
HUD_QUERY_IFACE_COM_CANONICAL_HUD_QUERY (self->skel) );
177
g_debug("<<< please speak >>>");
180
else if (g_str_has_prefix (line->str, "<voice-query-heard-something/>"))
182
if (!self->heard_something_emitted)
184
self->heard_something_emitted = TRUE;
185
hud_query_iface_com_canonical_hud_query_emit_voice_query_heard_something (
186
HUD_QUERY_IFACE_COM_CANONICAL_HUD_QUERY (self->skel) );
187
g_debug("<<< speech input >>>");
190
else if (g_str_has_prefix (line->str, "<voice-query-finished>"))
192
gchar *tmp = g_string_free (line, FALSE);
193
GRegex *regex = g_regex_new("<voice-query-finished>|</voice-query-finished>", 0, 0, NULL);
194
*self->query = g_regex_replace_literal(regex, g_strstrip(tmp), -1, 0, "", 0, NULL);
195
g_regex_unref(regex);
197
g_source_remove(self->timeout_source);
198
g_main_loop_quit(self->mainloop);
202
while (g_io_channel_get_buffer_condition (channel) == G_IO_IN);
203
g_string_free (line, TRUE);
207
if ((condition & G_IO_HUP) != 0)
209
g_io_channel_shutdown (channel, TRUE, NULL );
211
*self->error = g_error_new_literal(hud_julius_error_quark(), 0, "HUD Julius listening daemon failed");
212
g_source_remove(self->timeout_source);
213
g_main_loop_quit(self->mainloop);
221
hud_julius_kill(GPid pid)
227
hud_julius_listen (HudJulius *self, const gchar *gram, const gchar *hmm,
228
const gchar *hlist, gchar **query, GError **error)
230
gchar *program = hud_julius_get_daemon_path ();
231
g_debug("Julius listening program [%s]", program);
233
const gchar *argv[] =
234
{ program, "-input", "pulseaudio", "-gram", gram, "-h", hmm, "-hlist", hlist, NULL };
236
/* These are used inside the callbacks */
239
self->listen_emitted = FALSE;
240
self->heard_something_emitted = FALSE;
242
gint standard_output;
245
if (!g_spawn_async_with_pipes (NULL, (gchar **) argv, NULL, 0, NULL, NULL,
246
&pid, NULL, &standard_output, NULL, error))
248
g_warning("Failed to to load Julius daemon");
250
*error = g_error_new_literal (hud_julius_error_quark (), 0,
251
"Failed to load Julius daemon");
260
channel = g_io_channel_unix_new (standard_output);
261
g_io_channel_set_flags (channel,
262
g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK, NULL );
263
g_io_channel_set_encoding (channel, NULL, NULL );
265
self->watch_source = g_io_add_watch (channel,
266
G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
270
self->mainloop = g_main_loop_new (NULL, FALSE);
271
self->timeout_source = g_timeout_add (HUD_JULIUS_DEFAULT_TIMEOUT / 1000, timeout_quit_func, self);
272
g_main_loop_run (self->mainloop);
273
g_main_loop_unref (self->mainloop);
275
g_io_channel_unref (channel);
276
hud_julius_kill(pid);
277
g_spawn_close_pid(pid);
279
return (*self->error == NULL);
283
free_func (gpointer data)
285
g_ptr_array_free((GPtrArray*) data, TRUE);
288
static const gchar *VOCA_HEADER = "% NS_B\n"
296
hud_julius_write_new_vocabulary_entry(GOutputStream* voca_output, GHashTable *voca, const gint voca_id, GHashTable *pronounciations, const gchar *word)
298
g_hash_table_insert(voca, g_strdup(word), GINT_TO_POINTER(voca_id));
300
gchar *voca_id_str = g_strdup_printf("TOKEN_%d", voca_id);
301
gchar **phonetics_list = g_hash_table_lookup(pronounciations, word);
306
* <word> <phonetics 1>
307
* <word> <phonetics 2>
310
/* we only need to write out the vocab entry for a new token */
311
g_output_stream_write (voca_output, "% ", g_utf8_strlen ("% ", -1),
313
g_output_stream_write (voca_output, voca_id_str,
314
g_utf8_strlen (voca_id_str, -1), NULL, NULL );
315
g_output_stream_write (voca_output, ":\n", g_utf8_strlen (":\n", -1),
318
gchar **phonetics = phonetics_list;
321
gchar* lower = g_utf8_strdown(*phonetics, -1);
323
g_output_stream_write (voca_output, word,
324
g_utf8_strlen (word, -1), NULL, NULL );
325
g_output_stream_write (voca_output, "\t",
326
g_utf8_strlen ("\t", -1), NULL, NULL );
327
/* also add the phonetics */
328
g_output_stream_write (voca_output, lower,
329
g_utf8_strlen (lower, -1), NULL, NULL );
330
g_output_stream_write (voca_output, "\n", g_utf8_strlen ("\n", -1),
336
g_output_stream_write (voca_output, "\n", g_utf8_strlen ("\n", -1),
343
* This writes a grammar entry for the whole command read out, and individual grammar entries
344
* for each word in the command.
347
hud_julius_write_command(GOutputStream* grammar_output, GOutputStream* voca_output, GHashTable *voca, gint *voca_id_counter, GHashTable *pronounciations, GPtrArray *command)
349
/* First we write a grammar entry as the complete sequence of words in the command */
350
g_output_stream_write (grammar_output, "S : NS_B ", g_utf8_strlen ("S : NS_B ", -1),
354
for (i = 0; i < command->len; ++i)
356
const gchar *word = g_ptr_array_index(command, i);
357
gint voca_id = GPOINTER_TO_INT(g_hash_table_lookup(voca, word));
359
/* If this a new phonetic */
362
voca_id = ++(*voca_id_counter);
363
hud_julius_write_new_vocabulary_entry(voca_output, voca, voca_id, pronounciations, word);
366
gchar *voca_id_str = g_strdup_printf("TOKEN_%d", voca_id);
368
g_output_stream_write (grammar_output, voca_id_str,
369
g_utf8_strlen (voca_id_str, -1), NULL, NULL );
370
g_output_stream_write (grammar_output, " ", g_utf8_strlen (" ", -1),
375
g_output_stream_write (grammar_output, " NS_E\n",
376
g_utf8_strlen (" NS_E\n", -1), NULL, NULL );
378
/* Now we write a separate grammar entry for each word in the command */
379
for (i = 0; i < command->len; ++i)
381
g_output_stream_write (grammar_output, "S : NS_B ", g_utf8_strlen ("S : NS_B ", -1),
384
const gchar *word = g_ptr_array_index(command, i);
385
/* The voca_id will certainly be known as we've already written the while lot out once */
386
gint voca_id = GPOINTER_TO_INT(g_hash_table_lookup(voca, word));
388
gchar *voca_id_str = g_strdup_printf("TOKEN_%d", voca_id);
390
g_output_stream_write (grammar_output, voca_id_str,
391
g_utf8_strlen (voca_id_str, -1), NULL, NULL );
392
g_output_stream_write (grammar_output, " ", g_utf8_strlen (" ", -1),
396
g_output_stream_write (grammar_output, " NS_E\n",
397
g_utf8_strlen (" NS_E\n", -1), NULL, NULL );
402
hud_julius_build_grammar (HudJulius *self, GList *items, gchar **temp_dir, GError **error)
404
*temp_dir = g_dir_make_tmp ("hud-julius-XXXXXX", error);
405
if (*temp_dir == NULL )
407
g_warning("Failed to create temp dir [%s]", (*error)->message);
411
PronounceDict *dict = pronounce_dict_get_julius(error);
415
g_clear_pointer(temp_dir, g_free);
419
/* Get the pronounciations for the items */
420
GHashTable *pronounciations = g_hash_table_new_full (g_str_hash, g_str_equal,
421
g_free, (GDestroyNotify) g_strfreev);
422
GPtrArray *command_list = g_ptr_array_new_with_free_func (free_func);
423
GHashTable *unique_commands = g_hash_table_new(g_str_hash, g_str_equal);
424
HudItemPronunciationData pronounciation_data =
425
{ pronounciations, self->alphanumeric_regex, command_list, dict, unique_commands };
426
g_list_foreach (items, (GFunc) hud_item_insert_pronounciation,
427
&pronounciation_data);
428
g_hash_table_destroy(unique_commands);
430
if (command_list->len == 0)
432
*error = g_error_new_literal(hud_julius_error_quark(), 0, "Could not build Julius grammar. Is julius-voxforge installed?");
434
g_clear_pointer(temp_dir, g_free);
438
gint voca_id_counter = 0;
439
GHashTable *voca = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
442
gchar *voca_path = g_build_filename (*temp_dir, "hud.voca", NULL );
443
GFile *voca_file = g_file_new_for_path (voca_path);
445
GOutputStream* voca_output = G_OUTPUT_STREAM(g_file_create (voca_file,
446
G_FILE_CREATE_PRIVATE, NULL, error ));
447
if (voca_output == NULL )
449
g_warning("Failed to open voca file [%s]", (*error)->message);
451
g_hash_table_destroy(voca);
452
g_hash_table_destroy(pronounciations);
453
g_ptr_array_free (command_list, TRUE);
455
g_object_unref (voca_output);
456
g_object_unref (voca_file);
459
g_clear_pointer(temp_dir, g_free);
464
gchar *grammar_path = g_build_filename (*temp_dir, "hud.grammar", NULL );
465
GFile *grammar_file = g_file_new_for_path (grammar_path);
467
GOutputStream* grammar_output = G_OUTPUT_STREAM(g_file_create (grammar_file,
468
G_FILE_CREATE_PRIVATE, NULL, error ));
469
if (grammar_output == NULL )
471
g_warning("Failed to open grammar file [%s]", (*error)->message);
473
g_hash_table_destroy(voca);
474
g_hash_table_destroy(pronounciations);
475
g_ptr_array_free (command_list, TRUE);
477
g_object_unref (voca_output);
478
g_object_unref (voca_file);
480
g_object_unref (grammar_output);
481
g_object_unref (grammar_file);
484
g_clear_pointer(temp_dir, g_free);
489
g_output_stream_write (voca_output, VOCA_HEADER,
490
g_utf8_strlen (VOCA_HEADER, -1), NULL, NULL );
493
for (i = 0; i < command_list->len; ++i)
495
GPtrArray *command = g_ptr_array_index(command_list, i);
496
hud_julius_write_command(grammar_output, voca_output, voca, &voca_id_counter, pronounciations, command);
499
g_hash_table_destroy(voca);
500
g_hash_table_destroy(pronounciations);
501
g_ptr_array_free (command_list, TRUE);
503
g_object_unref (voca_output);
504
g_object_unref (voca_file);
506
g_object_unref (grammar_output);
507
g_object_unref (grammar_file);
510
g_free(grammar_path);
512
/* Now compile the grammar */
513
const gchar *argv[] = { "/usr/bin/mkdfa", "hud", NULL };
516
char *standard_output = NULL;
517
char *standard_error = NULL;
518
if (!g_spawn_sync (*temp_dir, (gchar **)argv, NULL, 0, NULL, NULL, &standard_output,
519
&standard_error, &exit_status, error))
521
g_warning("Compiling grammar failed: [%s]", (*error)->message);
522
g_debug("Compilation errors:\n%s", standard_error);
524
g_free(standard_output);
525
g_free(standard_error);
528
g_clear_pointer(temp_dir, g_free);
533
g_debug("Compilation output:\n%s", standard_output);
535
g_free(standard_output);
536
g_free(standard_error);
541
static void rm_rf(const gchar *path)
543
GError *error = NULL;
544
GDir *dir = g_dir_open(path, 0, &error);
547
g_warning("Could not open directory [%s] for deletion: [%s]", path, error->message);
551
const gchar *file_name;
552
gboolean remove_error = FALSE;
553
while ((file_name = g_dir_read_name(dir)) && !remove_error)
555
gchar *file_path = g_build_filename(path, file_name, NULL);
556
g_debug("removing file [%s]", file_path);
557
if (g_remove(file_path) != 0)
559
g_warning("Unable to remove file [%s]", file_path);
567
g_debug("removing dir [%s]", path);
571
g_debug("not removing directory [%s]", path);
576
hud_julius_voice_query (HudVoice *voice, HudSource *source, gchar **result, GError **error)
578
g_return_val_if_fail(HUD_IS_JULIUS(voice), FALSE);
579
HudJulius *self = HUD_JULIUS(voice);
581
if (source == NULL) {
582
/* No active window, that's fine, but we'll just move on */
584
*error = g_error_new_literal(hud_julius_error_quark(), 0, "Active source is null");
588
GList *items = hud_source_get_items(source);
590
/* The active window doesn't have items, that's cool. We'll move on. */
594
gchar *temp_dir = NULL;
595
if (!hud_julius_build_grammar(self, items, &temp_dir, error))
597
g_list_free_full(items, g_object_unref);
601
gchar *gram = g_build_filename(temp_dir, "hud", NULL);
602
gchar *hmm = g_build_filename(JULIUS_DICT_PATH, "hmmdefs", NULL);
603
gchar *hlist = g_build_filename(JULIUS_DICT_PATH, "tiedlist", NULL);
605
gboolean success = hud_julius_listen (self, gram, hmm, hlist, result, error);
609
g_list_free_full(items, g_object_unref);
619
hud_julius_is_installed()
621
gchar *filename = hud_julius_get_daemon_path();
622
gboolean result = g_file_test(filename, G_FILE_TEST_EXISTS);
628
hud_julius_new(HudQueryIfaceComCanonicalHudQuery * skel)
630
HudJulius *self = g_object_new (HUD_TYPE_JULIUS, NULL);
631
self->skel = g_object_ref(skel);