ru_tts

Форк
0
/
ru_tts.c 
400 строк · 10.9 Кб
1
/* The Russian text to speech converter
2
 *
3
 * Copyright (C) 1990, 1991 Speech Research Laboratory, Minsk
4
 * Copyright (C) 2005 Igor Poretsky <poretsky@mlbox.ru>
5
 * Copyright (C) 2021 Boris Lobanov <lobbormef@gmail.com>
6
 * Copyright (C) 2021 Alexander Ivanov <ivalex01@gmail.com>
7
 *
8
 * SPDX-License-Identifier: MIT
9
 */
10

11
#include <unistd.h>
12
#include <stdlib.h>
13
#include <string.h>
14
#include <stdio.h>
15
#include <math.h>
16
#include <limits.h>
17
#include <sys/types.h>
18
#include <sys/stat.h>
19
#include <fcntl.h>
20

21
#include "config.h"
22

23
#ifndef WITHOUT_DICTIONARY
24
#include <locale.h>
25
#include <ctype.h>
26
#ifdef RULEX_DLL
27
#include <dlfcn.h>
28
#else
29
#include <rulexdb.h>
30
#endif
31
#endif
32

33
#include "ru_tts.h"
34

35
#define WAVE_SIZE 4096
36

37
#ifndef WITHOUT_DICTIONARY
38
#ifdef RULEX_DLL
39
#define RULEXDB void
40
#define RULEXDB_MAX_KEY_SIZE 50
41
#define RULEXDB_BUFSIZE 256
42
#define RULEXDB_SEARCH 0
43
#define RULEXDB_SPECIAL 1
44

45
typedef RULEXDB *(*rulexdb_open_function)(const char *, int);
46
typedef void (*rulexdb_close_function)(RULEXDB *);
47
typedef int (*rulexdb_search_function)(RULEXDB *, const char *, char *, int);
48

49
static void *dll = NULL;
50
static rulexdb_search_function rulexdb_search;
51
static rulexdb_close_function rulexdb_close;
52

53
static RULEXDB *rulexdb_open(const char *path, int mode)
54
{
55
  char *err = NULL;
56
  dll = dlopen(RULEX_DLL, RTLD_LAZY);
57
  if (dll)
58
    {
59
      rulexdb_open_function do_open = (rulexdb_open_function) dlsym(dll, "rulexdb_open");
60
      rulexdb_search = (rulexdb_search_function) dlsym(dll, "rulexdb_search");
61
      rulexdb_close = (rulexdb_close_function) dlsym(dll, "rulexdb_close");
62
      err = dlerror();
63
      if (!err)
64
        return do_open(path, mode);
65
    }
66
  else err = dlerror();
67
  fprintf(stderr, "%s\nDictionary will not be searched\n", err);
68
  return NULL;
69
}
70
#endif
71

72
/* Acceptable character codes */
73
static const char symbols[] =
74
  {
75
    '+', '=',
76
    0xC1, 0xC2, 0xD7, /* а, б, в, */
77
    0xC7, 0xC4, 0xC5, /* г, д, е, */
78
    0xA3, 0xD6, 0xDA, /* ё, ж, з, */
79
    0xC9, 0xCA, 0xCB, /* и, й, к, */
80
    0xCC, 0xCD, 0xCE, /* л, м, н, */
81
    0xCF, 0xD0, 0xD2, /* о, п, р, */
82
    0xD3, 0xD4, 0xD5, /* с, т, у, */
83
    0xC6, 0xC8, 0xC3, /* ф, х, ц, */
84
    0xDE, 0xDB, 0xDD, /* ч, ш, щ, */
85
    0xDF, 0xD9, 0xD8, /* ъ, ы, ь, */
86
    0xDC, 0xC0, 0xD1, /* э, ю, я, */
87
    0
88
  };
89

90
static const char *charset = "ru_RU.koi8r";
91
static const char *alphabet;
92
#endif
93

94
static const char *help_msg =
95
  "Speech control options:\n"
96
  "-r value -- Speech rate factor.\n"
97
  "-p value -- Voice pitch factor.\n"
98
  "-e value -- Generated speech emotionality factor.\n"
99
  "-g value -- Interclause gaps duration factor.\n"
100
  "-a -- Use alternative (female) voice.\n\n"
101

102
  "Numbers treatment control options:\n"
103
  "-d. -- Only point should be treated as decimal separator.\n"
104
  "-d, -- Only comma should be treated as decimal separator.\n"
105
  "-d- -- Disable decimal separators treating.\n\n"
106

107
  "Other options:\n"
108
#ifndef WITHOUT_DICTIONARY
109
  "-s path -- Pronunciation dictionary location.\n"
110
  "-l path -- Log unknown words in specified file\n"
111
  "           (does matter only in conjunction with pronunciation dictionary).\n"
