Spaces:
Running
Running
Guy Mor-Lan
commited on
Commit
•
e35836c
1
Parent(s):
909a784
add files
Browse files- app.py +106 -0
- ar_en +1 -0
- ar_en_ct2/config.json +10 -0
- ar_en_ct2/model.bin +3 -0
- ar_en_ct2/shared_vocabulary.json +0 -0
- colorize.py +109 -0
- consts.py +104 -0
- diacritize.py +42 -0
- en_ar +1 -0
- en_ar_ct2/config.json +10 -0
- en_ar_ct2/model.bin +3 -0
- en_ar_ct2/shared_vocabulary.json +0 -0
- requirements.txt +10 -0
- semsearch.py +78 -0
- translate.py +101 -0
- translit.py +161 -0
- tts.py +86 -0
- wavs/1cfdc7d62daa8ab925371ac17ea4e792.wav +0 -0
- wavs/5fc7e1c64e032cdbfc6435dc9a6a32ce.wav +0 -0
- wavs/8300935681825cf0e5c467f08fb31325.wav +0 -0
app.py
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#%%
|
2 |
+
import gradio as gr
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
|
5 |
+
from translate import run_translate
|
6 |
+
from diacritize import diacritize, diacritize_if_not_already
|
7 |
+
from translit import taatik, translit
|
8 |
+
from semsearch import update_df
|
9 |
+
from tts import get_audio
|
10 |
+
from consts import CSS, ABOUT, JS_FUNC
|
11 |
+
load_dotenv()
|
12 |
+
|
13 |
+
with gr.Blocks(title = "Levanti - Levantine Arabic Translation Tools",
|
14 |
+
css=CSS,
|
15 |
+
theme="default") as demo:
|
16 |
+
# gr.HTML("<h2><span style='color: #2563eb'>Levantine Arabic</span> Translator</h2>")
|
17 |
+
gr.HTML("<h2><span><span style='color: #2563eb'>Levanti</span>ne Translator</span></h2>Levantine Arabic Translation Tools")
|
18 |
+
with gr.Tab('Translation', elem_id="tab1"):
|
19 |
+
with gr.Row():
|
20 |
+
with gr.Column():
|
21 |
+
input_text = gr.Textbox(label="Input",
|
22 |
+
info = "Colloquial Arabic or English",
|
23 |
+
placeholder="Enter text in Arabic or English",
|
24 |
+
lines=2,
|
25 |
+
elem_id="input")
|
26 |
+
|
27 |
+
gr.Examples(["I called him two times, he's not picking up", "خلينا ندور على مطعم تاني"],
|
28 |
+
input_text,
|
29 |
+
label="Examples")
|
30 |
+
|
31 |
+
btn = gr.Button("Translation")
|
32 |
+
with gr.Row():
|
33 |
+
dialect = gr.Radio(["Palestinian", "Syrian", "Lebanese", "Egyptian"],
|
34 |
+
label = "Dialect",
|
35 |
+
info="Affects translation to Arabic",
|
36 |
+
value="Palestinian")
|
37 |
+
|
38 |
+
# gr.Markdown("Built by [Guy Mor-Lan](mailto:guy.mor@mail.huji.ac.il). Pronunciation model is specifically tailored to urban Palestinian Arabic. Text-to-speech uses Microsoft Azure's API and may provide different result from the transliterated pronunciation.")
|
39 |
+
gr.Markdown("Create by [Guy Mor-Lan](mailto:guy.mor@mail.huji.ac.il) as part of the [Levanti](https://huggingface.co/datasets/guymorlan/levanti) project. Audio is produced using Azure TTS with predicted diacritics and heuristics.", elem_id="footer")
|
40 |
+
|
41 |
+
with gr.Column():
|
42 |
+
with gr.Group(elem_id="grp"):
|
43 |
+
gr.HTML("Translation")
|
44 |
+
# gr.Markdown("תרגום", elem_id="diacritized")
|
45 |
+
translation_output = gr.HTML("<br>", visible=True, label="Translation", elem_id="main")
|
46 |
+
|
47 |
+
hidden_arabic = gr.Textbox(lines=1, elem_id="trans", visible=False)
|
48 |
+
|
49 |
+
diacritized_output = gr.Textbox(label="Diacritization (experimental)", lines=1, elem_id="diacritized",
|
50 |
+
interactive=False)
|
51 |
+
taatik_output = gr.Textbox(label="Transliteration (Experimental)", lines=1, elem_id="taatik",
|
52 |
+
text_align="right", interactive=False)
|
53 |
+
# diacritized_output = gr.HTML("<br>", label="ניקוד")
|
54 |
+
# taatik_output = gr.HTML("<br>", label="תעתיק")
|
55 |
+
|
56 |
+
audio = gr.Audio(label="Audio (Azure)", interactive=False,
|
57 |
+
autoplay=True)
|
58 |
+
audio_button = gr.Button("Generate Audio")
|
59 |
+
audio_button.click(get_audio, inputs=[diacritized_output], outputs=[audio])
|
60 |
+
|
61 |
+
|
62 |
+
btn.click(run_translate, inputs=[input_text, dialect],
|
63 |
+
outputs=[translation_output, hidden_arabic], api_name="en2ar",
|
64 |
+
js="function jump(x, y){document.getElementById('main').scrollIntoView(); return [x, y];}")
|
65 |
+
|
66 |
+
input_text.submit(run_translate, inputs=[input_text, dialect],
|
67 |
+
outputs=[translation_output, hidden_arabic], scroll_to_output=True)
|
68 |
+
hidden_arabic.change(diacritize, inputs=[hidden_arabic], outputs=[diacritized_output])
|
69 |
+
diacritized_output.change(translit, inputs=[diacritized_output], outputs=[taatik_output])
|
70 |
+
# with gr.Row():
|
71 |
+
# nearest_df = gr.DataFrame(headers=["ערבית", "עברית", "מאומת"], visible=False, wrap=True,
|
72 |
+
# elem_id="nearest", label="תוצאות קרובות מתוך קורפוס Levanti", height=300)
|
73 |
+
|
74 |
+
# hidden_arabic.change(update_df, inputs=[hidden_arabic], outputs=[nearest_df])
|
75 |
+
|
76 |
+
with gr.Tab("Diacritization and Transliteration", elem_id="tab2"):
|
77 |
+
with gr.Row():
|
78 |
+
with gr.Column():
|
79 |
+
diac_text = gr.Textbox(label="Input", placeholder="Insert text in Arabic", lines=1,
|
80 |
+
info = "For transliteration only, insert diacritized text",
|
81 |
+
elem_id="diac_input")
|
82 |
+
gr.Examples(["خلينا ندور على مطعم تاني", "قَدِيْش حَقّ الْبَنْدُورَة؟"], diac_text,
|
83 |
+
label="Examples", elem_id="diac_ex")
|
84 |
+
btn2 = gr.Button("Send")
|
85 |
+
|
86 |
+
with gr.Column():
|
87 |
+
diacritized_output2 = gr.Textbox(label="Diacritization", lines=1,
|
88 |
+
elem_id="diacritized2")
|
89 |
+
taatik_output2 = gr.Textbox(label="Transliteration", lines=1,
|
90 |
+
elem_id="taatik2")
|
91 |
+
|
92 |
+
# input_text.submit(run_translate, inputs=[input_text, dialect],
|
93 |
+
# outputs=[translation_output], scroll_to_output=True)
|
94 |
+
# hidden_arabic.change(diacritize, inputs=[hidden_arabic], outputs=[diacritized_output])
|
95 |
+
# diacritized_output.change(taatik, inputs=[diacritized_output], outputs=[taatik_output])
|
96 |
+
btn2.click(diacritize_if_not_already, inputs=[diac_text], outputs=[diacritized_output2])
|
97 |
+
diac_text.submit(diacritize_if_not_already, inputs=[diac_text], outputs=[diacritized_output2])
|
98 |
+
diacritized_output2.change(translit, inputs=[diacritized_output2], outputs=[taatik_output2])
|
99 |
+
with gr.Tab("About", elem_id="tab3"):
|
100 |
+
with gr.Row():
|
101 |
+
gr.HTML("<h2>About</h2>")
|
102 |
+
gr.Markdown(ABOUT, elem_id="about")
|
103 |
+
|
104 |
+
|
105 |
+
|
106 |
+
demo.launch(ssl_verify=False)
|
ar_en
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
Subproject commit b5626c21d9814e83302354362e60d813003f8b97
|
ar_en_ct2/config.json
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"add_source_bos": false,
|
3 |
+
"add_source_eos": false,
|
4 |
+
"bos_token": "<s>",
|
5 |
+
"decoder_start_token": "</s>",
|
6 |
+
"eos_token": "</s>",
|
7 |
+
"layer_norm_epsilon": null,
|
8 |
+
"multi_query_attention": false,
|
9 |
+
"unk_token": "<unk>"
|
10 |
+
}
|
ar_en_ct2/model.bin
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:1e12f837210b68c3e3f915b1de10bd762feee9e0f7c515f56f521f79d0b6dc5c
|
3 |
+
size 306547250
|
ar_en_ct2/shared_vocabulary.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
colorize.py
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import matplotlib.pyplot as plt
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
|
5 |
+
def generate_diverging_colors(num_colors, palette='Set3'): # courtesy of ChatGPT
|
6 |
+
# Generate a colormap with a specified number of colors
|
7 |
+
cmap = plt.cm.get_cmap(palette, num_colors)
|
8 |
+
|
9 |
+
# Get the RGB values of the colors in the colormap
|
10 |
+
colors_rgb = cmap(np.arange(num_colors))
|
11 |
+
|
12 |
+
# Convert the RGB values to hexadecimal color codes
|
13 |
+
colors_hex = [format(int(color[0]*255)<<16|int(color[1]*255)<<8|int(color[2]*255), '06x') for color in colors_rgb]
|
14 |
+
|
15 |
+
return colors_hex
|
16 |
+
|
17 |
+
|
18 |
+
def align_words(outputs, tokenizer, encoder_input_ids, decoder_input_ids,
|
19 |
+
threshold=0.4, skip_first_src=True, skip_second_src=False,
|
20 |
+
layer=2, head=6):
|
21 |
+
|
22 |
+
alignment = []
|
23 |
+
# threshold = 0.05
|
24 |
+
for i, tok in enumerate(outputs.cross_attentions[layer][0][head]):
|
25 |
+
alignment.append([[i], (tok > threshold).nonzero().squeeze(-1).tolist()])
|
26 |
+
|
27 |
+
# for i in alignment:
|
28 |
+
# src_tok = [tokenizer.decode(decoder_input_ids[0][x]) for x in i[0]]
|
29 |
+
# trg_tok = [tokenizer.decode(encoder_input_ids[0][x]) for x in i[1]]
|
30 |
+
# print(src_tok, "=>", trg_tok)
|
31 |
+
|
32 |
+
merged = []
|
33 |
+
for i in alignment:
|
34 |
+
token = tokenizer.convert_ids_to_tokens([decoder_input_ids[0][i[0]]])[0]
|
35 |
+
# print(token)
|
36 |
+
if token not in ["</s>", "<pad>", "<unk>", "<s>"]:
|
37 |
+
if merged:
|
38 |
+
tomerge = False
|
39 |
+
# check overlap with previous entry
|
40 |
+
for x in i[1]:
|
41 |
+
if x in merged[-1][1]:# or tokenizer.convert_ids_to_tokens([encoder_input_ids[0][x]])[0][0] != "▁":
|
42 |
+
tomerge = True
|
43 |
+
break
|
44 |
+
# if first character is not a "▁"
|
45 |
+
if token[0] != "▁":
|
46 |
+
tomerge = True
|
47 |
+
if tomerge:
|
48 |
+
merged[-1][0] += i[0]
|
49 |
+
merged[-1][1] += i[1]
|
50 |
+
else:
|
51 |
+
merged.append(i)
|
52 |
+
else:
|
53 |
+
merged.append(i)
|
54 |
+
|
55 |
+
# print("=====MERGED=====")
|
56 |
+
# for i in merged:
|
57 |
+
# src_tok = [tokenizer.decode(decoder_input_ids[0][x]) for x in i[0]]
|
58 |
+
# trg_tok = [tokenizer.decode(encoder_input_ids[0][x]) for x in i[1]]
|
59 |
+
# print(src_tok, "=>", trg_tok)
|
60 |
+
|
61 |
+
colordict = {}
|
62 |
+
ncolors = 0
|
63 |
+
for i in merged:
|
64 |
+
src_tok = [f"src_{x}" for x in i[0]]
|
65 |
+
trg_tok = [f"trg_{x}" for x in i[1]]
|
66 |
+
all_tok = src_tok + trg_tok
|
67 |
+
# see if any tokens in entry already have associated color
|
68 |
+
newcolor = None
|
69 |
+
for t in all_tok:
|
70 |
+
if t in colordict:
|
71 |
+
newcolor = colordict[t]
|
72 |
+
break
|
73 |
+
if not newcolor:
|
74 |
+
newcolor = ncolors
|
75 |
+
ncolors += 1
|
76 |
+
for t in all_tok:
|
77 |
+
if t not in colordict:
|
78 |
+
colordict[t] = newcolor
|
79 |
+
|
80 |
+
colors = generate_diverging_colors(ncolors, palette="Set2")
|
81 |
+
id_to_color = {i: c for i, c in enumerate(colors)}
|
82 |
+
for k, v in colordict.items():
|
83 |
+
colordict[k] = id_to_color[v]
|
84 |
+
|
85 |
+
tgthtml = []
|
86 |
+
for i, token in enumerate(decoder_input_ids[0]):
|
87 |
+
if f"src_{i}" in colordict:
|
88 |
+
label = f"src_{i}"
|
89 |
+
tgthtml.append(f"<span style='color: #{colordict[label]}'>{tokenizer.convert_ids_to_tokens([token])[0]}</span>")
|
90 |
+
else:
|
91 |
+
tgthtml.append(f"<span style='color: --color-text-body'>{tokenizer.convert_ids_to_tokens([token])[0]}</span>")
|
92 |
+
tgthtml = "".join(tgthtml)
|
93 |
+
tgthtml = tgthtml.replace("▁", " ")
|
94 |
+
tgthtml = f"<span style='font-size: 25px'>{tgthtml}</span>"
|
95 |
+
|
96 |
+
srchtml = []
|
97 |
+
for i, token in enumerate(encoder_input_ids[0]):
|
98 |
+
if (i == 0 and skip_first_src) or (i == 1 and skip_second_src):
|
99 |
+
continue
|
100 |
+
|
101 |
+
if f"trg_{i}" in colordict:
|
102 |
+
label = f"trg_{i}"
|
103 |
+
srchtml.append(f"<span style='color: #{colordict[label]}'>{tokenizer.convert_ids_to_tokens([token])[0]}</span>")
|
104 |
+
else:
|
105 |
+
srchtml.append(f"<span style='color: --color-text-body'>{tokenizer.convert_ids_to_tokens([token])[0]}</span>")
|
106 |
+
srchtml = "".join(srchtml)
|
107 |
+
srchtml = srchtml.replace("▁", " ")
|
108 |
+
srchtml = f"<span style='font-size: 25px'>{srchtml}</span>"
|
109 |
+
return srchtml, tgthtml
|
consts.py
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
CSS = """
|
2 |
+
|
3 |
+
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Hebrew:wght@400;700&family=Noto+Naskh+Arabic:wght@400;700&display=swap');
|
4 |
+
|
5 |
+
#taatik textarea {
|
6 |
+
font-size: 25px;
|
7 |
+
font-family: 'Noto Sans Hebrew', 'Noto Naskh Arabic', 'SBL Hebrew', 'David CLM', 'FrankRuehl CLM', 'Narkisim', 'Arial', 'Arial Unicode MS', sans-serif;
|
8 |
+
}
|
9 |
+
|
10 |
+
@font-face {
|
11 |
+
font-family: 'Noto Sans Hebrew';
|
12 |
+
src: url('https://fonts.gstatic.com/s/notosanshebrew/v40/or3HQ7v33eiDlKj4557q0OGCZa662.woff2') format('woff2');
|
13 |
+
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
14 |
+
}
|
15 |
+
|
16 |
+
@font-face {
|
17 |
+
font-family: 'Noto Naskh Arabic';
|
18 |
+
src: url('https://fonts.gstatic.com/s/notonaskharabic/v30/RrQ5bpV-9Dd1b1OAGA6M9PkyDuVBePeKNaxcsss0Y7bwvc5Urqjc.woff2') format('woff2');
|
19 |
+
unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0898-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC;
|
20 |
+
}
|
21 |
+
|
22 |
+
#liter textarea, #trans textarea { font-size: 25px;}
|
23 |
+
#grp { padding: 10px; }
|
24 |
+
#diac_input textarea {direction: rtl;}
|
25 |
+
#diacritized textarea { direction: rtl; }
|
26 |
+
#diacritized2 textarea { direction: rtl; }
|
27 |
+
#diacritized textarea { font-size: 25px;}
|
28 |
+
#diacritized2 textarea { font-size: 25px;}
|
29 |
+
#taatik2 textarea { font-size: 25px;}
|
30 |
+
#input textarea { font-size: 20px;}
|
31 |
+
#diac_input textarea { font-size: 20px;}
|
32 |
+
#check { border-style: none !important; }
|
33 |
+
#nearest { font-family: 'SBL Hebrew', 'David CLM', 'FrankRuehl CLM', 'Narkisim', 'Arial'; }
|
34 |
+
:root {--button-secondary-background-focus: #2563eb !important;
|
35 |
+
--button-secondary-background-base: #2563eb !important;
|
36 |
+
--button-secondary-background-hover: linear-gradient(to bottom right, #0692e8, #5859c2);
|
37 |
+
--button-secondary-text-color-base: white !important;
|
38 |
+
--button-secondary-text-color-hover: white !important;
|
39 |
+
--button-secondary-background-focus: rgb(51 122 216 / 70%) !important;
|
40 |
+
--button-secondary-text-color-focus: white !important}
|
41 |
+
.dark {--button-secondary-background-base: #2563eb !important;
|
42 |
+
--button-secondary-background-focus: rgb(51 122 216 / 70%) !important;
|
43 |
+
--button-secondary-background-hover: linear-gradient(to bottom right, #0692e8, #5859c2)}
|
44 |
+
.feather-music { stroke: #2563eb; }
|
45 |
+
|
46 |
+
.dataframe {
|
47 |
+
font-family: 'Arial', 'Helvetica', sans-serif !important;
|
48 |
+
}
|
49 |
+
.dataframe th, .dataframe td {
|
50 |
+
font-family: inherit !important;
|
51 |
+
|
52 |
+
}
|
53 |
+
|
54 |
+
.gradio-container .dataframe {
|
55 |
+
font-family: Arial, sans-serif !important;
|
56 |
+
|
57 |
+
}
|
58 |
+
|
59 |
+
/* Target the table header cells */
|
60 |
+
.table th .cell-wrap {
|
61 |
+
text-align: right !important;
|
62 |
+
}
|
63 |
+
|
64 |
+
/* Target the span inside the header cells */
|
65 |
+
.table th .cell-wrap span {
|
66 |
+
text-align: right !important;
|
67 |
+
display: block;
|
68 |
+
font-family: Arial, sans-serif !important;
|
69 |
+
}
|
70 |
+
|
71 |
+
/* Ensure the sort button doesn't interfere with alignment */
|
72 |
+
.table th .cell-wrap .sort-button {
|
73 |
+
float: left;
|
74 |
+
}
|
75 |
+
|
76 |
+
/* Target the table body cells */
|
77 |
+
.table td {
|
78 |
+
text-align: right !important;
|
79 |
+
}
|
80 |
+
|
81 |
+
/* Target the span inside the body cells */
|
82 |
+
.table td .cell-wrap span {
|
83 |
+
text-align: right !important;
|
84 |
+
display: block;
|
85 |
+
font-family: Arial, sans-serif !important;
|
86 |
+
font-size: 20px;
|
87 |
+
}
|
88 |
+
|
89 |
+
"""
|
90 |
+
|
91 |
+
ABOUT = """
|
92 |
+
This tool was created by Guy Mor-Lan as part of the Levanti project. The tool translates text between Hebrew and various Levantine Arabic dialects using specialized AI models. The tool also supports automatic diacritization, conversion of diacritization to transliteration, and audio generation (experimental features). For more information, access to data and models, and the Hebrew version of the tool, see the [project page on Hugging Face](https://huggingface.co/datasets/guymorlan/levanti).
|
93 |
+
"""
|
94 |
+
|
95 |
+
JS_FUNC = """
|
96 |
+
function refresh() {
|
97 |
+
const url = new URL(window.location);
|
98 |
+
|
99 |
+
if (url.searchParams.get('__theme') !== 'dark') {
|
100 |
+
url.searchParams.set('__theme', 'dark');
|
101 |
+
window.location.href = url.href;
|
102 |
+
}
|
103 |
+
}
|
104 |
+
"""
|
diacritize.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#%%
|
2 |
+
from transformers import RobertaForTokenClassification, AutoTokenizer
|
3 |
+
model = RobertaForTokenClassification.from_pretrained("guymorlan/levanti_arabic2diacritics")
|
4 |
+
tokenizer = AutoTokenizer.from_pretrained("guymorlan/levanti_arabic2diacritics")
|
5 |
+
|
6 |
+
#%%
|
7 |
+
label2diacritic = {0: 'ّ', # SHADDA
|
8 |
+
1: 'َ', # FATHA
|
9 |
+
2: 'ِ', # KASRA
|
10 |
+
3: 'ُ', # DAMMA
|
11 |
+
4: 'ْ'} # SUKKUN
|
12 |
+
|
13 |
+
|
14 |
+
def arabic2diacritics(text, model, tokenizer):
|
15 |
+
tokens = tokenizer(text, return_tensors="pt")
|
16 |
+
preds = (model(**tokens).logits.sigmoid() > 0.5)[0][1:-1] # remove preds for BOS and EOS
|
17 |
+
new_text = []
|
18 |
+
for p, c in zip(preds, text):
|
19 |
+
new_text.append(c)
|
20 |
+
for i in range(1, 5):
|
21 |
+
if p[i]:
|
22 |
+
new_text.append(label2diacritic[i])
|
23 |
+
# check shadda last
|
24 |
+
if p[0]:
|
25 |
+
new_text.append(label2diacritic[0])
|
26 |
+
|
27 |
+
new_text = "".join(new_text)
|
28 |
+
return new_text
|
29 |
+
|
30 |
+
|
31 |
+
def diacritize(text):
|
32 |
+
return arabic2diacritics(text, model, tokenizer)
|
33 |
+
|
34 |
+
def diacritize_if_not_already(text):
|
35 |
+
if any(c in label2diacritic.values() for c in text):
|
36 |
+
return text
|
37 |
+
else:
|
38 |
+
return arabic2diacritics(text, model, tokenizer)
|
39 |
+
#%%
|
40 |
+
# text = "بديش اروح عالمدرسة بكرا"
|
41 |
+
# arabic2diacritics(text, model, tokenizer)
|
42 |
+
# %%
|
en_ar
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
Subproject commit 76d2a612d5c6b4cc8fe16bd7608e7a0809a96ba5
|
en_ar_ct2/config.json
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"add_source_bos": false,
|
3 |
+
"add_source_eos": false,
|
4 |
+
"bos_token": "<s>",
|
5 |
+
"decoder_start_token": "</s>",
|
6 |
+
"eos_token": "</s>",
|
7 |
+
"layer_norm_epsilon": null,
|
8 |
+
"multi_query_attention": false,
|
9 |
+
"unk_token": "<unk>"
|
10 |
+
}
|
en_ar_ct2/model.bin
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:00ce25a723eef083222e4700514043b654c329c9202e11204e630484810b2235
|
3 |
+
size 306481586
|
en_ar_ct2/shared_vocabulary.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
requirements.txt
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
transformers==4.42.3
|
2 |
+
torch==1.13.1
|
3 |
+
sentencepiece==0.1.97
|
4 |
+
sacremoses==0.0.53
|
5 |
+
pandas==1.5.1
|
6 |
+
azure-cognitiveservices-speech==1.25.0
|
7 |
+
matplotlib==3.7.0
|
8 |
+
python-dotenv
|
9 |
+
gradio==4.37.2
|
10 |
+
ctranslate2==4.1.0
|
semsearch.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import torch
|
3 |
+
import pandas as pd
|
4 |
+
import translate
|
5 |
+
import gradio as gr
|
6 |
+
|
7 |
+
# data = pd.read_csv("./embedding_data.csv")
|
8 |
+
# embeddings = np.load("./embeddings.npy")
|
9 |
+
|
10 |
+
def normalize_vector(v):
|
11 |
+
norm = np.linalg.norm(v)
|
12 |
+
if norm == 0:
|
13 |
+
return v
|
14 |
+
return v / norm
|
15 |
+
|
16 |
+
|
17 |
+
def embed_one(model, tokenizer, text, normalize=True):
|
18 |
+
tokens = tokenizer(text, return_tensors="pt", truncation=True)
|
19 |
+
with torch.no_grad():
|
20 |
+
embedding = model.model.encoder(**tokens).last_hidden_state.mean(axis=1)
|
21 |
+
embedding = embedding.detach().numpy()[0]
|
22 |
+
|
23 |
+
if normalize:
|
24 |
+
return normalize_vector(embedding)
|
25 |
+
else:
|
26 |
+
return embedding
|
27 |
+
|
28 |
+
|
29 |
+
def knn(query_embedding, embeddings, df, k=5, hebrew=True):
|
30 |
+
sims = np.dot(embeddings, query_embedding.T)
|
31 |
+
outs = np.argsort(sims, axis=0)[-k:][::-1]
|
32 |
+
select = outs.ravel()
|
33 |
+
if hebrew:
|
34 |
+
return df.iloc[select][["arabic", "hebrew", "validated"]]
|
35 |
+
else:
|
36 |
+
return df.iloc[select][["arabic", "english", "validated"]]
|
37 |
+
|
38 |
+
def run_knn(text, k=5):
|
39 |
+
print(text)
|
40 |
+
query_embedding = embed_one(translate.model_from_ar,
|
41 |
+
translate.tokenizer_from_ar, text)
|
42 |
+
return knn(query_embedding, embeddings, data, k=k, hebrew=True)
|
43 |
+
|
44 |
+
|
45 |
+
def style_dataframe(df):
|
46 |
+
styled_df = df.style.set_properties(**{
|
47 |
+
'font-family': 'Arial, sans-serif',
|
48 |
+
'font-size': '20px',
|
49 |
+
'text-align': 'right',
|
50 |
+
'direction': 'rtl',
|
51 |
+
'align': 'right'
|
52 |
+
}).set_table_styles([
|
53 |
+
{'selector': 'th', 'props': [('text-align', 'right')]}
|
54 |
+
])
|
55 |
+
return styled_df
|
56 |
+
|
57 |
+
|
58 |
+
def style_dataframe(df):
|
59 |
+
return df.style.set_table_styles([
|
60 |
+
{'selector': 'thead', 'props': [('text-align', 'right')]},
|
61 |
+
{'selector': '.index_name', 'props': [('text-align', 'right')]},
|
62 |
+
]).set_properties(**{
|
63 |
+
'text-align': 'right',
|
64 |
+
}) # Replace 'column_name' with your actual column name
|
65 |
+
|
66 |
+
|
67 |
+
def update_df(hidden_arabic):
|
68 |
+
df = run_knn(hidden_arabic, 100)
|
69 |
+
# replace true and false in validated column with checkmark and x emoji
|
70 |
+
df["validated"] = df["validated"].apply(lambda x: "✅" if x else "❌")
|
71 |
+
# replace name validated with "מאומת"
|
72 |
+
df = df.rename(columns={"validated": "מאומת"})
|
73 |
+
# replace name arabic with "ערבית"
|
74 |
+
df = df.rename(columns={"arabic": "ערבית"})
|
75 |
+
# replace name hebrew with "עברית"
|
76 |
+
df = df.rename(columns={"hebrew": "עברית"})
|
77 |
+
styled_df = style_dataframe(df)
|
78 |
+
return gr.DataFrame(value=styled_df, visible=True)
|
translate.py
ADDED
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from transformers import MarianMTModel, AutoTokenizer
|
3 |
+
import ctranslate2
|
4 |
+
from colorize import align_words
|
5 |
+
import logging
|
6 |
+
|
7 |
+
# Create a logger
|
8 |
+
logger = logging.getLogger()
|
9 |
+
logger.setLevel(logging.INFO) # Set to debug to capture all levels of logs
|
10 |
+
file_handler = logging.FileHandler('app.log', mode='a') # 'a' mode appends to the file
|
11 |
+
file_handler.setLevel(logging.INFO)
|
12 |
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
13 |
+
file_handler.setFormatter(formatter)
|
14 |
+
logger.addHandler(file_handler)
|
15 |
+
|
16 |
+
model_to_ar = MarianMTModel.from_pretrained("./en_ar/", output_attentions=True)
|
17 |
+
model_from_ar = MarianMTModel.from_pretrained("./ar_en/", output_attentions=True)
|
18 |
+
model_to_ar_ct2 = ctranslate2.Translator("./en_ar_ct2/")
|
19 |
+
model_from_ar_ct2 = ctranslate2.Translator("./ar_en_ct2/")
|
20 |
+
|
21 |
+
tokenizer_to_ar = AutoTokenizer.from_pretrained("./en_ar/")
|
22 |
+
tokenizer_from_ar = AutoTokenizer.from_pretrained("./ar_en/")
|
23 |
+
print("Done loading models")
|
24 |
+
|
25 |
+
dialect_map = {
|
26 |
+
"Palestinian": "P",
|
27 |
+
"Syrian": "S",
|
28 |
+
"Lebanese": "L",
|
29 |
+
"Egyptian": "E",
|
30 |
+
"פלסטיני": "P",
|
31 |
+
"סורי": "S",
|
32 |
+
"לבנוני": "L",
|
33 |
+
"מצרי": "E"
|
34 |
+
}
|
35 |
+
|
36 |
+
|
37 |
+
def translate(text, ct_model, hf_model, tokenizer, to_arabic=True,
|
38 |
+
threshold=None, layer=2, head=6):
|
39 |
+
|
40 |
+
logger.info(f"Translating: {text}")
|
41 |
+
inp_tokens = tokenizer.convert_ids_to_tokens(tokenizer.encode(text))
|
42 |
+
out_tokens = ct_model.translate_batch([inp_tokens])[0].hypotheses[0]
|
43 |
+
out_string = tokenizer.convert_tokens_to_string(out_tokens)
|
44 |
+
|
45 |
+
encoder_input_ids = torch.tensor(tokenizer.convert_tokens_to_ids(inp_tokens)).unsqueeze(0)
|
46 |
+
decoder_input_ids = torch.tensor(tokenizer.convert_tokens_to_ids(["<pad>"] + out_tokens +
|
47 |
+
['</s>'])).unsqueeze(0)
|
48 |
+
|
49 |
+
colorization_output = hf_model(input_ids=encoder_input_ids,
|
50 |
+
decoder_input_ids=decoder_input_ids)
|
51 |
+
|
52 |
+
if not threshold:
|
53 |
+
if len(inp_tokens) < 10:
|
54 |
+
threshold = 0.05
|
55 |
+
elif len(inp_tokens) < 20:
|
56 |
+
threshold = 0.10
|
57 |
+
else:
|
58 |
+
threshold = 0.05
|
59 |
+
|
60 |
+
srchtml, tgthtml = align_words(colorization_output,
|
61 |
+
tokenizer,
|
62 |
+
encoder_input_ids,
|
63 |
+
decoder_input_ids,
|
64 |
+
threshold,
|
65 |
+
skip_first_src=to_arabic,
|
66 |
+
skip_second_src=False,
|
67 |
+
layer=layer,
|
68 |
+
head=head)
|
69 |
+
|
70 |
+
html = f"<div style='direction: rtl'>{srchtml}<br><br>{tgthtml}</div>"
|
71 |
+
|
72 |
+
arabic = out_string if is_arabic(out_string) else text
|
73 |
+
return html, arabic
|
74 |
+
|
75 |
+
|
76 |
+
#%%
|
77 |
+
|
78 |
+
|
79 |
+
def is_arabic(text):
|
80 |
+
# return True if text has more than 50% arabic characters, False otherwise
|
81 |
+
text = text.replace(" ", "")
|
82 |
+
arabic_chars = 0
|
83 |
+
for c in text:
|
84 |
+
if "\u0600" <= c <= "\u06FF":
|
85 |
+
arabic_chars += 1
|
86 |
+
|
87 |
+
return arabic_chars / len(text) > 0.5
|
88 |
+
|
89 |
+
def run_translate(text, dialect=None):
|
90 |
+
if not text:
|
91 |
+
return ""
|
92 |
+
if is_arabic(text):
|
93 |
+
return translate(text, model_from_ar_ct2, model_from_ar, tokenizer_from_ar,
|
94 |
+
to_arabic=False, threshold=None, layer=2, head=7)
|
95 |
+
else:
|
96 |
+
if dialect in dialect_map:
|
97 |
+
dialect = dialect_map[dialect]
|
98 |
+
|
99 |
+
text = f"{dialect} {text}" if dialect else text
|
100 |
+
return translate(text, model_to_ar_ct2, model_to_ar, tokenizer_to_ar,
|
101 |
+
to_arabic=True, threshold=None, layer=2, head=7)
|
translit.py
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#%%
|
2 |
+
from transformers import CanineForTokenClassification, AutoTokenizer
|
3 |
+
import re
|
4 |
+
import torch
|
5 |
+
|
6 |
+
# instantiate module logger
|
7 |
+
import logging
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
logger.setLevel(logging.INFO)
|
10 |
+
|
11 |
+
model = CanineForTokenClassification.from_pretrained("guymorlan/levanti_diacritics2translit")
|
12 |
+
tokenizer = AutoTokenizer.from_pretrained("guymorlan/levanti_diacritics2translit")
|
13 |
+
|
14 |
+
#%%
|
15 |
+
|
16 |
+
def diacritics2hebrew_vowels(text, model, tokenizer):
|
17 |
+
tokens = tokenizer(text, return_tensors="pt")
|
18 |
+
with torch.no_grad():
|
19 |
+
pred = model(**tokens)
|
20 |
+
pred = pred.logits.argmax(-1).tolist()
|
21 |
+
|
22 |
+
pred = pred[0][1:-1] # remove CLS and SEP
|
23 |
+
output = []
|
24 |
+
for p, c in zip(pred, text):
|
25 |
+
if p != model.config.label2id["O"]:
|
26 |
+
output.append(model.config.id2label[p])
|
27 |
+
else:
|
28 |
+
output.append(c)
|
29 |
+
output = "".join(output)
|
30 |
+
|
31 |
+
# print("Done converting to Hebrew vowels")
|
32 |
+
logger.warning("Done converting to Hebrew vowels")
|
33 |
+
return output
|
34 |
+
|
35 |
+
#%%
|
36 |
+
|
37 |
+
|
38 |
+
arabic_to_english = {
|
39 |
+
"ا": "a", "أ": "a", "إ": "a", "ء": "a", "ئ": "a", "ؤ": "a",
|
40 |
+
"آ": "aa", "ى": "a", "ب": "b", "ت": "t", "ث": "th", "ج": "j",
|
41 |
+
"ح": "h", "خ": "kh", "د": "d", "ذ": "dh", "ر": "r", "ز": "z",
|
42 |
+
"س": "s", "ش": "sh", "ص": "s", "ض": "d", "ط": "t", "ظ": "z",
|
43 |
+
"ع": "a", "غ": "gh", "ف": "f", "ق": "q", "ك": "k", "ل": "l",
|
44 |
+
"م": "m", "ن": "n", "ه": "h", "و": "w", "ي": "y", "ة": "h",
|
45 |
+
"َ": "a", "ُ": "u", "ِ": "i",
|
46 |
+
"،": ",",
|
47 |
+
"ֹ": "o", # holam
|
48 |
+
"ַ": "a", # patah
|
49 |
+
"ִ": "i", # hiriq
|
50 |
+
"ְ": "", # shva
|
51 |
+
"ֻ": "u", # kubutz
|
52 |
+
'ֵ': "e",
|
53 |
+
"ّ": "SHADDA" # shadda
|
54 |
+
}
|
55 |
+
|
56 |
+
arabic_to_hebrew = {
|
57 |
+
# regular letters
|
58 |
+
"ا": "א", "أ": "א", "إ": "א", "ء": "א", "ئ": "א", "ؤ": "א",
|
59 |
+
"آ": "אא", "ى": "א", "ب": "בּ", "ت": "ת", "ث": "ת'", "ج": "ג'",
|
60 |
+
"ح": "ח", "خ": "ח'", "د": "ד", "ذ": "ד'", "ر": "ר", "ز": "ז",
|
61 |
+
"س": "ס", "ش": "ש", "ص": "צ", "ض": "צ'", "ط": "ט", "ظ": "ט'",
|
62 |
+
"ع": "ע", "غ": "ע'", "ف": "פ", "ق": "ק", "ك": "כּ", "ل": "ל",
|
63 |
+
"م": "מ", "ن": "נ", "ه": "ה", "و": "ו", "ي": "י", "ة": "ה",
|
64 |
+
# special characters
|
65 |
+
"،": ",", "َ": "ַ", "ُ": "ֻ", "ِ": "ִ",
|
66 |
+
"؟": "?", "؛": ";", "ـ": "",
|
67 |
+
# shadda to \u0598
|
68 |
+
"ّ": "\u0598",
|
69 |
+
}
|
70 |
+
|
71 |
+
vowels = ["،", ",", "َ", "ַ", "ُ", "ֻ", "ِ", "ִ", 'ֵ']
|
72 |
+
|
73 |
+
final_letters = {
|
74 |
+
"ن": "ן", "م": "ם", "ص": "ץ", "ض": "ץ'", "ف": "ף",
|
75 |
+
}
|
76 |
+
|
77 |
+
def reorder_hebrew_nikkud(input_string):
|
78 |
+
# in case of 2 character letter transliteration, move the nikkud back to the first character and not the apostrophe
|
79 |
+
|
80 |
+
# Define a dictionary for the nikkud signs
|
81 |
+
nikkud_signs = {
|
82 |
+
"ֹ": "o", # holam
|
83 |
+
"ַ": "a", # patah
|
84 |
+
"ִ": "i", # hiriq
|
85 |
+
"ְ": "", # shva
|
86 |
+
"ֻ": "u", # kubutz
|
87 |
+
"ֵ": "e", # tsere
|
88 |
+
"ّ": "SHADDA" # shadda
|
89 |
+
}
|
90 |
+
|
91 |
+
# Create a regex pattern for Hebrew letter followed by apostrophe and one of the nikkud signs
|
92 |
+
pattern = r'([\u0590-\u05FF])(\')([' + ''.join(nikkud_signs.keys()) + '])'
|
93 |
+
replacement = r'\1\3\2'
|
94 |
+
|
95 |
+
result = re.sub(pattern, replacement, input_string)
|
96 |
+
|
97 |
+
return result
|
98 |
+
|
99 |
+
def reverse_holam_shadda_vav(input_string):
|
100 |
+
# For better readability, replace (holam, shadda, ו) with (shadda, ו, holam)
|
101 |
+
# instead of shadda we use the replacement \u0598
|
102 |
+
pattern = r'(\u05B9)(\u0598)(\u05D5)'
|
103 |
+
replacement = r'\2\3\1'
|
104 |
+
result = re.sub(pattern, replacement, input_string)
|
105 |
+
|
106 |
+
return result
|
107 |
+
|
108 |
+
def to_taatik(arabic):
|
109 |
+
taatik = []
|
110 |
+
for index, letter in enumerate(arabic):
|
111 |
+
if (
|
112 |
+
(index == len(arabic) - 1 or arabic[index + 1] in {" ", ".", "،"}) and
|
113 |
+
letter in final_letters
|
114 |
+
):
|
115 |
+
taatik.append(final_letters[letter])
|
116 |
+
elif letter not in arabic_to_hebrew:
|
117 |
+
taatik.append(letter)
|
118 |
+
else:
|
119 |
+
taatik.append(arabic_to_hebrew[letter])
|
120 |
+
reversed = reverse_holam_shadda_vav("".join(taatik))
|
121 |
+
reordered = reorder_hebrew_nikkud(reversed)
|
122 |
+
# print("Done converting to taatik")
|
123 |
+
logger.warning("Done converting to taatik")
|
124 |
+
return reordered
|
125 |
+
|
126 |
+
|
127 |
+
|
128 |
+
|
129 |
+
def to_translit(arabic):
|
130 |
+
translit = []
|
131 |
+
for letter in arabic:
|
132 |
+
if letter not in arabic_to_english:
|
133 |
+
translit.append([letter, letter])
|
134 |
+
else:
|
135 |
+
if arabic_to_english[letter] == "SHADDA":
|
136 |
+
if translit[-1][0] in vowels:
|
137 |
+
translit[-2][1] = translit[-2][1].upper()
|
138 |
+
else:
|
139 |
+
translit[-1][1] = translit[-1][1].upper()
|
140 |
+
|
141 |
+
else:
|
142 |
+
translit.append([letter, arabic_to_english[letter]])
|
143 |
+
|
144 |
+
return "".join([x[1] for x in translit])
|
145 |
+
|
146 |
+
|
147 |
+
# %%
|
148 |
+
|
149 |
+
def taatik(text):
|
150 |
+
return to_taatik(diacritics2hebrew_vowels(text, model, tokenizer))
|
151 |
+
|
152 |
+
def translit(text):
|
153 |
+
return to_translit(diacritics2hebrew_vowels(text, model, tokenizer))
|
154 |
+
|
155 |
+
# text = "لَازِم نِعْطِي رَشَّات وِقَائِيِّة لِلشَّجَر "
|
156 |
+
# heb_vowels = diacritics2hebrew_vowels(text, model, tokenizer)
|
157 |
+
# #%%
|
158 |
+
# to_taatik(heb_vowels)
|
159 |
+
# #%%
|
160 |
+
# to_translit(heb_vowels)
|
161 |
+
# # %%
|
tts.py
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#%%
|
2 |
+
import azure.cognitiveservices.speech as speechsdk
|
3 |
+
import re
|
4 |
+
import os
|
5 |
+
import hashlib
|
6 |
+
import random
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
load_dotenv(".env")
|
9 |
+
|
10 |
+
print(os.environ.get('SPEECH_KEY'))
|
11 |
+
print(os.environ.get('SPEECH_REGION'))
|
12 |
+
speech_config = speechsdk.SpeechConfig(subscription=os.environ.get('SPEECH_KEY'),
|
13 |
+
region=os.environ.get('SPEECH_REGION'))
|
14 |
+
|
15 |
+
def do_cleanup(dir='wavs', num_files=100):
|
16 |
+
files = os.listdir(dir)
|
17 |
+
if len(files) > num_files:
|
18 |
+
for file in files[:len(files) - num_files]:
|
19 |
+
os.remove(f"{dir}/{file}")
|
20 |
+
|
21 |
+
def add_sukun(text):
|
22 |
+
# Define Arabic letters and sukun
|
23 |
+
arabic_letters = 'اأإآةبتثجحخدذرزسشصضطظعغفقكلمنهوي'
|
24 |
+
shadda = 'ّ'
|
25 |
+
arabic_letters += shadda
|
26 |
+
sukun = 'ْ'
|
27 |
+
punctuation = '.,;!?،؛؟'
|
28 |
+
|
29 |
+
def process_word(word):
|
30 |
+
# If the last character is punctuation, process the letter before it
|
31 |
+
if word[-1] in punctuation:
|
32 |
+
if len(word) > 1 and word[-2] in arabic_letters and word[-2] != sukun:
|
33 |
+
return word[:-2] + word[-2] + sukun + word[-1]
|
34 |
+
return word
|
35 |
+
# If the last character is an Arabic letter and does not have a sukun, add one
|
36 |
+
elif word[-1] in arabic_letters and word[-1] != sukun:
|
37 |
+
return word + sukun
|
38 |
+
return word
|
39 |
+
|
40 |
+
# Use regex to split text into words and punctuation
|
41 |
+
words = re.findall(r'\S+|[.,;!?،؛؟]', text)
|
42 |
+
processed_text = ' '.join(process_word(word) for word in words)
|
43 |
+
return processed_text
|
44 |
+
|
45 |
+
def get_ssml(text, voice='de-DE-SeraphinaMultilingualNeural'):
|
46 |
+
return f'<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="ar-SA"><voice name="{voice}"><lang xml:lang="ar-SA">{text}</lang></voice></speak>'
|
47 |
+
|
48 |
+
|
49 |
+
def get_audio(input_text, voice='de-DE-FlorianMultilingualNeural', use_ssml=True):
|
50 |
+
|
51 |
+
input_text = add_sukun(input_text)
|
52 |
+
hash = hashlib.md5(input_text.encode()).hexdigest()
|
53 |
+
|
54 |
+
if os.path.exists(f"wavs/{hash}.wav"):
|
55 |
+
return f"wavs/{hash}.wav"
|
56 |
+
|
57 |
+
audio_config = speechsdk.audio.AudioOutputConfig(filename=f"wavs/{hash}.wav")
|
58 |
+
# speech_config.speech_synthesis_voice_name=voice
|
59 |
+
# speech_config.speech_synthesis_language = "ar-EG"
|
60 |
+
speech_config.set_speech_synthesis_output_format(
|
61 |
+
speechsdk.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm
|
62 |
+
)
|
63 |
+
|
64 |
+
speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config,
|
65 |
+
audio_config=audio_config)
|
66 |
+
if use_ssml:
|
67 |
+
# print("Using SSML")
|
68 |
+
ssml = get_ssml(input_text, voice=voice)
|
69 |
+
result = speech_synthesizer.speak_ssml_async(ssml).get()
|
70 |
+
else:
|
71 |
+
# print("Using text")
|
72 |
+
result = speech_synthesizer.speak_text_async(input_text).get()
|
73 |
+
|
74 |
+
if result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
|
75 |
+
print("Speech synthesized for text [{}]".format(input_text))
|
76 |
+
elif result.reason == speechsdk.ResultReason.Canceled:
|
77 |
+
cancellation_details = result.cancellation_details
|
78 |
+
print("Speech synthesis canceled: {}".format(cancellation_details.reason))
|
79 |
+
if cancellation_details.reason == speechsdk.CancellationReason.Error:
|
80 |
+
print("Error details: {}".format(cancellation_details.error_details))
|
81 |
+
|
82 |
+
# randomly every 50 calls, clean up the wavs folder
|
83 |
+
if random.randint(1, 50) == 1:
|
84 |
+
do_cleanup()
|
85 |
+
|
86 |
+
return f"wavs/{hash}.wav"
|
wavs/1cfdc7d62daa8ab925371ac17ea4e792.wav
ADDED
Binary file (133 kB). View file
|
|
wavs/5fc7e1c64e032cdbfc6435dc9a6a32ce.wav
ADDED
Binary file (226 kB). View file
|
|
wavs/8300935681825cf0e5c467f08fb31325.wav
ADDED
Binary file (128 kB). View file
|
|