Paul Nechifor

[english]

Cum să faci calculatorul să vorbească română

Ai nevoie de programul mbrola care se găsește pentru foarte multe sisteme de operare și baza de date în română (ro1). Astea se găsesc pe saitul MBROLA.

De asemenea ai nevoie de un player de fișiere WAV care funcționează din consolă. Eu am folosit programul play care se găsește în pachetul sox (Sound Exchange) în Ubuntu și Arch Linux.

De pe saitul ăsta ai nevoie de programul Python romsun.py și dictionarul. Acest program nu încearcă să ghicească citirea pentru cuvintele scrise fără diacritice sau cu diacritice greșite (adică „ș“ și „ț“ cu sedilă în loc de virgulă sau „ă“ cu tilda). Deci dacă nu poți să scrii cu literele corecte... nasol.

Colorarea sursei am obținut-o cu pygmentize -o romsun.html -O style=colorful,linenos=1 romsun.py

Codul sursă îl scriu mai jos, dar se găsește și în fișierul romsun.py.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# Nume: Romsun - Generator sunete pentru texte în română.
# Autor: Paul Nechifor <[email protected]>
# Inceput: 01.08.2009
# Terminat: 12.08.2009

u"""Romsun - Generator sunete pentru texte în română.

Acest script python transformă texte scrise în limba română în fișiere .pho care
pot fi citite de către programul mbrola pentru a produce fișiere audio.

Folosire: python romsun.py [opțiuni]

Opțiuni:
  -t ..., --text=...       Fișierul care va fi citit. Dacă nu este specificat,
                           se citește de la intrarea standard.
  -s ..., --scriere=...    Unde va fi scris fișierul. Dacă nu este specificat,
                           se scrie la ieșirea standard.
  -v xx, --viteza=xx       Coeficientul cu care este înmulțită durata sunetelor.
                           Dacă nu este specificat valoarea va fi 0,75.
  -p xx, --spatii=xx       Coeficientul cu care este înmulțită durata spațiilor.
                           Dacă nu este specificat valoarea va fi 0,20.
  -h, --help               Arată textul ăsta.

Exemple:
  ./romsun.py -t exemplu.txt -s exemplu.pho
  ./romsun.py --viteza=0,50 -t exemplu.txt -s exemplu.pho
"""

import random, os, sys, types, getopt

_viteza = 0.75
_vspatii = 0.20
dictionar = {}
for linie in open('dictionar', 'r'):
	p = unicode(linie.strip(), 'utf-8').split('=')
	if len(p[0]) > 0: dictionar[p[0]] = p[1]

class EroareNumar(Exception): pass
class EroareSunet(Exception): pass
class EroareCitire(Exception): pass
class EroarePereche(Exception): pass

def urm(s, i):
	if i < len(s) and i >= 0: return s[i]
	else: return ""