112
#endif
113
  "-v -- Print name and version on stderr and exit.\n"
114
  "-h -- Get this help.\n\n"
115

116
  "All numeric parameters must be positive numbers. Default value is 1.0.\n\n"
117

118
  "A value for -g option may be prepended by one of the following\n"
119
  "punctuations: ',', '.', ';', ':', '?', '!'. In this case the factor\n"
120
  "will be applied only to the gaps implied by that punctuation.\n"
121
  "If the value is prepended by symbol '-', it will be used for\n"
122
  "intonational gaps not caused by any punctuation.\n"
123
  "Otherwise all interclause gaps will be affected.\n"
124
  "Use this option several times to adjust various gaps.\n";
125

126
static const char *clause_separators = ",.;:?!-";
127

128
static int wave_consumer(void *buffer, size_t size, void *user_data)
129
{
130
  int *rc = user_data;
131
  *rc = (write(1, buffer, size) == -1);
132
  if (*rc)
133
    perror("Output error");
134
  return *rc;
135
}
136

137
static void *xrealloc(void *p, unsigned int n)
138
{
139
  void *u = realloc(p, n);
140
  if (u) return u;
141
  else
142
    {
143
      perror("Memory allocation error");
144
      exit(EXIT_FAILURE);
145
    }
146
}
147

148
static void *xmalloc(unsigned int n)
149
{
150
  void *u = NULL;
151
  return xrealloc(u, n);
152
}
153

154
static int getval(const char *s)
155
{
156
  char *t;
157
  double value = strtod(s, &t);
158
  if ((s == t) || (value < 0.0))
159
    {
160
      fprintf(stderr, "Illegal option value \"%s\"\n", s);
161
      fprintf(stderr, "Must be a positive number\n");
162
      exit(EXIT_FAILURE);
163
    }
164
  return rint(value * 100.0);
165
}
166

167
static void usage(const char* name)
168
{
169
  fprintf(stderr, "Usage:\n");
170
  fprintf(stderr, "%s [options]\n\n", name);
171
  fprintf(stderr, "%s\n", help_msg);
172
}
173

