263 lines
7.2 KiB
C
263 lines
7.2 KiB
C
/*
|
|
* A tool to add eSpeak-generated pronunciation to dictionaries
|
|
*
|
|
* Here I use the `espeak' process rather than libespeak because of the GPL.
|
|
*
|
|
* Copyright (c) 2013, Přemysl Janouch <p.janouch@gmail.com>
|
|
* All rights reserved.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <glib.h>
|
|
#include <gio/gio.h>
|
|
|
|
#include "stardict.h"
|
|
|
|
|
|
// --- Pronunciation generator -------------------------------------------------
|
|
|
|
typedef struct worker_data WorkerData;
|
|
|
|
struct worker_data
|
|
{
|
|
guint32 start_entry; //! The first entry to be processed
|
|
guint32 end_entry; //! Past the last entry to be processed
|
|
|
|
/* Reader, writer */
|
|
GMutex *dict_mutex; //! Locks the dictionary object
|
|
|
|
/* Reader */
|
|
GThread *main_thread; //! A handle to the reader thread
|
|
StardictDict *dict; //! The dictionary object
|
|
gpointer output; //! Linked-list of pronunciation data
|
|
|
|
GMutex *remaining_mutex; //! Locks the progress stats
|
|
GCond *remaining_cond; //! Signals a change in progress
|
|
guint32 remaining; //! How many entries remain
|
|
|
|
/* Writer */
|
|
StardictIterator *iterator; //! Iterates over the dictionary
|
|
FILE *child_stdin; //! Standard input of eSpeak
|
|
};
|
|
|
|
/** Writes to espeak's stdin. */
|
|
static gpointer
|
|
worker_writer (WorkerData *data)
|
|
{
|
|
while (stardict_iterator_get_offset (data->iterator) != data->end_entry)
|
|
{
|
|
g_mutex_lock (data->dict_mutex);
|
|
const gchar *word = stardict_iterator_get_word (data->iterator);
|
|
g_mutex_unlock (data->dict_mutex);
|
|
|
|
stardict_iterator_next (data->iterator);
|
|
if (fprintf (data->child_stdin, "%s\n", word) < 0)
|
|
g_error ("write to eSpeak failed: %s", strerror (errno));
|
|
}
|
|
|
|
g_object_unref (data->iterator);
|
|
return GINT_TO_POINTER (fclose (data->child_stdin));
|
|
}
|
|
|
|
/** Reads from espeak's stdout. */
|
|
static gpointer
|
|
worker (WorkerData *data)
|
|
{
|
|
/* Spawn eSpeak */
|
|
static gchar *cmdline[] = { "espeak", "--ipa", "-q", NULL };
|
|
gint child_in, child_out;
|
|
|
|
GError *error;
|
|
if (!g_spawn_async_with_pipes (NULL, cmdline, NULL,
|
|
G_SPAWN_SEARCH_PATH, NULL, NULL,
|
|
NULL, &child_in, &child_out, NULL, &error))
|
|
g_error ("g_spawn() failed: %s", error->message);
|
|
|
|
data->child_stdin = fdopen (child_in, "wb");
|
|
if (!data->child_stdin)
|
|
perror ("fdopen");
|
|
|
|
FILE *child_stdout = fdopen (child_out, "rb");
|
|
if (!child_stdout)
|
|
perror ("fdopen");
|
|
|
|
/* Spawn a writer thread */
|
|
g_mutex_lock (data->dict_mutex);
|
|
data->iterator = stardict_iterator_new (data->dict, data->start_entry);
|
|
g_mutex_unlock (data->dict_mutex);
|
|
|
|
GThread *writer = g_thread_new ("write worker",
|
|
(GThreadFunc) worker_writer, data);
|
|
|
|
/* Read the output */
|
|
g_mutex_lock (data->remaining_mutex);
|
|
guint32 remaining = data->remaining;
|
|
g_mutex_unlock (data->remaining_mutex);
|
|
|
|
data->output = NULL;
|
|
gpointer *output_end = &data->output;
|
|
while (remaining)
|
|
{
|
|
static gchar next[sizeof (gpointer)];
|
|
GString *s = g_string_new (NULL);
|
|
g_string_append_len (s, next, sizeof next);
|
|
|
|
gint c;
|
|
while ((c = fgetc (child_stdout)) != EOF && c != '\n')
|
|
g_string_append_c (s, c);
|
|
if (c == EOF)
|
|
g_error ("eSpeak process died too soon");
|
|
|
|
gchar *translation = g_string_free (s, FALSE);
|
|
*output_end = translation;
|
|
output_end = (gpointer *) translation;
|
|
|
|
/* We limit progress reporting so that
|
|
* the mutex doesn't spin like crazy */
|
|
if ((--remaining & 1023) != 0)
|
|
continue;
|
|
|
|
g_mutex_lock (data->remaining_mutex);
|
|
data->remaining = remaining;
|
|
g_cond_broadcast (data->remaining_cond);
|
|
g_mutex_unlock (data->remaining_mutex);
|
|
}
|
|
|
|
fclose (child_stdout);
|
|
return g_thread_join (writer);
|
|
}
|
|
|
|
// --- Main --------------------------------------------------------------------
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
gint n_processes = 1;
|
|
|
|
GOptionEntry entries[] =
|
|
{
|
|
{ "processes", 'N', G_OPTION_FLAG_IN_MAIN,
|
|
G_OPTION_ARG_INT, &n_processes,
|
|
"the number of espeak processes run in parallel", "PROCESSES" },
|
|
{ NULL }
|
|
};
|
|
|
|
GError *error = NULL;
|
|
GOptionContext *ctx = g_option_context_new
|
|
("input.ifo output.ifo - add pronunciation to dictionaries");
|
|
g_option_context_add_main_entries (ctx, entries, NULL);
|
|
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
|
{
|
|
g_print ("option parsing failed: %s\n", error->message);
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
if (argc != 3)
|
|
{
|
|
gchar *help = g_option_context_get_help (ctx, TRUE, FALSE);
|
|
g_print ("%s", help);
|
|
g_free (help);
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
StardictDict *dict = stardict_dict_new (argv[1], &error);
|
|
if (!dict)
|
|
{
|
|
g_printerr ("opening the dictionary failed: %s\n", error->message);
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
gsize n_words = stardict_info_get_word_count
|
|
(stardict_dict_get_info (dict));
|
|
|
|
if (n_processes <= 0)
|
|
{
|
|
g_printerr ("Error: there must be at least one process\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
if ((gsize) n_processes > n_words * 1024)
|
|
{
|
|
n_processes = n_words / 1024;
|
|
if (!n_processes)
|
|
n_processes = 1;
|
|
g_printerr ("Warning: too many processes, reducing to %d\n",
|
|
n_processes);
|
|
}
|
|
|
|
/* Spawn worker threads to generate pronunciations */
|
|
static GMutex dict_mutex;
|
|
|
|
static GMutex remaining_mutex;
|
|
static GCond remaining_cond;
|
|
|
|
WorkerData *data = g_alloca (sizeof *data * n_processes);
|
|
|
|
gint i;
|
|
for (i = 0; i < n_processes; i++)
|
|
{
|
|
data[i].start_entry = (n_words - 1) * i / n_processes;
|
|
data[i].end_entry = (n_words - 1) * (i + 1) / n_processes;
|
|
|
|
data[i].remaining = data[i].end_entry - data[i].start_entry;
|
|
data[i].remaining_mutex = &remaining_mutex;
|
|
data[i].remaining_cond = &remaining_cond;
|
|
|
|
data[i].dict = dict;
|
|
data[i].dict_mutex = &dict_mutex;
|
|
|
|
data->main_thread = g_thread_new ("worker", (GThreadFunc) worker, data);
|
|
}
|
|
|
|
/* Loop while the threads still have some work to do and report status */
|
|
g_mutex_lock (&remaining_mutex);
|
|
for (;;)
|
|
{
|
|
gboolean all_finished = TRUE;
|
|
printf ("\rRetrieving pronunciation... ");
|
|
for (i = 0; i < n_processes; i++)
|
|
{
|
|
printf ("%3u%% ", data[i].remaining * 100
|
|
/ (data[i].end_entry - data[i].start_entry));
|
|
if (data[i].remaining)
|
|
all_finished = FALSE;
|
|
}
|
|
|
|
if (all_finished)
|
|
break;
|
|
g_cond_wait (&remaining_cond, &remaining_mutex);
|
|
}
|
|
g_mutex_unlock (&remaining_mutex);
|
|
|
|
for (i = 0; i < n_processes; i++)
|
|
g_thread_join (data[i].main_thread);
|
|
|
|
// TODO after all processing is done, the program will go through the whole
|
|
// dictionary and put extended data entries into a new one.
|
|
StardictIterator *iterator = stardict_iterator_new (dict, 0);
|
|
while (stardict_iterator_is_valid (iterator))
|
|
{
|
|
// ...
|
|
stardict_iterator_next (iterator);
|
|
}
|
|
|
|
return 0;
|
|
}
|