def scriereNumar(n, m=True, nr=True):
	u"""Scrie cum se citește un număr primit ca int sau float în parametrul n."""

	p = ['zero', 'unu', 'doi', 'trei', 'patru', 'cinci', u'șase', u'șapte', 'opt', u'nouă', 'zece', \
		'unsprezece', 'doisprezece', 'treisprezece', 'paisprezece', 'cincisprezece', u'șaisprezece', \
		u'șaptisprezece', 'optisprezece', u'nouăsprezece']
	zeci = (u'douăzeci', u'treizeci', u'patruzeci', u'cincizeci', u'șaizeci', u'șaptezeci', u'optzeci', u'nouăzeci')
	ms = ('o mie', 'un milion', 'un miliard', 'un trilion')
	mp = ('mii', 'milioane', 'miliarde', 'trilioane')

	if m:
		if nr: pass
		else:
			p[0] = 'niciun'
			p[1] = 'un'
	else:
		p[1] = 'o'
		p[2] = u'două'
		p[12] = u'douăsprezece'
		if nr: p[1] = 'una'
		else:
			p[0] = 'nicio'
			p[1] = 'o'

	if n == 0:
		return p[0]
	elif type(n) == types.FloatType:
		s = str(n).split(".")
		return scriereNumar(int(s[0]), m, nr) + u' virgulă ' + scriereNumar(int(s[1]), m, nr)
	elif n < 0:
		return 'minus ' + scriereNumar(abs(n), m, nr)
	elif n < 20:
		return p[n]
	elif n < 100:
		if n % 10 == 0: return zeci[n/10 - 2]
		if m: p[1] = 'unu'
		else: p[1] = 'una'
		return  zeci[n/10 - 2] + u' și ' + p[n%10]
	elif n < 1000:
		f = ''
		primul = n / 100
		if primul == 1: f += u'o sută'
		elif primul == 2: f += u'două sute'
		else: f += p[primul] + ' sute'
		n %= 100
		if n == 0: return f
		if m: p[1] = u'unu'
		else: p[1] = u'și una'
		if n < 20: return f + ' ' + p[n]
		if not m: p[1] = 'una'
		if n % 10 == 0: return f + ' ' + zeci[n/10 - 2]
		return f + ' ' + zeci[n/10 - 2] + u' și ' + p[n%10]
	else:
		gr = []
		f = ''
		while n > 0:
			gr.append(n % 1000)
			n /= 1000
		if len(gr) > len(mp)+1:
			return u'Nu știu să citesc număr așa de mare'
		for i in xrange(len(gr)):
			if i == 0:
				if gr[0] == 0: pass
				elif gr[0] == 1: f = u'și una'
				else: f = scriereNumar(gr[0], m, nr)
			else:
				if gr[i] == 0: pass
				elif gr[i] == 1:
					if f == '':	f = ms[i-1]
					else: f = ms[i-1] + ' ' + f
				else: 
					if gr[i] % 100 >= 20: de = ' de'
					else: de = ''
					f = scriereNumar(gr[i], m, nr) + de + ' ' + mp[i-1] + ' ' + f
		return f
		
def scriereNumarString(n, m=True, nr=True):
	u"""Scrie cum se citește un număr care este primit ca string de forma 1234,56 în 
	parametrul n. Se poate și punct în loc de virgulă."""

	n = n.replace(',', '.')
	if n.count('.') > 1: raise EroareNumar, u'Nu poate fi decât un singur punct sau virgulă'.encode('utf-8')
	if not n.replace('.', '').isdigit(): raise EroareNumar, u'Nu este parte de număr'.encode('utf-8')
	if n.count('.') == 0: return scriereNumar(int(n), m, nr)
	else: return scriereNumar(float(n), m, nr)

class Sunet:
	"""Gestionează un sunet.
	   
	Simbolurile pentru sunetele posibile sunt:
		- vocale = i, I, e, a, @, o, u, 1
		- semivocale = j, E, w, O
		- consoane = p, b, t, d, k, g, T, C, G, f, v, s, z, S, J, h, m, n, l, r
	"""
	sunete = ('_', 'i', 'I', 'e', 'a', '@', 'o', 'u', '1', 'j', 'E', 'w', 'O', 'p', 'b', 't', \
			'd', 'k', 'g', 'T', 'C', 'G', 'f', 'v', 's', 'z', 'S', 'J', 'h', 'm', 'n', 'l', 'r')
	def __init__(self, valoare, durata = 100, mod = []):
		if not valoare in self.__class__.sunete: raise EroareSunet, (u"nu există sunetul '%s'" % valoare).encode('utf-8')
		if durata < 0: raise EroareSunet, u'durata nu poate fi mai mică de zero'.encode('utf-8')
		if len(mod) % 2 != 0: raise EroareSunet, u'numărul de modificări trebuie să fie par'.encode('utf-8')
		self.valoare =  valoare
		self.durata = durata
		self.mod = mod

	def text(self):
		if self.mod == []: mod = ""
		else: mod = " " + " ".join(self.mod)
		if self.valoare == '_': m = _vspatii
		else: m = _viteza
		return self.valoare + " " + str(int(self.durata * m)) + mod