174
int main(int argc, char **argv)
175
{
176
  size_t size = 64;
177
  int write_error;
178
  char c, *s, *text, *input = NULL;
179
  void *wave;
180
#ifndef WITHOUT_DICTIONARY
181
  FILE *slog = NULL;
182
  RULEXDB *db = NULL;
183
#endif
184
  ru_tts_conf_t ru_tts_config;
185

186
  ru_tts_config_init(&ru_tts_config);
187
  while ((c = getopt(argc, argv, "s:l:r:p:g:e:d:ahv")) != -1)
188
    {
189
      switch (c)
190
        {
191
          case 's':
192
#ifndef WITHOUT_DICTIONARY
193
            db = rulexdb_open(optarg, RULEXDB_SEARCH);
194
#ifdef RULEX_DLL
195
            if (dll)
196
#endif
197
            if (!db) perror(optarg);
198
#else
199
            fprintf(stderr, "Unsupported option \"-s\" is ignored\n");
200
#endif
201
            break;
202
          case 'l':
203
#ifndef WITHOUT_DICTIONARY
204
            input = optarg;
205
#else
206
            fprintf(stderr, "Unsupported option \"-l\" is ignored\n");
207
#endif
208
            break;
209
          case 'a':
210
            ru_tts_config.flags |= USE_ALTERNATIVE_VOICE;
211
            break;
212
          case 'r':
213
            ru_tts_config.speech_rate = getval(optarg);
214
            break;
215
          case 'p':
216
            ru_tts_config.voice_pitch = getval(optarg);
217
            break;
218
          case 'g':
219
            if (strchr(clause_separators, *optarg))
220
              switch (*optarg)
221
                {
222
                case ',':
223
                  ru_tts_config.comma_gap_factor = getval(optarg + 1);
224
                  break;
225
                case '.':
226
                  ru_tts_config.dot_gap_factor = getval(optarg + 1);
227
                  break;
228
                case ';':
229
                  ru_tts_config.semicolon_gap_factor = getval(optarg + 1);
230
                  break;
231
                case ':':
232
                  ru_tts_config.colon_gap_factor = getval(optarg + 1);
233
                  break;
234
                case '?':
235
                  ru_tts_config.question_gap_factor = getval(optarg + 1);
236
                  break;
237
                case '!':
238
                  ru_tts_config.exclamation_gap_factor = getval(optarg + 1);
239
                  break;
240
                case '-':
241
                  ru_tts_config.intonational_gap_factor = getval(optarg + 1);
242
                  break;
243
                default:
244
                  break;
245
                }
246
            else ru_tts_config.general_gap_factor = getval(optarg);
247
            break;
248
          case 'e':
249
            ru_tts_config.intonation = getval(optarg);
250
            break;
251
          case 'd':
252
            ru_tts_config.flags &= ~(DEC_SEP_POINT | DEC_SEP_COMMA);
253
            switch (*optarg)
254
              {
255
              case '.':
256
                ru_tts_config.flags |= DEC_SEP_POINT;
257
                break;
258
              case ',':
259
                ru_tts_config.flags |= DEC_SEP_COMMA;
260
              case '-':
261
                break;
262
              default:
263
                fprintf(stderr, "Invalid option \"-d%s\"\n\n", optarg);
264
                usage(argv[0]);
265
                return EXIT_FAILURE;
266
              }
267
            break;
268
          case 'h':
269
            usage(argv[0]);
270
            return EXIT_SUCCESS;
271
          case 'v':
272
            fprintf(stderr, "%s version %s\n\n", PACKAGE_NAME, PACKAGE_VERSION);
273
            return EXIT_SUCCESS;
274
          default:
275
            fprintf(stderr, "\n");
276
            usage(argv[0]);
277
            return EXIT_FAILURE;
278
        }
279
    }
280

281
#ifndef WITHOUT_DICTIONARY
282
  if (db)
283
    {
284
      if (setlocale(LC_CTYPE, charset))
285
        {
286
          if (input)
287
            {
288
              slog = fopen(input, "a");
289
              if (!slog) perror(input);
290
            }
291
          alphabet = symbols + 2;
292
        }
293
      else
294
        {
295
          fprintf(stderr, "Cannot set \"%s\" locale.\n", charset);
296
          fprintf(stderr, "Dictionary will not be searched\n");
297
          rulexdb_close(db);
298
          db = NULL;
299
#ifdef RULEX_DLL
300
          if (dll)
301
            {
302
              dlclose(dll);
303
              dll = NULL;
304
            }
305
#endif
306
        }
307
    }
308
#endif
309

310
  /* doing tts in the loop */
311
  wave = xmalloc(WAVE_SIZE);
312
  text = xmalloc(size);
313
  s = text;
314
  do
315
    {
316
      input = fgets(s, text + size - s, stdin);
317
      if (!input)
318
        strcpy(s, "\n");
319
      s = text + strlen(text) - 1;
320
      if (*s == '\n')
321
        {
322
          for (*s-- = 0; *text; s--)
323
            {
324
              if (*s == ' ') *s = 0;
325
              else break;
326
            }
327
#ifndef WITHOUT_DICTIONARY
328
          if (db)
329
            {
330
              char *stressed = xmalloc(strlen(text) << 1);
331
              char *t;
332
              unsigned int n;
333
              for (s = text; *s; s++)
334
                if (isupper(*s))
335
                  *s = tolower(*s);
336
              s = text;
337
              t = stressed;
338
              while (*s)
339
                {
340
                  if ((n = strcspn(s, symbols)))
341
                    {
342
                      strncpy(t, s, n);
343
                      s += n;
344
                      t += n;
345
                    }
346
                  if ((n = strspn(s, symbols)))
347
                    {
348
                      if ((n <= RULEXDB_MAX_KEY_SIZE) && (n <= strspn(s, alphabet)))
349
                        {
350
                          char *key = xmalloc(RULEXDB_BUFSIZE);
351
                          strncpy(key, s, n);
352
                          key[n] = 0;
353
                          if (rulexdb_search(db, key, t, 0)
354
                              == RULEXDB_SPECIAL)
355
                            if (slog) (void)fprintf(slog, "%s\n", key);
356
                          free(key);
357
                        }
358
                      else
359
                        {
360
                          strncpy(t, s, n);
361
                          t[n] = 0;
362
                        }
363
                      s += n;
364
                      t += strlen(t);
365
                    }
366
                }
367
              *t = 0;
368
              ru_tts_transfer(&ru_tts_config, stressed, wave, WAVE_SIZE, wave_consumer, &write_error);
369
              free(stressed);
370
            }
371
          else
372
#endif
373
            ru_tts_transfer(&ru_tts_config, text, wave, WAVE_SIZE, wave_consumer, &write_error);
374
          if (write_error)
375
            break;
376
          s = text;
377
        }
378
      else
379
        {
380
          text = xrealloc(text, size <<= 1);
381
          s = text + strlen(text);
382
        }
383
    }
384
  while (input);
385

386
#ifndef WITHOUT_DICTIONARY
387
  if (db)
388
    rulexdb_close(db);
389
  if (slog)
390
    fclose(slog);
391
#ifdef RULEX_DLL
392
  if (dll)
393
    dlclose(dll);
394
#endif
395
#endif
396

397
  free(text);
398
  free(wave);
399
  return write_error ? EXIT_FAILURE : EXIT_SUCCESS;
400
}
401

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.