class Discurs:
	u"""Gestionează o listă de sunete și afișarea lor."""

	def __init__(self):
		self.sunete = [Sunet("_")]

	def adauga(self, sunet):
		u"""Adaugă sunetul primit ca parametru in lista de sunete a discursului."""

		# trebuie să verific să nu fie vreo pereche imposibilă
		penultimul = self.sunete[-1].valoare
		ultimul = sunet.valoare

		if penultimul == 'a' and ultimul == 'e': raise EroarePereche, 'Nu se poate a-e!'
		if penultimul == 'I' and ultimul == 'i': raise EroarePereche, 'Nu se poate I-i!'
		if penultimul == 'O' and ultimul != 'a': raise EroarePereche, 'Nu se poate O-%s!' % ultimul
		if penultimul == 'E':
			if ultimul != 'a' and ultimul != 'o':
				raise EroarePereche, 'Nu se poate E-%s!' % ultimul
		if ultimul == 'I':
			if penultimul in ('_', 'a', '@', 'e', 'i', '1', 'j', 'o', 's', 'u', 'w'):
				raise EroarePereche, 'Nu se poate %s-I!' % penultimul
		if ultimul == 'E':
			if penultimul in ('@', 'i', '1', 'o', 'u', 'w'):
				raise EroarePereche, 'Nu se poate %s-E!' % penultimul
		if ultimul == '_':
			if penultimul == 'C' or penultimul == 'G':
				raise EroarePereche, 'Nu se poate %s-_!' % penultimul

		self.sunete.append(sunet)

	def text(self):
		return '\n'.join([x.text() for x in self.sunete]) + '\n'

class Cititor:
	u"""Citește câte un cuvânt. Citirea unui cuvânt este căutată într-un dicționar. Dacă nu este găsit cuvântul,
	citirea este formată după niște reguli simple, care nu dau de multe ori răspunsul corect."""

	citire = {'a':'a',  u'ă':'@',  u'â':'1',   'b':'b',   'd':'d',   'e':'e',   'f':'f',            u'î':'1', \
			  'j':'J',   'k':'k',   'l':'l',   'm':'m',   'n':'n',   'o':'o',   'p':'p',   'q':'k',  'r':'r', \
			  's':'s',  u'ș':'S',   't':'t',  u'ț':'T',   'u':'u',   'v':'v',   'w':'v',   'y':'i',  'z':'z'}

	def __init__(self, discurs):
		self.discurs = discurs

	def adaugaCuvant(self, cuv):
		global dictionar

		if cuv == '': return

		# dacă nu a fost adaugată o pauză trebuie să o adaug
		if self.discurs.sunete[-1].valoare != '_': self.crud('_')

		try:
			self.crud(dictionar[cuv])
		except KeyError:
			pronuntare = ""
			for i in xrange(len(cuv)):
				try:
					pronuntare += self.__class__.citire[cuv[i]]
				except KeyError:
					if cuv[i] == 'i':
						if urm(cuv, i+1) == "" and not (pronuntare[-1] in ('a', '@', 'e', 'i', '1', 'j', 'o', 's', 'u', 'w')): pronuntare += 'I'
						else: pronuntare += 'i'
					elif cuv[i] == 'c':
						if urm(cuv, i+1) in ('e', 'i'): pronuntare += 'C'
						else: pronuntare += 'k'
					elif cuv[i] == 'g':
						if urm(cuv, i+1) in ('e', 'i'): pronuntare += 'G'
						else: pronuntare += 'g'
					elif cuv[i] == 'h':
						if urm(cuv, i-1) in ('c', 'g'): pass
						else: pronuntare += 'h'
					elif cuv[i] == 'x':
						pronuntare += 'ks'
					elif cuv[i] == '-' or cuv[i] == "'": pass
					else: raise EroareCitire, (u"Nu este parte de cuvânt '%s' din '%s'!" % (cuv[i], cuv)).encode('utf-8')
			self.crud(pronuntare)

	def crud(self, cuv):
		lista = cuv.split("#")
		if len(lista) == 1:
			for c in lista[0]:
				self.discurs.adauga(Sunet(c))
		else:
			numere = lista[1].split("|")
			if len(numere) != len(lista[0]):
				raise EroareCitire, (u"Trebuie să se potrivească literele și numerele! (%s, %s)" % (numere, lista[0])).encode('utf-8')
			sir = []
			for n in numere:
				if n == "": sir.append([])
				else: sir.append([ int(x) for x in n.split(",") ])

			for i in range(len(lista[0])):
				if len(sir[i]) == 0:
					self.discurs.adauga(Sunet(lista[0][i]))
				elif len(sir[i]) == 1:
					self.discurs.adauga(Sunet(lista[0][i], sir[i][0], sir[i][1:]))

class Procesor(Cititor):
	u"""Citește orice text prin extragerea cuvintelor."""

	atomCuvant = ('a', u'ă', u'â', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', u'î', 'j', 'k', 'l', 'm', 'n', 'o', 'p', \
					'q', 'r', 's', u'ș', 't', u'ț', 'u', 'v', 'w',  'x', 'y', 'z', '-', "'")

	def __init__(self, discurs, deInceput=''):
		self.discurs = discurs
		if deInceput != '': self.proceseaza(deInceput)

	def proceseaza(self, text):
		text = self.inlocuiesteNumere(text.lower().replace('\n', ''))
		i = 0
		cuvant = ''
		while i < len(text):
			if text[i] in self.__class__.atomCuvant:
				cuvant += text[i]
				i += 1
			else:
				self.adaugaCuvant(cuvant)
				cuvant = ''
				if text[i] == ' ': pauza = 100
				elif text[i] in (',', ':'): pauza = 150
				elif text[i] == u'—': pauza = 250 # linie de pauză
				else: pauza = 200
				self.discurs.adauga(Sunet('_', pauza))
				while i < len(text) and not (text[i] in self.__class__.atomCuvant): i += 1
		if len(cuvant) > 0: self.adaugaCuvant(cuvant)

	def inlocuiesteNumere(self, text):
		# TODO: să citească corect și numerele negative sau cu virgulă XXX
		nr = ""
		textNou = ""
		for c in text:
			if ord(c) >= ord('0') and ord(c) <= ord('9'):
				nr += c
			else:
				if len(nr) > 1:
					textNou += scriereNumarString(nr)
					nr = ""
				textNou += c
		if len(nr) > 1: textNou += scriereNumarString(nr)
		return textNou

# ================================================== MAIN ================================================== 

if __name__ == '__main__':
	try:
		opts, args = getopt.getopt(sys.argv[1:], 't:s:v:p:h', ['text=', 'scriere=', 'viteza=', 'spatii=', 'help'])
	except getopt.GetoptError:
		print __doc__
		exit(2)
	text = sys.stdin
	scriere = sys.stdout
	for opt, arg in opts:
		if opt in ('-h', '--help'):
			print __doc__
			exit()
		elif opt in ('-t', '--text'):
			text = open(arg)
		elif opt in ('-s', '--scriere'):
			scriere = open(arg)
		elif opt in ('-v', '--viteza'):
			_viteza = float(arg.replace(',', '.'))
		elif opt in ('-p', '--spatii'):
			_vspatii = float(arg.replace(',', '.'))

	d = Discurs()
	p = Procesor(d)
	p.proceseaza(unicode(text.read(), 'utf-8'))	
	scriere.write(d.text())

Creat în 2009 de Paul NechiforProiecte